diff options
| -rw-r--r-- | odinfmt.bat | 2 | ||||
| -rw-r--r-- | src/common/config.odin | 1 | ||||
| -rw-r--r-- | src/common/util_windows.odin | 10 | ||||
| -rw-r--r-- | src/odin/printer/document.odin | 86 | ||||
| -rw-r--r-- | src/odin/printer/printer.odin | 7 | ||||
| -rw-r--r-- | src/odin/printer/visit.odin | 239 | ||||
| -rw-r--r-- | src/server/analysis.odin | 12 | ||||
| -rw-r--r-- | src/server/format.odin | 16 | ||||
| -rw-r--r-- | tests/builtin/builtin.odin | 27 | ||||
| -rw-r--r-- | tools/odinfmt/flag/flag.odin | 2 | ||||
| -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 |
19 files changed, 1446 insertions, 140 deletions
diff --git a/odinfmt.bat b/odinfmt.bat index a50406c..19c0202 100644 --- a/odinfmt.bat +++ b/odinfmt.bat @@ -1 +1 @@ -odin build tools/odinfmt -show-timings -collection:shared=src -out:odinfmt -opt:2
\ No newline at end of file +odin build tools/odinfmt -show-timings -collection:shared=src -out:odinfmt.exe -o:minimal
\ No newline at end of file diff --git a/src/common/config.odin b/src/common/config.odin index 387e8b8..d90e5fa 100644 --- a/src/common/config.odin +++ b/src/common/config.odin @@ -28,6 +28,7 @@ Config :: struct { Format_Config :: struct { tabs: bool, characters: int, + spaces: int, } config: Config;
\ No newline at end of file diff --git a/src/common/util_windows.odin b/src/common/util_windows.odin index 194bd4d..716d4be 100644 --- a/src/common/util_windows.odin +++ b/src/common/util_windows.odin @@ -32,10 +32,10 @@ get_case_sensitive_path :: proc(path: string, allocator := context.temp_allocato file := win32.CreateFileW(&wide[0], 0, win32.FILE_SHARE_READ, nil, win32.OPEN_EXISTING, win32.FILE_FLAG_BACKUP_SEMANTICS, nil) if(file == win32.INVALID_HANDLE) - { + { log_last_error() - return ""; - } + return ""; + } buffer := make([]u16, 512, context.temp_allocator) @@ -43,6 +43,8 @@ get_case_sensitive_path :: proc(path: string, allocator := context.temp_allocato res, _ := win32.utf16_to_utf8(buffer[4:], allocator) + win32.CloseHandle(file) + return res } @@ -127,4 +129,4 @@ run_executable :: proc(command: string, stdout: ^[]byte) -> (u32, bool, []byte) win32.CloseHandle(stdout_read) return exit_code, true, stdout[0:index] -}
\ No newline at end of file +} diff --git a/src/odin/printer/document.odin b/src/odin/printer/document.odin index 1f104a3..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 { @@ -59,10 +68,13 @@ Document_Align :: struct { Document_Group_Mode :: enum { Flat, Break, - Fill, Fit, } +Document_Group_Options :: struct { + id: string, +} + empty :: proc(allocator := context.allocator) -> ^Document { document := new(Document, allocator) document^ = Document_Nil {} @@ -94,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 { @@ -154,19 +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 { - document := new(Document, allocator) - document^ = Document_Group { - document = grouped_document, - } - return document -} - -fill_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, - mode = .Fill, + options = options, } return document } @@ -211,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 @@ -226,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 } @@ -235,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: @@ -249,9 +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 if data.mode == .Fill && v.newline && width > 0 { return true } else { width -= len(v.value) @@ -260,13 +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 true + return width > 0 } format_newline :: proc(indentation: int, alignment: int, consumed: ^int, builder: ^strings.Builder, p: ^Printer) { @@ -278,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) { @@ -303,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}) @@ -318,11 +332,6 @@ format :: proc(width: int, list: ^[dynamic]Tuple, builder: ^strings.Builder, p: case Document_Break: if data.mode == .Break && v.newline { format_newline(data.indentation, data.alignment, &consumed, builder, p) - } else if data.mode == .Fill && consumed + len(v.value) < width { - strings.write_string(builder, v.value) - consumed += len(v.value) - } else if data.mode == .Fill && v.newline { - format_newline(data.indentation, data.alignment, &consumed, builder, p) } else { strings.write_string(builder, v.value) consumed += len(v.value) @@ -332,31 +341,36 @@ 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 == .Fill { - append(list, Tuple {indentation = data.indentation, mode = .Fill, document = v.document, alignment = data.alignment}) - } else if v.mode == .Fit { + 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/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 4e07f02..14d5d4e 100644 --- a/src/odin/printer/visit.odin +++ b/src/odin/printer/visit.odin @@ -1,10 +1,12 @@ package odin_printer import "core:odin/ast" +import "core:odin/parser" import "core:odin/tokenizer" import "core:strings" import "core:fmt" import "core:sort" +import "core:strconv" //right now the attribute order is not linearly parsed(bug?) @(private) @@ -306,11 +308,15 @@ visit_decl :: proc(p: ^Printer, decl: ^ast.Decl, called_in_stmt := false) -> ^Do } if len(v.values) > 0 { - if is_values_binary(p, v.values) { - return cons(document, fill_group(cons_with_opl(group(lhs), align(fill_group(rhs))))) + if is_values_nestable_assign(v.values) { + 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)))) - } + return cons(document, group(cons_with_nopl(group(lhs), group(rhs)))) + } } else { return cons(document, group(lhs)) } @@ -322,10 +328,23 @@ visit_decl :: proc(p: ^Printer, decl: ^ast.Decl, called_in_stmt := false) -> ^Do } @(private) -is_values_binary :: proc(p: ^Printer, list: []^ast.Expr) -> bool { +is_values_nestable_assign :: proc(list: []^ast.Expr) -> bool { for expr in list { - if _, bin := expr.derived.(^ast.Binary_Expr); bin { - return true + #partial switch v in expr.derived { + 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 } } return false @@ -340,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 { @@ -422,7 +440,7 @@ visit_union_exprs :: proc(p: ^Printer, union_type: ast.Union_Type, options := Li for expr, i in union_type.variants { if i == 0 && .Enforce_Newline in options { - comment, ok := visit_comments(p, union_type.variants[i].pos) + comment, ok := visit_comments(p, union_type.variants[i].pos) if _, is_nil := comment.(Document_Nil); !is_nil { comment = cons(comment, newline(1)) } @@ -531,6 +549,9 @@ visit_state_flags :: proc(p: ^Printer, flags: ast.Node_State_Flags) -> ^Document if .No_Bounds_Check in flags { return cons(text("#no_bounds_check"), break_with_no_newline()) } + if .Bounds_Check in flags { + return cons(text("#bounds_check"), break_with_no_newline()) + } return empty() } @@ -621,7 +642,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener } if v.cond != nil { - if_document = cons_with_nopl(if_document, fill_group(visit_expr(p, v.cond))) + if_document = cons_with_nopl(if_document, group(visit_expr(p, v.cond))) } document = cons(document, group(hang(3, if_document))) @@ -698,18 +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))) - - if block_stmt { - if should_align_assignment_stmt(p, v^) { - assign_document = fill_group(cons(assign_document, align(cons(break_with_space(), visit_exprs(p, v.rhs, {.Add_Comma}, .Assignment_Stmt))))) - } else { - assign_document = fill_group(cons(assign_document, cons(break_with_space(), visit_exprs(p, v.rhs, {.Add_Comma}, .Assignment_Stmt)))) - } + 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 { - assign_document = cons_with_nopl(assign_document, visit_exprs(p, v.rhs, {.Add_Comma}, .Assignment_Stmt)) + document = group(cons_with_opl(assign_document, group(rhs))) } - document = cons(document, group(assign_document)) case ^Expr_Stmt: document = cons(document, visit_expr(p, v.expr)) case ^For_Stmt: @@ -730,7 +751,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener if v.cond != nil { set_source_position(p, v.cond.pos) - for_document = cons_with_opl(for_document, fill_group(visit_expr(p, v.cond))) + for_document = cons_with_opl(for_document, group(visit_expr(p, v.cond))) } if v.post != nil { @@ -895,20 +916,6 @@ contains_do_in_expression :: proc(p: ^Printer, expr: ^ast.Expr) -> bool { } @(private) -should_align_assignment_stmt :: proc(p: ^Printer, stmt: ast.Assign_Stmt) -> bool { - if len(stmt.rhs) == 0 { - return false - } - - for expr in stmt.rhs { - if _, ok := stmt.rhs[0].derived.(^ast.Binary_Expr); ok { - return true - } - } - return false -} - -@(private) push_where_clauses :: proc(p: ^Printer, clauses: []^ast.Expr) -> ^Document { if len(clauses) == 0 { return empty() @@ -1054,7 +1061,7 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, called_from: Expr_Called_Type = document = cons_with_nopl(document, text("{")) document = cons(document, text("}")) } else { - document = cons_with_opl(document, visit_begin_brace(p, v.pos, .Generic)) + document = cons_with_nopl(document, visit_begin_brace(p, v.pos, .Generic)) set_source_position(p, v.variants[0].pos) document = cons(document, nest(p.indentation_count, cons(newline_position(p, 1, v.pos), visit_union_exprs(p, v^, {.Add_Comma, .Trailing, .Enforce_Newline})))) set_source_position(p, v.end) @@ -1139,7 +1146,7 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, called_from: Expr_Called_Type = case ^Basic_Lit: document = text_token(p, v.tok) case ^Binary_Expr: - document = visit_binary_expr(p, v^) + document = visit_binary_expr(p, v^, true) case ^Implicit_Selector_Expr: document = cons(text("."), text_position(p, v.field.name, v.field.pos)) case ^Call_Expr: @@ -1173,17 +1180,17 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, called_from: Expr_Called_Type = case ^Selector_Expr: document = cons(visit_expr(p, v.expr), cons(text_token(p, v.op), visit_expr(p, v.field))) case ^Paren_Expr: - document = cons(text("("), cons(visit_expr(p, v.expr), text(")"))) + document = group(cons(text("("), cons(nest(p.indentation_count, visit_expr(p, v.expr)), text(")")))) case ^Index_Expr: document = visit_expr(p, v.expr) document = cons(document, text("[")) - document = cons(document, fill_group(align(visit_expr(p, v.index)))) + document = cons(document, group(align(visit_expr(p, v.index)))) document = cons(document, text("]")) case ^Proc_Group: document = text_token(p, v.tok) if len(v.args) != 0 { - document = cons_with_opl(document, visit_begin_brace(p, v.pos, .Generic)) + document = cons_with_nopl(document, visit_begin_brace(p, v.pos, .Generic)) set_source_position(p, v.args[0].pos) document = cons(document, nest(p.indentation_count, cons(newline_position(p, 1, v.args[0].pos), visit_exprs(p, v.args, {.Add_Comma, .Trailing, .Enforce_Newline})))) document = cons(document, visit_end_brace(p, v.end, 1)) @@ -1199,17 +1206,28 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, called_from: Expr_Called_Type = if v.type != nil { document = cons_with_nopl(document, visit_expr(p, v.type)) - } + + if matrix_type, ok := v.type.derived.(^ast.Matrix_Type); ok && len(v.elems) > 0 && is_matrix_type_constant(matrix_type) { + document = cons_with_nopl(document, visit_begin_brace(p, v.pos, .Generic)) + + set_source_position(p, v.open) + document = cons(document, nest(p.indentation_count, cons(newline_position(p, 1, v.elems[0].pos), visit_matrix_comp_lit(p, v, matrix_type)))) + set_source_position(p, v.end) - //If we call from the value declartion, we want it to be nicely newlined and aligned + document = cons(document, cons(newline(1), text_position(p, "}", v.end))) + + break + } + } + if (should_align_comp_lit(p, v^) || contains_comments_in_range(p, v.pos, v.end)) && (called_from == .Value_Decl || called_from == .Assignment_Stmt) && len(v.elems) != 0 { - document = cons_with_opl(document, visit_begin_brace(p, v.pos, .Generic)) + document = cons_with_nopl(document, visit_begin_brace(p, v.pos, .Generic)) set_source_position(p, v.open) document = cons(document, nest(p.indentation_count, cons(newline_position(p, 1, v.elems[0].pos), visit_comp_lit_exprs(p, v^, {.Add_Comma, .Trailing, .Enforce_Newline})))) set_source_position(p, v.end) - document = cons(document, cons(newline(1), text_position(p, "}", v.end))) + document = cons(document, cons(newline(1), text_position(p, "}", v.end))) } else { document = cons(document, text("{")) document = cons(document, nest(p.indentation_count, cons(break_with(""), visit_exprs(p, v.elems, {.Add_Comma, .Group})))) @@ -1232,11 +1250,11 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, called_from: Expr_Called_Type = document = cons(document, text(")")) } case ^Pointer_Type: - return cons(text("^"), visit_expr(p, v.elem)) + document = cons(text("^"), visit_expr(p, v.elem)) case ^Multi_Pointer_Type: - return cons(text("[^]"), visit_expr(p, v.elem)) + document = cons(text("[^]"), visit_expr(p, v.elem)) case ^Implicit: - return text_token(p, v.tok) + document = text_token(p, v.tok) case ^Poly_Type: document = cons(text("$"), visit_expr(p, v.type)) @@ -1256,7 +1274,10 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, called_from: Expr_Called_Type = document = cons(document, text("]")) document = cons(document, visit_expr(p, v.value)) case ^Helper_Type: - document = visit_expr(p, v.type) + if v.tok == .Hash { + document = cons(document, text("#type")) + } + document = cons_with_nopl(document, visit_expr(p, v.type)) case ^Matrix_Type: document = text_position(p, "matrix", v.pos) document = cons(document, text("[")) @@ -1264,7 +1285,7 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, called_from: Expr_Called_Type = document = cons(document, text(",")) document = cons_with_opl(document, visit_expr(p, v.column_count)) document = cons(document, text("]")) - document = cons(document, visit_expr(p, v.elem)) + document = cons(group(document), visit_expr(p, v.elem)) case ^Matrix_Index_Expr: document = visit_expr(p, v.expr) document = cons(document, text("[")) @@ -1280,6 +1301,44 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, called_from: Expr_Called_Type = } @(private) +is_matrix_type_constant :: proc(matrix_type: ^ast.Matrix_Type) -> bool { + if row_count, is_lit := matrix_type.row_count.derived.(^ast.Basic_Lit); is_lit { + _, ok := strconv.parse_int(row_count.tok.text) + return ok + } + + if column_count, is_lit := matrix_type.column_count.derived.(^ast.Basic_Lit); is_lit { + _, ok := strconv.parse_int(column_count.tok.text) + return ok + } + + return false +} + +@(private) +visit_matrix_comp_lit :: proc(p: ^Printer, comp_lit: ^ast.Comp_Lit, matrix_type: ^ast.Matrix_Type) -> ^Document { + document := empty() + + //these values have already been validated + row_count, _ := strconv.parse_int(matrix_type.row_count.derived.(^ast.Basic_Lit).tok.text) + column_count, _ := strconv.parse_int(matrix_type.column_count.derived.(^ast.Basic_Lit).tok.text) + + for row := 0; row < row_count; row += 1 { + for column := 0; column < column_count; column += 1 { + document = cons(document, visit_expr(p, comp_lit.elems[column + row * column_count])) + document = cons(document, text(", ")) + } + + if row_count - 1 != row { + document = cons(document, newline(1)) + } + } + + return document +} + + +@(private) visit_begin_brace :: proc(p: ^Printer, begin: tokenizer.Pos, type: Block_Type, count := 0, same_line_spaces_before := 1) -> ^Document { set_source_position(p, begin) @@ -1409,6 +1468,29 @@ visit_field_list :: proc(p: ^Printer, list: ^ast.Field_List, options := List_Opt } @(private) +visit_proc_tags :: proc(p: ^Printer, proc_tags: ast.Proc_Tags) -> ^Document { + document := empty() + + if .Bounds_Check in proc_tags { + document = cons_with_opl(document, text("#bounds_check")) + } + + if .No_Bounds_Check in proc_tags { + document = cons_with_opl(document, text("#no_bounds_check")) + } + + if .Optional_Ok in proc_tags { + document = cons_with_opl(document, text("#optional_ok")) + } + + if .Optional_Second in proc_tags { + document = cons_with_opl(document, text("#optional_second")) + } + + return document +} + +@(private) visit_proc_type :: proc(p: ^Printer, proc_type: ast.Proc_Type) -> ^Document { document := text("proc") @@ -1459,29 +1541,49 @@ visit_proc_type :: proc(p: ^Printer, proc_type: ast.Proc_Type) -> ^Document { document = cons_with_nopl(document, text("!")) } + document = cons_with_opl(document, visit_proc_tags(p, proc_type.tags)) + return document } + @(private) -visit_binary_expr :: proc(p: ^Printer, binary: ast.Binary_Expr) -> ^Document { - lhs: ^Document - rhs: ^Document +visit_binary_expr :: proc(p: ^Printer, binary: ast.Binary_Expr, first := false) -> ^Document { + document := empty() - if v, ok := binary.left.derived.(^ast.Binary_Expr); ok { - lhs = visit_binary_expr(p, v^) - } else { - lhs = visit_expr(p, binary.left) - } + nest_first_expression := false - if v, ok := binary.right.derived.(^ast.Binary_Expr); ok { - rhs = visit_binary_expr(p, v^) - } else { - rhs = visit_expr(p, binary.right) + if binary.left != nil { + if b, ok := binary.left.derived.(^ast.Binary_Expr); ok { + pa := parser.Parser { + allow_in_expr = true, + } + nest_first_expression = parser.token_precedence(&pa, b.op.kind) != parser.token_precedence(&pa, binary.op.kind) + document = cons(document, visit_binary_expr(p, b^)) + } else { + document = cons(document, visit_expr(p, binary.left)) + } + } + + if true { + if nest_first_expression { + document = nest(p.indentation_count, document) + document = group(document) + } + } - op := text(binary.op.text) + document = cons_with_nopl(document, text(binary.op.text)) - return cons_with_nopl(lhs, cons_with_opl(op, rhs)) + if binary.right != nil { + if b, ok := binary.right.derived.(^ast.Binary_Expr); ok { + document = cons_with_opl(document, group(nest(p.indentation_count, visit_binary_expr(p, b^)))) + } else { + document = cons_with_opl(document, group(nest(p.indentation_count, visit_expr(p, binary.right)))) + } + } + + return document } @(private) @@ -1494,8 +1596,9 @@ visit_call_exprs :: proc(p: ^Printer, call_expr: ^ast.Call_Expr) -> ^Document { if i == len(call_expr.args) - 1 && ellipsis { document = cons(document, text("..")) } + document = cons(document, group(visit_expr(p, expr))) - + if i != len(call_expr.args) - 1 { document = cons(document, text(",")) @@ -1607,13 +1710,15 @@ repeat_space :: proc(amount: int) -> ^Document { get_node_length :: proc(node: ^ast.Node) -> int { #partial switch v in node.derived { case ^ast.Ident: - return len(v.name) + return strings.rune_count(v.name) case ^ast.Basic_Lit: - return len(v.tok.text) + return strings.rune_count(v.tok.text) case ^ast.Implicit_Selector_Expr: - return len(v.field.name) + 1 + return strings.rune_count(v.field.name) + 1 case ^ast.Binary_Expr: return 0 + case ^ast.Selector_Expr: + return get_node_length(v.expr) + strings.rune_count(v.op.text) + strings.rune_count(v.field.name) case: panic(fmt.aprintf("unhandled get_node_length case %v", node.derived)) } diff --git a/src/server/analysis.odin b/src/server/analysis.odin index 8043148..933ce8a 100644 --- a/src/server/analysis.odin +++ b/src/server/analysis.odin @@ -1785,8 +1785,18 @@ make_symbol_union_from_ast :: proc(ast_context: ^AstContext, v: ast.Union_Type, symbol.name = "union" } + types := make([dynamic]^ast.Expr, ast_context.allocator) + + for variant in v.variants { + if v.poly_params != nil { + append(&types, clone_type(variant, ast_context.allocator, nil)) + } else { + append(&types, variant) + } + } + symbol.value = SymbolUnionValue { - types = v.variants, + types = types[:], } if v.poly_params != nil { diff --git a/src/server/format.odin b/src/server/format.odin index e461044..57243ff 100644 --- a/src/server/format.odin +++ b/src/server/format.odin @@ -1,9 +1,10 @@ package server import "shared:common" - import "shared:odin/printer" +import "core:log" + FormattingOptions :: struct { tabSize: uint, insertSpaces: bool, //tabs or spaces @@ -19,9 +20,16 @@ DocumentFormattingParams :: struct { get_complete_format :: proc(document: ^Document, config: ^common.Config) -> ([]TextEdit, bool) { style := printer.default_style - style.max_characters = config.formatter.characters style.tabs = config.formatter.tabs + if config.formatter.characters != 0 { + style.max_characters = config.formatter.characters + } + + if config.formatter.spaces != 0 { + style.spaces = config.formatter.spaces + } + prnt := printer.make_printer(style, context.temp_allocator) if document.ast.syntax_error_count > 0 { @@ -34,7 +42,9 @@ get_complete_format :: proc(document: ^Document, config: ^common.Config) -> ([]T src := printer.print(&prnt, &document.ast) - end_line := 0 + log.error(src) + + end_line := 0 end_charcter := 0 last := document.text[0] 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/flag/flag.odin b/tools/odinfmt/flag/flag.odin index 8d57a06..010a48b 100644 --- a/tools/odinfmt/flag/flag.odin +++ b/tools/odinfmt/flag/flag.odin @@ -146,7 +146,7 @@ reflect_args_structure :: proc(ctx: ^Flag_Context, v: any) -> Flag_Error { type := types[i]; if named_type, ok := type.variant.(Type_Info_Named); ok { - if union_type, ok := named_type.base.variant.(Type_Info_Union); ok && union_type.maybe && len(union_type.variants) == 1 { + if union_type, ok := named_type.base.variant.(Type_Info_Union); ok && len(union_type.variants) == 1 { flag.optional = true; flag.tag_ptr = rawptr(uintptr(union_type.tag_offset) + uintptr(v.data) + uintptr(offsets[i])); type = union_type.variants[0]; 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 + } + } +} + |