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