A type prefixed with `!` is flagged as an error variant. When any variant in a tagged union carries the flag, `?` propagation uses those (and only those) as the error subset; the unflagged variant is the success type. The legacy "first variant = success" rule still applies when no `!`-flag is present, so existing code keeps working. - TK_NOT in parsetype → N_TBANG wrapper (lhs = inner type expr). Appended to Nkind tail for wwdump-diff byte stability. - resolve_type N_TBANG: wraps primitives in a fresh Type copy so the iserror bit doesn't taint shared globals like ty_str/ty_i32; flips the bit in place on NAMED (already unique per alias decl). - Type.iserror; type_named and typedecl inherit it from under. - New check.c helpers: tagged_has_errflag, tagged_is_error_variant, tagged_success_type. N_TRYPROP uses them to find the error subset and verify each error variant is propagatable to the enclosing return. - cgen mirrors with cg_tagged_success_tag + cg_variant_is_error. `?` compares AX against the success tag (no longer always 0) and remaps each error variant's tag for the enclosing fn. `!` aborts on any non-success tag. strconv.invalid and strconv.overflow now use `!`-flagged shape (`!i32` and `!void`) — visible signal in the API surface that they are error types, matching Hare. The (i64 | invalid | overflow) return shape and behavior are unchanged for callers; their match arms still bind the same way. Selfhost: lib/ww/parse/parse.ww recognises `!T` and emits N_TBANG. The selfhost typechecker and cgen ignore the flag — none of the selfhost sources use `!`, so byte-identity gates are unaffected. The selfhost mirror catches up when there's a source using it.
1053 lines
35 KiB
C
1053 lines
35 KiB
C
/*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
|
|
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 */
|
|
{ "use 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, slice-arg call, write
|
|
* the formatted number to stdout. exit code = number length. */
|
|
{ "use os;\n"
|
|
"use strconv;\n"
|
|
"fn main() i32 = {\n"
|
|
" let buf: [32]u8;\n"
|
|
" let s: []u8 = buf[0:32];\n"
|
|
" let n: i32 = strconv.i64tos(s, 12345);\n"
|
|
" os.write(1, buf.ptr, n: u64);\n"
|
|
" os.write(1, \"\\n\".ptr, 1u64);\n"
|
|
" return n;\n"
|
|
"};", 5 },
|
|
/* alloc + free via mmap-backed runtime — write through allocated
|
|
* memory and free it. exit = 0 if the allocation succeeded. */
|
|
{ "use os;\n"
|
|
"fn main() i32 = {\n"
|
|
" let p: *void = os.alloc(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 ints + strings */
|
|
{ "use fmt;\n"
|
|
"fn main() i32 = {\n"
|
|
" fmt.println(\"ww\");\n"
|
|
" fmt.printlnint(42);\n"
|
|
" fmt.printlnint(-7);\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. */
|
|
{ "use 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. */
|
|
{ "use strings;\n"
|
|
"use 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) */
|
|
{ "use 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 */
|
|
{ "use os;\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 */
|
|
{ "use 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 */
|
|
{ "use os;\n"
|
|
"fn main() i32 = {\n"
|
|
" let s: []u8 = alloc([], 16);\n"
|
|
" append(s, 72u8, 105u8);\n"
|
|
" return s.cap;\n"
|
|
"};", 16 },
|
|
/* variadic spread: append(dst, src...) iterates src */
|
|
{ "use 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 */
|
|
{ "use os;\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 },
|
|
/* 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 },
|
|
/* `!`-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 },
|
|
/* 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 | str) over a
|
|
* real syscall. Validates that imported tagged-union returns
|
|
* survive the linker as well as the call ABI. */
|
|
{ "use os;\n"
|
|
"fn main() i32 = {\n"
|
|
" let buf: [3]u8;\n"
|
|
" buf[0] = 88: u8;\n"
|
|
" let ok: (i64 | str) = os.trywrite(1, buf.ptr, 1u64);\n"
|
|
" let bad: (i64 | str) = 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: str => acc += -100;\n"
|
|
" };\n"
|
|
" match (bad) {\n"
|
|
" case let n: i64 => acc += -100;\n"
|
|
" case let e: str => acc += e.len: i32;\n"
|
|
" };\n"
|
|
" return acc;\n"
|
|
"};", 13 }, /* 1 byte written to fd 1, plus len(\"write failed\")=12 */
|
|
/* strconv.stoi64: fallible signed decimal, graduated to
|
|
* (i64 | invalid | overflow). invalid carries the offending
|
|
* index; overflow is the void variant. */
|
|
{ "use strconv;\n"
|
|
"type r_t = (i64 | strconv.invalid | strconv.overflow);\n"
|
|
"fn main() i32 = {\n"
|
|
" let r1: r_t = strconv.stoi64(\"42\");\n"
|
|
" let r2: r_t = strconv.stoi64(\"-7\");\n"
|
|
" let r3: r_t = strconv.stoi64(\"abc\");\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. */
|
|
{ "use strconv;\n"
|
|
"type r_t = (u64 | strconv.invalid | strconv.overflow);\n"
|
|
"fn main() i32 = {\n"
|
|
" let r1: r_t = strconv.stou64(\"123\");\n"
|
|
" let r2: r_t = strconv.stou64(\"-1\");\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 and strings.index: now (i32 | void). */
|
|
{ "use 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, 44u8), -1);\n"
|
|
" let i2: i32 = pick(strings.byteindex(s, 122u8), -1);\n"
|
|
" let i3: i32 = pick(strings.index(s, \"world\"), -1);\n"
|
|
" let i4: i32 = pick(strings.index(s, \"nope\"), -1);\n"
|
|
" return i1 + i2 + i3 + i4;\n"
|
|
"};", 10 }, /* 5 + (-1) + 7 + (-1) */
|
|
/* bytes.index: substring search over []u8, (i32 | void). */
|
|
{ "use 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.equal — sentinel comparison through a (T | error) union.
|
|
* Sets up two errors, dispatches each, and confirms the matching
|
|
* sentinel detection. */
|
|
{ "use errors;\n"
|
|
"fn parse(n: i64) (i64 | errors.error) = {\n"
|
|
" if (n < 0) { return errors.eof; };\n"
|
|
" if (n == 0) { return errors.underread; };\n"
|
|
" return n;\n"
|
|
"};\n"
|
|
"fn main() i32 = {\n"
|
|
" let r1: (i64 | errors.error) = parse(-1);\n"
|
|
" let r2: (i64 | errors.error) = parse(0);\n"
|
|
" let acc: i32 = 0;\n"
|
|
" match (r1) {\n"
|
|
" case let v: i64 => acc += -100;\n"
|
|
" case let e: errors.error =>\n"
|
|
" if (errors.equal(e, errors.eof)) { acc += 1; }\n"
|
|
" else { acc += -100; };\n"
|
|
" };\n"
|
|
" match (r2) {\n"
|
|
" case let v: i64 => acc += -100;\n"
|
|
" case let e: errors.error =>\n"
|
|
" if (errors.equal(e, errors.underread)) { acc += 10; }\n"
|
|
" else { acc += -100; };\n"
|
|
" };\n"
|
|
" return acc;\n"
|
|
"};", 11 },
|
|
/* bufio.readline: drain successive '\\n'-terminated lines from a
|
|
* pre-filled buffer, then a trailing fragment that returns the
|
|
* `linerr` variant carrying \"no newline\" (10 chars). */
|
|
{ "use bufio;\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 b: bufio.buf;\n"
|
|
" b.s = nil; b.data = raw.ptr; b.cap = 11; b.r = 0; b.w = 11;\n"
|
|
" let acc: i32 = 0;\n"
|
|
" let l1: (str | bufio.linerr) = bufio.readline(&b);\n"
|
|
" match (l1) {\n"
|
|
" case let s: str => acc += s.len: i32;\n"
|
|
" case let e: bufio.linerr => acc += -100;\n"
|
|
" };\n"
|
|
" let l2: (str | bufio.linerr) = bufio.readline(&b);\n"
|
|
" match (l2) {\n"
|
|
" case let s: str => acc += s.len: i32;\n"
|
|
" case let e: bufio.linerr => acc += -100;\n"
|
|
" };\n"
|
|
" let l3: (str | bufio.linerr) = bufio.readline(&b);\n"
|
|
" match (l3) {\n"
|
|
" case let s: str => acc += -100;\n"
|
|
" case let e: bufio.linerr => acc += e.len: i32;\n"
|
|
" };\n"
|
|
" return acc;\n"
|
|
"};", 16 }, /* 3 + 3 + len(\"no newline\")=10 */
|
|
/* `(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 },
|
|
{ "use 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 },
|
|
{ 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;
|
|
}
|