diff options
| author | Daniel Gavin <danielgavin5@hotmail.com> | 2022-07-09 12:31:46 +0200 |
|---|---|---|
| committer | Daniel Gavin <danielgavin5@hotmail.com> | 2022-07-09 12:31:46 +0200 |
| commit | 65cb2d90ff01e310381b0ad84b10e813bc47b08f (patch) | |
| tree | 97e4e65883146726a57aff74ba6bfb855841731c /tools | |
| parent | 9b19888219305c3740f36d9490e08e04e148a413 (diff) | |
Add new snapshot system for odinfmt testing.
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/odinfmt/snapshot/snapshot.odin | 83 | ||||
| -rw-r--r-- | tools/odinfmt/tests.bat | 2 | ||||
| -rw-r--r-- | tools/odinfmt/tests.odin | 12 | ||||
| -rw-r--r-- | tools/odinfmt/tests/.snapshots/binary_expressions.odin | 47 | ||||
| -rw-r--r-- | tools/odinfmt/tests/.snapshots/calls.odin | 48 | ||||
| -rw-r--r-- | tools/odinfmt/tests/binary_expressions.odin | 25 | ||||
| -rw-r--r-- | tools/odinfmt/tests/calls.odin | 18 | ||||
| -rw-r--r-- | tools/odinfmt/tests/random/.snapshots/document.odin | 572 | ||||
| -rw-r--r-- | tools/odinfmt/tests/random/document.odin | 377 |
9 files changed, 1184 insertions, 0 deletions
diff --git a/tools/odinfmt/snapshot/snapshot.odin b/tools/odinfmt/snapshot/snapshot.odin new file mode 100644 index 0000000..c4f860d --- /dev/null +++ b/tools/odinfmt/snapshot/snapshot.odin @@ -0,0 +1,83 @@ +package odinfmt_testing + +import "core:testing" +import "core:os" +import "core:path/filepath" +import "core:strings" +import "core:fmt" + +import "shared:odin/format" + +format_file :: proc(filepath: string, allocator := context.allocator) -> (string, bool) { + style := format.default_style + style.max_characters = 80 + style.newline_style = .LF //We want to make sure it works on linux and windows. + + if data, ok := os.read_entire_file(filepath, allocator); ok { + return format.format(filepath, string(data), style, {.Optional_Semicolons}, allocator); + } else { + return "", false; + } +} + +snapshot_directory :: proc(directory: string) -> bool { + matches, err := filepath.glob(fmt.tprintf("%v/*", directory)) + + if err != .None { + fmt.eprintf("Error in globbing directory: %v", directory) + } + + for match in matches { + if strings.contains(match, ".odin") { + snapshot_file(match) or_return + } + } + + for match in matches { + if !strings.contains(match, ".snapshots") { + if os.is_dir(match) { + snapshot_directory(match) + } + } + } + + return true +} + +snapshot_file :: proc(path: string) -> bool { + fmt.printf("Testing snapshot %v", path) + + + snapshot_path := filepath.join(elems = {filepath.dir(path, context.temp_allocator), "/.snapshots", filepath.base(path)}, allocator = context.temp_allocator); + + formatted, ok := format_file(path, context.temp_allocator) + + if !ok { + fmt.eprintf("Format failed on file %v", path) + return false + } + + if os.exists(snapshot_path) { + if snapshot_data, ok := os.read_entire_file(snapshot_path, context.temp_allocator); ok { + if cast(string)snapshot_data != formatted { + fmt.eprintf("Formatted file was different from snapshot file: %v", snapshot_path) + os.write_entire_file(fmt.tprintf("%v_failed", snapshot_path), transmute([]u8)formatted) + return false + } + } else { + fmt.eprintf("Failed to read snapshot file %v", snapshot_path) + return false + } + } else { + os.make_directory(filepath.dir(snapshot_path, context.temp_allocator)) + ok = os.write_entire_file(snapshot_path, transmute([]byte)formatted) + if !ok { + fmt.eprintf("Failed to write snapshot file %v", snapshot_path) + return false + } + } + + fmt.print(" - SUCCESS \n") + + return true +}
\ No newline at end of file diff --git a/tools/odinfmt/tests.bat b/tools/odinfmt/tests.bat new file mode 100644 index 0000000..13172ef --- /dev/null +++ b/tools/odinfmt/tests.bat @@ -0,0 +1,2 @@ +odin run tests.odin -file -show-timings -collection:shared=../../src -out:tests.exe + diff --git a/tools/odinfmt/tests.odin b/tools/odinfmt/tests.odin new file mode 100644 index 0000000..025ea4b --- /dev/null +++ b/tools/odinfmt/tests.odin @@ -0,0 +1,12 @@ +package odinfmt_tests + +import "core:testing" +import "core:os" +import "core:fmt" + +import "snapshot" + + +main :: proc() { + snapshot.snapshot_directory("tests") +} diff --git a/tools/odinfmt/tests/.snapshots/binary_expressions.odin b/tools/odinfmt/tests/.snapshots/binary_expressions.odin new file mode 100644 index 0000000..7758a03 --- /dev/null +++ b/tools/odinfmt/tests/.snapshots/binary_expressions.odin @@ -0,0 +1,47 @@ +package odin_fmt_tests + + +binary :: proc() { + + addings := + 1111111111111111 + + 222222222222222222222 + + 3333333333333333333 + + 44444444444444444444 + + addings = + 1111111111111111 + + 222222222222222222222 + + 3333333333333333333 + + 44444444444444444444 + + + multiplication := + 1000 * 1111111111111111 + + 222222222222222222222 + + 3333333333333333333 * 2323 + + 44444444444444444444 + + multiplication = + 1000 * 1111111111111111 + + 222222222222222222222 + + 3333333333333333333 * 2323 + + 44444444444444444444 + + + logical_operator_1 := + 1111111111111111 == 222222222 && 111123411111 == 33333333434343433333 + + logical_operator_1 = + 1111111111111111 == 222222222 && 111123411111 == 33333333434343433333 + + logical_operator_2 := + 111111111111111111111111 == 22222222222222222222232323222 && + 111123432411123232311 == 3333332323232432333333333333333333 + + logical_operator_2 = + 111111111111111111111111 == 22222222222222222222232323222 && + 111123432411123232311 == 3333332323232432333333333333333333 + + +} diff --git a/tools/odinfmt/tests/.snapshots/calls.odin b/tools/odinfmt/tests/.snapshots/calls.odin new file mode 100644 index 0000000..3d6a3d3 --- /dev/null +++ b/tools/odinfmt/tests/.snapshots/calls.odin @@ -0,0 +1,48 @@ +package odin_fmt_tests + + +calls :: proc() { + + + aaaaaaaaaaaaa44444444777aaesult := + vk.CreateInsaaaaaadafaddddadwadawdwadawdawddgddaaaknce( + my_really_cool_call( + aaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + cccccccccccccccccccccccccccccccc, + ddddddddddddddddddddddddddddddddddddd, + ), + ) + + + aaaaaaaaaaaaa44444444777aaesult = + vk.CreateInsaaaaaadafaddddadwadawdwadawdawddgddaaaknce( + my_really_cool_call( + aaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + cccccccccccccccccccccccccccccccc, + ddddddddddddddddddddddddddddddddddddd, + ), + ) + + result := vk.CreateInsance( + my_really_cool_call( + aaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + cccccccccccccccccccccccccccccccc, + ddddddddddddddddddddddddddddddddddddd, + ), + ) + + + result = vk.CreateInsance( + my_really_cool_call( + aaaaaaaaaaaaaaaaaaaaa, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, + cccccccccccccccccccccccccccccccc, + ddddddddddddddddddddddddddddddddddddd, + ), + ) + + +} diff --git a/tools/odinfmt/tests/binary_expressions.odin b/tools/odinfmt/tests/binary_expressions.odin new file mode 100644 index 0000000..85dc63c --- /dev/null +++ b/tools/odinfmt/tests/binary_expressions.odin @@ -0,0 +1,25 @@ +package odin_fmt_tests + + +binary :: proc() { + + addings := 1111111111111111 + 222222222222222222222 + 3333333333333333333 + 44444444444444444444 + + addings = 1111111111111111 + 222222222222222222222 + 3333333333333333333 + 44444444444444444444 + + + multiplication := 1000 * 1111111111111111 + 222222222222222222222 + 3333333333333333333 * 2323 + 44444444444444444444 + + multiplication = 1000 * 1111111111111111 + 222222222222222222222 + 3333333333333333333 * 2323 + 44444444444444444444 + + + logical_operator_1 := 1111111111111111 == 222222222 && 111123411111 == 33333333434343433333 + + logical_operator_1 = 1111111111111111 == 222222222 && 111123411111 == 33333333434343433333 + + logical_operator_2 := 111111111111111111111111 == 22222222222222222222232323222 && 111123432411123232311 == 3333332323232432333333333333333333 + + logical_operator_2 = 111111111111111111111111 == 22222222222222222222232323222 && 111123432411123232311 == 3333332323232432333333333333333333 + + +}
\ No newline at end of file diff --git a/tools/odinfmt/tests/calls.odin b/tools/odinfmt/tests/calls.odin new file mode 100644 index 0000000..f450368 --- /dev/null +++ b/tools/odinfmt/tests/calls.odin @@ -0,0 +1,18 @@ +package odin_fmt_tests + + +calls :: proc() { + + + aaaaaaaaaaaaa44444444777aaesult := vk.CreateInsaaaaaadafaddddadwadawdwadawdawddgddaaaknce(my_really_cool_call(aaaaaaaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, cccccccccccccccccccccccccccccccc, ddddddddddddddddddddddddddddddddddddd)) + + + aaaaaaaaaaaaa44444444777aaesult = vk.CreateInsaaaaaadafaddddadwadawdwadawdawddgddaaaknce(my_really_cool_call(aaaaaaaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, cccccccccccccccccccccccccccccccc, ddddddddddddddddddddddddddddddddddddd)) + + result := vk.CreateInsance(my_really_cool_call(aaaaaaaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, cccccccccccccccccccccccccccccccc, ddddddddddddddddddddddddddddddddddddd)) + + + result = vk.CreateInsance(my_really_cool_call(aaaaaaaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, cccccccccccccccccccccccccccccccc, ddddddddddddddddddddddddddddddddddddd)) + + +} diff --git a/tools/odinfmt/tests/random/.snapshots/document.odin b/tools/odinfmt/tests/random/.snapshots/document.odin new file mode 100644 index 0000000..228d571 --- /dev/null +++ b/tools/odinfmt/tests/random/.snapshots/document.odin @@ -0,0 +1,572 @@ +package odin_printer + +import "core:strings" +import "core:fmt" + +Document :: union { + Document_Nil, + Document_Newline, + Document_Text, + Document_Nest, + Document_Break, + Document_Group, + Document_Cons, + Document_If_Break, + Document_Align, + Document_Nest_If_Break, +} + +Document_Nil :: struct {} + +Document_Newline :: struct { + amount: int, +} + +Document_Text :: struct { + value: string, +} + +Document_Nest :: struct { + indentation: int, + alignment: int, + document: ^Document, +} + +Document_Nest_If_Break :: struct { + indentation: int, + alignment: int, + document: ^Document, + group_id: string, +} + +Document_Break :: struct { + value: string, + newline: bool, +} + +Document_If_Break :: struct { + value: string, +} + +Document_Group :: struct { + document: ^Document, + mode: Document_Group_Mode, + options: Document_Group_Options, +} + +Document_Cons :: struct { + lhs: ^Document, + rhs: ^Document, +} + +Document_Align :: struct { + document: ^Document, +} + +Document_Group_Mode :: enum { + Flat, + Break, + Fit, +} + +Document_Group_Options :: struct { + id: string, +} + +empty :: proc(allocator := context.allocator) -> ^Document { + document := new(Document, allocator) + document^ = Document_Nil{} + return document +} + +text :: proc(value: string, allocator := context.allocator) -> ^Document { + document := new(Document, allocator) + document^ = Document_Text { + value = value, + } + return document +} + +newline :: proc(amount: int, allocator := context.allocator) -> ^Document { + document := new(Document, allocator) + document^ = Document_Newline { + amount = amount, + } + return document +} + +nest :: proc( + level: int, + nested_document: ^Document, + allocator := context.allocator, +) -> ^Document { + document := new(Document, allocator) + document^ = Document_Nest { + indentation = level, + document = nested_document, + } + return document +} + +nest_if_break :: proc( + level: int, + nested_document: ^Document, + group_id := "", + allocator := context.allocator, +) -> ^Document { + document := new(Document, allocator) + document^ = Document_Nest_If_Break { + indentation = level, + document = nested_document, + group_id = group_id, + } + return document +} + +hang :: proc( + align: int, + hanged_document: ^Document, + allocator := context.allocator, +) -> ^Document { + document := new(Document, allocator) + document^ = Document_Nest { + alignment = align, + document = hanged_document, + } + return document +} + +enforce_fit :: proc( + fitted_document: ^Document, + allocator := context.allocator, +) -> ^Document { + document := new(Document, allocator) + document^ = Document_Group { + document = fitted_document, + mode = .Fit, + } + return document +} + +enforce_break :: proc( + fitted_document: ^Document, + allocator := context.allocator, +) -> ^Document { + document := new(Document, allocator) + document^ = Document_Group { + document = fitted_document, + mode = .Break, + } + return document +} + +align :: proc(aligned_document: ^Document, allocator := context.allocator) -> + ^Document { + document := new(Document, allocator) + document^ = Document_Align { + document = aligned_document, + } + return document +} + +if_break :: proc(value: string, allocator := context.allocator) -> ^Document { + document := new(Document, allocator) + document^ = Document_If_Break { + value = value, + } + return document +} + +break_with :: proc( + value: string, + newline := true, + allocator := context.allocator, +) -> ^Document { + document := new(Document, allocator) + document^ = Document_Break { + value = value, + newline = newline, + } + return document +} + +break_with_space :: proc(allocator := context.allocator) -> ^Document { + return break_with(" ", true, allocator) +} + +break_with_no_newline :: proc(allocator := context.allocator) -> ^Document { + return break_with(" ", false, allocator) +} + +group :: proc( + grouped_document: ^Document, + options := Document_Group_Options{}, + allocator := context.allocator, +) -> ^Document { + document := new(Document, allocator) + document^ = Document_Group { + document = grouped_document, + options = options, + } + return document +} + +cons :: proc(lhs: ^Document, rhs: ^Document, allocator := context.allocator) -> + ^Document { + document := new(Document, allocator) + document^ = Document_Cons { + lhs = lhs, + rhs = rhs, + } + return document +} + +cons_with_opl :: proc( + lhs: ^Document, + rhs: ^Document, + allocator := context.allocator, +) -> ^Document { + if _, ok := lhs.(Document_Nil); ok { + return rhs + } + + if _, ok := rhs.(Document_Nil); ok { + return lhs + } + + return cons(lhs, cons(break_with_space(allocator), rhs), allocator) +} + +cons_with_nopl :: proc( + lhs: ^Document, + rhs: ^Document, + allocator := context.allocator, +) -> ^Document { + if _, ok := lhs.(Document_Nil); ok { + return rhs + } + + if _, ok := rhs.(Document_Nil); ok { + return lhs + } + + return cons(lhs, cons(break_with_no_newline(allocator), rhs), allocator) +} + +Tuple :: struct { + indentation: int, + alignment: int, + mode: Document_Group_Mode, + document: ^Document, +} + +fits :: proc(width: int, list: ^[dynamic]Tuple) -> bool { + assert(list != nil) + + start_width := width + width := width + + if len(list) == 0 { + return true + } else if width < 0 { + return false + } + + for len(list) != 0 { + data: Tuple = pop(list) + + if width <= 0 { + return false + } + + switch v in data.document { + case Document_Nil: + case Document_Newline: + if v.amount > 0 { + return true + } + case Document_Cons: + append( + list, + Tuple{ + indentation = data.indentation, + mode = data.mode, + document = v.rhs, + alignment = data.alignment, + }, + ) + append( + list, + Tuple{ + indentation = data.indentation, + mode = data.mode, + document = v.lhs, + alignment = data.alignment, + }, + ) + case Document_Align: + append( + list, + Tuple{ + indentation = 0, + mode = data.mode, + document = v.document, + alignment = start_width - width, + }, + ) + case Document_Nest: + append( + list, + Tuple{ + indentation = data.indentation + v.indentation, + mode = data.mode, + document = v.document, + alignment = data.alignment + v.alignment, + }, + ) + case Document_Text: + width -= len(v.value) + case Document_Break: + if data.mode == .Break && v.newline { + return true + } else { + width -= len(v.value) + } + case Document_If_Break: + if data.mode == .Break { + width -= len(v.value) + } + case Document_Nest_If_Break: + if data.mode == .Break { + append( + list, + Tuple{ + indentation = data.indentation + v.indentation, + mode = data.mode, + document = v.document, + alignment = data.alignment + v.alignment, + }, + ) + } else { + append( + list, + Tuple{ + indentation = data.indentation, + mode = data.mode, + document = v.document, + alignment = data.alignment, + }, + ) + } + case Document_Group: + append( + list, + Tuple{ + indentation = data.indentation, + mode = data.mode, + document = v.document, + alignment = data.alignment, + }, + ) + } + } + + return width > 0 +} + +format_newline :: proc( + indentation: int, + alignment: int, + consumed: ^int, + builder: ^strings.Builder, + p: ^Printer, +) { + strings.write_string(builder, p.newline) + for i := 0; i < indentation; i += 1 { + strings.write_string(builder, p.indentation) + } + for i := 0; i < alignment; i += 1 { + strings.write_string(builder, " ") + } + + consumed^ = indentation * p.indentation_width + alignment +} + +format :: proc( + width: int, + list: ^[dynamic]Tuple, + builder: ^strings.Builder, + p: ^Printer, +) { + assert(list != nil) + assert(builder != nil) + + consumed := 0 + + for len(list) != 0 { + data: Tuple = pop(list) + + switch v in data.document { + case Document_Nil: + case Document_Newline: + if v.amount > 0 { + for i := 0; i < v.amount; i += 1 { + strings.write_string(builder, p.newline) + } + for i := 0; i < data.indentation; i += 1 { + strings.write_string(builder, p.indentation) + } + for i := 0; i < data.alignment; i += 1 { + strings.write_string(builder, " ") + } + consumed = + data.indentation * p.indentation_width + data.alignment + } + case Document_Cons: + append( + list, + Tuple{ + indentation = data.indentation, + mode = data.mode, + document = v.rhs, + alignment = data.alignment, + }, + ) + append( + list, + Tuple{ + indentation = data.indentation, + mode = data.mode, + document = v.lhs, + alignment = data.alignment, + }, + ) + case Document_Nest: + append( + list, + Tuple{ + indentation = data.indentation + v.indentation, + mode = data.mode, + document = v.document, + alignment = data.alignment + v.alignment, + }, + ) + case Document_Align: + append( + list, + Tuple{ + indentation = 0, + mode = data.mode, + document = v.document, + alignment = consumed, + }, + ) + case Document_Text: + strings.write_string(builder, v.value) + consumed += len(v.value) + case Document_Break: + if data.mode == .Break && v.newline { + format_newline( + data.indentation, + data.alignment, + &consumed, + builder, + p, + ) + } else { + strings.write_string(builder, v.value) + consumed += len(v.value) + } + case Document_If_Break: + if data.mode == .Break { + strings.write_string(builder, v.value) + consumed += len(v.value) + } + case Document_Nest_If_Break: + mode := v.group_id != "" ? p.group_modes[v.group_id] : data.mode + if mode == .Break { + append( + list, + Tuple{ + indentation = data.indentation + v.indentation, + mode = data.mode, + document = v.document, + alignment = data.alignment + v.alignment, + }, + ) + } else { + append( + list, + Tuple{ + indentation = data.indentation, + mode = data.mode, + document = v.document, + alignment = data.alignment, + }, + ) + } + case Document_Group: + l := make([dynamic]Tuple, 0, len(list)) + + for element in list { + append(&l, element) + } + + append( + &l, + Tuple{ + indentation = data.indentation, + mode = .Flat, + document = v.document, + alignment = data.alignment, + }, + ) + + if data.mode == .Fit { + append( + list, + Tuple{ + indentation = data.indentation, + mode = .Fit, + document = v.document, + alignment = data.alignment, + }, + ) + } else if fits(width - consumed, &l) && v.mode != .Break { + append( + list, + Tuple{ + indentation = data.indentation, + mode = .Flat, + document = v.document, + alignment = data.alignment, + }, + ) + } else { + if v.mode == .Fit { + append( + list, + Tuple{ + indentation = data.indentation, + mode = .Fit, + document = v.document, + alignment = data.alignment, + }, + ) + } else { + append( + list, + Tuple{ + indentation = data.indentation, + mode = .Break, + document = v.document, + alignment = data.alignment, + }, + ) + } + } + + p.group_modes[v.options.id] = list[len(list) - 1].mode + } + } +} diff --git a/tools/odinfmt/tests/random/document.odin b/tools/odinfmt/tests/random/document.odin new file mode 100644 index 0000000..d77a490 --- /dev/null +++ b/tools/odinfmt/tests/random/document.odin @@ -0,0 +1,377 @@ +package odin_printer + +import "core:strings" +import "core:fmt" + +Document :: union { + Document_Nil, + Document_Newline, + Document_Text, + Document_Nest, + Document_Break, + Document_Group, + Document_Cons, + Document_If_Break, + Document_Align, + Document_Nest_If_Break, +} + +Document_Nil :: struct { + +} + +Document_Newline :: struct { + amount: int, +} + +Document_Text :: struct { + value: string, +} + +Document_Nest :: struct { + indentation: int, + alignment: int, + document: ^Document, +} + +Document_Nest_If_Break :: struct { + indentation: int, + alignment: int, + document: ^Document, + group_id: string, +} + +Document_Break :: struct { + value: string, + newline: bool, +} + +Document_If_Break :: struct { + value: string, +} + +Document_Group :: struct { + document: ^Document, + mode: Document_Group_Mode, + options: Document_Group_Options, +} + +Document_Cons :: struct { + lhs: ^Document, + rhs: ^Document, +} + +Document_Align :: struct { + document: ^Document, +} + +Document_Group_Mode :: enum { + Flat, + Break, + Fit, +} + +Document_Group_Options :: struct { + id: string, +} + +empty :: proc(allocator := context.allocator) -> ^Document { + document := new(Document, allocator) + document^ = Document_Nil {} + return document +} + +text :: proc(value: string, allocator := context.allocator) -> ^Document { + document := new(Document, allocator) + document^ = Document_Text { + value = value, + } + return document +} + +newline :: proc(amount: int, allocator := context.allocator) -> ^Document { + document := new(Document, allocator) + document^ = Document_Newline { + amount = amount, + } + return document +} + +nest :: proc(level: int, nested_document: ^Document, allocator := context.allocator) -> ^Document { + document := new(Document, allocator) + document^ = Document_Nest { + indentation = level, + document = nested_document, + } + return document +} + +nest_if_break :: proc(level: int, nested_document: ^Document, group_id := "", allocator := context.allocator) -> ^Document { + document := new(Document, allocator) + document^ = Document_Nest_If_Break { + indentation = level, + document = nested_document, + group_id = group_id, + } + return document +} + +hang :: proc(align: int, hanged_document: ^Document, allocator := context.allocator) -> ^Document { + document := new(Document, allocator) + document^ = Document_Nest { + alignment = align, + document = hanged_document, + } + return document +} + +enforce_fit :: proc(fitted_document: ^Document, allocator := context.allocator) -> ^Document { + document := new(Document, allocator) + document^ = Document_Group { + document = fitted_document, + mode = .Fit, + } + return document +} + +enforce_break :: proc(fitted_document: ^Document, allocator := context.allocator) -> ^Document { + document := new(Document, allocator) + document^ = Document_Group { + document = fitted_document, + mode = .Break, + } + return document +} + +align :: proc(aligned_document: ^Document, allocator := context.allocator) -> ^Document { + document := new(Document, allocator) + document^ = Document_Align { + document = aligned_document, + } + return document +} + +if_break :: proc(value: string, allocator := context.allocator) -> ^Document { + document := new(Document, allocator) + document^ = Document_If_Break { + value = value, + } + return document +} + +break_with :: proc(value: string, newline := true, allocator := context.allocator) -> ^Document { + document := new(Document, allocator) + document^ = Document_Break { + value = value, + newline = newline, + } + return document +} + +break_with_space :: proc(allocator := context.allocator) -> ^Document { + return break_with(" ", true, allocator) +} + +break_with_no_newline :: proc(allocator := context.allocator) -> ^Document { + return break_with(" ", false, allocator) +} + +group :: proc(grouped_document: ^Document, options := Document_Group_Options{}, allocator := context.allocator) -> ^Document { + document := new(Document, allocator) + document^ = Document_Group { + document = grouped_document, + options = options, + } + return document +} + +cons :: proc(lhs: ^Document, rhs: ^Document, allocator := context.allocator) -> ^Document { + document := new(Document, allocator) + document^ = Document_Cons { + lhs = lhs, + rhs = rhs, + } + return document +} + +cons_with_opl :: proc(lhs: ^Document, rhs: ^Document, allocator := context.allocator) -> ^Document { + if _, ok := lhs.(Document_Nil); ok { + return rhs + } + + if _, ok := rhs.(Document_Nil); ok { + return lhs + } + + return cons(lhs, cons(break_with_space(allocator), rhs), allocator) +} + +cons_with_nopl:: proc(lhs: ^Document, rhs: ^Document, allocator := context.allocator) -> ^Document { + if _, ok := lhs.(Document_Nil); ok { + return rhs + } + + if _, ok := rhs.(Document_Nil); ok { + return lhs + } + + return cons(lhs, cons(break_with_no_newline(allocator), rhs), allocator) +} + +Tuple :: struct { + indentation: int, + alignment: int, + mode: Document_Group_Mode, + document: ^Document, +} + +fits :: proc(width: int, list: ^[dynamic]Tuple) -> bool { + assert(list != nil) + + start_width := width + width := width + + if len(list) == 0 { + return true + } else if width < 0 { + return false + } + + for len(list) != 0 { + data: Tuple = pop(list) + + if width <= 0 { + return false + } + + switch v in data.document { + case Document_Nil: + case Document_Newline: + if v.amount > 0 { + return true + } + case Document_Cons: + append(list, Tuple {indentation = data.indentation, mode = data.mode, document = v.rhs, alignment = data.alignment}) + append(list, Tuple {indentation = data.indentation, mode = data.mode, document = v.lhs, alignment = data.alignment}) + case Document_Align: + append(list, Tuple {indentation = 0, mode = data.mode, document = v.document, alignment = start_width - width}) + case Document_Nest: + append(list, Tuple {indentation = data.indentation + v.indentation, mode = data.mode, document = v.document, alignment = data.alignment + v.alignment}) + case Document_Text: + width -= len(v.value) + case Document_Break: + if data.mode == .Break && v.newline { + return true + } else { + width -= len(v.value) + } + case Document_If_Break: + if data.mode == .Break { + width -= len(v.value) + } + case Document_Nest_If_Break: + if data.mode == .Break { + append(list, Tuple {indentation = data.indentation + v.indentation, mode = data.mode, document = v.document, alignment = data.alignment + v.alignment}) + } else { + append(list, Tuple {indentation = data.indentation, mode = data.mode, document = v.document, alignment = data.alignment}) + } + case Document_Group: + append(list, Tuple {indentation = data.indentation, mode = data.mode, document = v.document, alignment = data.alignment}) + } + } + + return width > 0 +} + +format_newline :: proc(indentation: int, alignment: int, consumed: ^int, builder: ^strings.Builder, p: ^Printer) { + strings.write_string(builder, p.newline) + for i := 0; i < indentation; i += 1 { + strings.write_string(builder, p.indentation) + } + for i := 0; i < alignment; i += 1 { + strings.write_string(builder, " ") + } + + consumed^ = indentation * p.indentation_width + alignment +} + +format :: proc(width: int, list: ^[dynamic]Tuple, builder: ^strings.Builder, p: ^Printer) { + assert(list != nil) + assert(builder != nil) + + consumed := 0 + + for len(list) != 0 { + data: Tuple = pop(list) + + switch v in data.document { + case Document_Nil: + case Document_Newline: + if v.amount > 0 { + for i := 0; i < v.amount; i += 1 { + strings.write_string(builder, p.newline) + } + for i := 0; i < data.indentation; i += 1 { + strings.write_string(builder, p.indentation) + } + for i := 0; i < data.alignment; i += 1 { + strings.write_string(builder, " ") + } + consumed = data.indentation * p.indentation_width + data.alignment + } + case Document_Cons: + append(list, Tuple {indentation = data.indentation, mode = data.mode, document = v.rhs, alignment = data.alignment}) + append(list, Tuple {indentation = data.indentation, mode = data.mode, document = v.lhs, alignment = data.alignment}) + case Document_Nest: + append(list, Tuple {indentation = data.indentation + v.indentation, mode = data.mode, document = v.document, alignment = data.alignment + v.alignment}) + case Document_Align: + append(list, Tuple {indentation = 0, mode = data.mode, document = v.document, alignment = consumed}) + case Document_Text: + strings.write_string(builder, v.value) + consumed += len(v.value) + case Document_Break: + if data.mode == .Break && v.newline { + format_newline(data.indentation, data.alignment, &consumed, builder, p) + } else { + strings.write_string(builder, v.value) + consumed += len(v.value) + } + case Document_If_Break: + if data.mode == .Break { + strings.write_string(builder, v.value) + consumed += len(v.value) + } + case Document_Nest_If_Break: + mode := v.group_id != "" ? p.group_modes[v.group_id] : data.mode + if mode == .Break { + append(list, Tuple {indentation = data.indentation + v.indentation, mode = data.mode, document = v.document, alignment = data.alignment + v.alignment}) + } else { + append(list, Tuple {indentation = data.indentation, mode = data.mode, document = v.document, alignment = data.alignment}) + } + case Document_Group: + l := make([dynamic]Tuple, 0, len(list)) + + for element in list { + append(&l, element) + } + + append(&l, Tuple {indentation = data.indentation, mode = .Flat, document = v.document, alignment = data.alignment}) + + if data.mode == .Fit { + append(list, Tuple {indentation = data.indentation, mode = .Fit, document = v.document, alignment = data.alignment}) + } + else if fits(width-consumed, &l) && v.mode != .Break { + append(list, Tuple {indentation = data.indentation, mode = .Flat, document = v.document, alignment = data.alignment}) + } else { + if v.mode == .Fit { + append(list, Tuple {indentation = data.indentation, mode = .Fit, document = v.document, alignment = data.alignment}) + } else { + append(list, Tuple {indentation = data.indentation, mode = .Break, document = v.document, alignment = data.alignment}) + } + } + + p.group_modes[v.options.id] = list[len(list)-1].mode + } + } +} + |