Files
ww/test/wwc/700_e2e.c
Hojun-Cho 635818eb13 6c: float compare, fn-address @symbol, indirect call
Three N_BIN/N_IDENT/N_CALL sites missed cases that bit ncurses
demos and dyn-linker exercises:

- Float compare emitted CMPQ + signed Jcc on f64 bits. Now goes
  through UCOMISD/UCOMISS + the unsigned Jcc family, mirroring
  Plan 9 6c (txt.c around AUCOMISD).
- LEAQ-of-fn-ident (taking the address of a function as a value)
  used n->str without ffi_resolve, so binding a *fn from an
  @symbol("name") fn declaration produced a reference to the
  ww ident rather than the C symbol.
- N_CALL on a bare ident with no localfind hit was a direct
  CALL ident(SB). Now checks localfind first and indirects
  through AX when the callee names a local fn-pointer slot.

Hare/QBE handles both shapes the same way (sort/search.ha:15
calls cmp(...) where cmp is a *cmpfunc parameter).
2026-05-11 09:42:02 +09:00

884 lines
28 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.i64toa(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 },
/* user-defined slice append via *[]u8: stdlib slices.appendu8.
* Demonstrates allocator + ptr-to-slice fields + scaled index write. */
{ "use os;\n"
"use slices;\n"
"fn main() i32 = {\n"
" let s: []u8;\n"
" s.ptr = nil; s.len = 0; s.cap = 0;\n"
" slices.appendu8(&s, 65u8);\n"
" slices.appendu8(&s, 66u8);\n"
" slices.appendu8(&s, 67u8);\n"
" os.write(1, s.ptr, s.len: u64);\n"
" os.write(1, \"\\n\".ptr, 1u64);\n"
" return s.len;\n"
"};", 3 },
/* Hare-style builtins: append(s, v) and len(s). The compiler
* lowers these to slices.appendu8 / s.len access. */
{ "use os;\n"
"use slices;\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"
"use slices;\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"
"use slices;\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"
"use slices;\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"
"use slices;\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 },
/* 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 },
/* ? success unwrap when the first variant is itself str */
{ "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.parse64: fallible signed decimal. Two successful
* parses contribute their values; one bad parse contributes
* the error message length (20 = len(\"parse: invalid digit\")). */
{ "use strconv;\n"
"fn main() i32 = {\n"
" let r1: (i64 | str) = strconv.parse64(\"42\");\n"
" let r2: (i64 | str) = strconv.parse64(\"-7\");\n"
" let r3: (i64 | str) = strconv.parse64(\"abc\");\n"
" let acc: i32 = 0;\n"
" match (r1) {\n"
" case let v: i64 => acc += v: i32;\n"
" case let e: str => acc += -100;\n"
" };\n"
" match (r2) {\n"
" case let v: i64 => acc += v: i32;\n"
" case let e: str => acc += -100;\n"
" };\n"
" match (r3) {\n"
" case let v: i64 => acc += -100;\n"
" case let e: str => acc += e.len: i32;\n"
" };\n"
" return acc;\n"
"};", 55 }, /* 42 + (-7) + 20 */
/* strconv.parseu64: success path 123, error path captures
* len(\"parse: invalid digit\") = 20 for the leading-sign reject. */
{ "use strconv;\n"
"fn main() i32 = {\n"
" let r1: (u64 | str) = strconv.parseu64(\"123\");\n"
" let r2: (u64 | str) = strconv.parseu64(\"-1\");\n"
" let acc: i32 = 0;\n"
" match (r1) {\n"
" case let v: u64 => acc += v: i32;\n"
" case let e: str => acc += -100;\n"
" };\n"
" match (r2) {\n"
" case let v: u64 => acc += -100;\n"
" case let e: str => acc += e.len: i32;\n"
" };\n"
" return acc;\n"
"};", 143 }, /* 123 + 20 */
/* strings.indexbyte (Plan 9 -1) and strings.index (substring). */
{ "use strings;\n"
"fn main() i32 = {\n"
" let s: str = \"hello, world\";\n"
" let i1: i32 = strings.indexbyte(s, 44u8);\n"
" let i2: i32 = strings.indexbyte(s, 122u8);\n"
" let i3: i32 = strings.index(s, \"world\");\n"
" let i4: i32 = strings.index(s, \"nope\");\n"
" return i1 + i2 + i3 + i4;\n"
"};", 10 }, /* 5 + (-1) + 7 + (-1) */
/* bytes.indexsub: substring search over []u8. */
{ "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"
" return bytes.indexsub(buf[0:12], needle[0:3]);\n"
"};", 7 },
/* errors.is — 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.eEOF; };\n"
" if (n == 0) { return errors.eShortRead; };\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.is(e, errors.eEOF)) { 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.is(e, errors.eShortRead)) { acc += 10; }\n"
" else { acc += -100; };\n"
" };\n"
" return acc;\n"
"};", 11 },
/* bufio.takeline: 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.takeline(&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.takeline(&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.takeline(&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 */
{ 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;
}