From 952250b6d79063aa1066b0e945d58360bd70d09f Mon Sep 17 00:00:00 2001 From: Mel Date: Wed, 6 May 2026 18:49:26 +0200 Subject: Lowering pass test harness, and initial pass test suite Signed-off-by: Mel --- boot/catboot.c | 5 +++- boot/tests/lower/c_for_loop.cskt | 35 +++++++++++++++++++++++++ boot/tests/lower/compound_assigns.cskt | 31 +++++++++++++++++++++++ boot/tests/lower/empty_main.cskt | 23 +++++++++++++++++ boot/tests/lower/if_else_chain.cskt | 38 ++++++++++++++++++++++++++++ boot/tests/lower/range_for_loop.cskt | 33 ++++++++++++++++++++++++ boot/tests/lower/shadowing_error.cskt | 29 +++++++++++++++++++++ boot/tests/lower/simple_var.cskt | 26 +++++++++++++++++++ boot/tests/lower/structural_dedup.cskt | 29 +++++++++++++++++++++ boot/tests/lower/type_cycle_error.cskt | 29 +++++++++++++++++++++ boot/tests/lower/type_simple_struct.cskt | 23 +++++++++++++++++ boot/tests/lower/type_with_pointer_self.cskt | 21 +++++++++++++++ boot/tests/lower/while_loop.cskt | 34 +++++++++++++++++++++++++ boot/tests/test.c | 23 +++++++++++++---- 14 files changed, 373 insertions(+), 6 deletions(-) create mode 100644 boot/tests/lower/c_for_loop.cskt create mode 100644 boot/tests/lower/compound_assigns.cskt create mode 100644 boot/tests/lower/empty_main.cskt create mode 100644 boot/tests/lower/if_else_chain.cskt create mode 100644 boot/tests/lower/range_for_loop.cskt create mode 100644 boot/tests/lower/shadowing_error.cskt create mode 100644 boot/tests/lower/simple_var.cskt create mode 100644 boot/tests/lower/structural_dedup.cskt create mode 100644 boot/tests/lower/type_cycle_error.cskt create mode 100644 boot/tests/lower/type_simple_struct.cskt create mode 100644 boot/tests/lower/type_with_pointer_self.cskt create mode 100644 boot/tests/lower/while_loop.cskt (limited to 'boot') diff --git a/boot/catboot.c b/boot/catboot.c index c75efed..3919a2a 100644 --- a/boot/catboot.c +++ b/boot/catboot.c @@ -133,7 +133,10 @@ debug_lower_pass(struct Source_File source_file) lower_tree(&tree, source_file, &unit); printer(&unit); - return unit.had_error ? 1 : 0; + // diagnostics are part of the printed unit, so the caller can read + // them from the dump. always returning success keeps snapshot tests + // viable for both the green and the diagnostic paths. + return 0; } integer diff --git a/boot/tests/lower/c_for_loop.cskt b/boot/tests/lower/c_for_loop.cskt new file mode 100644 index 0000000..98d78f0 --- /dev/null +++ b/boot/tests/lower/c_for_loop.cskt @@ -0,0 +1,35 @@ +c-style for loops are turned into the single loop kind, +with the initializer and the iteration step statements synthesized into +statements within the block. + +<<< + +main = fun () { + for i int = 0, i < 10, i++ { + var x int = i + } +} + +>>> + +(unit + (types + (type 0 int primitive) + (type 1 uint primitive) + (type 2 bool primitive) + (type 3 string primitive) + (type 4 float primitive) + (type 5 byte primitive) + (type 6 ascii primitive) + (type 7 void primitive)) + (functions + (function 0 main main (returns (ref void)) (block + (block + (declaration i (ref int) (initializer (expr 0))) + (loop (condition (expr (binary < (expr (name i)) (expr 10)))) (block + (declaration x (ref int) (initializer (expr (name i)))) + (assign (expr (name i)) (expr (binary + (expr (name i)) (expr 1)))) + )) + ) + ))) + (emission_order 0 1 2 3 4 5 6 7)) diff --git a/boot/tests/lower/compound_assigns.cskt b/boot/tests/lower/compound_assigns.cskt new file mode 100644 index 0000000..214fe59 --- /dev/null +++ b/boot/tests/lower/compound_assigns.cskt @@ -0,0 +1,31 @@ +compound assignment operators are split during lowering. + +<<< + +main = fun () { + var x int = 0 + x += 1 + x *= 2 + x -= 3 +} + +>>> + +(unit + (types + (type 0 int primitive) + (type 1 uint primitive) + (type 2 bool primitive) + (type 3 string primitive) + (type 4 float primitive) + (type 5 byte primitive) + (type 6 ascii primitive) + (type 7 void primitive)) + (functions + (function 0 main main (returns (ref void)) (block + (declaration x (ref int) (initializer (expr 0))) + (assign (expr (name x)) (expr (binary + (expr (name x)) (expr 1)))) + (assign (expr (name x)) (expr (binary * (expr (name x)) (expr 2)))) + (assign (expr (name x)) (expr (binary - (expr (name x)) (expr 3)))) + ))) + (emission_order 0 1 2 3 4 5 6 7)) diff --git a/boot/tests/lower/empty_main.cskt b/boot/tests/lower/empty_main.cskt new file mode 100644 index 0000000..8e4635f --- /dev/null +++ b/boot/tests/lower/empty_main.cskt @@ -0,0 +1,23 @@ +the simplest possible translation unit. seeds the eight primitive +types and registers a single, empty `main` function. + +<<< + +main = fun () { +} + +>>> + +(unit + (types + (type 0 int primitive) + (type 1 uint primitive) + (type 2 bool primitive) + (type 3 string primitive) + (type 4 float primitive) + (type 5 byte primitive) + (type 6 ascii primitive) + (type 7 void primitive)) + (functions + (function 0 main main (returns (ref void)) (block))) + (emission_order 0 1 2 3 4 5 6 7)) \ No newline at end of file diff --git a/boot/tests/lower/if_else_chain.cskt b/boot/tests/lower/if_else_chain.cskt new file mode 100644 index 0000000..a8bf358 --- /dev/null +++ b/boot/tests/lower/if_else_chain.cskt @@ -0,0 +1,38 @@ +a three-branch if/else-if/else chain lowers to a single flat +conditional. + +<<< + +classify = fun (n int) int { + if n < 0 { + return -1 + } else if n == 0 { + return 0 + } else { + return 1 + } +} + +>>> + +(unit + (types + (type 0 int primitive) + (type 1 uint primitive) + (type 2 bool primitive) + (type 3 string primitive) + (type 4 float primitive) + (type 5 byte primitive) + (type 6 ascii primitive) + (type 7 void primitive)) + (functions + (function 0 classify (returns (ref int)) (param n (ref int)) (block + (conditional (when (expr (binary < (expr (name n)) (expr 0)))) (block + (return (expr (unary - (expr 1)))) + ) (when (expr (binary == (expr (name n)) (expr 0)))) (block + (return (expr 0)) + ) (else) (block + (return (expr 1)) + )) + ))) + (emission_order 0 1 2 3 4 5 6 7)) diff --git a/boot/tests/lower/range_for_loop.cskt b/boot/tests/lower/range_for_loop.cskt new file mode 100644 index 0000000..64ffe0b --- /dev/null +++ b/boot/tests/lower/range_for_loop.cskt @@ -0,0 +1,33 @@ +a range for-each is rewritten into a c-style loop. + +<<< + +main = fun () { + for i int = 0..10 { + var x int = i + } +} + +>>> + +(unit + (types + (type 0 int primitive) + (type 1 uint primitive) + (type 2 bool primitive) + (type 3 string primitive) + (type 4 float primitive) + (type 5 byte primitive) + (type 6 ascii primitive) + (type 7 void primitive)) + (functions + (function 0 main main (returns (ref void)) (block + (block + (declaration i (ref int) (initializer (expr 0))) + (loop (condition (expr (binary < (expr (name i)) (expr 10)))) (block + (declaration x (ref int) (initializer (expr (name i)))) + (assign (expr (name i)) (expr (binary + (expr (name i)) (expr 1)))) + )) + ) + ))) + (emission_order 0 1 2 3 4 5 6 7)) diff --git a/boot/tests/lower/shadowing_error.cskt b/boot/tests/lower/shadowing_error.cskt new file mode 100644 index 0000000..b22dddf --- /dev/null +++ b/boot/tests/lower/shadowing_error.cskt @@ -0,0 +1,29 @@ +declaring two locals with the same name in overlapping scopes is +a hard error. + +<<< + +main = fun () { + var x int = 1 + var x int = 2 +} + +>>> + +(unit + (types + (type 0 int primitive) + (type 1 uint primitive) + (type 2 bool primitive) + (type 3 string primitive) + (type 4 float primitive) + (type 5 byte primitive) + (type 6 ascii primitive) + (type 7 void primitive)) + (functions + (function 0 main main (returns (ref void)) (block + (declaration x (ref int) (initializer (expr 1))) + ))) + (emission_order 0 1 2 3 4 5 6 7) + (diagnostics + (error "name 'x' shadows an existing binding"))) diff --git a/boot/tests/lower/simple_var.cskt b/boot/tests/lower/simple_var.cskt new file mode 100644 index 0000000..b1b0c92 --- /dev/null +++ b/boot/tests/lower/simple_var.cskt @@ -0,0 +1,26 @@ +a single typed variable declaration with an integer initializer +inside the body of `main`. + +<<< + +main = fun () { + var x int = 5 +} + +>>> + +(unit + (types + (type 0 int primitive) + (type 1 uint primitive) + (type 2 bool primitive) + (type 3 string primitive) + (type 4 float primitive) + (type 5 byte primitive) + (type 6 ascii primitive) + (type 7 void primitive)) + (functions + (function 0 main main (returns (ref void)) (block + (declaration x (ref int) (initializer (expr 5))) + ))) + (emission_order 0 1 2 3 4 5 6 7)) \ No newline at end of file diff --git a/boot/tests/lower/structural_dedup.cskt b/boot/tests/lower/structural_dedup.cskt new file mode 100644 index 0000000..ee4bcd3 --- /dev/null +++ b/boot/tests/lower/structural_dedup.cskt @@ -0,0 +1,29 @@ +two functions taking the same anonymous tuple type produce just +one synthesized type entry. + +<<< + +f = fun (a (int, int)) int { return 0 } +g = fun (b (int, int)) int { return 0 } + +>>> + +(unit + (types + (type 0 int primitive) + (type 1 uint primitive) + (type 2 bool primitive) + (type 3 string primitive) + (type 4 float primitive) + (type 5 byte primitive) + (type 6 ascii primitive) + (type 7 void primitive) + (type 8 __cat_type_0 synthetic structure (field _0 (ref int)) (field _1 (ref int)) (depends_on 0 0))) + (functions + (function 0 f (returns (ref int)) (param a (ref __cat_type_0)) (block + (return (expr 0)) + )) + (function 1 g (returns (ref int)) (param b (ref __cat_type_0)) (block + (return (expr 0)) + ))) + (emission_order 0 1 2 3 4 5 6 7 8)) diff --git a/boot/tests/lower/type_cycle_error.cskt b/boot/tests/lower/type_cycle_error.cskt new file mode 100644 index 0000000..29b791e --- /dev/null +++ b/boot/tests/lower/type_cycle_error.cskt @@ -0,0 +1,29 @@ +two types referencing each other by value form a cycle that +we cannot resolve. +the diagnostic should point out the correct cycle. + +<<< + +A = type { b B } +B = type { a A } + +>>> + +(unit + (types + (type 0 int primitive) + (type 1 uint primitive) + (type 2 bool primitive) + (type 3 string primitive) + (type 4 float primitive) + (type 5 byte primitive) + (type 6 ascii primitive) + (type 7 void primitive) + (type 8 A structure (field b (ref B)) (depends_on 9)) + (type 9 B structure (field a (ref A)) (depends_on 8))) + (emission_order 0 1 2 3 4 5 6 7) + (diagnostics + (error "type cycle detected: + A (line 1) depends on B + B (line 2) depends on A +hint: break the cycle with a reference (use `&T` instead of `T`). :)"))) diff --git a/boot/tests/lower/type_simple_struct.cskt b/boot/tests/lower/type_simple_struct.cskt new file mode 100644 index 0000000..085d9db --- /dev/null +++ b/boot/tests/lower/type_simple_struct.cskt @@ -0,0 +1,23 @@ +a top-level struct declaration produces a structure type entry +with each field's type interned. +the by-value dependencies are correctly added as dependencies +and contribute to the ordering. + +<<< + +Point = type { x int, y int } + +>>> + +(unit + (types + (type 0 int primitive) + (type 1 uint primitive) + (type 2 bool primitive) + (type 3 string primitive) + (type 4 float primitive) + (type 5 byte primitive) + (type 6 ascii primitive) + (type 7 void primitive) + (type 8 Point structure (field x (ref int)) (field y (ref int)) (depends_on 0 0))) + (emission_order 0 1 2 3 4 5 6 7 8)) diff --git a/boot/tests/lower/type_with_pointer_self.cskt b/boot/tests/lower/type_with_pointer_self.cskt new file mode 100644 index 0000000..6a0b689 --- /dev/null +++ b/boot/tests/lower/type_with_pointer_self.cskt @@ -0,0 +1,21 @@ +a self-referential structure relying on indirection via a reference. +a dependency should not be added as the reference is not by-value. + +<<< + +Node = type { value int, next &Node } + +>>> + +(unit + (types + (type 0 int primitive) + (type 1 uint primitive) + (type 2 bool primitive) + (type 3 string primitive) + (type 4 float primitive) + (type 5 byte primitive) + (type 6 ascii primitive) + (type 7 void primitive) + (type 8 Node structure (field value (ref int)) (field next (ref & Node)) (depends_on 0))) + (emission_order 0 1 2 3 4 5 6 7 8)) diff --git a/boot/tests/lower/while_loop.cskt b/boot/tests/lower/while_loop.cskt new file mode 100644 index 0000000..b191df5 --- /dev/null +++ b/boot/tests/lower/while_loop.cskt @@ -0,0 +1,34 @@ +while loops are 1:1 with the intermediate representation's +single loop kind. +compound assignments desugar into a simple pair of a simple +assignment and binary operation. + +<<< + +main = fun () { + var i int = 0 + while i < 10 { + i++ + } +} + +>>> + +(unit + (types + (type 0 int primitive) + (type 1 uint primitive) + (type 2 bool primitive) + (type 3 string primitive) + (type 4 float primitive) + (type 5 byte primitive) + (type 6 ascii primitive) + (type 7 void primitive)) + (functions + (function 0 main main (returns (ref void)) (block + (declaration i (ref int) (initializer (expr 0))) + (loop (condition (expr (binary < (expr (name i)) (expr 10)))) (block + (assign (expr (name i)) (expr (binary + (expr (name i)) (expr 1)))) + )) + ))) + (emission_order 0 1 2 3 4 5 6 7)) diff --git a/boot/tests/test.c b/boot/tests/test.c index 841df09..051dbc4 100644 --- a/boot/tests/test.c +++ b/boot/tests/test.c @@ -63,9 +63,10 @@ enum Test_Target TARGET_NONE = 0, TARGET_LEX = 1 << 0, TARGET_PARSE = 1 << 1, - TARGET_TRANSPILE = 1 << 2, + TARGET_LOWER = 1 << 2, + TARGET_TRANSPILE = 1 << 3, - TARGET_ALL = TARGET_LEX | TARGET_PARSE | TARGET_TRANSPILE, + TARGET_ALL = TARGET_LEX | TARGET_PARSE | TARGET_LOWER | TARGET_TRANSPILE, }; struct Known_Target @@ -73,9 +74,8 @@ struct Known_Target const ascii* name; enum Test_Target target; } known_targets[] = { - { "lex", TARGET_LEX }, - { "parse", TARGET_PARSE }, - { "transpile", TARGET_TRANSPILE }, + { "lex", TARGET_LEX }, { "parse", TARGET_PARSE }, + { "lower", TARGET_LOWER }, { "transpile", TARGET_TRANSPILE }, { "all", TARGET_ALL }, }; @@ -531,6 +531,15 @@ parse_tests(bool adjust) return tests(base_path, base_command, adjust); } +struct Result_Summary +lower_tests(bool adjust) +{ + const ascii* base_path = "./boot/tests/lower/"; + const ascii* base_command = "./build/catboot --test-lower"; + + return tests(base_path, base_command, adjust); +} + struct Result_Summary transpile_tests(bool adjust) { @@ -582,6 +591,10 @@ main(int argc, const ascii** argv) summary = parse_tests(adjust); break; + case TARGET_LOWER: + summary = lower_tests(adjust); + break; + case TARGET_TRANSPILE: summary = transpile_tests(adjust); break; -- cgit 1.4.1