diff --git a/Makefile b/Makefile
index 1b48299b..87a87f56 100644
--- a/Makefile
+++ b/Makefile
@@ -374,7 +374,7 @@ SEP_WW_TARGETS = $(SEP_WW_TESTS:%=wwtest/%)
# they run under test-compiler.
XMOD_WW_TESTS = test/xmod/collide_test.ww test/xmod/m1_test.ww \
test/xmod/label_test.ww test/xmod/typecheck_test.ww \
- test/xmod/enumcap_test.ww
+ test/xmod/enumcap_test.ww test/xmod/modreset_test.ww
XMOD_WW_TARGETS = $(XMOD_WW_TESTS:%=wwtest/%)
BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \
test/wcc/991_w6a_ww.c \
diff --git a/test/wcc/989_modresetadj_run.c b/test/wcc/989_modresetadj_run.c
deleted file mode 100644
index 9b097669..00000000
--- a/test/wcc/989_modresetadj_run.c
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * 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).
- *
- * Every intermediate is `-o`-redirected to /tmp 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;
-}
diff --git a/test/xmod/modreset_test.ww b/test/xmod/modreset_test.ww
new file mode 100644
index 00000000..a8a5b5b0
--- /dev/null
+++ b/test/xmod/modreset_test.ww
@@ -0,0 +1,82 @@
+package modreset_test;
+
+// BUG-A (#9) regression pin: the lexer must not leave a stale
+// TK_MODPATH pending past a `//ww:module-reset`. Port of the retired
+// native carrier test/wcc/989_modresetadj_run.c; every assertion
+// preserved.
+//
+// The composed sep-unit puts `//ww:module e` and `//ww:module-reset`
+// in ONE skipws run with no real token between (the shape the sep
+// driver emits for an empty/export-less inlined module body); pre-fix
+// the stale modpath=e survived the reset and `package main` was
+// rejected against import path "e". The unit is fed STRAIGHT to
+// w6c/w6c_ww: the natural empty-module dir route trips the separate
+// #11 wwi_emit bug first, and the driver cannot locate the phantom
+// package e (which is also why the corpus cannot host it -- the
+// blanket data byte-id gate drives every non-error fixture through
+// `ww build`).
+//
+// Legs: (1) w6c accepts the adjacency (pre-fix REJECT -- the teeth);
+// (2) w6c_ww accepts it (rule-10 twin); (3a) the two .s are
+// byte-identical; (3b) assemble+link+run to exit 42 (the emitted code
+// is valid, not just parse-clean).
+
+import os;
+import os.exec;
+import strings;
+import testenv;
+import time;
+
+fn fail(why: str) void = {
+ let m: str = strings.concat("modreset FAIL: ", why, "\n");
+ os.write(2, m.ptr, m.len: u64);
+ assert(false);
+};
+
+fn tmo() time.duration = {
+ return (240i64 * (time.second: i64)): time.duration;
+};
+
+// -1 encodes an abnormal (non-EXIT) termination, never a valid code.
+fn runcode(dir: str, name: str, argv: []str) i32 = {
+ let co: testenv.commandout;
+ testenv.runcommand(dir, dir, name, argv, tmo(), &co);
+ if (co.termination != exec.termination.EXIT) { return -1; };
+ return co.code;
+};
+
+@test fn modresetadj() void = {
+ let td: str = testenv.fresh();
+ let unit: str = strings.concat(td, "/mradj.unit.ww");
+ testenv.writefile(unit, strings.concat(
+ "//ww:module e\n",
+ "//ww:module-reset\n",
+ "package main;\n",
+ "import e;\n",
+ "export fn main() i32 = { return 42; };\n"));
+ let scs: str = strings.concat(td, "/cs.s");
+ let sww: str = strings.concat(td, "/ww.s");
+ let cav: []str = [testenv.driver("w6c"), "-c", "-o", scs, unit];
+ if (runcode(td, "w6c", cav) != 0) {
+ fail("w6c rejected the adjacency unit (BUG-A stale modpath)");
+ };
+ let wav: []str = [testenv.driver("w6c_ww"), "-c", "-o", sww, unit];
+ if (runcode(td, "w6c_ww", wav) != 0) {
+ fail("w6c_ww rejected the adjacency unit (BUG-A)");
+ };
+ if (!testenv.same(testenv.readfile(scs), testenv.readfile(sww))) {
+ fail("w6c vs w6c_ww .s differ (rule 10)");
+ };
+ let obj: str = strings.concat(td, "/mradj.o");
+ let aav: []str = [testenv.driver("w6a"), "-o", obj, scs];
+ if (runcode(td, "w6a", aav) != 0) { fail("w6a errored"); };
+ let prog: str = strings.concat(td, "/mradj.bin");
+ let lav: []str = [testenv.driver("w6l"), "-o", prog, obj,
+ strings.concat(testenv.repo(), "/out/lib/libwwrt.a")];
+ if (runcode(td, "w6l", lav) != 0) { fail("w6l errored"); };
+ let rav: []str = [prog];
+ if (runcode(td, "run", rav) != 42) {
+ fail("program exit != 42 (main did not run to its return)");
+ };
+ testenv.clean(td);
+};