/* * 989_modresetadj_run — BUG-A (#9) regression pin: the lexer must NOT * leave a stale TK_MODPATH pending past a `//ww:module-reset`. * * THE BUG: `//ww:module

` and `//ww:module-reset` are both recognized * inside skipws() in SOURCE ORDER, each setting a sticky lexer flag; * lexnext() drains them in a FIXED order (modreset first, modpath second). * When the two land in the SAME skipws run with no real token between them * — i.e. `//ww:module e` directly followed by `//ww:module-reset`, which the * sep driver emits for an empty / export-less inlined module body — the * modreset drained first, then the STALE TK_MODPATH=e surfaced PAST the * reset boundary, re-binding pathmod=e. The root `package main` was then * validated against import path "e" → "package main does not match import * path e" → HARD REJECT (cmd/wcc/parse.c:1437, both stages). * * THE FIX (cmd/wcc/lex.c + lib/ww/syntax/lex.ww, skipws): a reset * recognized in source clears any modpath set earlier in the same skipws * run. In the NORMAL non-empty boundary (reset THEN the next module's path) * the clear is a no-op — the path is set after the reset and legitimately * survives (the 990-997 byte-id gates cover that case). * * WHY a direct-frontend pin and not a `ww build` dir-fixture: the driver * materializes an empty module's interface (`.wwi`) BEFORE composing the * root unit, and wwi_emit defaults a decl-less module's package line to the * literal "main" (task #11, a SEPARATE bug) — so a natural empty-module dir * import never produces the directive ADJACENCY this bug needs; it trips #11 * first. The pin therefore feeds the composed unit (with the adjacency) * straight to w6c / w6c_ww, immune to #11. * * Three legs (the teeth): (1) w6c accepts the adjacency unit (pre-fix it * REJECTS — the BUG-A teeth); (2) w6c_ww accepts it (rule-10 twin); * (3) the two `.s` are byte-identical (rule-10 stage symmetry) and the * assembled+linked program runs to its return value (the emitted code is * valid, not just parse-clean). * * Light wwstage-driver test (CLAUDE.md rule 14): every intermediate is * `-o`-redirected to /tmp, so it is phase-1 parallel-safe and never lands * next to a source. Models 989_declns_sep + 994_w6c_ww conventions; 989 * prefix per the sep-gate precedent (the 7xx range is exhausted). */ #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; } static const char * absbin(void) { const char *b = getenv("BIN"); if (!b) b = "out/bin"; if (b[0] == '/') return b; static char buf[2048]; char cwd[1024]; if (getcwd(cwd, sizeof cwd) == NULL) return NULL; snprintf(buf, sizeof buf, "%s/%s", cwd, b); return buf; } static int slurp(const char *path, char **outbuf, size_t *outlen) { FILE *f = fopen(path, "rb"); if (!f) return -1; fseek(f, 0, SEEK_END); long n = ftell(f); fseek(f, 0, SEEK_SET); if (n < 0) { fclose(f); return -1; } char *b = malloc((size_t)n + 1); if (!b) { fclose(f); return -1; } if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; } b[n] = '\0'; fclose(f); *outbuf = b; *outlen = (size_t)n; return 0; } static int files_eq(const char *a, const char *b) { char *ba = NULL, *bb = NULL; size_t na = 0, nb = 0; if (slurp(a, &ba, &na) < 0 || slurp(b, &bb, &nb) < 0) { free(ba); free(bb); return -1; } int eq = (na == nb && memcmp(ba, bb, na) == 0); free(ba); free(bb); return eq ? 0 : 1; } static int write_file(const char *path, const char *content) { FILE *f = fopen(path, "wb"); if (!f) return -1; fputs(content, f); fclose(f); return 0; } /* * The composed sep-unit carrying the bug's trigger: `//ww:module e` * (opens module e, sets the pending modpath) immediately followed — with * no real token between — by `//ww:module-reset` (closes the empty body). * Both directives land in ONE skipws run. The primary body then declares * `package main` against the reset boundary; pre-fix the stale modpath=e * survived and the clause was checked against "e". RET is distinctive so * the run leg proves main actually executed. */ #define RET 42 static const char UNIT[] = "//ww:module e\n" "//ww:module-reset\n" "package main;\n" "import e;\n" "export fn main() i32 = { return 42; };\n"; int main(void) { const char *bin = absbin(); if (!bin) return 1; int pid = (int)getpid(); char unit[64], scs[64], sww[64], obj[64], prog[64], cmd[4096]; snprintf(unit, sizeof unit, "/tmp/mradj_%d.unit.ww", pid); snprintf(scs, sizeof scs, "/tmp/mradj_%d_cs.s", pid); snprintf(sww, sizeof sww, "/tmp/mradj_%d_ww.s", pid); snprintf(obj, sizeof obj, "/tmp/mradj_%d.o", pid); snprintf(prog, sizeof prog, "/tmp/mradj_%d.bin", pid); int fail = 0; if (write_file(unit, UNIT) != 0) { fprintf(stderr, "modresetadj: cannot write %s\n", unit); return 1; } /* Leg 1 — cstage w6c accepts the adjacency (pre-fix: REJECT). */ snprintf(cmd, sizeof cmd, "%s/w6c -c -o %s %s 2>/dev/null", bin, scs, unit); if (runwait(cmd) != 0) { fprintf(stderr, "modresetadj FAIL: w6c rejected the adjacency unit (BUG-A)\n"); fail++; } /* Leg 2 — wwstage w6c_ww accepts it (rule-10 twin). */ snprintf(cmd, sizeof cmd, "%s/w6c_ww -c -o %s %s 2>/dev/null", bin, sww, unit); if (runwait(cmd) != 0) { fprintf(stderr, "modresetadj FAIL: w6c_ww rejected the adjacency unit (BUG-A)\n"); fail++; } /* Leg 3a — the two .s are byte-identical (rule-10 stage symmetry). */ if (!fail && files_eq(scs, sww) != 0) { fprintf(stderr, "modresetadj FAIL: w6c vs w6c_ww .s differ (rule 10)\n"); fail++; } /* Leg 3b — the emitted code assembles, links, and runs to RET. */ if (!fail) { snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s 2>/dev/null", bin, obj, scs); if (runwait(cmd) != 0) { fprintf(stderr, "modresetadj FAIL: w6a errored\n"); fail++; } } if (!fail) { snprintf(cmd, sizeof cmd, "%s/w6l -o %s %s %s/../lib/libwwrt.a 2>/dev/null", bin, prog, obj, bin); if (runwait(cmd) != 0) { fprintf(stderr, "modresetadj FAIL: w6l errored\n"); fail++; } } if (!fail) { int got = runwait(prog); if (got != RET) { fprintf(stderr, "modresetadj FAIL: program exit=%d want=%d\n", got, RET); fail++; } } unlink(unit); unlink(scs); unlink(sww); unlink(obj); unlink(prog); if (fail) return 1; printf("modresetadj: w6c/w6c_ww accept the //ww:module + //ww:module-reset " "adjacency, byte-identical .s, program runs to %d (#9)\n", RET); return 0; }