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 | |
| parent | 9b19888219305c3740f36d9490e08e04e148a413 (diff) | |
Add new snapshot system for odinfmt testing.
| -rw-r--r-- | src/odin/printer/document.odin | 66 | ||||
| -rw-r--r-- | src/odin/printer/printer.odin | 7 | ||||
| -rw-r--r-- | src/odin/printer/visit.odin | 30 | ||||
| -rw-r--r-- | tests/builtin/builtin.odin | 27 | ||||
| -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 |
13 files changed, 1264 insertions, 50 deletions
diff --git a/src/odin/printer/document.odin b/src/odin/printer/document.odin index 1e5ce3a..d77a490 100644 --- a/src/odin/printer/document.odin +++ b/src/odin/printer/document.odin @@ -13,6 +13,7 @@ Document :: union { Document_Cons, Document_If_Break, Document_Align, + Document_Nest_If_Break, } Document_Nil :: struct { @@ -33,6 +34,13 @@ Document_Nest :: struct { document: ^Document, } +Document_Nest_If_Break :: struct { + indentation: int, + alignment: int, + document: ^Document, + group_id: string, +} + Document_Break :: struct { value: string, newline: bool, @@ -45,6 +53,7 @@ Document_If_Break :: struct { Document_Group :: struct { document: ^Document, mode: Document_Group_Mode, + options: Document_Group_Options, } Document_Cons :: struct { @@ -62,6 +71,10 @@ Document_Group_Mode :: enum { Fit, } +Document_Group_Options :: struct { + id: string, +} + empty :: proc(allocator := context.allocator) -> ^Document { document := new(Document, allocator) document^ = Document_Nil {} @@ -93,6 +106,16 @@ nest :: proc(level: int, nested_document: ^Document, allocator := context.alloca 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 { @@ -153,10 +176,11 @@ break_with_no_newline :: proc(allocator := context.allocator) -> ^Document { return break_with(" ", false, allocator) } -group :: proc(grouped_document: ^Document, allocator := context.allocator) -> ^Document { +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 } @@ -201,7 +225,7 @@ Tuple :: struct { document: ^Document, } -fits :: proc(width: int, list: ^[dynamic]Tuple, consumed: ^int) -> bool { +fits :: proc(width: int, list: ^[dynamic]Tuple) -> bool { assert(list != nil) start_width := width @@ -216,8 +240,7 @@ fits :: proc(width: int, list: ^[dynamic]Tuple, consumed: ^int) -> bool { for len(list) != 0 { data: Tuple = pop(list) - if width < 0 { - consumed^ = start_width + if width <= 0 { return false } @@ -225,7 +248,6 @@ fits :: proc(width: int, list: ^[dynamic]Tuple, consumed: ^int) -> bool { case Document_Nil: case Document_Newline: if v.amount > 0 { - consumed^ = start_width - width return true } case Document_Cons: @@ -239,7 +261,6 @@ fits :: proc(width: int, list: ^[dynamic]Tuple, consumed: ^int) -> bool { width -= len(v.value) case Document_Break: if data.mode == .Break && v.newline { - consumed^ = start_width - width return true } else { width -= len(v.value) @@ -248,14 +269,18 @@ fits :: proc(width: int, list: ^[dynamic]Tuple, consumed: ^int) -> bool { 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 = .Flat, document = v.document, alignment = data.alignment}) + append(list, Tuple {indentation = data.indentation, mode = data.mode, document = v.document, alignment = data.alignment}) } } - consumed^ = start_width - width - - return width <= 0 + return width > 0 } format_newline :: proc(indentation: int, alignment: int, consumed: ^int, builder: ^strings.Builder, p: ^Printer) { @@ -267,7 +292,7 @@ format_newline :: proc(indentation: int, alignment: int, consumed: ^int, builder strings.write_string(builder, " ") } - consumed^ = indentation + alignment + consumed^ = indentation * p.indentation_width + alignment } format :: proc(width: int, list: ^[dynamic]Tuple, builder: ^strings.Builder, p: ^Printer) { @@ -292,7 +317,7 @@ format :: proc(width: int, list: ^[dynamic]Tuple, builder: ^strings.Builder, p: for i := 0; i < data.alignment; i += 1 { strings.write_string(builder, " ") } - consumed = data.indentation + data.alignment + 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}) @@ -316,21 +341,26 @@ format :: proc(width: int, list: ^[dynamic]Tuple, builder: ^strings.Builder, p: 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}) - - fits_consumed := 0 + 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, &fits_consumed) && v.mode != .Break { + 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 { @@ -339,6 +369,8 @@ format :: proc(width: int, list: ^[dynamic]Tuple, builder: ^strings.Builder, p: 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/src/odin/printer/printer.odin b/src/odin/printer/printer.odin index 5e1cb63..6f39c7c 100644 --- a/src/odin/printer/printer.odin +++ b/src/odin/printer/printer.odin @@ -22,8 +22,10 @@ Printer :: struct { indentation: string, newline: string, indentation_count: int, + indentation_width: int, disabled_lines: map[int]Disabled_Info, disabled_until_line: int, + group_modes: map[string]Document_Group_Mode, src: string, } @@ -37,6 +39,7 @@ Config :: struct { spaces: int, //Spaces per indentation newline_limit: int, //The limit of newlines between statements and declarations. tabs: bool, //Enable or disable tabs + tabs_width: int, convert_do: bool, //Convert all do statements to brace blocks brace_style: Brace_Style, indent_cases: bool, @@ -77,6 +80,7 @@ when ODIN_OS == .Windows { newline_limit = 2, convert_do = false, tabs = true, + tabs_width = 4, brace_style = ._1TBS, indent_cases = false, newline_style = .CRLF, @@ -88,6 +92,7 @@ when ODIN_OS == .Windows { newline_limit = 2, convert_do = false, tabs = true, + tabs_width = 4, brace_style = ._1TBS, indent_cases = false, newline_style = .LF, @@ -164,9 +169,11 @@ print_file :: proc(p: ^Printer, file: ^ast.File) -> string { if p.config.tabs { p.indentation = "\t" p.indentation_count = 1 + p.indentation_width = p.config.tabs_width } else { p.indentation_count = p.config.spaces p.indentation = " " + p.indentation_width = 1 } if p.config.newline_style == .CRLF { diff --git a/src/odin/printer/visit.odin b/src/odin/printer/visit.odin index 411a6b4..bc5d57c 100644 --- a/src/odin/printer/visit.odin +++ b/src/odin/printer/visit.odin @@ -309,7 +309,11 @@ visit_decl :: proc(p: ^Printer, decl: ^ast.Decl, called_in_stmt := false) -> ^Do if len(v.values) > 0 { if is_values_nestable_assign(v.values) { - return cons(document, nest(p.indentation_count, group(cons_with_opl(group(lhs), group(rhs))))) + return cons(document, group(nest(p.indentation_count, cons_with_opl(lhs, group(rhs))))) + } else if is_values_nestable_if_break_assign(v.values) { + assignments := cons(lhs, group(nest(p.indentation_count, break_with_space()), Document_Group_Options { id = "assignments"})) + assignments = cons(assignments, nest_if_break(p.indentation_count, group(rhs), "assignments")) + return cons(document, group(assignments)) } else { return cons(document, group(cons_with_nopl(group(lhs), group(rhs)))) } @@ -327,7 +331,19 @@ visit_decl :: proc(p: ^Printer, decl: ^ast.Decl, called_in_stmt := false) -> ^Do is_values_nestable_assign :: proc(list: []^ast.Expr) -> bool { for expr in list { #partial switch v in expr.derived { - case ^ast.Ident, ^ast.Binary_Expr, ^ast.Index_Expr, ^ast.Call_Expr, ^ast.Ternary_If_Expr, ^ast.Ternary_When_Expr, ^ast.Or_Else_Expr, ^ast.Or_Return_Expr: + case ^ast.Ident, ^ast.Binary_Expr, ^ast.Index_Expr, ^ast.Ternary_If_Expr, ^ast.Ternary_When_Expr, ^ast.Or_Else_Expr, ^ast.Or_Return_Expr: + return true + } + } + return false +} + + +@(private) +is_values_nestable_if_break_assign :: proc(list: []^ast.Expr) -> bool { + for expr in list { + #partial switch v in expr.derived { + case ^ast.Call_Expr, ^ast.Comp_Lit: return true } } @@ -343,7 +359,6 @@ visit_exprs :: proc(p: ^Printer, list: []^ast.Expr, options := List_Options{}, c document := empty() for expr, i in list { - p.source_position = expr.pos if .Enforce_Newline in options { @@ -704,15 +719,18 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener document = cons_with_nopl(document, visit_stmt(p, v.tag)) document = cons_with_nopl(document, visit_stmt(p, v.body, .Switch_Stmt)) case ^Assign_Stmt: - assign_document := group(cons_with_nopl(visit_exprs(p, v.lhs, {.Add_Comma, .Glue}), text(v.op.text))) + assign_document := group(cons(visit_exprs(p, v.lhs, {.Add_Comma, .Glue}), cons(text(" "), text(v.op.text)))) rhs := visit_exprs(p, v.rhs, {.Add_Comma}, .Assignment_Stmt) if is_values_nestable_assign(v.rhs) { document = group(nest(p.indentation_count, cons_with_opl(assign_document, group(rhs)))) + } else if is_values_nestable_if_break_assign(v.rhs) { + document = cons(assign_document, group(nest(p.indentation_count, break_with_space()), Document_Group_Options { id = "assignments"})) + document = cons(document, nest_if_break(p.indentation_count, group(rhs), "assignments")) + document = group(document) } else { - document = group(cons_with_nopl(assign_document, group(rhs))) + document = group(cons_with_opl(assign_document, group(rhs))) } - case ^Expr_Stmt: document = cons(document, visit_expr(p, v.expr)) case ^For_Stmt: diff --git a/tests/builtin/builtin.odin b/tests/builtin/builtin.odin deleted file mode 100644 index 009a7eb..0000000 --- a/tests/builtin/builtin.odin +++ /dev/null @@ -1,27 +0,0 @@ -package ols_builtin - -// Procedures -len :: proc(array: Array_Type) -> int --- -cap :: proc(array: Array_Type) -> int --- - -size_of :: proc($T: typeid) -> int --- -align_of :: proc($T: typeid) -> int --- -offset_of :: proc($T: typeid) -> uintptr --- -type_of :: proc(x: expr) -> type --- -type_info_of :: proc($T: typeid) -> ^runtime.Type_Info --- -typeid_of :: proc($T: typeid) -> typeid --- - -swizzle :: proc(x: [N]T, indices: ..int) -> [len(indices)]T --- - -complex :: proc(real, imag: Float) -> Complex_Type --- -quaternion :: proc(real, imag, jmag, kmag: Float) -> Quaternion_Type --- -real :: proc(value: Complex_Or_Quaternion) -> Float --- -imag :: proc(value: Complex_Or_Quaternion) -> Float --- -jmag :: proc(value: Quaternion) -> Float --- -kmag :: proc(value: Quaternion) -> Float --- -conj :: proc(value: Complex_Or_Quaternion) -> Complex_Or_Quaternion --- - -min :: proc(values: ..T) -> T --- -max :: proc(values: ..T) -> T --- -abs :: proc(value: T) -> T --- -clamp :: proc(value, minimum, maximum: T) -> T ---
\ No newline at end of file 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 + } + } +} + |