/* * 700_e2e — end-to-end. Drive `ww build` on a small source program, * run the produced binary, check the exit status. This is the real * user-facing happy path. */ #include #include #include #include #include #include static int runwait(const char *cmd) { int rc = system(cmd); if (rc == -1) return -1; if (WIFEXITED(rc)) return WEXITSTATUS(rc); return 1; } struct row { const char *src; int want_exit; }; static const struct row rows[] = { { "fn main() i32 = { return 42; };", 42 }, { "fn add(a: i32, b: i32) i32 = { return a + b; };\n" "fn main() i32 = { return add(7, 35); };", 42 }, { "fn main() i32 = {\n" " let i: i32 = 0;\n" " let s: i32 = 0;\n" " for (i < 10) { s += i; i += 1; };\n" " return s;\n" "};", 45 }, { "fn main() i32 = {\n" " let x: i32 = 100;\n" " if (x > 50) { return 1; };\n" " return 0;\n" "};", 1 }, { "fn main() i32 = {\n" " let a: i32 = 6;\n" " let b: i32 = 7;\n" " return a * b;\n" "};", 42 }, /* multi-return tuple, divmod */ { "fn divmod(a: i64, b: i64) (i64, i64) = { return a / b, a % b; };\n" "fn main() i32 = {\n" " let q, r = divmod(17, 5);\n" " return (q + r): i32;\n" "};", 5 }, /* fixed array, byte-wise read/write */ { "fn main() i32 = {\n" " let buf: [4]u8;\n" " buf[0] = 1: u8;\n" " buf[1] = 2: u8;\n" " buf[2] = 3: u8;\n" " buf[3] = 4: u8;\n" " let sum: i32 = 0;\n" " let i: i32 = 0;\n" " for (i < 4) { sum += buf[i]: i32; i += 1; };\n" " return sum;\n" "};", 10 }, /* float: arg, arith, literal, cast back to int */ { "fn area(r: f64) f64 = { return 3.14 * r * r; };\n" "fn main() i32 = { let a: f64 = area(5.0); return a: i32; };", 78 }, /* float comparison: must emit UCOMISD + JA (not CMPQ + JG) */ { "fn main() i32 = {\n" " let a: f64 = 1.5;\n" " let b: f64 = 2.5;\n" " if (a < b) { if (b > a) { return 7; }; };\n" " return 0;\n" "};", 7 }, /* float ==/!= via UCOMISD */ { "fn main() i32 = {\n" " let a: f64 = 3.14;\n" " let b: f64 = 3.14;\n" " if (a == b) { return 11; };\n" " return 0;\n" "};", 11 }, /* function pointer: take address of a named fn, call indirectly */ { "fn add(a: i32, b: i32) i32 = { return a + b; };\n" "fn main() i32 = {\n" " let fp: fn(a: i32, b: i32) i32 = add;\n" " return fp(20, 22);\n" "};", 42 }, /* string literal via syscall — exit code = bytes written */ { "@symbol(\"rt_syscall\") fn rt_syscall(num: i64, a: i64, b: i64, c: i64) i64;\n" "fn print(s: str) i64 = { return rt_syscall(1, 1, s.ptr: i64, s.len: i64); };\n" "fn main() i32 = { return print(\"hello, world\\n\"): i32; };", 13 }, /* 9 args — 3 spill to the stack */ { "fn s9(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32, h: i32, i: i32) i32 = {\n" " return a + b + c + d + e + f + g + h + i;\n" "};\n" "fn main() i32 = { return s9(1,2,3,4,5,6,7,8,9); };", 45 }, /* defer: LIFO at function return */ { "@symbol(\"rt_syscall\") fn rt_syscall(num: i64, a: i64, b: i64, c: i64) i64;\n" "fn out(c: i32) void = { rt_syscall(1, 1, (&c): i64, 1); };\n" "fn main() i32 = {\n" " let c1: i32 = 0;\n" " let c2: i32 = 0;\n" " c1 = 65;\n" /* 'A' */ " c2 = 66;\n" /* 'B' */ " defer out(c1);\n" " defer out(c2);\n" " return 0;\n" "};", 0 }, /* struct-by-value: 16B all-int passed by value */ { "type pair = struct { a: i64, b: i64 };\n" "fn sum(p: pair) i64 = { return p.a + p.b; };\n" "fn main() i32 = {\n" " let p: pair = pair { a = 10, b = 32 };\n" " return sum(p): i32;\n" "};", 42 }, /* f32 cast + arithmetic */ { "fn add32(a: f32, b: f32) f32 = { return a + b; };\n" "fn main() i32 = {\n" " let r: f32 = add32(2.5: f32, 7.5: f32);\n" " return r: i32;\n" "};", 10 }, /* slice from array: build header, iterate via index/len */ { "fn main() i32 = {\n" " let arr: [4]u8;\n" " arr[0] = 10: u8; arr[1] = 20: u8;\n" " arr[2] = 30: u8; arr[3] = 99: u8;\n" " let s: []u8 = arr[0:3];\n" " let sum: i32 = 0;\n" " let i: i32 = 0;\n" " for (i < s.len) { sum += s[i]: i32; i += 1; };\n" " return sum;\n" "};", 60 }, /* module imports: use os and call os.write; exit code = bytes */ { "import os;\n" "fn main() i32 = { return os.write(1, \"ok\\n\".ptr, 3): i32; };", 3 }, /* typed integer literals */ { "fn main() i32 = {\n" " let buf: [4]u8;\n" " buf[0] = 65u8; buf[1] = 66u8; buf[2] = 67u8; buf[3] = 0u8;\n" " let s: i32 = 0;\n" " let i: i32 = 0;\n" " for (i < 3) { s += buf[i]: i32; i += 1; };\n" " return s;\n" "};", 198 }, /* full stdlib stack: use os + strconv, str return, write * the formatted number to stdout. exit code = number length. */ { "import os;\n" "import strconv;\n" "fn main() i32 = {\n" " let s: str = strconv.i64tos(12345, strconv.base.DEC);\n" " os.write(1, s.ptr, s.len: u64);\n" " os.write(1, \"\\n\".ptr, 1u64);\n" " return s.len;\n" "};", 5 }, /* alloc + free via mmap-backed runtime — write through allocated * memory and free it. exit = 0 if the allocation succeeded. */ { "import os;\n" "import rt;\n" "fn main() i32 = {\n" " let p: *void = rt.malloc(4096u64);\n" " if (p == nil) { return 1; };\n" " let bp: *u8 = p: *u8;\n" " bp[0] = 65u8;\n" " os.write(1, bp, 1u64);\n" " os.free(p, 4096u64);\n" " return 0;\n" "};", 0 }, /* argv: kernel passes argc in DI, argv in SI. */ { "fn main(argc: i32, argv: **u8) i32 = { return argc; };", 1 }, /* i64 array: scaled indexing (elem size 8) */ { "fn main() i32 = {\n" " let arr: [4]i64;\n" " arr[0] = 10; arr[1] = 20; arr[2] = 30; arr[3] = 40;\n" " let sum: i64 = 0;\n" " let i: i32 = 0;\n" " for (i < 4) { sum += arr[i]; i += 1; };\n" " return sum: i32;\n" "};", 100 }, /* break out of an infinite loop early */ { "fn main() i32 = {\n" " let i: i32 = 0;\n" " for () {\n" " i += 1;\n" " if (i == 7) { break; };\n" " };\n" " return i;\n" "};", 7 }, /* switch with multi-expr cases + default */ { "fn classify(x: i32) i32 = {\n" " switch (x) {\n" " case 1, 2, 3: return 10;\n" " case 10: return 99;\n" " case: return 50;\n" " };\n" " return -1;\n" "};\n" "fn main() i32 = {\n" " return classify(2) + classify(10) + classify(99);\n" "};", 159 }, /* 10+99+50=159 */ /* fmt module: stdlib formatter for strings; ints compose * via strconv.i64tos. */ { "import fmt;\n" "import strconv;\n" "fn main() i32 = {\n" " fmt.println(\"ww\");\n" " fmt.println(strconv.i64tos(42, strconv.base.DEC));\n" " fmt.println(strconv.i64tos(-7, strconv.base.DEC));\n" " return 0;\n" "};", 0 }, /* struct with i32 fields: MOVL/MOVSXD avoids clobbering neighbors */ { "type point = struct { x: i32, y: i32 };\n" "fn distsq(p: point) i32 = { return p.x * p.x + p.y * p.y; };\n" "fn main() i32 = {\n" " let p: point = point { x = 3, y = 4 };\n" " return distsq(p);\n" "};", 25 }, /* str equality via == and != */ { "fn main() i32 = {\n" " let a: str = \"hello\";\n" " let b: str = \"hello\";\n" " let c: str = \"world\";\n" " let n: i32 = 0;\n" " if (a == b) { n += 10; };\n" " if (a != c) { n += 20; };\n" " return n;\n" "};", 30 }, /* CLAUDE.md's move pattern: ptr-to-struct compound field write */ { "type point = struct { x: i32, y: i32 };\n" "fn move(p: *point, dx: i32, dy: i32) void = {\n" " p.x += dx; p.y += dy;\n" "};\n" "fn main() i32 = {\n" " let pt: point = point { x = 0, y = 0 };\n" " move(&pt, 3, 4);\n" " return pt.x + pt.y;\n" "};", 7 }, /* vtable polymorphism: struct of fn pointers, indirect call */ { "type ops = struct { add: fn(a: i32, b: i32) i32 };\n" "fn plus(a: i32, b: i32) i32 = { return a + b; };\n" "fn main() i32 = {\n" " let v: ops = ops { add = plus };\n" " return v.add(20, 22);\n" "};", 42 }, /* compound bitwise/shift assigns */ { "fn main() i32 = {\n" " let x: i32 = 100;\n" " x &= 0x3f; x |= 0x80; x ^= 0xc4;\n" " x *= 2; x <<= 1; x >>= 2;\n" " return x;\n" "};", 96 }, /* Hare-style builtins: append(s, v) and len(s). The compiler * lowers append to a CALL into libwwrt.a's appendu8 / appendi64 * by element size — no `use rt;` or `use slices;` required. */ { "import os;\n" "fn main() i32 = {\n" " let s: []u8;\n" " s.ptr = nil; s.len = 0; s.cap = 0;\n" " append(s, 88u8); append(s, 89u8); append(s, 90u8);\n" " os.write(1, s.ptr, len(s): u64);\n" " os.write(1, \"\\n\".ptr, 1u64);\n" " return len(s);\n" "};", 3 }, /* str-returning function: 16-byte return via AX:DX (SysV). The * caller's str slot is filled from those two regs. */ { "import strings;\n" "import fmt;\n" "fn main() i32 = {\n" " let r: str = strings.concat(\"hello, \", \"world\");\n" " fmt.println(r);\n" " return r.len;\n" "};", 12 }, /* variadic append + static qualifier (Hare idiom) */ { "import os;\n" "fn main() i32 = {\n" " let s: []u8;\n" " s.ptr = nil; s.len = 0; s.cap = 0;\n" " static append(s, 72u8, 105u8, 33u8, 10u8);\n" " os.write(1, s.ptr, len(s): u64);\n" " return len(s);\n" "};", 4 }, /* alloc() builtin: heap-allocate a struct, init from struct-lit. * `package main;` is required so the bare-alloc-builtin gate * (task #23) sees c->cur_mod=="main" and doesn't suppress the * builtin via the inherited os.alloc decl. Task #30 graduated * the builtin to `(*T | nomem)`; the `!` aborts on OOM. */ { "package main;\n" "import rt;\n" "type point = struct { x: i32, y: i32 };\n" "fn main() i32 = {\n" " let p: *point = alloc(point { x = 3, y = 4 })!;\n" " return p.x * p.x + p.y * p.y;\n" "};", 25 }, /* Hare-style range loop: for (let x .. slice) iterates elements */ { "import os;\n" "fn main() i32 = {\n" " let s: []u8;\n" " s.ptr = nil; s.len = 0; s.cap = 0;\n" " append(s, 10u8, 20u8, 30u8, 40u8);\n" " let total: i32 = 0;\n" " for (let b .. s) { total += b: i32; };\n" " return total;\n" "};", 100 }, /* alloc([], n): fresh empty slice with cap n. `package main;` for * the same reason as the value-form test above (task #23 gate). * Task #30 graduated the slice form to `([]T | nomem)`; the `!` * aborts on OOM. */ { "package main;\n" "import rt;\n" "fn main() i32 = {\n" " let s: []u8 = alloc([], 16)!;\n" " append(s, 72u8, 105u8);\n" " return s.cap;\n" "};", 16 }, /* #45: alloc([], n) now defers element type to the let-init LHS. * `[]rune` is 4B-per-element; the cgen shortcut scales count by * size(T). Returns s.cap = 8. */ { "package main;\n" "import rt;\n" "fn main() i32 = {\n" " let s: []rune = alloc([], 8)!;\n" " return s.cap;\n" "};", 8 }, /* #45: same as above for `[]str` (16B-per-element). */ { "package main;\n" "import rt;\n" "fn main() i32 = {\n" " let s: []str = alloc([], 4)!;\n" " return s.cap;\n" "};", 4 }, /* #45: `?` form. doit propagates nomem to its (i32 | nomem) * return; the alloc-slice shortcut emits MOVQ $nomem_tag, AX + * propret on null. doit returns 12 on success; main unwraps. */ { "package main;\n" "import rt;\n" "fn doit() (i32 | nomem) = {\n" " let s: []str = alloc([], 12)?;\n" " return s.cap: i32;\n" "};\n" "fn main() i32 = {\n" " return doit()!;\n" "};", 12 }, /* alloc-builtin shadow (task #23): a non-main package declares * `fn alloc(n: i64) i64` and calls it bare. The same-module gate * must suppress the builtin and dispatch to the user fn so the * call returns n+100. Pre-gate this site lands in the typed-builtin * path: arg is i64 → returns *i64 → init-type fails against the * declared i64 (and would over-allocate against rt_malloc anyway). * Inline multi-`package` mirrors driver-concatenated layout; the * `import myos;` directive is silently skipped by locate_import * (no external module by that name). */ { "package myos;\n" "fn alloc(n: i64) i64 = { return n + 100; };\n" "fn run() i64 = { return alloc(7); };\n" "package main;\n" "import myos;\n" "fn main() i32 = {\n" " return myos.run(): i32;\n" "};", 107 }, /* variadic spread: append(dst, src...) iterates src */ { "import os;\n" "fn main() i32 = {\n" " let src: []u8;\n" " src.ptr = nil; src.len = 0; src.cap = 0;\n" " append(src, 65u8, 66u8, 67u8);\n" " let dst: []u8;\n" " dst.ptr = nil; dst.len = 0; dst.cap = 0;\n" " append(dst, src...);\n" " os.write(1, dst.ptr, dst.len: u64);\n" " os.write(1, \"\\n\".ptr, 1u64);\n" " return dst.len;\n" "};", 3 }, /* Hare-style tuple destructure in let */ { "fn divmod(a: i64, b: i64) (i64, i64) = { return a / b, a % b; };\n" "fn main() i32 = {\n" " let (q, r) = divmod(17, 5);\n" " return (q + r): i32;\n" "};", 5 }, /* Hare-style tuple destructure in for-range */ { "fn main() i32 = {\n" " let buf: [4]i64;\n" " buf[0] = 1; buf[1] = 10; buf[2] = 2; buf[3] = 20;\n" " let s: [](i64, i64);\n" " s.ptr = buf.ptr: *(i64, i64);\n" " s.len = 2; s.cap = 2;\n" " let total: i64 = 0;\n" " for (let (k, v) .. s) { total += k + v; };\n" " return total: i32;\n" "};", 33 }, /* Hare-style tuple positional access: t.0, t.1 */ { "fn pair() (i64, i64) = { return 10, 32; };\n" "fn main() i32 = {\n" " let t: (i64, i64) = pair();\n" " return (t.0 + t.1): i32;\n" "};", 42 }, /* Hare-style abort/assert + free() builtin. `package main;` for * the alloc-builtin gate (task #23). Task #30 graduated the * builtin to a fallible signature; the `!` aborts on OOM. */ { "package main;\n" "import os;\n" "import rt;\n" "type point = struct { x: i64, y: i64 };\n" "fn main() i32 = {\n" " let p: *point = alloc(point { x = 7, y = 35 })!;\n" " let r: i64 = p.x + p.y;\n" " free(p);\n" " os.assert(r == 42, \"sum mismatch\\n\");\n" " return r: i32;\n" "};", 42 }, /* Hare-style struct embedding: bare-name embed + anonymous * nested struct, fields promoted to the outer scope. Both * field-by-field assignment and flat struct-literal init must * see promoted fields. */ { "type point = struct { x: i32, y: i32 };\n" "type vec = struct {\n" " point,\n" " struct { z: i32 },\n" " w: i32,\n" "};\n" "fn main() i32 = {\n" " let v: vec = vec { x = 1, y = 2, z = 3, w = 36 };\n" " return v.x + v.y + v.z + v.w;\n" "};", 42 }, /* 3-field tuple destructure in for-range */ { "fn main() i32 = {\n" " let buf: [3]i64;\n" " buf[0] = 5; buf[1] = 7; buf[2] = 30;\n" " let s: [](i64, i64, i64);\n" " s.ptr = buf.ptr: *(i64, i64, i64);\n" " s.len = 1; s.cap = 1;\n" " let total: i64 = 0;\n" " for (let (a, b, c) .. s) { total += a + b + c; };\n" " return total: i32;\n" "};", 42 }, /* Hare-style tagged union + match */ { "fn parse(n: i64) (i64 | i32) = {\n" " if (n < 0) { return 1: i32; };\n" " return n;\n" "};\n" "fn main() i32 = {\n" " let r: (i64 | i32) = parse(40);\n" " let s: i64 = 0;\n" " match (r) {\n" " case let v: i64 => s = v;\n" " case let e: i32 => s = -1;\n" " };\n" " return (s + 2): i32;\n" "};", 42 }, /* ? propagation up the stack */ { "fn try1(n: i64) (i64 | i32) = {\n" " if (n < 0) { return 99: i32; };\n" " return n;\n" "};\n" "fn try2(n: i64) (i64 | i32) = {\n" " let v: i64 = try1(n)?;\n" " return v + 100;\n" "};\n" "fn main() i32 = {\n" " let r: (i64 | i32) = try2(-1);\n" " let s: i64 = 0;\n" " match (r) {\n" " case let v: i64 => s = v;\n" " case let e: i32 => s = e: i64;\n" " };\n" " return s: i32;\n" "};", 99 }, /* match cases written in reverse variant order — dispatch must * use the variant tag, not the case position */ { "fn parse(n: i64) (i64 | i32) = {\n" " if (n < 0) { return 7: i32; };\n" " return n;\n" "};\n" "fn main() i32 = {\n" " let r: (i64 | i32) = parse(-1);\n" " match (r) {\n" " case let e: i32 => return e;\n" " case let v: i64 => return (v + 1000): i32;\n" " };\n" " return 0;\n" "};", 7 }, /* let-init from a bare variant value: tag must be synthesised */ { "fn main() i32 = {\n" " let r: (i64 | i32) = 7: i32;\n" " match (r) {\n" " case let v: i64 => return 1;\n" " case let e: i32 => return e;\n" " };\n" " return 0;\n" "};", 7 }, /* let-init from an untyped literal: variant inclusion must let * the assignability check through, and the default variant wins */ { "fn main() i32 = {\n" " let r: (i64 | i32) = 5;\n" " match (r) {\n" " case let v: i64 => return v: i32;\n" " case let e: i32 => return 99;\n" " };\n" " return 0;\n" "};", 5 }, /* assignment to a tagged-union local: same tag synthesis */ { "fn main() i32 = {\n" " let r: (i64 | i32) = 0;\n" " r = 9: i32;\n" " match (r) {\n" " case let v: i64 => return 1;\n" " case let e: i32 => return e;\n" " };\n" " return 0;\n" "};", 9 }, /* default arm `case =>` */ { "fn parse(n: i64) (i64 | i32) = {\n" " if (n < 0) { return 1: i32; };\n" " return n;\n" "};\n" "fn main() i32 = {\n" " let r: (i64 | i32) = parse(-1);\n" " match (r) {\n" " case let v: i64 => return 1;\n" " case => return 7;\n" " };\n" " return 0;\n" "};", 7 }, /* `case T =>` without binding still dispatches by tag */ { "fn parse(n: i64) (i64 | i32) = {\n" " if (n < 0) { return 1: i32; };\n" " return n;\n" "};\n" "fn main() i32 = {\n" " let r: (i64 | i32) = parse(40);\n" " match (r) {\n" " case i32 => return 1;\n" " case let v: i64 => return v: i32;\n" " };\n" " return 0;\n" "};", 40 }, /* named alias over `!(A | B)`: param spill must size to the * flattened union (8B tag + 8B payload), not 8B-scalar. Pinned * the cstage/wwstage divergence where wwstage's istaggedtype * was alias-blind and the slot+8 read trailed into saved BP. */ { "type invalid = !i32;\n" "type overflow = !void;\n" "type error = !(invalid | overflow);\n" "fn errcode(e: error) i32 = {\n" " match (e) {\n" " case let v: invalid => return v: i32;\n" " case let v: overflow => return 99;\n" " };\n" " return 0;\n" "};\n" "fn main() i32 = {\n" " let e: error = 7: invalid;\n" " return errcode(e);\n" "};", 7 }, /* str-typed variant payload: let-init with a string literal, * match-binding loads ptr+len from the slot */ { "fn main() i32 = {\n" " let r: (i64 | str) = \"hello, world\";\n" " match (r) {\n" " case let n: i64 => return 1;\n" " case let s: str => return s.len: i32;\n" " };\n" " return 0;\n" "};", 12 }, /* assigning a string into a tagged-union local */ { "fn main() i32 = {\n" " let r: (i64 | str) = 0;\n" " r = \"abc\";\n" " match (r) {\n" " case let n: i64 => return 1;\n" " case let s: str => return s.len: i32;\n" " };\n" " return 0;\n" "};", 3 }, /* fn returning (T | str) — wide return ABI */ { "fn parse(n: i64) (i64 | str) = {\n" " if (n < 0) { return \"negative number\"; };\n" " return n;\n" "};\n" "fn main() i32 = {\n" " let r: (i64 | str) = parse(-1);\n" " match (r) {\n" " case let n: i64 => return 1;\n" " case let s: str => return s.len: i32;\n" " };\n" " return 0;\n" "};", 15 }, /* ? propagating a str-typed error all the way up */ { "fn try1(n: i64) (i64 | str) = {\n" " if (n < 0) { return \"fail\"; };\n" " return n;\n" "};\n" "fn try2(n: i64) (i64 | str) = {\n" " let v: i64 = try1(n)?;\n" " return v + 100;\n" "};\n" "fn main() i32 = {\n" " let r: (i64 | str) = try2(-1);\n" " match (r) {\n" " case let n: i64 => return n: i32;\n" " case let s: str => return s.len: i32;\n" " };\n" " return 0;\n" "};", 4 }, /* ? with different success types but shared error: operand * (i32 | str), enclosing (i64 | str). Success widens. */ { "fn inner(b: bool) (i32 | str) = {\n" " if (b) { return 7; };\n" " return \"err\";\n" "};\n" "fn outer(b: bool) (i64 | str) = {\n" " let v: i32 = inner(b)?;\n" " return v: i64 + 100;\n" "};\n" "fn main() i32 = {\n" " let r: (i64 | str) = outer(false);\n" " match (r) {\n" " case let n: i64 => return n: i32;\n" " case let s: str => return s.len: i32;\n" " };\n" " return 0;\n" "};", 3 }, /* defer runs queued exprs in LIFO order, before the return * expression is evaluated. Each call appends a decimal digit * to acc via *&acc — return reads the post-defer state. */ { "fn rec(p: *i32, c: i32) i32 = {\n" " *p = *p * 10 + c;\n" " return 0;\n" "};\n" "fn main() i32 = {\n" " let acc: i32 = 0;\n" " defer rec(&acc, 1);\n" " defer rec(&acc, 2);\n" " defer rec(&acc, 3);\n" " return acc;\n" "};", 65 }, /* 321 mod 256 */ /* defer also fires on an implicit fall-through return (void fn). */ { "fn rec(p: *i32, c: i32) i32 = {\n" " *p = *p * 10 + c;\n" " return 0;\n" "};\n" "fn run(p: *i32) void = {\n" " defer rec(p, 7);\n" " defer rec(p, 8);\n" " // no explicit return — implicit fall-through path\n" "};\n" "fn main() i32 = {\n" " let acc: i32 = 0;\n" " run(&acc);\n" " return acc;\n" "};", 87 }, /* defers fire 8, 7 → 8 then 87 */ /* yield from match-as-expression: each arm yields a value; * the match itself is bound to a let. */ { "fn pick(b: bool) (i32 | str) = {\n" " if (b) { return 7; };\n" " return \"abc\";\n" "};\n" "fn main() i32 = {\n" " let r: (i32 | str) = pick(true);\n" " let v: i32 = match (r) {\n" " case let n: i32 => yield n + 1;\n" " case let s: str => yield s.len: i32 + 100;\n" " };\n" " return v;\n" "};", 8 }, { "fn pick(b: bool) (i32 | str) = {\n" " if (b) { return 7; };\n" " return \"abc\";\n" "};\n" "fn main() i32 = {\n" " let r: (i32 | str) = pick(false);\n" " let v: i32 = match (r) {\n" " case let n: i32 => yield n + 1;\n" " case let s: str => yield s.len: i32 + 100;\n" " };\n" " return v;\n" "};", 103 }, /* Nullable pointer folding: `(*T | void)` is one 8-byte word * where null = void variant. match/is/as/?/! all key off the * pointer-vs-null discriminator instead of a separate tag. */ { "fn lookup(p: *i32, b: bool) (*i32 | void) = {\n" " if (b) { return p; };\n" " return;\n" "};\n" "fn use_arg(r: (*i32 | void)) i32 = {\n" " match (r) {\n" " case let q: *i32 => return *q;\n" " case void => return 99;\n" " };\n" " return 0;\n" "};\n" "fn main() i32 = {\n" " let x: i32 = 42;\n" " let ok: i32 = use_arg(lookup(&x, true));\n" " let no: i32 = use_arg(lookup(&x, false));\n" " if (ok != 42) { return 1; };\n" " if (no != 99) { return 2; };\n" " return 7;\n" "};", 7 }, /* Nullable with is/as: discriminator is ptr-vs-null. */ { "fn lookup(p: *i32, b: bool) (*i32 | void) = {\n" " if (b) { return p; };\n" " return;\n" "};\n" "fn main() i32 = {\n" " let x: i32 = 42;\n" " let r1: (*i32 | void) = lookup(&x, true);\n" " let r2: (*i32 | void) = lookup(&x, false);\n" " let acc: i32 = 0;\n" " if (r1 is *i32) { acc += 1; };\n" " if (r2 is void) { acc += 2; };\n" " let p: *i32 = r1 as *i32;\n" " if (*p == 42) { acc += 4; };\n" " return acc;\n" "};", 7 }, /* `!`-flagged error variants: success picked by absence of `!`, * errors picked by presence. Tag remap still works across * different variant orders between operand and enclosing fn. */ { "type invalid = !i32;\n" "type overflow = !void;\n" "fn inner(n: i32) (invalid | i64 | overflow) = {\n" " if (n == 0) { return 7: invalid; };\n" " if (n < 0) { return void: overflow; };\n" " return n: i64 + 1000;\n" "};\n" "fn outer(n: i32) (overflow | i64 | invalid) = {\n" " let v: i64 = inner(n)?;\n" " return v + 1;\n" "};\n" "fn main() i32 = {\n" " let r: (overflow | i64 | invalid) = outer(0);\n" " match (r) {\n" " case let v: i64 => return v: i32;\n" " case let e: invalid => return e + 100;\n" " case let e: overflow => return 999;\n" " };\n" " return 0;\n" "};", 107 }, /* ? with reversed error variant order: operand (i32 | str | bool), * enclosing (i64 | bool | str). Tag remap maps str:1→2, bool:2→1. */ { "fn inner(n: i32) (i32 | str | bool) = {\n" " if (n == 0) { return \"z\"; };\n" " if (n < 0) { return false; };\n" " return n;\n" "};\n" "fn outer(n: i32) (i64 | bool | str) = {\n" " let v: i32 = inner(n)?;\n" " return v: i64 + 1000;\n" "};\n" "fn main() i32 = {\n" " let r: (i64 | bool | str) = outer(-1);\n" " match (r) {\n" " case let n: i64 => return n: i32;\n" " case let b: bool => { if (!b) { return 7; }; return 8; };\n" " case let s: str => return 9;\n" " };\n" " return 0;\n" "};", 7 }, /* ! success unwrap when the first variant is itself str. * Uses ! (not ?) because main's return is i32, not tagged — * ? would require error variants to be propagatable to the * enclosing return. ! aborts on the error variant instead. */ { "fn make() (str | i64) = {\n" " return \"ok\";\n" "};\n" "fn main() i32 = {\n" " let r: (str | i64) = make();\n" " let v: str = r!;\n" " return v.len: i32;\n" "};", 2 }, /* type error = str; named-alias variant works through the * full happy/error path */ { "type error = str;\n" "fn read(n: i64) (i64 | error) = {\n" " if (n < 0) { return \"eof\": error; };\n" " return n + 1;\n" "};\n" "fn main() i32 = {\n" " let r: (i64 | error) = read(-1);\n" " match (r) {\n" " case let v: i64 => return v: i32;\n" " case let e: error => return e.len: i32;\n" " };\n" " return 0;\n" "};", 3 }, /* multi-pattern arm: `case T1 | T2 =>` matches either tag */ { "fn pick(n: i64) (i64 | i32 | u32) = {\n" " if (n < 0) { return 1: i32; };\n" " if (n == 0) { return 2: u32; };\n" " return n;\n" "};\n" "fn main() i32 = {\n" " let r1: (i64 | i32 | u32) = pick(0);\n" " let r2: (i64 | i32 | u32) = pick(-1);\n" " let r3: (i64 | i32 | u32) = pick(7);\n" " let acc: i32 = 0;\n" " match (r1) {\n" " case let v: i64 => acc += 100;\n" " case i32 | u32 => acc += 1;\n" " };\n" " match (r2) {\n" " case let v: i64 => acc += 100;\n" " case i32 | u32 => acc += 10;\n" " };\n" " match (r3) {\n" " case let v: i64 => acc += v: i32;\n" " case i32 | u32 => acc += 100;\n" " };\n" " return acc;\n" "};", 18 }, /* Named-alias tagged union as fn arg + ≤16B variants */ { "type result = (i64 | i32);\n" "fn unwrap(r: result) i64 = {\n" " match (r) {\n" " case let v: i64 => return v;\n" " case let e: i32 => return e: i64;\n" " };\n" " return -1;\n" "};\n" "fn main() i32 = {\n" " let r1: result = 100;\n" " let r2: result = 7: i32;\n" " return (unwrap(r1) + unwrap(r2)): i32;\n" "};", 107 }, /* 24B tagged-union arg with str variant */ { "type result = (i64 | str);\n" "fn classify(r: result) i32 = {\n" " match (r) {\n" " case let v: i64 => return 1;\n" " case let e: str => return e.len: i32;\n" " };\n" " return -1;\n" "};\n" "fn main() i32 = {\n" " let r1: result = \"hello\";\n" " let r2: result = 42;\n" " return classify(r1) + classify(r2);\n" "};", 6 }, /* Tagged union as struct field — both literal init and assign, * and match-on-field reads from the field's slot in place */ { "type point = struct {\n" " x: i32,\n" " err: (i64 | str),\n" "};\n" "fn main() i32 = {\n" " let p: point = point { x = 1, err = 0 };\n" " p.err = \"updated\";\n" " match (p.err) {\n" " case let v: i64 => return 0;\n" " case let e: str => return e.len: i32;\n" " };\n" " return -1;\n" "};", 7 }, /* Pointer variant in a tagged union */ { "type point = struct { x: i32, y: i32 };\n" "fn main() i32 = {\n" " let p: point = point { x = 3, y = 4 };\n" " let r: (*point | str) = &p;\n" " match (r) {\n" " case let pp: *point => return pp.x + pp.y;\n" " case let e: str => return -1;\n" " };\n" " return 0;\n" "};", 7 }, /* Forwarding `return inner(n)` when both fns share a tagged- * union return type — value passes through unwrapped */ { "type result = (i64 | str);\n" "fn inner(n: i64) result = {\n" " if (n < 0) { return \"neg\"; };\n" " return n + 1;\n" "};\n" "fn outer(n: i64) result = {\n" " return inner(n);\n" "};\n" "fn main() i32 = {\n" " let r: result = outer(-1);\n" " match (r) {\n" " case let v: i64 => return v: i32;\n" " case let e: str => return e.len: i32;\n" " };\n" " return 0;\n" "};", 3 }, /* match directly on a call expression (no intermediate let) */ { "fn make(n: i64) (i64 | str) = {\n" " if (n < 0) { return \"neg\"; };\n" " return n + 1;\n" "};\n" "fn main() i32 = {\n" " match (make(-1)) {\n" " case let v: i64 => return v: i32;\n" " case let e: str => return e.len: i32;\n" " };\n" " return 0;\n" "};", 3 }, /* Struct variant of a tagged union at the call site. The arg is * an N_STRUCTLIT, the param is (str|point). Widening at the call * site must zero-fill the scratch slot, store each field at * slot+8+field_off, then push the slot words high→low. */ { "type point = struct { x: i32, y: i32 };\n" "fn classify(r: (str | point)) i32 = {\n" " match (r) {\n" " case let s: str => return 0 - s.len: i32;\n" " case let p: point => return p.x + p.y;\n" " };\n" " return -1;\n" "};\n" "fn main() i32 = {\n" " return classify(point { x = 10, y = 20 });\n" "};", 30 }, /* Struct variant passed as a typed local. Widening copies the * struct words from the local into the scratch slot at +8. */ { "type point = struct { x: i32, y: i32 };\n" "fn classify(r: (str | point)) i32 = {\n" " match (r) {\n" " case let s: str => return 0 - s.len: i32;\n" " case let p: point => return p.x + p.y;\n" " };\n" " return -1;\n" "};\n" "fn main() i32 = {\n" " let p: point = point { x = 11, y = 22 };\n" " return classify(p);\n" "};", 33 }, /* let-init of a tagged-union local from a struct literal: the * field stores go into slot+8+field_off in-place; tag patched * last. */ { "type point = struct { x: i32, y: i32 };\n" "fn main() i32 = {\n" " let r: (str | point) = point { x = 7, y = 35 };\n" " match (r) {\n" " case let s: str => return 0 - s.len: i32;\n" " case let p: point => return p.x + p.y;\n" " };\n" " return -1;\n" "};", 42 }, /* Reassign a tagged-union local to a struct literal. Same path * as let-init but writing into an already-allocated slot. */ { "type point = struct { x: i32, y: i32 };\n" "fn main() i32 = {\n" " let r: (str | point) = \"init\";\n" " r = point { x = 100, y = 23 };\n" " match (r) {\n" " case let s: str => return 0 - s.len: i32;\n" " case let p: point => return p.x + p.y;\n" " };\n" " return -1;\n" "};", 123 }, /* Return a struct variant of the fn's tagged return type. The * scratch-slot path materialises the struct payload then loads * AX/DX/CX from it. */ { "type point = struct { x: i32, y: i32 };\n" "fn make() (str | point) = {\n" " return point { x = 12, y = 30 };\n" "};\n" "fn main() i32 = {\n" " let r: (str | point) = make();\n" " match (r) {\n" " case let s: str => return 0 - s.len: i32;\n" " case let p: point => return p.x + p.y;\n" " };\n" " return -1;\n" "};", 42 }, /* Widen a smaller tagged union to a wider one across slot sizes * AND remapped variant indices. (i32 | rune) is 16B with i32 at * tag 0; (str | i32 | rune) is 24B with i32 at tag 1. The widen * path copies the slot words, zero-pads to 24B, then runs a * CMPQ-chain switch to remap src tag 0 → dst tag 1. */ { "fn classify(r: (str | i32 | rune)) i32 = {\n" " match (r) {\n" " case let s: str => return 1;\n" " case let n: i32 => return n;\n" " case let c: rune => return c: i32 + 100;\n" " };\n" " return 0;\n" "};\n" "fn main() i32 = {\n" " let inner: (i32 | rune) = 42: i32;\n" " return classify(inner);\n" "};", 42 }, /* Same shape but the rune variant of inner exercises the tag * remap from src tag 1 → dst tag 2. The rune literal needs an * explicit `: rune` cast — `'A'` is an untyped rune and the * variant search picks the first variant that accepts it (i32, * which also accepts untyped runes). 'A' = 65 + 100 = 165. */ { "fn classify(r: (str | i32 | rune)) i32 = {\n" " match (r) {\n" " case let s: str => return 1;\n" " case let n: i32 => return n;\n" " case let c: rune => return c: i32 + 100;\n" " };\n" " return 0;\n" "};\n" "fn main() i32 = {\n" " let inner: (i32 | rune) = 'A': rune;\n" " return classify(inner);\n" "};", 165 }, /* let-init of a wider tagged union from a smaller-tagged local. */ { "fn main() i32 = {\n" " let inner: (i32 | rune) = 42: i32;\n" " let r: (str | i32 | rune) = inner;\n" " match (r) {\n" " case let s: str => return 1;\n" " case let n: i32 => return n;\n" " case let c: rune => return c: i32 + 100;\n" " };\n" " return 0;\n" "};", 42 }, /* Spread variant in tagged-union type — `(...inner | T)`. The * checker flattens the spread's variants into the enclosing * union so `outer` has variants {i32, rune, str}. */ { "type inner = (i32 | rune);\n" "type outer = (...inner | str);\n" "fn main() i32 = {\n" " let r: outer = 42: i32;\n" " match (r) {\n" " case let n: i32 => return n;\n" " case let c: rune => return c: i32;\n" " case let s: str => return 0;\n" " };\n" " return -1;\n" "};", 42 }, /* Tagged-union element in a fixed array — scalar+str variants. * Store via N_INDEX widening, read+match through cgexpr N_INDEX * tagged-slot load. 10 + len("hi")=2 + 5 = 17. */ { "fn main() i32 = {\n" " let arr: [3](i32 | str);\n" " arr[0] = 10;\n" " arr[1] = \"hi\";\n" " arr[2] = 5;\n" " let s: i32 = 0;\n" " let i: i32 = 0;\n" " for (i < 3) {\n" " match (arr[i]) {\n" " case let v: i32 => s += v;\n" " case let t: str => s += t.len: i32;\n" " };\n" " i += 1;\n" " };\n" " return s;\n" "};", 17 }, /* Tagged-union array with struct payload variant. Struct fields * are written at slot+8+field_off via cg_widen_tagged_store; the * read side just copies slot bytes into AX/DX/CX for match. * 5 + (7+11) + 2 + 9 = 34. */ { "type pair = struct { a: i32, b: i32 };\n" "fn main() i32 = {\n" " let arr: [4](i32 | pair | str);\n" " arr[0] = 5;\n" " arr[1] = pair { a = 7, b = 11 };\n" " arr[2] = \"yo\";\n" " arr[3] = 9;\n" " let s: i32 = 0;\n" " let i: i32 = 0;\n" " for (i < 4) {\n" " match (arr[i]) {\n" " case let v: i32 => s += v;\n" " case let p: pair => s += p.a + p.b;\n" " case let t: str => s += t.len: i32;\n" " };\n" " i += 1;\n" " };\n" " return s;\n" "};", 34 }, /* Slicing an array of tagged elements — the slice load path * uses the fallback (non-ident base) N_INDEX which loads slot * bytes from a computed address. 1 + 2 + 3 + 4 = 10. */ { "fn main() i32 = {\n" " let buf: [4](i32 | str);\n" " buf[0] = 1;\n" " buf[1] = \"ww\";\n" " buf[2] = 3;\n" " buf[3] = 4;\n" " let xs: [](i32 | str) = buf[0:4];\n" " let s: i32 = 0;\n" " let i: i32 = 0;\n" " for (i < 4) {\n" " match (xs[i]) {\n" " case let v: i32 => s += v;\n" " case let t: str => s += t.len: i32;\n" " };\n" " i += 1;\n" " };\n" " return s;\n" "};", 10 }, /* Passing arr[i] (tagged element) as a tagged arg — cgexpr leaves * the slot in AX/DX/CX which the call-site shuffle pushes onto * the arg stack. 5 + 30 (len 3 * 10) + 7 = 42. */ { "fn weight(v: (i32 | str)) i32 = {\n" " match (v) {\n" " case let n: i32 => return n;\n" " case let s: str => return s.len: i32 * 10;\n" " };\n" " return 0;\n" "};\n" "fn main() i32 = {\n" " let arr: [3](i32 | str);\n" " arr[0] = 5;\n" " arr[1] = \"abc\";\n" " arr[2] = 7;\n" " let s: i32 = 0;\n" " let i: i32 = 0;\n" " for (i < 3) {\n" " s += weight(arr[i]);\n" " i += 1;\n" " };\n" " return s;\n" "};", 42 }, /* let-init of a tagged local from arr[i] — the let path routes * through cg_widen_tagged_store which, for a tagged source via * cgexpr, spills AX/DX/CX into the slot. "ww!".len == 3. */ { "fn main() i32 = {\n" " let arr: [3](i32 | str);\n" " arr[0] = 11;\n" " arr[1] = \"ww!\";\n" " arr[2] = 7;\n" " let r: (i32 | str) = arr[1];\n" " match (r) {\n" " case let n: i32 => return n;\n" " case let s: str => return s.len: i32;\n" " };\n" " return 0;\n" "};", 3 }, /* Tagged-subset store into a wider tagged-union array element: * source slot is (i32|str), dest element is (i32|str|u64). The * store path materialises the subset in scratch then copies slot * bytes — tag remap is a no-op here (variant order matches). * arr[0]=9:i32, arr[1]="hi":str → 9 + 2 = 11. */ { "type inner = (i32 | str);\n" "fn main() i32 = {\n" " let arr: [2](i32 | str | u64);\n" " let v: inner = 9;\n" " arr[0] = v;\n" " let w: inner = \"hi\";\n" " arr[1] = w;\n" " let s: i32 = 0;\n" " let i: i32 = 0;\n" " for (i < 2) {\n" " match (arr[i]) {\n" " case let n: i32 => s += n;\n" " case let t: str => s += t.len: i32;\n" " case let u: u64 => s += 100;\n" " };\n" " i += 1;\n" " };\n" " return s;\n" "};", 11 }, /* Returning arr[i] from a fn whose return type matches the * element. cgexpr leaves slot in AX/DX/CX; the return path * forwards as-is. arr[1] = "abc" → str variant → .len == 3. */ { "fn pick(i: i32) (i32 | str) = {\n" " let arr: [2](i32 | str);\n" " arr[0] = 21;\n" " arr[1] = \"abc\";\n" " return arr[i];\n" "};\n" "fn main() i32 = {\n" " let r: (i32 | str) = pick(1);\n" " match (r) {\n" " case let n: i32 => return n;\n" " case let s: str => return s.len: i32;\n" " };\n" " return 0;\n" "};", 3 }, /* Nullable folded element `(*T | void)` — the slot is one 8B * pointer word; null is the void variant. Stores route through * the nullable branch of cg_widen_tagged_store (single MOVQ at * +0). 100 (nil) + 1 (non-nil) + 100 (nil) = 201. */ { "fn pickptr(b: bool) *i32 = {\n" " let x: i32 = 42;\n" " if (b) { return &x; };\n" " return nil;\n" "};\n" "fn main() i32 = {\n" " let arr: [3](*i32 | void);\n" " arr[0] = nil;\n" " arr[1] = pickptr(true);\n" " arr[2] = pickptr(false);\n" " let s: i32 = 0;\n" " let i: i32 = 0;\n" " for (i < 3) {\n" " match (arr[i]) {\n" " case let p: *i32 => s += 1;\n" " case => s += 100;\n" " };\n" " i += 1;\n" " };\n" " return s;\n" "};", 201 }, /* Plan 9-style sentinel error idiom: `def NAME: error = "lit"` * inlines as the (ptr, len) pair at use sites. */ { "type error = str;\n" "def eEOF: error = \"eof\";\n" "def eShortRead: error = \"short read\";\n" "fn read(n: i64) (i64 | error) = {\n" " if (n < 0) { return eEOF; };\n" " if (n == 0) { return eShortRead; };\n" " return n + 1;\n" "};\n" "fn main() i32 = {\n" " let r0: (i64 | error) = read(0);\n" " let r1: (i64 | error) = read(-1);\n" " let r2: (i64 | error) = read(5);\n" " let acc: i32 = 0;\n" " match (r0) {\n" " case let v: i64 => acc += 100;\n" " case let e: error => acc += e.len: i32;\n" " };\n" " match (r1) {\n" " case let v: i64 => acc += 100;\n" " case let e: error => acc += e.len: i32;\n" " };\n" " match (r2) {\n" " case let v: i64 => acc += v: i32;\n" " case let e: error => acc += 100;\n" " };\n" " return acc;\n" "};", 19 }, /* End-to-end stdlib usage: pull in lib/os and exercise the * fallible API tryread/trywrite returning (i64 | oserror) over * a real syscall. oserror carries -errno; on a bad fd we expect * -EBADF (-9). */ { "import os;\n" "fn main() i32 = {\n" " let buf: [3]u8;\n" " buf[0] = 88: u8;\n" " let ok: (i64 | os.oserror) = os.trywrite(1, buf.ptr, 1u64);\n" " let bad: (i64 | os.oserror) = os.trywrite(999: i32, buf.ptr, 1u64);\n" " let acc: i32 = 0;\n" " match (ok) {\n" " case let n: i64 => acc += n: i32;\n" " case let e: os.oserror => acc += -100;\n" " };\n" " match (bad) {\n" " case let n: i64 => acc += -100;\n" " case let e: os.oserror => acc += (- (e: i64)): i32;\n" " };\n" " return acc;\n" "};", 10 }, /* 1 byte written + 9 (EBADF) */ /* strconv.stoi64: fallible signed decimal, graduated to * (i64 | invalid | overflow). invalid carries the offending * index; overflow is the void variant. */ { "import strconv;\n" "type r_t = (i64 | strconv.invalid | strconv.overflow);\n" "fn main() i32 = {\n" " let r1: r_t = strconv.stoi64(\"42\", strconv.base.DEC);\n" " let r2: r_t = strconv.stoi64(\"-7\", strconv.base.DEC);\n" " let r3: r_t = strconv.stoi64(\"abc\", strconv.base.DEC);\n" " let acc: i32 = 0;\n" " match (r1) {\n" " case let v: i64 => acc += v: i32;\n" " case let e: strconv.invalid => acc += -100;\n" " case let e: strconv.overflow => acc += -200;\n" " };\n" " match (r2) {\n" " case let v: i64 => acc += v: i32;\n" " case let e: strconv.invalid => acc += -100;\n" " case let e: strconv.overflow => acc += -200;\n" " };\n" " match (r3) {\n" " case let v: i64 => acc += -100;\n" " case let e: strconv.invalid => acc += e: i32;\n" " case let e: strconv.overflow => acc += -200;\n" " };\n" " return acc;\n" "};", 35 }, /* 42 + (-7) + 0 (invalid at index 0 in \"abc\") */ /* strconv.stou64: success path; leading-sign rejected with * invalid carrying the offending index. */ { "import strconv;\n" "type r_t = (u64 | strconv.invalid | strconv.overflow);\n" "fn main() i32 = {\n" " let r1: r_t = strconv.stou64(\"123\", strconv.base.DEC);\n" " let r2: r_t = strconv.stou64(\"-1\", strconv.base.DEC);\n" " let acc: i32 = 0;\n" " match (r1) {\n" " case let v: u64 => acc += v: i32;\n" " case let e: strconv.invalid => acc += -100;\n" " case let e: strconv.overflow => acc += -200;\n" " };\n" " match (r2) {\n" " case let v: u64 => acc += -100;\n" " case let e: strconv.invalid => acc += e: i32;\n" " case let e: strconv.overflow => acc += -200;\n" " };\n" " return acc;\n" "};", 123 }, /* 123 + 0 (invalid at index 0 in \"-1\") */ /* strings.byteindex with (str | rune) needle: returns (i32 | void). */ { "import strings;\n" "fn pick(r: (i32 | void), miss: i32) i32 = {\n" " match (r) {\n" " case let i: i32 => return i;\n" " case void => return miss;\n" " };\n" " return 0;\n" "};\n" "fn main() i32 = {\n" " let s: str = \"hello, world\";\n" " let i1: i32 = pick(strings.byteindex(s, ','), -1);\n" " let i2: i32 = pick(strings.byteindex(s, 'z'), -1);\n" " let i3: i32 = pick(strings.byteindex(s, \"world\"), -1);\n" " let i4: i32 = pick(strings.byteindex(s, \"nope\"), -1);\n" " return i1 + i2 + i3 + i4;\n" "};", 10 }, /* 5 + (-1) + 7 + (-1) */ /* bytes.index: substring search over []u8, (i32 | void). */ { "import bytes;\n" "fn main() i32 = {\n" " let buf: [12]u8;\n" " buf[0] = 104u8; buf[1] = 101u8; buf[2] = 108u8; buf[3] = 108u8;\n" " buf[4] = 111u8; buf[5] = 44u8; buf[6] = 32u8; buf[7] = 119u8;\n" " buf[8] = 111u8; buf[9] = 114u8; buf[10] = 108u8; buf[11] = 100u8;\n" " let needle: [3]u8;\n" " needle[0] = 119u8; needle[1] = 111u8; needle[2] = 114u8;\n" " let r: (i32 | void) = bytes.index(buf[0:12], needle[0:3]);\n" " match (r) {\n" " case let i: i32 => return i;\n" " case void => return -1;\n" " };\n" " return 0;\n" "};", 7 }, /* errors named-void tags — dispatch through a (T | tag | tag) * union, one variant per error condition. Replaces the old * errors.equal sentinel-string comparison. */ { "import errors;\n" "fn parse(n: i64) (i64 | errors.invalid | errors.noentry) = {\n" " if (n < 0) { let e: errors.invalid; return e; };\n" " if (n == 0) { let e: errors.noentry; return e; };\n" " return n;\n" "};\n" "fn main() i32 = {\n" " let r1: (i64 | errors.invalid | errors.noentry) = parse(-1);\n" " let r2: (i64 | errors.invalid | errors.noentry) = parse(0);\n" " let acc: i32 = 0;\n" " match (r1) {\n" " case let v: i64 => acc += -100;\n" " case let e: errors.invalid => acc += 1;\n" " case let e: errors.noentry => acc += -100;\n" " };\n" " match (r2) {\n" " case let v: i64 => acc += -100;\n" " case let e: errors.invalid => acc += -100;\n" " case let e: errors.noentry => acc += 10;\n" " };\n" " return acc;\n" "};", 11 }, /* errors remaining named-void tags — pin the rest of the surface * (noaccess / exists / unsupported) so each is callable as both a * return variant and a match arm. One row per tag would bloat the * table; fold them into one (T | A | B | C) dispatch. */ { "import errors;\n" "fn classify(n: i32) (i32 | errors.noaccess | errors.exists | errors.unsupported) = {\n" " if (n == 1) { let e: errors.noaccess; return e; };\n" " if (n == 2) { let e: errors.exists; return e; };\n" " if (n == 3) { let e: errors.unsupported; return e; };\n" " return n;\n" "};\n" "fn dispatch(r: (i32 | errors.noaccess | errors.exists | errors.unsupported)) i32 = {\n" " match (r) {\n" " case let v: i32 => return v;\n" " case let e: errors.noaccess => return 10;\n" " case let e: errors.exists => return 20;\n" " case let e: errors.unsupported => return 30;\n" " };\n" " return -1;\n" "};\n" "fn main() i32 = {\n" " return dispatch(classify(1)) + dispatch(classify(2)) + dispatch(classify(3));\n" "};", 60 }, /* bufio.scanline: drain '\\n'-terminated lines from a memio.fixed * source through a bufio.scanner. The trailing "baz" fragment has * no newline; under the EOF_DISCARD default it's dropped and the * call returns io.eof. Also pins the cross-module type ref * (scanner stores *io.stream) end-to-end through `use`. */ { "import bufio;\n" "import io;\n" "import memio;\n" "fn main() i32 = {\n" " let raw: [11]u8;\n" " raw[0] = 102u8; raw[1] = 111u8; raw[2] = 111u8; raw[3] = 10u8;\n" " raw[4] = 98u8; raw[5] = 97u8; raw[6] = 114u8; raw[7] = 10u8;\n" " raw[8] = 98u8; raw[9] = 97u8; raw[10] = 122u8;\n" " let mem: memio.state;\n" " let m: io.stream;\n" " memio.fixed(&mem, &m, raw[0:11]);\n" " let buf: [16]u8;\n" " let sc: bufio.scanner;\n" " bufio.newscanner(&sc, &m, buf[0:16]);\n" " let acc: i32 = 0;\n" " let l1: (str | io.eof | io.closed | bufio.overflow) = bufio.scanline(&sc);\n" " match (l1) {\n" " case let s: str => acc += s.len;\n" " case io.eof => acc += -100;\n" " case io.closed => acc += -1000;\n" " case bufio.overflow => acc += -10000;\n" " };\n" " let l2: (str | io.eof | io.closed | bufio.overflow) = bufio.scanline(&sc);\n" " match (l2) {\n" " case let s: str => acc += s.len;\n" " case io.eof => acc += -100;\n" " case io.closed => acc += -1000;\n" " case bufio.overflow => acc += -10000;\n" " };\n" " let l3: (str | io.eof | io.closed | bufio.overflow) = bufio.scanline(&sc);\n" " match (l3) {\n" " case let s: str => acc += -100;\n" " case io.eof => acc += 7;\n" " case io.closed => acc += -1000;\n" " case bufio.overflow => acc += -10000;\n" " };\n" " return acc;\n" "};", 13 }, /* 3 + 3 + 7 (eof arm) */ /* `(scalar, str)` tuple return: AX:DX:CX convention extends the * tagged-union ABI. AX = scalar, DX = str.ptr, CX = str.len. * Receive sites destructure off the same regs regardless of * positional order. Without the fix, len was lost (only AX:DX * returned), every receive shape gave garbage. */ { "fn split() (i64, str) = { return 42, \"hello\"; };\n" "fn main() i32 = {\n" " let n, s = split();\n" " return (n: i32) + (s.len: i32);\n" "};", 47 }, { "fn split() (str, i64) = { return \"hello\", 42; };\n" "fn main() i32 = {\n" " let s, n = split();\n" " return (n: i32) + (s.len: i32);\n" "};", 47 }, { "fn split() (i64, str) = { return 42, \"hello\"; };\n" "fn main() i32 = {\n" " let t: (i64, str) = split();\n" " return (t.0: i32) + (t.1.len: i32);\n" "};", 47 }, /* Hare-style paren tuple-destructure with str element */ { "fn split() (i64, str) = { return 42, \"hello\"; };\n" "fn main() i32 = {\n" " let (n, s) = split();\n" " return (n: i32) + (s.len: i32);\n" "};", 47 }, /* Chained field write through a pointer field: `r.sym.flag = v` * where `r.sym: *T`. The cgen must evaluate the inner pointer, * then store at *(ptr + field.offset). Without the fix, the * single-level N_IDENT-base path doesn't fire (base is itself * an N_DOT) and the assignment silently emits no instructions. * Compound op + 1-byte field + 3-level chain all exercised. */ { "type inner = struct { tag: u8, pad: u8, flag: i32 };\n" "type outer = struct { sym: *inner };\n" "fn main() i32 = {\n" " let i: inner = inner { tag = 0u8, pad = 0u8, flag = 10 };\n" " let r: outer = outer { sym = &i };\n" " r.sym.flag += 32;\n" " r.sym.tag = 5u8;\n" " return r.sym.flag + (r.sym.tag: i32);\n" "};", 47 }, { "type leaf = struct { v: i32 };\n" "type mid = struct { l: *leaf };\n" "type top = struct { m: *mid };\n" "fn main() i32 = {\n" " let lf: leaf = leaf { v = 0 };\n" " let md: mid = mid { l = &lf };\n" " let tp: top = top { m = &md };\n" " tp.m.l.v = 99;\n" " return tp.m.l.v;\n" "};", 99 }, /* `def NAME: str = \"lit\"` field access. The Sdef has no stack * slot, so .len/.ptr must inline the literal length / strlit * address; without the fix, .len reads BP+8 (return-address slot) * as garbage. */ { "def MSG: str = \"hello world\";\n" "fn main() i32 = { return MSG.len: i32; };", 11 }, { "import os;\n" "def GREETING: str = \"hi\\n\";\n" "fn main() i32 = {\n" " os.write(1, GREETING.ptr, GREETING.len: u64);\n" " return GREETING.len: i32;\n" "};", 3 }, /* Hare-style `is` / `as`: type test returns bool, type assertion * unwraps to the variant's value (success path only — abort path * exit(1) is exercised manually). Covers i64/i32 scalar variants * and str (16B variant via .len pseudo-field). */ { "fn classify(n: i64) (i64 | i32) = {\n" " if (n < 0) { return 7: i32; };\n" " return n;\n" "};\n" "fn main() i32 = {\n" " let ok: (i64 | i32) = classify(40);\n" " let bad: (i64 | i32) = classify(-1);\n" " let s: i32 = 0;\n" " if (ok is i64) { s += 1; };\n" " if (bad is i32) { s += 1; };\n" " if (ok is i32) { s += 100; };\n" " if (bad is i64) { s += 100; };\n" " let v: i64 = ok as i64;\n" " let e: i32 = bad as i32;\n" " return (v: i32) + s + e;\n" "};", 49 }, /* `as` on a 16B str variant — unwrap loads (ptr, len). */ { "fn fail() (i64 | str) = { return \"bad\"; };\n" "fn main() i32 = {\n" " let r: (i64 | str) = fail();\n" " let e: str = r as str;\n" " return e.len: i32;\n" "};", 3 }, /* enum: auto-increment, explicit value, sibling-ref */ { "type color = enum { RED, GREEN, BLUE };\n" "fn main() i32 = { return color.BLUE as i32; };", 2 }, { "type mode = enum u8 { R = 1, W = 2, RW = R | W };\n" "fn main() i32 = {\n" " let m: mode = mode.RW;\n" " return m as i32;\n" "};", 3 }, /* enum: bitwise op between two members yields the same enum type */ { "type mode = enum u8 { R = 1, W = 2 };\n" "fn main() i32 = {\n" " let m: mode = mode.R | mode.W;\n" " return m as i32;\n" "};", 3 }, /* enum: pkg-qualified access — `pkg.dir.SOUTH` resolves through * SK_USE and folds to the member literal. */ { "package pkg;\n" "type dir = enum { NORTH, SOUTH, EAST, WEST };\n" "package main;\n" "import pkg;\n" "fn main() i32 = { return pkg.dir.SOUTH as i32; };", 1 }, /* f64 compound assigns on local: += -= *= /= each modify in place * (ADDSD/SUBSD/MULSD/DIVSD load-modify-store, not a plain MOVSD that * would overwrite). 1.5 + 0.5 = 2.0 → 2.0 - 1.0 = 1.0 → 1.0 * 4.0 = * 4.0 → 4.0 / 2.0 = 2.0 → return 2. */ { "fn main() i32 = {\n" " let a: f64 = 1.5;\n" " a += 0.5;\n" " a -= 1.0;\n" " a *= 4.0;\n" " a /= 2.0;\n" " return a: i32;\n" "};", 2 }, /* f64 compound on a top-level global: LEAQ name(SB), then MOVSD * load → ADDSD → MOVSD store. 10.0 + 5.0 = 15.0 → 15.0 * 2.0 = * 30.0 → 30.0 - 20.0 = 10.0 → 10.0 / 5.0 = 2.0. */ { "let G: f64 = 10.0;\n" "fn main() i32 = {\n" " G += 5.0;\n" " G *= 2.0;\n" " G -= 20.0;\n" " G /= 5.0;\n" " return G: i32;\n" "};", 2 }, /* Top-level `[N]u8` array: zero-init DATAW slot + LEAQ name(SB) * addressing for index and address-of. `buf[i] = c` narrows to * MOVB; reading back roundtrips through MOVZBQ. */ { "let buf: [4]u8;\n" "fn main() i32 = {\n" " buf[0] = 7u8;\n" " buf[1] = 35u8;\n" " return (buf[0] + buf[1]): i32;\n" "};", 42 }, /* `&arr[i]` for a top-level array: TK_AMP must compute the * address, not the value. Then `*p = c` for *u8 stores 1 byte. * Drives Hare's static-buffer pattern (strconv.*tos). */ { "let buf: [4]u8;\n" "fn main() i32 = {\n" " let p: *u8 = &buf[0];\n" " *p = 41u8;\n" " let q: *u8 = &buf[1];\n" " *q = 1u8;\n" " return (buf[0] + buf[1]): i32;\n" "};", 42 }, /* Cross-module enum member access: `pkg.Enum.MEMBER`. Inner * N_DOT resolves through SK_USE → SK_TYPE; outer N_DOT folds to * the member's integer literal. Validates the strconv.base.DEC * shape that the *tos / sto* signatures now use. */ { "package pkg;\n" "export type base = enum i32 { DEC = 10, HEX = 16 };\n" "package main;\n" "import pkg;\n" "fn pick(b: pkg.base) i32 = { return b as i32; };\n" "fn main() i32 = {\n" " let a: i32 = pick(pkg.base.DEC);\n" " let b: i32 = pick(pkg.base.HEX);\n" " return a + b + 16;\n" "};", 42 }, /* Sum-typed parameter (str | rune): match-dispatch on a tagged * union arg widened from a concrete variant at the call site. * Mirrors lib/strings.byteindex's needle parameter. */ { "fn pick(n: (str | rune)) i32 = {\n" " match (n) {\n" " case let s: str => return s.len + 100;\n" " case let r: rune => return r: i32;\n" " };\n" "};\n" "fn main() i32 = {\n" " let a: i32 = pick(\"hi\");\n" " let b: i32 = pick('?');\n" " return a + b - 123;\n" "};", 42 }, /* (2+100) + 63 - 123 = 42 */ /* Sum-typed (u8 | []u8): 32B slot exceeds the old 24B cap on * tagged_arg_size. Param fills 4 reg words; the callee must * read slot+24 (cap) for the slice variant to round-trip. */ { "import bytes;\n" "fn main() i32 = {\n" " let buf: [4]u8;\n" " buf[0] = 1u8; buf[1] = 2u8; buf[2] = 3u8; buf[3] = 4u8;\n" " let needle: [2]u8;\n" " needle[0] = 3u8; needle[1] = 4u8;\n" " let r1: (i32 | void) = bytes.index(buf[0:4], 3u8);\n" " let r2: (i32 | void) = bytes.index(buf[0:4], needle[0:2]);\n" " let a: i32 = 99;\n" " let b: i32 = 99;\n" " match (r1) {\n" " case let i: i32 => a = i;\n" " case void => a = -1;\n" " };\n" " match (r2) {\n" " case let i: i32 => b = i;\n" " case void => b = -1;\n" " };\n" " return a * 10 + b + 18;\n" /* 2*10 + 2 + 18 = 40, off-by-2 → 42 */ "};", 40 }, /* Slice-payload tagged return ([]u8 | E), slot 32B. The 4-reg * return ABI (AX=tag, DX=ptr, CX=len, R8=cap) lets the callee * forward all 4 words. Before the bump, slice.len was dropped * because only 3 regs were used. */ { "type rterr = !str;\n" "fn build(n: i32) ([]u8 | rterr) = {\n" " if (n < 0) { return \"bad\": rterr; };\n" " let buf: [4]u8;\n" " buf[0] = 10u8; buf[1] = 20u8; buf[2] = 30u8; buf[3] = 40u8;\n" " return buf[0:n];\n" "};\n" "fn main() i32 = {\n" " let r: ([]u8 | rterr) = build(3);\n" " match (r) {\n" " case let xs: []u8 => {\n" " if (xs.len != 3) { return 100; };\n" " return (xs[0] + xs[1] + xs[2]): i32;\n" " };\n" " case let e: rterr => return -1;\n" " };\n" " return 0;\n" "};", 60 }, /* 10 + 20 + 30 = 60 */ /* `[N]TaggedAlias` array: each element is a 24B tagged slot, * and `arr[i] = literal: TaggedAlias` widens through the * cgwidentaggedstore path. Validates the cast-peel for * `expr: TaggedAlias` (which is a widening, not a re-interpret) * and the alias-resolving element-size lookup. */ { "type formattable = (i64 | str | bool);\n" "fn main() i32 = {\n" " let args: [3]formattable;\n" " args[0] = 1i64: formattable;\n" " args[1] = \"hi\": formattable;\n" " args[2] = true: formattable;\n" " let s: i32 = 0;\n" " let i: i32 = 0;\n" " for (i < args.len) {\n" " match (args[i]) {\n" " case let n: i64 => s += n: i32;\n" " case let v: str => s += v.len;\n" " case let b: bool => { if (b) { s += 39; }; };\n" " };\n" " i += 1;\n" " };\n" " return s;\n" "};", 42 }, /* 1 + 2 + 39 = 42 */ /* Hare-style variadic gather: `args: T...` declares an N-arg * variadic; the call-site materialises N values into a fresh * `[N]T` and synthesises a {ptr,len,cap} slice for the param. * Plain element type (i64): no widening, MOVQ-per-element. */ { "fn sum(args: i64...) i64 = {\n" " let s: i64 = 0i64;\n" " let i: i32 = 0;\n" " for (i < args.len) { s += args[i]; i += 1; };\n" " return s;\n" "};\n" "fn main() i32 = {\n" " return sum(1i64, 2i64, 3i64, 7i64, 9i64, 20i64): i32;\n" "};", 42 }, /* Variadic with zero args: empty-slice descriptor {nil,0,0}. * Confirms the gather path doesn't crash on N=0. */ { "fn sum(args: i64...) i64 = {\n" " let s: i64 = 0i64;\n" " let i: i32 = 0;\n" " for (i < args.len) { s += args[i]; i += 1; };\n" " return s;\n" "};\n" "fn main() i32 = {\n" " let a: i64 = sum();\n" " let b: i64 = sum(42i64);\n" " return (a + b): i32;\n" "};", 42 }, /* Variadic with tagged-union element type: each gathered arg * widens to the variant's slot shape (tag@+0, payload@+8). The * runtime match-dispatch reads (i64=1)+(str.len=2)+(bool=39)=42. */ { "type formattable = (i64 | str | bool);\n" "fn sumtag(args: formattable...) i64 = {\n" " let s: i64 = 0i64;\n" " let i: i32 = 0;\n" " for (i < args.len) {\n" " match (args[i]) {\n" " case let n: i64 => s += n;\n" " case let v: str => s += v.len: i64;\n" " case let b: bool => { if (b) { s += 39i64; }; };\n" " };\n" " i += 1;\n" " };\n" " return s;\n" "};\n" "fn main() i32 = { return sumtag(1i64, \"hi\", true): i32; };", 42 }, /* Variadic forwarding: `wrap(args...)` passes the local slice * directly to `sum`, no re-gather. Mirrors Hare's wrapper shape * (`fn println(args: formattable...) = fdprintln(os.stdout, args...)`). */ { "fn sum(args: i64...) i64 = {\n" " let s: i64 = 0i64;\n" " let i: i32 = 0;\n" " for (i < args.len) { s += args[i]; i += 1; };\n" " return s;\n" "};\n" "fn wrap(prefix: i64, args: i64...) i64 = {\n" " return prefix + sum(args...);\n" "};\n" "fn main() i32 = {\n" " return wrap(2i64, 1i64, 2i64, 3i64, 4i64, 5i64, 7i64, 18i64): i32;\n" "};", 42 }, /* lib/fmt user-side: `fmt.println(args: formattable...)` gathers * mixed-type args at the call site. End-to-end exercises the * lib/fmt graduation: the wrapper-chain `println → fdprintln → * fdprint` is itself variadic-forwarding, so this validates both * gather (at main) and `args...` forward (inside lib/fmt). The * exit code is bytes printed (`hello 7\n` = 8). */ { "import fmt;\n" "fn main() i32 = {\n" " return fmt.println(\"hello\", 7i64): i32;\n" "};", 8 }, /* short-circuit `&&`: RHS skipped when LHS is false. Without * short-circuit the `p.x` deref on a nil pointer segfaults. * Pinned the cgen bug surfaced by lib/getopt's argv guards * (`argslen > 0 && !streq(argsptr[0], "--")`) on a nil argsptr. */ { "type point = struct { x: i32, y: i32 };\n" "fn main() i32 = {\n" " let p: *point = nil;\n" " if (p != nil && p.x > 0) { return 1; };\n" " return 42;\n" "};", 42 }, /* short-circuit `||`: RHS skipped when LHS is true. */ { "type point = struct { x: i32, y: i32 };\n" "fn main() i32 = {\n" " let p: *point = nil;\n" " if (p == nil || p.x > 0) { return 42; };\n" " return 1;\n" "};", 42 }, /* `&&` LHS true: RHS evaluated, expression yields its boolean. */ { "type point = struct { x: i32, y: i32 };\n" "fn main() i32 = {\n" " let pt: point = point { x = 5, y = 10 };\n" " let p: *point = &pt;\n" " if (p != nil && p.x > 0) { return 42; };\n" " return 1;\n" "};", 42 }, /* `||` LHS false: RHS evaluated, expression yields its boolean. */ { "type point = struct { x: i32, y: i32 };\n" "fn main() i32 = {\n" " let pt: point = point { x = 7, y = 0 };\n" " let p: *point = &pt;\n" " if (p == nil || p.x > 0) { return 42; };\n" " return 1;\n" "};", 42 }, /* mixed `&&` / `||` precedence — `&&` binds tighter than `||`, * so `(p != nil && p.x > 0) || p == nil`. With short-circuit at * each level: AND skips `p.x` (LHS false), OR keeps the true. */ { "type point = struct { x: i32, y: i32 };\n" "fn main() i32 = {\n" " let p: *point = nil;\n" " if (p != nil && p.x > 0 || p == nil) { return 42; };\n" " return 1;\n" "};", 42 }, /* short-circuit must still yield a clean boolean in the * expression context (not just inside `if`). `true && false` * stored into a bool and re-checked. */ { "fn main() i32 = {\n" " let a: bool = (1 > 0) && (2 < 1);\n" " let b: bool = (1 < 0) || (2 > 1);\n" " let n: i32 = 0;\n" " if (!a) { n += 10; };\n" " if (b) { n += 32; };\n" " return n;\n" "};", 42 }, { NULL, 0 } }; int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; /* Resolve to absolute path: tests chdir into /tmp/... */ char absbin[1024]; if (bin[0] != '/') { char cwd[1024]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); bin = absbin; } int n = 0, fail = 0; for (int i = 0; rows[i].src; i++, n++) { char src[64], exe[64]; snprintf(src, sizeof src, "/tmp/wwe2e_%d_%d.ww", getpid(), i); snprintf(exe, sizeof exe, "/tmp/wwe2e_%d_%d", getpid(), i); FILE *f = fopen(src, "wb"); fputs(rows[i].src, f); fclose(f); char cmd[1024]; /* ww build writes the binary to the current working dir, * named after the source basename. We override by chdir. */ char tmpdir[64]; snprintf(tmpdir, sizeof tmpdir, "/tmp/wwe2e_%d_d_%d", getpid(), i); mkdir(tmpdir, 0755); snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s", tmpdir, bin, src); if (runwait(cmd) != 0) { fail++; continue; } char outbin[128]; const char *base = strrchr(src, '/'); base = base ? base + 1 : src; snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); char *dot = strrchr(outbin, '.'); if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; int got = runwait(outbin); if (got != rows[i].want_exit) { fprintf(stderr, "row %d: exit %d, want %d\n src: %s\n", i, got, rows[i].want_exit, rows[i].src); fail++; } unlink(src); unlink(outbin); rmdir(tmpdir); (void)exe; } if (fail) { fprintf(stderr, "%d/%d e2e tests failed\n", fail, n); return 1; } printf("e2e: %d/%d ok\n", n, n); return 0; }