aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Gavin <danielgavin5@hotmail.com>2022-07-09 18:36:01 +0200
committerDaniel Gavin <danielgavin5@hotmail.com>2022-07-09 18:36:01 +0200
commit28d4e76ecc635c1c386ff914d4db6c92eea092e8 (patch)
treee7b181f43a0e65358917988c11ad2e04c0cbeabd
parent9e9f4958f0ff2686cd0ace5a247c54c5ab775106 (diff)
parentfb5b19056827afbad2133f486d68d2ea162930e7 (diff)
Merge branch 'master' into index-caching
-rw-r--r--odinfmt.bat2
-rw-r--r--src/common/config.odin1
-rw-r--r--src/common/util_windows.odin10
-rw-r--r--src/odin/printer/document.odin86
-rw-r--r--src/odin/printer/printer.odin7
-rw-r--r--src/odin/printer/visit.odin239
-rw-r--r--src/server/analysis.odin12
-rw-r--r--src/server/format.odin16
-rw-r--r--tests/builtin/builtin.odin27
-rw-r--r--tools/odinfmt/flag/flag.odin2
-rw-r--r--tools/odinfmt/snapshot/snapshot.odin83
-rw-r--r--tools/odinfmt/tests.bat2
-rw-r--r--tools/odinfmt/tests.odin12
-rw-r--r--tools/odinfmt/tests/.snapshots/binary_expressions.odin47
-rw-r--r--tools/odinfmt/tests/.snapshots/calls.odin48
-rw-r--r--tools/odinfmt/tests/binary_expressions.odin25
-rw-r--r--tools/odinfmt/tests/calls.odin18
-rw-r--r--tools/odinfmt/tests/random/.snapshots/document.odin572
-rw-r--r--tools/odinfmt/tests/random/document.odin377
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
+ }
+ }
+}
+