/* * 807_insert_elem — cstage and wwstage agree, byte-for-byte and at * runtime, that `insert(xs[idx], v)` inserts v BEFORE idx: len += 1, * the tail [idx..oldlen) shifts up one stride, v lands at idx * (the insert-half of task #35, delete()'s twin; regex fold-3's * `|`/`?`/`*` compile-arm consumers, regex.ha:347/419/441). * * Lowering (BOTH stages, converged byte-identical by construction) * is a DESUGAR: append(xs, v) — reusing append's grow (rt_ensure) * and the whole #34 value-store dispatch (scalar / str-slice header / * tagged widen / struct fill) verbatim, one boxing choke-point — * lands v at slot len-1; then a rotate-right of [idx, len) moves it * home through an esz frame scratch (@insscr). The rotate is * delete's shift loop in reverse (descending j, the safe memmove-up * direction) and is a same-slice whole-stride raw byte move — no * boxing exists for any element kind. idx evaluates BEFORE the grow * (Hare's left-to-right operand order — the pregrow_len_idx row pins * insert(xs[len(xs)-1], v) reading the pre-grow len; an end-insert * via len(xs) cannot discriminate, see the end_insert_len row). * * idx == len is a legal end-insert per harec's shared append/insert * checker arm (ref/harec/src/check.c:745, "insert" at :786; the * ref/hare os/exec/platform_cmd.ha:86 `insert(cmd.env[len(...)...]` * idiom) — pinned by the i64_end / end_insert_len rows. * * row | shape | want * ----------------+----------------------------------------+------ * i64_front | [7,11,13], insert(xs[0], 5) | 115 * i64_middle | [7,11,13], insert(xs[1], 5) | 97 * i64_end | [7,11,13], insert(xs[3], 5) (idx==len, | 157 * | rotate loop never runs) | * end_insert_len | insert(xs[len(xs)], v) — idx==len | 13 * | spelled through a dynamic len read | * pregrow_len_idx | insert(xs[len(xs)-1], v) — the eval- | 48 * | order pin: pre-grow idx=1 -> [7,13,11],| * | post-grow idx=2 -> [7,11,13] | * i32_narrow | []i32 esz=4 — the MOVL copy tail | 42 * u16_narrow | []u16 esz=2 — the MOVW copy tail | 43 * u8_narrow | []u8 esz=1 — the MOVB copy tail | 44 * empty_insert | insert(xs[0], v) on an empty slice | 15 * | (grow 0->1; rotate degenerates) | * str_elem | []str esz=24 — 3-qword headers move | 45 * | whole | * struct_elem | []p2t esz=16 — struct body insert + | 46 * | survivor shift | * tagged_56b | [](s6|bool) esz=56, value from a TYPED | * | LOCAL (the regex newinst shape, | * | ha:347); tag+payload survive the | 217 * | rotate; match head + raw tail byte | * tagged_cast | insert(insts[k], (7: inst_split)) — | * | CAST-rvalue value boxed by append's | 27 * | widen choke-point (ha:419/441 shape) | * regex_shape | insert((*p)[i], v) behind *[]i64 — | * | deref-of-local base, delete's | 171 * | regex_shape twin | * insert_in_loop | front-insert 1,2,3,4 -> [4,3,2,1] — | 47 * | len bookkeeping under iteration + | * | per-position checks | * tagged_pregrow_val | #50 value eval-order pin, tagged | 50 * | dst: v = (xs.len: size)+2 reads the | * | PRE-grow len (post-grow boxing read 5) | * scalar_pregrow_val | #50 scalar control: value-first | 55 * | order was already Hare-correct | * tagged_selfref_val | #50: v = xs[1], an element of the | 51 * | dst — pre-grow boxing reads OLD base | * tagged_str_payload | #50: str-variant box runs pre-grow | 52 * tagged_regex_minrep | #50: the regex {,0} fold-5b shape, | 53 * | len-reading value cast to a named | * | variant alias (regex.ww:643-650) | * tagged_realloc_selfref_loop | #50 ken k50a: 30-append | 56 * | self-ref loop, value reads xs[0] across| * | actual rt_ensure base moves | * tagged_append_pregrow_val | #50 direct append() pin (the | 54 * | fix site; insert inherits via desugar) | * tagged_seq_positions | #50 ken k50b: sequenced inserts at | 57 * | 0 then mid, both len-reading values, | * | full final-order check [200,10,4,20] | * tagged_void_variant | #50 ken k50c: void box (tag-only) | 58 * | through @apptagscr, then a len-reading | * | size insert over the mixed slice | * * Wants stay under 256 (the exit-status byte); the if-ladder rows * return a distinct small failure code per check, so a wrong element * pinpoints itself. * reject_array | insert(t[0], v) on [3]i64 | BUILD_FAIL * reject_nonindex | insert(xs, v) | BUILD_FAIL * reject_range | insert(xs[0:1], v) — not Hare (harec | BUILD_FAIL * | only parses an index place) | * reject_arity1 | insert(xs[0]) | BUILD_FAIL * reject_arity3 | insert(xs[0], a, b) — also covers the | BUILD_FAIL * | harec with-length form (#35) | * reject_spread | insert(xs[0], vs...) — multi form | BUILD_FAIL * | deferred, message cites task #35 | * * BUILD_FAIL rows also assert the diagnostic TEXT (stderr substring, * both stages) — a build that fails for any other reason (parse error, * crash) is a vacuous reject and fails the row. * * Every non-BUILD_FAIL row also asserts cstage/wwstage asm byte-id, * which subsumes the frame canary (TEXT main,$N — @insscr sizing) and * the ins_l/ins_e label-counter symmetry. */ #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; } /* want == BUILD_FAIL: the row must FAIL to build on both stages AND * emit expect_err on stderr (the checker reject set — message text * included, esp. the #35 cite on the deferred spread form — is part * of the contract: rule 7, never a silent acceptance; without the * message check a row would pass vacuously on any unrelated build * failure). */ #define BUILD_FAIL (-2147483647 - 1) struct row { const char *label; const char *src; int want; const char *expect_err; /* BUILD_FAIL rows: required stderr substring */ }; static const struct row rows[] = { { "i64_front", "package main;\n" "export fn main() i32 = {\n" "\tlet xs: []i64 = [];\n" "\tappend(xs, 7);\n" "\tappend(xs, 11);\n" "\tappend(xs, 13);\n" "\tinsert(xs[0], 5);\n" "\treturn (xs[0] + xs[1]*10): i32 + len(xs)*10;\n" "};\n", 115, NULL }, { "i64_middle", "package main;\n" "export fn main() i32 = {\n" "\tlet xs: []i64 = [];\n" "\tappend(xs, 7);\n" "\tappend(xs, 11);\n" "\tappend(xs, 13);\n" "\tinsert(xs[1], 5);\n" "\treturn (xs[0] + xs[1]*10): i32 + len(xs)*10;\n" "};\n", 97, NULL }, { "i64_end", "package main;\n" "export fn main() i32 = {\n" "\tlet xs: []i64 = [];\n" "\tappend(xs, 7);\n" "\tappend(xs, 11);\n" "\tappend(xs, 13);\n" "\tinsert(xs[3], 5);\n" "\treturn (xs[0] + xs[1]*10): i32 + len(xs)*10;\n" "};\n", 157, NULL }, /* idx == len spelled THROUGH a dynamic len(xs) read (the * ref/hare os/exec idiom). NOTE: this row does NOT discriminate * idx eval order — under the desugar, post-grow idx=newlen * degenerates the rotate and lands v at the end too; the * pregrow_len_idx row below is the eval-order pin. */ { "end_insert_len", "package main;\n" "export fn main() i32 = {\n" "\tlet xs: []i64 = [];\n" "\tappend(xs, 7);\n" "\tappend(xs, 11);\n" "\tinsert(xs[len(xs)], 13);\n" "\tif (len(xs) != 3) { return 1; };\n" "\treturn xs[2]: i32;\n" "};\n", 13, NULL }, /* THE pre-grow eval-order pin (Hare's left-to-right operand * order): idx = len(xs)-1 reads the PRE-grow len -> idx=1 -> * [7,13,11]; a post-grow evaluation would compute idx=2 and * produce a plain end-insert [7,11,13] — every position is * checked, so the orders are distinguishable. */ { "pregrow_len_idx", "package main;\n" "export fn main() i32 = {\n" "\tlet xs: []i64 = [];\n" "\tappend(xs, 7);\n" "\tappend(xs, 11);\n" "\tinsert(xs[len(xs) - 1], 13);\n" "\tif (len(xs) != 3) { return 1; };\n" "\tif (xs[0] != 7) { return 2; };\n" "\tif (xs[1] != 13) { return 3; };\n" "\tif (xs[2] != 11) { return 4; };\n" "\treturn 48;\n" "};\n", 48, NULL }, { "i32_narrow", "package main;\n" "export fn main() i32 = {\n" "\tlet xs: []i32 = [];\n" "\tappend(xs, 4i32);\n" "\tappend(xs, 2i32);\n" "\tinsert(xs[1], 9i32);\n" "\tif (len(xs) != 3) { return 1; };\n" "\tif (xs[0] != 4i32) { return 2; };\n" "\tif (xs[1] != 9i32) { return 3; };\n" "\tif (xs[2] != 2i32) { return 4; };\n" "\treturn 42;\n" "};\n", 42, NULL }, { "u16_narrow", "package main;\n" "export fn main() i32 = {\n" "\tlet xs: []u16 = [];\n" "\tappend(xs, 4u16);\n" "\tappend(xs, 2u16);\n" "\tinsert(xs[1], 9u16);\n" "\tif (len(xs) != 3) { return 1; };\n" "\tif (xs[0] != 4u16) { return 2; };\n" "\tif (xs[1] != 9u16) { return 3; };\n" "\tif (xs[2] != 2u16) { return 4; };\n" "\treturn 43;\n" "};\n", 43, NULL }, { "u8_narrow", "package main;\n" "export fn main() i32 = {\n" "\tlet xs: []u8 = [];\n" "\tappend(xs, 5u8);\n" "\tappend(xs, 3u8);\n" "\tinsert(xs[0], 2u8);\n" "\tif (len(xs) != 3) { return 1; };\n" "\tif (xs[0] != 2u8) { return 2; };\n" "\tif (xs[1] != 5u8) { return 3; };\n" "\tif (xs[2] != 3u8) { return 4; };\n" "\treturn 44;\n" "};\n", 44, NULL }, { "empty_insert", "package main;\n" "export fn main() i32 = {\n" "\tlet xs: []i64 = [];\n" "\tinsert(xs[0], 5);\n" "\treturn xs[0]: i32 + len(xs)*10;\n" "};\n", 15, NULL }, { "str_elem", "package main;\n" "export fn main() i32 = {\n" "\tlet xs: []str = [];\n" "\tlet a: str = \"abc\";\n" "\tlet g: str = \"fghi\";\n" "\tappend(xs, a);\n" "\tappend(xs, g);\n" "\tlet b: str = \"de\";\n" "\tinsert(xs[1], b);\n" "\tif (len(xs) != 3) { return 1; };\n" "\tif (xs[0].len != 3) { return 2; };\n" "\tif (xs[1].len != 2) { return 3; };\n" "\tif (xs[2].len != 4) { return 4; };\n" "\treturn 45;\n" "};\n", 45, NULL }, { "struct_elem", "package main;\n" "type p2t = struct { x: i64, y: i64 };\n" "export fn main() i32 = {\n" "\tlet xs: []p2t = [];\n" "\tappend(xs, p2t { x = 1, y = 2 });\n" "\tappend(xs, p2t { x = 5, y = 6 });\n" "\tinsert(xs[1], p2t { x = 3, y = 4 });\n" "\tif (len(xs) != 3) { return 1; };\n" "\tif (xs[0].x != 1 || xs[0].y != 2) { return 2; };\n" "\tif (xs[1].x != 3 || xs[1].y != 4) { return 3; };\n" "\tif (xs[2].x != 5 || xs[2].y != 6) { return 4; };\n" "\treturn 46;\n" "};\n", 46, NULL }, /* 56B element from a TYPED LOCAL — the regex fold-3 newinst * shape (regex.ha:347: `insert(insts[split_idx], newinst)`). * Tag qword + 48B payload: seven whole-qword moves through * @insscr; tag AND payload must survive both the append store * and the rotate. Readback is split like 804's tagged_56b row: * match proves tag + head (s.a), and the tail qword of the * SHIFTED element (f = 18, element 2 byte 48 -> absolute byte * 160) is read RAW via a *u8 over xs.ptr — the indexed-match * payload cursor truncates past 32B (pre-existing, task #43). */ { "tagged_56b", "package main;\n" "type s6 = struct { a: i64, b: i64, c: i64, d: i64, e: i64, f: i64 };\n" "type cell = (s6 | bool);\n" "export fn main() i32 = {\n" "\tlet xs: []cell = [];\n" "\tlet p0: s6 = s6 { a = 1, b = 2, c = 3, d = 4, e = 5, f = 6 };\n" "\tlet p2: s6 = s6 { a = 13, b = 14, c = 15, d = 16, e = 17, f = 18 };\n" "\tappend(xs, p0);\n" "\tappend(xs, p2);\n" "\tlet p1: cell = (s6 { a = 7, b = 8, c = 9, d = 10, e = 11, f = 12 });\n" "\tinsert(xs[1], p1);\n" "\tlet r: i32 = 0;\n" "\tmatch (xs[1]) {\n" "\tcase let s: s6 => r = s.a: i32;\n" "\tcase bool => r = 99;\n" "\t};\n" "\tlet bp: *u8 = xs.ptr: *u8;\n" "\tr += (bp[160]: i32) * 10;\n" "\treturn r + len(xs)*10;\n" "};\n", 217, NULL }, /* CAST-rvalue value into a tagged slice — the regex ha:419/441 * shape `insert(insts[term_start_idx], after_idx: inst_split)`; * append's widen choke-point boxes it PRE-grow (#50). */ { "tagged_cast", "package main;\n" "type inst_lit = rune;\n" "type inst_split = i64;\n" "type inst = (inst_lit | inst_split);\n" "export fn main() i32 = {\n" "\tlet insts: []inst = [];\n" "\tappend(insts, ('a': inst_lit));\n" "\tappend(insts, ('b': inst_lit));\n" "\tinsert(insts[1], (7: inst_split));\n" "\tif (len(insts) != 3) { return 1; };\n" "\tlet r: i32 = 0;\n" "\tmatch (insts[1]) {\n" "\tcase let z: inst_split => r += (z: i32);\n" "\tcase => return 2;\n" "\t};\n" "\tmatch (insts[2]) {\n" "\tcase let l: inst_lit => { if ((l: rune) == 'b') { r += 20; }; };\n" "\tcase => return 3;\n" "\t};\n" "\treturn r;\n" "};\n", 27, NULL }, /* The deref-of-local base — delete's regex_shape twin: the * header lives behind a *[]i64 param. */ { "regex_shape", "package main;\n" "fn ins_at(i: i64, p: *[]i64, v: i64) void = {\n" "\tinsert((*p)[i], v);\n" "};\n" "export fn main() i32 = {\n" "\tlet xs: []i64 = [];\n" "\tappend(xs, 1);\n" "\tappend(xs, 3);\n" "\tins_at(1, &xs, 2);\n" "\tif (len(xs) != 3) { return 1; };\n" "\treturn (xs[0] + xs[1]*10 + xs[2]*50): i32;\n" "};\n", 171, NULL }, /* Pins len bookkeeping under iteration: each front-insert must * see the grown len and shift the whole accumulated tail; * per-position checks of [4,3,2,1] make any wrong order, missed * shift, or stale len visible. */ { "insert_in_loop", "package main;\n" "export fn main() i32 = {\n" "\tlet xs: []i64 = [];\n" "\tlet v: i64 = 1;\n" "\tfor (v <= 4) {\n" "\t\tinsert(xs[0], v);\n" "\t\tv += 1;\n" "\t};\n" "\tif (len(xs) != 4) { return 1; };\n" "\tlet k: i32 = 0;\n" "\tfor (k < 4) {\n" "\t\tif (xs[k] != (4 - k): i64) { return 2 + k; };\n" "\t\tk += 1;\n" "\t};\n" "\treturn 47;\n" "};\n", 47, NULL }, /* #50 THE value eval-order pin for the TAGGED-dst arm (ken's * f50v4_tagged shape exact): v = (xs.len: size)+2 widens into * the box. Pre-grow len=2 -> v=4; the pre-#50 post-grow boxing * read len=3 -> v=5 (the exit-15 path). The scalar arm always * ordered value-first (scalar_pregrow_val below); #50 aligns * the boxing arm to it. */ { "tagged_pregrow_val", "package main;\n" "type un = (size | void);\n" "export fn main() i32 = {\n" "\tlet xs: []un = [];\n" "\tappend(xs, 10: size);\n" "\tappend(xs, 20: size);\n" "\tinsert(xs[1], ((xs.len: size) + 2));\n" "\tmatch (xs[1]) {\n" "\tcase let v: size => { if (v != 4) { return (v: i32) + 10; }; };\n" "\tcase void => { return 2; };\n" "\t};\n" "\tif (len(xs) != 3) { return 3; };\n" "\treturn 50;\n" "};\n", 50, NULL }, /* #50 scalar no-regress control (ken's f50_insertorder): the * scalar-dst arm was Hare-correct pre-#50 — value reads the * pre-grow len. */ { "scalar_pregrow_val", "package main;\n" "export fn main() i32 = {\n" "\tlet xs: []size = [];\n" "\tappend(xs, 10: size);\n" "\tappend(xs, 20: size);\n" "\tinsert(xs[1], len(xs): size);\n" "\tif (xs[1] != 2) { return 1; };\n" "\tif (len(xs) != 3) { return 2; };\n" "\treturn 55;\n" "};\n", 55, NULL }, /* #50 SELF-REFERENCE value: v is an element of the destination * itself — pre-grow boxing must read the OLD base (and survive * a rt_ensure realloc via the frame scratch): [10,20] -> * insert(xs[0], xs[1]) -> [20,10,20]. */ { "tagged_selfref_val", "package main;\n" "type un = (size | void);\n" "export fn main() i32 = {\n" "\tlet xs: []un = [];\n" "\tappend(xs, 10: size);\n" "\tappend(xs, 20: size);\n" "\tinsert(xs[0], xs[1]);\n" "\tmatch (xs[0]) {\n" "\tcase let v: size => { if (v != 20) { return 1; }; };\n" "\tcase void => { return 2; };\n" "\t};\n" "\tmatch (xs[1]) {\n" "\tcase let v: size => { if (v != 10) { return 3; }; };\n" "\tcase void => { return 4; };\n" "\t};\n" "\tmatch (xs[2]) {\n" "\tcase let v: size => { if (v != 20) { return 5; }; };\n" "\tcase void => { return 6; };\n" "\t};\n" "\tif (len(xs) != 3) { return 7; };\n" "\treturn 51;\n" "};\n", 51, NULL }, /* #50 str-payload tagged box: the widen store's str branch * (AX=ptr BX=len) also runs pre-grow now — header lands * whole. */ { "tagged_str_payload", "package main;\n" "type su = (str | void);\n" "export fn main() i32 = {\n" "\tlet ss: []su = [];\n" "\tappend(ss, \"aa\");\n" "\tinsert(ss[0], \"bb\");\n" "\tmatch (ss[0]) {\n" "\tcase let s: str => {\n" "\t\tif (s.len != 2) { return 1; };\n" "\t\tif (s[0] != 'b') { return 2; };\n" "\t};\n" "\tcase void => { return 3; };\n" "\t};\n" "\tmatch (ss[1]) {\n" "\tcase let s: str => { if (s[0] != 'a') { return 4; }; };\n" "\tcase void => { return 5; };\n" "\t};\n" "\treturn 52;\n" "};\n", 52, NULL }, /* #50 the regex {,0}-class shape standalone (regex.ww:643-650, * the fold-5b consumer reviewer-5b's mutant killed): a * len-reading value CAST to a named variant alias — * `((insts.len: size) + 2): inst_split`. Pre-grow len=2 -> 4. */ { "tagged_regex_minrep", "package main;\n" "type ispl = size;\n" "type un = (ispl | void);\n" "export fn main() i32 = {\n" "\tlet xs: []un = [];\n" "\tappend(xs, 7: ispl);\n" "\tappend(xs, 8: ispl);\n" "\tinsert(xs[0], ((xs.len: size) + 2): ispl);\n" "\tmatch (xs[0]) {\n" "\tcase let v: ispl => { if (v != 4) { return (v: i32) + 10; }; };\n" "\tcase void => { return 2; };\n" "\t};\n" "\tif (len(xs) != 3) { return 3; };\n" "\treturn 53;\n" "};\n", 53, NULL }, /* #50 ken's k50a: 30 appends force repeated rt_ensure realloc; * every value reads xs[0] (as-unwrap + arithmetic) off the * potentially-moved base — the one shape that pins the OLD-base * read across an ACTUAL base move, which single-grow rows * can't. */ { "tagged_realloc_selfref_loop", "package main;\n" "type un = (void | size);\n" "export fn main() i32 = {\n" "\tlet xs: []un = [];\n" "\tappend(xs, 100: size);\n" "\tlet i: size = 0;\n" "\tfor (i < 30) {\n" "\t\tappend(xs, ((xs[0] as size) + i));\n" "\t\ti += 1;\n" "\t};\n" "\tif (len(xs) != 31) { return 1; };\n" "\tlet k: size = 1;\n" "\tfor (k < 31) {\n" "\t\tmatch (xs[k]) {\n" "\t\tcase let v: size => { if (v != 100 + (k - 1)) { return 2; }; };\n" "\t\tcase void => { return 3; };\n" "\t\t};\n" "\t\tk += 1;\n" "\t};\n" "\treturn 56;\n" "};\n", 56, NULL }, /* #50 direct append() (no insert desugar) — the fix lives in * append's tagged arm, so pin it without the rotate. */ { "tagged_append_pregrow_val", "package main;\n" "type un = (size | void);\n" "export fn main() i32 = {\n" "\tlet xs: []un = [];\n" "\tappend(xs, 10: size);\n" "\tappend(xs, 20: size);\n" "\tappend(xs, (xs.len: size) + 2);\n" "\tmatch (xs[2]) {\n" "\tcase let v: size => { if (v != 4) { return (v: i32) + 10; }; };\n" "\tcase void => { return 2; };\n" "\t};\n" "\tif (len(xs) != 3) { return 3; };\n" "\treturn 54;\n" "};\n", 54, NULL }, /* #50 ken's k50b: SEQUENCED inserts at position edges (0, then * mid), each with a len-reading value — every boxing must see * its own pre-grow len (2 -> 200, then 3 -> 4), and the second * insert's rotate must shift the first's result correctly: * [10,20] -> [200,10,20] -> [200,10,4,20]. */ { "tagged_seq_positions", "package main;\n" "type un = (void | size);\n" "export fn main() i32 = {\n" "\tlet xs: []un = [];\n" "\tappend(xs, 10: size);\n" "\tappend(xs, 20: size);\n" "\tinsert(xs[0], ((xs.len: size) * 100));\n" "\tmatch (xs[0]) {\n" "\tcase let v: size => { if (v != 200) { return 1; }; };\n" "\tcase void => { return 2; };\n" "\t};\n" "\tinsert(xs[2], ((xs.len: size) + 1));\n" "\tmatch (xs[2]) {\n" "\tcase let v: size => { if (v != 4) { return 3; }; };\n" "\tcase void => { return 4; };\n" "\t};\n" "\tmatch (xs[1]) { case let v: size => { if (v != 10) { return 5; }; }; case void => { return 6; }; };\n" "\tmatch (xs[3]) { case let v: size => { if (v != 20) { return 7; }; }; case void => { return 8; }; };\n" "\tif (len(xs) != 4) { return 9; };\n" "\treturn 57;\n" "};\n", 57, NULL }, /* #50 ken's k50c: VOID-variant value — a tag-only box through * the fresh @apptagscr (the zeroed scratch IS the payload), * then a len-reading size insert over the mixed slice. */ { "tagged_void_variant", "package main;\n" "type un = (void | size);\n" "export fn main() i32 = {\n" "\tlet xs: []un = [];\n" "\tappend(xs, 5: size);\n" "\tinsert(xs[0], void);\n" "\tif (!(xs[0] is void)) { return 1; };\n" "\tmatch (xs[1]) { case let v: size => { if (v != 5) { return 2; }; }; case void => { return 3; }; };\n" "\tinsert(xs[1], ((xs.len: size)));\n" "\tmatch (xs[1]) { case let v: size => { if (v != 2) { return 4; }; }; case void => { return 5; }; };\n" "\tif (len(xs) != 3) { return 6; };\n" "\treturn 58;\n" "};\n", 58, NULL }, { "reject_array", "package main;\n" "export fn main() i32 = {\n" "\tlet t: [3]i64 = [1, 2, 3];\n" "\tinsert(t[0], 9);\n" "\treturn 0;\n" "};\n", BUILD_FAIL, "insert must operate on a slice" }, { "reject_nonindex", "package main;\n" "export fn main() i32 = {\n" "\tlet xs: []u8 = [];\n" "\tappend(xs, 5u8);\n" "\tinsert(xs, 6u8);\n" "\treturn 0;\n" "};\n", BUILD_FAIL, "insert: operand must be an indexing expression" }, { "reject_range", "package main;\n" "export fn main() i32 = {\n" "\tlet xs: []u8 = [];\n" "\tappend(xs, 5u8);\n" "\tappend(xs, 6u8);\n" "\tinsert(xs[0:1], 7u8);\n" "\treturn 0;\n" "};\n", BUILD_FAIL, "insert: range place is invalid" }, { "reject_arity1", "package main;\n" "export fn main() i32 = {\n" "\tlet xs: []u8 = [];\n" "\tappend(xs, 5u8);\n" "\tinsert(xs[0]);\n" "\treturn 0;\n" "};\n", BUILD_FAIL, "insert: takes exactly two arguments" }, { "reject_arity3", "package main;\n" "export fn main() i32 = {\n" "\tlet xs: []u8 = [];\n" "\tappend(xs, 5u8);\n" "\tinsert(xs[0], 6u8, 7u8);\n" "\treturn 0;\n" "};\n", BUILD_FAIL, "insert: takes exactly two arguments" }, { "reject_spread", "package main;\n" "export fn main() i32 = {\n" "\tlet xs: []u8 = [];\n" "\tlet vs: []u8 = [];\n" "\tappend(vs, 1u8);\n" "\tinsert(xs[0], vs...);\n" "\treturn 0;\n" "};\n", BUILD_FAIL, "insert: spread form insert(xs[i], vs...) unimplemented (task #35)" }, }; /* errlog_has — the build-failure stderr must carry the row's expected * diagnostic; any other failure (parse error, crash) is a vacuous * reject and must not pass. */ static int errlog_has(const char *path, const char *needle) { FILE *f = fopen(path, "rb"); if (!f) return 0; char buf[8192]; size_t got = fread(buf, 1, sizeof buf - 1, f); fclose(f); buf[got] = '\0'; return strstr(buf, needle) != NULL; } static int run_driver(const char *driver, const struct row *r, int i) { char tmpdir[64], src[128], outbin[128], errlog[128], rmcmd[160], cmd[1200]; snprintf(tmpdir, sizeof tmpdir, "/tmp/inse_%d_d_%d", getpid(), i); mkdir(tmpdir, 0755); snprintf(src, sizeof src, "%s/inse_%d_%d.ww", tmpdir, getpid(), i); snprintf(outbin, sizeof outbin, "%s/inse_%d_%d", tmpdir, getpid(), i); snprintf(errlog, sizeof errlog, "%s/err", tmpdir); snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); FILE *f = fopen(src, "wb"); if (!f) { runwait(rmcmd); return -1; } fputs(r->src, f); fclose(f); snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>%s", driver, outbin, src, errlog); if (runwait(cmd) != 0) { int rc = -1; if (r->want != BUILD_FAIL) { fprintf(stderr, "row[%s]: build via %s failed\n", r->label, driver); } else if (r->expect_err && !errlog_has(errlog, r->expect_err)) { fprintf(stderr, "row[%s]: %s build failed without " "expected diagnostic \"%s\"\n", r->label, driver, r->expect_err); rc = -3; /* failed, but for the wrong reason */ } runwait(rmcmd); return rc; } int got = runwait(outbin); runwait(rmcmd); return got; } /* asm_byte_identical — generate .s via cstage's w6c and wwstage's * w6c_ww and diff. Both insert lowerings are written fresh, so this is * the converged-by-construction gate: any drift in the rotate loop, * the ins_l/ins_e label sequence, the @insscr frame slot, or the * reused append body shows here. */ static int asm_byte_identical(const char *bin, const struct row *r, int i) { char src[64], cs[64], ws[64], cmd[1024]; snprintf(src, sizeof src, "/tmp/inse_asm_%d_%d.ww", getpid(), i); snprintf(cs, sizeof cs, "/tmp/inse_asm_%d_%d_c.s", getpid(), i); snprintf(ws, sizeof ws, "/tmp/inse_asm_%d_%d_w.s", getpid(), i); FILE *f = fopen(src, "wb"); if (!f) return -1; fputs(r->src, f); fclose(f); snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: w6c errored\n", r->label); unlink(src); return -1; } snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null", bin, ws, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label); unlink(src); unlink(cs); return -1; } FILE *fc = fopen(cs, "rb"); FILE *fw = fopen(ws, "rb"); int rc = 0; if (!fc || !fw) { rc = -1; } else { for (;;) { int a = fgetc(fc); int b = fgetc(fw); if (a != b) { rc = -1; break; } if (a == EOF) break; } } if (fc) fclose(fc); if (fw) fclose(fw); if (rc != 0) fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n", r->label); unlink(src); unlink(cs); unlink(ws); return rc; } int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; 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; } char cdrv[1024]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); char wdrv[1024]; snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); struct { const char *name; const char *path; int gated_on_existence; } drivers[] = { { "cstage", cdrv, 0 }, { "wwstage", wdrv, 1 }, { NULL, NULL, 0 }, }; int n = (int)(sizeof rows / sizeof rows[0]); int total = 0, fail = 0; for (int d = 0; drivers[d].name; d++) { if (drivers[d].gated_on_existence && access(drivers[d].path, X_OK) != 0) { fprintf(stderr, "insert_elem: skip %s (no %s)\n", drivers[d].name, drivers[d].path); continue; } for (int i = 0; i < n; i++) { int got = run_driver(drivers[d].path, &rows[i], i); total++; int bad = rows[i].want == BUILD_FAIL ? (got != -1) : (got != rows[i].want); if (bad) { fprintf(stderr, "insert_elem[%s][%s]: exit=%d want=%d\n", drivers[d].name, rows[i].label, got, rows[i].want); fail++; } } } if (access(wdrv, X_OK) == 0) { for (int i = 0; i < n; i++) { if (rows[i].want == BUILD_FAIL) continue; total++; if (asm_byte_identical(bin, &rows[i], i) != 0) fail++; } } if (fail) { fprintf(stderr, "insert_elem: %d/%d fixtures failed\n", fail, total); return 1; } printf("insert_elem: %d fixtures passed\n", total); return 0; }