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:
@@ -33,10 +33,13 @@ main(int argc, char **argv)
|
||||
{
|
||||
const char *src = NULL;
|
||||
const char *out = NULL;
|
||||
int testmode = 0;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
const char *a = argv[i];
|
||||
if (strcmp(a, "-o") == 0 && i + 1 < argc) {
|
||||
out = argv[++i];
|
||||
} else if (strcmp(a, "-T") == 0) {
|
||||
testmode = 1;
|
||||
} else if (a[0] == '-') {
|
||||
fprintf(stderr, "w6c: unknown flag %s\n", a);
|
||||
return 2;
|
||||
@@ -48,7 +51,7 @@ main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
if (src == NULL) {
|
||||
fputs("usage: w6c [-o out.s] file.ww\n", stderr);
|
||||
fputs("usage: w6c [-T] [-o out.s] file.ww\n", stderr);
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -71,6 +74,7 @@ main(int argc, char **argv)
|
||||
if (l.errs || p.errs) return 1;
|
||||
|
||||
check_init(&c, a);
|
||||
c.is_test = testmode;
|
||||
check_file(&c, file);
|
||||
if (c.errs) return 1;
|
||||
|
||||
|
||||
@@ -2650,6 +2650,7 @@ check_init(Checker *c, Arena *a)
|
||||
{
|
||||
memset(c, 0, sizeof *c);
|
||||
c->a = a;
|
||||
c->is_test = 0; /* #15: caller (w6c main) sets it after init */
|
||||
typesinit(a);
|
||||
c->top = newscope(a, NULL);
|
||||
c->cur = c->top;
|
||||
@@ -2951,6 +2952,92 @@ check_file(Checker *c, Node *file)
|
||||
}
|
||||
c->cur_mod = NULL;
|
||||
|
||||
/*
|
||||
* #15 @test harness — under `w6c -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-check (pass 2 below, which stamps the
|
||||
* appended entry for free). This 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, which
|
||||
* only flips a mode bit (ref/hare/cmd/hare/build.ha:46-49). cgen is
|
||||
* untouched: the appended N_FNDECL rides the existing cgfn path, so
|
||||
* byte-id holds at the gated choke point by construction.
|
||||
*
|
||||
* D1-D5 SANCTIONED PLAN-9-LEAN REDUCTION of hare-test (user-ratified,
|
||||
* reinstatable post-CSP; this is a documented departure, not a hare
|
||||
* cite): D1 no __test_array linker section — straight-line calls;
|
||||
* D2 call @test fns by their real symbols (no testfunc.%d rename);
|
||||
* 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 exit.
|
||||
*/
|
||||
if (c->is_test) {
|
||||
Pos fp = file->pos;
|
||||
/* (b) the synth entry OWNS `main` — loud-reject a user one. */
|
||||
for (Node *d = file->list; d; d = d->next)
|
||||
if (d->kind == N_FNDECL && d->str
|
||||
&& strcmp(d->str, "main") == 0 && d->body != NULL)
|
||||
err(c, d->pos, "test mode: main is synthesized "
|
||||
"by -T; remove the explicit main");
|
||||
/* (c) collect @test fns in file->list order, build the body. */
|
||||
Node *bhead = NULL, *btail = NULL;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (d->kind != N_FNDECL)
|
||||
continue;
|
||||
int istest = 0;
|
||||
for (Node *at = d->attr; at; at = at->next)
|
||||
if (at->str && strcmp(at->str, "test") == 0) {
|
||||
istest = 1;
|
||||
break;
|
||||
}
|
||||
if (!istest)
|
||||
continue;
|
||||
/* `fn f() void` parses the explicit `void` into d->lhs
|
||||
* (parse.c:1329), so void-returning is lhs==NULL OR an
|
||||
* N_TNAME "void" — not lhs==NULL alone. */
|
||||
int retvoid = d->lhs == NULL
|
||||
|| (d->lhs->kind == N_TNAME && d->lhs->str
|
||||
&& strcmp(d->lhs->str, "void") == 0);
|
||||
if (d->list != NULL || !retvoid) {
|
||||
err(c, d->pos, "@test fn '%s' must be fn() void",
|
||||
d->str);
|
||||
continue;
|
||||
}
|
||||
Node *call = newnode(c->a, N_CALL, fp);
|
||||
call->lhs = newnode(c->a, N_IDENT, fp);
|
||||
call->lhs->str = d->str;
|
||||
Node *es = newnode(c->a, N_EXPRSTMT, fp);
|
||||
es->lhs = call;
|
||||
if (bhead == NULL) bhead = es;
|
||||
else btail->next = es;
|
||||
btail = es;
|
||||
}
|
||||
Node *ret = newnode(c->a, N_RETURN, fp);
|
||||
ret->lhs = newnode(c->a, N_INTLIT, fp);
|
||||
ret->lhs->uval = 0;
|
||||
if (bhead == NULL) bhead = ret;
|
||||
else btail->next = ret;
|
||||
Node *body = newnode(c->a, N_BLOCK, fp);
|
||||
body->list = bhead;
|
||||
Node *m = newnode(c->a, N_FNDECL, fp);
|
||||
m->str = "main";
|
||||
m->export = 1;
|
||||
m->lhs = newnode(c->a, N_TNAME, fp);
|
||||
m->lhs->str = "i32";
|
||||
m->body = body;
|
||||
m->type = build_fn_type(c, m);
|
||||
/* pass 1 already ran, so the install loop never stamped m's
|
||||
* type; set it explicitly (pass 2 below reads d->type). */
|
||||
Node *tl = file->list;
|
||||
if (tl == NULL) file->list = m;
|
||||
else {
|
||||
while (tl->next) tl = tl->next;
|
||||
tl->next = m;
|
||||
}
|
||||
}
|
||||
|
||||
/* pass 2: check def initialisers and fn bodies */
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
c->cur_mod = decl_mod(file, d);
|
||||
|
||||
@@ -555,6 +555,8 @@ struct Checker {
|
||||
* would shadow an imported module bareword. */
|
||||
int loops; /* nesting count for break/continue */
|
||||
int errs;
|
||||
int is_test; /* #15: `w6c -T` — collect @test fns + synth
|
||||
* the entry; loud-reject a user main. */
|
||||
Node *alloc_octx; /* #3/B': the one empty `alloc([], n)` call node
|
||||
* that has let-declared slice context this walk;
|
||||
* any OTHER empty alloc has no element-type hint
|
||||
|
||||
Reference in New Issue
Block a user