wcc: -T test-mode collects @test fns + synthesizes entry, both stages (#15)
@test was parsed then dropped (no consumer); `ww test` needed a hand-written
main listing each test by hand, so adding a @test and forgetting the call
silently skipped it. -T makes the checker collect @test N_FNDECLs in source
order, loud-reject a user main, and append a synthetic
`export fn main() i32 { t0(); ...; return 0; }` at the install->body-check seam;
the existing cgfn emits it (cgen untouched) -> byte-identical by construction.
Mirrors harec's checker-side is_test placement.
Plan-9-lean reduction (user-sanctioned, reinstatable post-CSP): sequential,
abort/nonzero=fail; no setjmp isolation, no fnmatch filter, no file:line.
910/997 rewired from a regex scanner to driving `w6c -T` directly (thin trusted
drivers; the @test content stays ww), with a cross-stage byte-id assert on the
-T output. attest_userman/attest_badsig pin the user-main and bad-signature
rejects.
This commit is contained in:
@@ -10311,6 +10311,8 @@ type checker = struct {
|
||||
nresolved: i32,
|
||||
nunresolved: i32,
|
||||
errs: i32,
|
||||
istest: i32, // #15: `w6c_ww -T` — collect @test fns +
|
||||
// synth the entry; loud-reject a user main.
|
||||
verbose: i32, // when non-zero, log each unresolved name
|
||||
fnret: *node, // enclosing fn's return type AST (for `?`)
|
||||
curmod: str, // importing-module bareword for the decl
|
||||
@@ -15705,6 +15707,7 @@ export fn checkinit(c: *checker, tc: *tctx) void = {
|
||||
c.nresolved = 0;
|
||||
c.nunresolved = 0;
|
||||
c.errs = 0;
|
||||
c.istest = 0i32; // #15: caller (w6c main) sets it after init
|
||||
c.verbose = 0;
|
||||
c.fnret = nil;
|
||||
let empty: str;
|
||||
@@ -15726,6 +15729,110 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
d = d.next;
|
||||
};
|
||||
|
||||
// #15 @test harness — under `w6c_ww -T`, synthesize the entry the
|
||||
// driver would otherwise hand-wire. We sit at the seam between
|
||||
// fn-install (Pass 1, all names now in scope so the synth callees
|
||||
// resolve) and fn-body-resolve (Pass 2 below, which stamps the
|
||||
// appended entry for free). Mirrors harec's checker-side is_test
|
||||
// work — keep @test fns + suppress/own the hosted main
|
||||
// (ref/harec/src/check.c:3941,4000) — NOT the build driver. cgen is
|
||||
// untouched: the appended N_FNDECL rides cgfn, byte-id by
|
||||
// construction (rule 10; cstage twin at cmd/wcc/check.c).
|
||||
//
|
||||
// D1-D5 SANCTIONED PLAN-9-LEAN REDUCTION of hare-test (user-ratified,
|
||||
// reinstatable post-CSP; documented departure, not a hare cite):
|
||||
// D1 no __test_array section — straight-line calls; D2 call @test
|
||||
// fns by their real symbols (no testfunc.%d); D3 no per-test
|
||||
// setjmp/onabort isolation — run-to-completion = pass, abort/div0/
|
||||
// SIGSEGV/nonzero = fail and STOPS the run; D4 no sort, no fnmatch
|
||||
// filter — source/collection order; D5 no reflective file:line, no
|
||||
// per-test ok/FAIL line — failure is the nonzero process exit.
|
||||
if (c.istest != 0) {
|
||||
let pf: str = file.file;
|
||||
let pl: i32 = file.line;
|
||||
let pc: i32 = file.col;
|
||||
// (b) the synth entry OWNS `main` — loud-reject a user one.
|
||||
let u: *node = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == nkind.N_FNDECL && u.body != nil
|
||||
&& streq(u.str, "main")) {
|
||||
cerr(u.file);
|
||||
cerr(": error: test mode: main is synthesized by -T; remove the explicit main\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
// (c) collect @test fns in file.list order; build the body chain.
|
||||
let bhead: *node = nil;
|
||||
let btail: *node = nil;
|
||||
let t: *node = file.list;
|
||||
for (t != nil) {
|
||||
if (t.kind == nkind.N_FNDECL) {
|
||||
let istest: bool = false;
|
||||
let at: *node = t.attr;
|
||||
for (at != nil) {
|
||||
if (at.kind == nkind.N_ATTR
|
||||
&& streq(at.str, "test")) {
|
||||
istest = true;
|
||||
};
|
||||
at = at.next;
|
||||
};
|
||||
if (istest) {
|
||||
// `fn f() void` parses the explicit void
|
||||
// into t.lhs, so void-returning is lhs==nil
|
||||
// OR an N_TNAME "void".
|
||||
let retvoid: bool = t.lhs == nil
|
||||
|| (t.lhs.kind == nkind.N_TNAME
|
||||
&& streq(t.lhs.str, "void"));
|
||||
if (t.list != nil || !retvoid) {
|
||||
cerr(t.file);
|
||||
cerr(": error: @test fn '");
|
||||
cerr(t.str);
|
||||
cerr("' must be fn() void\n");
|
||||
c.errs += 1;
|
||||
} else {
|
||||
let call: *node = newnode(nkind.N_CALL, pf, pl, pc);
|
||||
let cid: *node = newnode(nkind.N_IDENT, pf, pl, pc);
|
||||
cid.str = t.str;
|
||||
call.lhs = cid;
|
||||
let es: *node = newnode(nkind.N_EXPRSTMT, pf, pl, pc);
|
||||
es.lhs = call;
|
||||
if (bhead == nil) { bhead = es; }
|
||||
else { btail.next = es; };
|
||||
btail = es;
|
||||
};
|
||||
};
|
||||
};
|
||||
t = t.next;
|
||||
};
|
||||
let ret: *node = newnode(nkind.N_RETURN, pf, pl, pc);
|
||||
let zero: *node = newnode(nkind.N_INTLIT, pf, pl, pc);
|
||||
zero.uval = 0u64;
|
||||
ret.lhs = zero;
|
||||
if (bhead == nil) { bhead = ret; } else { btail.next = ret; };
|
||||
btail = ret;
|
||||
let body: *node = newnode(nkind.N_BLOCK, pf, pl, pc);
|
||||
body.list = bhead;
|
||||
let m: *node = newnode(nkind.N_FNDECL, pf, pl, pc);
|
||||
m.str = "main";
|
||||
m.exported = 1i32;
|
||||
let rety: *node = newnode(nkind.N_TNAME, pf, pl, pc);
|
||||
rety.str = "i32";
|
||||
m.lhs = rety;
|
||||
m.body = body;
|
||||
// Append to file.list tail. No type_ pre-set: installdecl
|
||||
// never stamps a fn's type_ on the ww side either — cgfn reads
|
||||
// lhs/list on demand, like every parsed fn. Pass-2 below
|
||||
// resolve-walks the appended body.
|
||||
let tl: *node = file.list;
|
||||
if (tl == nil) {
|
||||
file.list = m;
|
||||
} else {
|
||||
for (tl.next != nil) { tl = tl.next; };
|
||||
tl.next = m;
|
||||
};
|
||||
};
|
||||
|
||||
// Pass 2: walk decl bodies/types and resolve identifiers.
|
||||
// Track the per-decl module bareword so bare-leaf lookups inside
|
||||
// the body prefer same-module entries over alphabetically-earlier
|
||||
@@ -42878,6 +42985,7 @@ fn slurp(path: *u8) (*u8, u64) = {
|
||||
export fn main(argc: i32, argv: **u8) i32 = {
|
||||
let src: *u8 = nil;
|
||||
let out: *u8 = nil;
|
||||
let testmode: i32 = 0i32; // #15: `-T` test-mode
|
||||
|
||||
let i: i32 = 1;
|
||||
for (i < argc) {
|
||||
@@ -42890,6 +42998,8 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
return 2;
|
||||
};
|
||||
out = argv[i];
|
||||
} else { if (cstreq(a, "-T")) {
|
||||
testmode = 1i32;
|
||||
} else { if (a[0u64] == 45u8) {
|
||||
let m: str = "w6c: unknown flag\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
@@ -42901,12 +43011,12 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
return 2;
|
||||
};
|
||||
src = a;
|
||||
}; };
|
||||
}; }; };
|
||||
i += 1;
|
||||
};
|
||||
|
||||
if (src == nil) {
|
||||
let m: str = "usage: w6c_ww [-o out.s] file.ww\n";
|
||||
let m: str = "usage: w6c_ww [-T] [-o out.s] file.ww\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
return 2;
|
||||
};
|
||||
@@ -42967,6 +43077,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
typesinit(&tc);
|
||||
let ck: checker;
|
||||
checkinit(&ck, &tc);
|
||||
ck.istest = testmode;
|
||||
checkfile(&ck, f);
|
||||
if (ck.errs > 0) { return 1; };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user