wcc/ww: .wwi export-data producer + check_exported_type (#22 M2)
New `w6c -I <out.wwi>` flag (both stages) writes a re-parseable ww-prototype rendering of a package's EXPORTED surface. M2 dead-code: nothing consumes .wwi yet (combined.ww stays the live path); the flag is off on every existing invocation, so the 990-997 byte-id gates and all prior tests are unperturbed. The unparse walks the AST type-expr subtree (N_T* nodes), not the tinfo Type* (which collapses nominal pkg.Name identity). Deterministic output: package line, byte-sorted imports, byte-sorted decls — a pure function of the exported API. cmd/w6c/wwi.c + selfhost/cmd/wcc/wwi.ww emit byte-identical .wwi (new cross-stage byte-id substrate, rule 10). check_exported_type (drew) rides the producer entry, flag-gated: an exported signature naming a non-exported nominal is loud-rejected before any byte is written, identically on both stages. Ports harec check.c:4092-4168, recursing the type-AST and gating on the resolved SK_TYPE sym's decl export flag (Sym.exported is vestigial in both stages; the predeclared synthetic `nomem` decl carries no source position and is treated as a builtin leaf — cstage parity). Two wwstage checker AST-mutations are normalized to cstage's pristine view for byte-id: the N_TPARAM tuple-element wrapper (unwrapped) and the variadic `T...`→`[]T` param desugar (peeled). Gate 989_m2wwi_run: ascii/strings/getopt each produce a .wwi that re-parses (wwdump -a) and is cs==ww byte-identical; a private-type-leak fixture is rejected identically by both stages (non-vacuous check).
This commit is contained in:
9
Makefile
9
Makefile
@@ -43,7 +43,7 @@ WD_SRC = cmd/wwdump/main.c
|
||||
WD_OBJ = $(WD_SRC:cmd/wwdump/%.c=$(OBJ)/wwdump/%.o)
|
||||
|
||||
W6C_SRC = cmd/w6c/main.c cmd/w6c/cgen.c cmd/w6c/txt.c cmd/w6c/swt.c \
|
||||
cmd/w6c/peep.c cmd/w6c/reg.c
|
||||
cmd/w6c/peep.c cmd/w6c/reg.c cmd/w6c/wwi.c
|
||||
W6C_OBJ = $(W6C_SRC:cmd/w6c/%.c=$(OBJ)/w6c/%.o)
|
||||
|
||||
W6A_SRC = cmd/w6a/main.c cmd/w6a/lex.c cmd/w6a/parse.c cmd/w6a/asm.c cmd/w6a/obj.c
|
||||
@@ -146,7 +146,7 @@ $(BIN)/wwdump_ww: selfhost/cmd/wwdump/main.ww \
|
||||
$(BIN)/w6c_ww: selfhost/cmd/w6c/main.ww \
|
||||
lib/ww/lex/lex.ww lib/ww/lex/tok.ww lib/ww/ast.ww \
|
||||
lib/ww/parse/parse.ww lib/ww/parse/expr.ww lib/ww/parse/stmt.ww lib/ww/parse/decl.ww lib/ww/typ.ww lib/ww/sym.ww \
|
||||
selfhost/cmd/wcc/check.ww \
|
||||
selfhost/cmd/wcc/check.ww selfhost/cmd/wcc/wwi.ww \
|
||||
selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \
|
||||
selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \
|
||||
selfhost/cmd/wcc/cgendecl.ww \
|
||||
@@ -561,6 +561,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_siphash_run $(BIN)/test_sha256_run \
|
||||
$(BIN)/test_regex_run \
|
||||
$(BIN)/test_lib_byteid \
|
||||
$(BIN)/test_m2wwi_run \
|
||||
$(BIN)/test_floatlit_run \
|
||||
$(BIN)/test_checked_run \
|
||||
$(BIN)/test_floatarr_run \
|
||||
@@ -3082,6 +3083,10 @@ $(BIN)/test_lib_byteid: test/wcc/989_lib_byteid.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_m2wwi_run: test/wcc/989_m2wwi_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6c_ww $(BIN)/wwdump $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_floatlit_run: test/wcc/989_floatlit_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
@@ -56,6 +56,11 @@ void emit(Cg*, Prog*);
|
||||
/* txt.c */
|
||||
void txt_emit(FILE*, Prog *head);
|
||||
|
||||
/* wwi.c — `.wwi` export-data producer (w6c -I). M2 dead-code: writes a
|
||||
* re-parseable ww-prototype rendering of the package's exported surface.
|
||||
* Returns non-zero if check_exported_type rejects a dangling export. */
|
||||
int wwi_emit(Checker *c, FILE *of, Node *file);
|
||||
|
||||
/* swt.c, peep.c, reg.c — placeholders for now */
|
||||
void peephole(Cg*);
|
||||
void regalloc_init(Cg*);
|
||||
|
||||
@@ -33,11 +33,14 @@ main(int argc, char **argv)
|
||||
{
|
||||
const char *src = NULL;
|
||||
const char *out = NULL;
|
||||
const char *wwiout = NULL; /* -I <out.wwi>: M2 export-data producer */
|
||||
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, "-I") == 0 && i + 1 < argc) {
|
||||
wwiout = argv[++i];
|
||||
} else if (strcmp(a, "-T") == 0) {
|
||||
testmode = 1;
|
||||
} else if (a[0] == '-') {
|
||||
@@ -78,6 +81,19 @@ main(int argc, char **argv)
|
||||
check_file(&c, file);
|
||||
if (c.errs) return 1;
|
||||
|
||||
/* M2 export-data: write the `.wwi` after a clean check, before cgen.
|
||||
* Dead on the live path (no existing invocation passes -I); the
|
||||
* producer's check_exported_type may reject a dangling export. */
|
||||
if (wwiout) {
|
||||
FILE *wf = fopen(wwiout, "wb");
|
||||
if (wf == NULL) {
|
||||
fprintf(stderr, "w6c: cannot open %s\n", wwiout);
|
||||
return 1;
|
||||
}
|
||||
if (wwi_emit(&c, wf, file) != 0) { fclose(wf); return 1; }
|
||||
fclose(wf);
|
||||
}
|
||||
|
||||
FILE *of = stdout;
|
||||
if (out) {
|
||||
of = fopen(out, "wb");
|
||||
|
||||
457
cmd/w6c/wwi.c
Normal file
457
cmd/w6c/wwi.c
Normal file
@@ -0,0 +1,457 @@
|
||||
/*
|
||||
* wwi.c — `.wwi` export-data producer (w6c -I). M2 DEAD-CODE: writes a
|
||||
* re-parseable ww-prototype rendering of a package's EXPORTED surface.
|
||||
* Nothing consumes `.wwi` yet (combined.ww stays the live path); the only
|
||||
* caller is the new -I flag, off on every existing invocation.
|
||||
*
|
||||
* Specs: .ai/rob-M2-spec.md (producer) + .ai/drew-M2-checkexported.md
|
||||
* (check_exported_type). Two load-bearing choices follow them:
|
||||
*
|
||||
* - Unparse walks the AST type-expr subtree (the N_T* nodes), NOT the
|
||||
* checked Type* — tinfo collapses nominal pkg.Name identity (MEMORY
|
||||
* tinfo_lossy_nominal). The AST preserves surface syntax + names.
|
||||
* - check_exported_type rides the producer entry (flag-gated), so M2
|
||||
* stays dead on the normal `.s` path. It rejects exactly one thing:
|
||||
* an exported signature naming a non-exported nominal type.
|
||||
*
|
||||
* A `.wwi` is ONE package's interface. The M2 gate feeds w6c a driver-
|
||||
* combined unit (imports concatenated ahead of the target), so the emit
|
||||
* filters to PRIMARY decls (imported == 0) — in the real per-package
|
||||
* compile every decl is primary, so the filter is a no-op there. Output
|
||||
* is a pure function of the exported API: package line, byte-sorted
|
||||
* imports, byte-sorted decls (no map/hash iteration order).
|
||||
*/
|
||||
#include "gc.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int
|
||||
wwi_primary(Node *n)
|
||||
{
|
||||
/* imported == 1 marks a decl reached through a `//ww:module <path>`
|
||||
* boundary (an imported module's concatenated section). */
|
||||
return n && n->imported == 0;
|
||||
}
|
||||
|
||||
/* --- check_exported_type (drew) ------------------------------------- *
|
||||
* Resolve an N_TNAME to its type sym WITHOUT the side effects of
|
||||
* resolve_typename (no on-demand resolve, no double "unknown type"
|
||||
* error). A primitive/keyword resolves to no SK_TYPE → leaf. By the
|
||||
* time the producer runs, check_file has finished and c->cur == c->top.
|
||||
*/
|
||||
static Sym *
|
||||
wwi_typesym(Checker *c, const char *nm)
|
||||
{
|
||||
if (nm == NULL) return NULL;
|
||||
Sym *s = scope_lookup_type(c->cur, NULL, nm);
|
||||
if (s == NULL) {
|
||||
const char *dot = strrchr(nm, '.');
|
||||
if (dot)
|
||||
s = scope_lookup_type(c->cur, NULL, dot + 1);
|
||||
}
|
||||
if (s && s->kind == SK_TYPE)
|
||||
return s;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
wwi_check_type(Checker *c, Pos loc, Node *t)
|
||||
{
|
||||
int bad = 0;
|
||||
if (t == NULL)
|
||||
return 0;
|
||||
switch (t->kind) {
|
||||
case N_TNAME: {
|
||||
Sym *s = wwi_typesym(c, t->str);
|
||||
/* Sym.exported is vestigial (the checker never sets it); the
|
||||
* nominal's export status lives on its decl node, parser-set.
|
||||
* No file-guard twin to wwstage's: cstage resolves `nomem` and
|
||||
* the primitives through lookup_builtin (no scope SK_TYPE), so
|
||||
* scope_lookup_type returns NULL for them and they never reach
|
||||
* this reject — harec's STORAGE_NOMEM leaf-arm, by construction. */
|
||||
if (s && s->decl && s->decl->kind == N_TYPEDECL
|
||||
&& s->decl->export == 0) {
|
||||
errorf(loc, "exported declaration references "
|
||||
"unexported type '%s'", t->str);
|
||||
bad = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case N_TPTR:
|
||||
case N_TSLICE:
|
||||
case N_TBANG:
|
||||
case N_TCHAN:
|
||||
bad |= wwi_check_type(c, loc, t->lhs);
|
||||
break;
|
||||
case N_TARRAY:
|
||||
/* element only; the length is a const-expr, not a type. */
|
||||
bad |= wwi_check_type(c, loc, t->lhs);
|
||||
break;
|
||||
case N_TFN:
|
||||
for (Node *p = t->list; p; p = p->next)
|
||||
bad |= wwi_check_type(c, loc, p->lhs);
|
||||
bad |= wwi_check_type(c, loc, t->lhs);
|
||||
break;
|
||||
case N_TSTRUCT:
|
||||
for (Node *f = t->list; f; f = f->next)
|
||||
bad |= wwi_check_type(c, loc, f->lhs);
|
||||
break;
|
||||
case N_TTAGGED:
|
||||
case N_TTUPLE:
|
||||
for (Node *e = t->list; e; e = e->next)
|
||||
bad |= wwi_check_type(c, loc, e);
|
||||
break;
|
||||
case N_TENUM:
|
||||
/* the inline enum body is the definition, not a reference;
|
||||
* recurse only its storage type (members are values). */
|
||||
bad |= wwi_check_type(c, loc, t->lhs);
|
||||
break;
|
||||
default:
|
||||
/* a non-type node in type position should not occur — leaf. */
|
||||
break;
|
||||
}
|
||||
return bad;
|
||||
}
|
||||
|
||||
static int
|
||||
wwi_check_decl(Checker *c, Node *d)
|
||||
{
|
||||
int bad = 0;
|
||||
switch (d->kind) {
|
||||
case N_FNDECL:
|
||||
for (Node *p = d->list; p; p = p->next)
|
||||
bad |= wwi_check_type(c, d->pos, p->lhs);
|
||||
bad |= wwi_check_type(c, d->pos, d->lhs); /* ret */
|
||||
break;
|
||||
case N_TYPEDECL:
|
||||
bad |= wwi_check_type(c, d->pos, d->lhs);
|
||||
break;
|
||||
case N_DEF:
|
||||
/* the declared type; the rhs const value is not a type. */
|
||||
bad |= wwi_check_type(c, d->pos, d->lhs);
|
||||
break;
|
||||
case N_LET:
|
||||
bad |= wwi_check_type(c, d->pos, d->lhs);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return bad;
|
||||
}
|
||||
|
||||
/* --- type-expr + const-expr unparser (rob §2.2/§2.4) ---------------- */
|
||||
|
||||
static void wwi_expr(FILE *of, Node *e);
|
||||
static void wwi_type(FILE *of, Node *t);
|
||||
|
||||
static void
|
||||
wwi_quote(FILE *of, const char *s, u64 n)
|
||||
{
|
||||
fputc('"', of);
|
||||
for (u64 i = 0; i < n; i++) {
|
||||
unsigned char c = (unsigned char)s[i];
|
||||
switch (c) {
|
||||
case '"': fputs("\\\"", of); break;
|
||||
case '\\': fputs("\\\\", of); break;
|
||||
case '\n': fputs("\\n", of); break;
|
||||
case '\t': fputs("\\t", of); break;
|
||||
default:
|
||||
if (c < 0x20) fprintf(of, "\\x%02x", c);
|
||||
else fputc(c, of);
|
||||
}
|
||||
}
|
||||
fputc('"', of);
|
||||
}
|
||||
|
||||
static void
|
||||
wwi_param(FILE *of, Node *p)
|
||||
{
|
||||
if (p->str && strcmp(p->str, "...") == 0) { /* C-style FFI `...` */
|
||||
fputs("...", of);
|
||||
return;
|
||||
}
|
||||
if (p->str && p->str[0]) {
|
||||
fputs(p->str, of);
|
||||
fputs(": ", of);
|
||||
}
|
||||
wwi_type(of, p->lhs);
|
||||
if (p->op == TK_ELLIPSIS) /* Hare `T...` variadic */
|
||||
fputs("...", of);
|
||||
}
|
||||
|
||||
static void
|
||||
wwi_type(FILE *of, Node *t)
|
||||
{
|
||||
if (t == NULL) { /* absent return type spells void */
|
||||
fputs("void", of);
|
||||
return;
|
||||
}
|
||||
switch (t->kind) {
|
||||
case N_TNAME:
|
||||
fputs(t->str ? t->str : "void", of);
|
||||
break;
|
||||
case N_TPTR:
|
||||
fputc('*', of);
|
||||
wwi_type(of, t->lhs);
|
||||
break;
|
||||
case N_TSLICE:
|
||||
fputs("[]", of);
|
||||
wwi_type(of, t->lhs);
|
||||
break;
|
||||
case N_TARRAY:
|
||||
fputc('[', of);
|
||||
if (t->rhs) wwi_expr(of, t->rhs);
|
||||
else fputc('_', of);
|
||||
fputc(']', of);
|
||||
wwi_type(of, t->lhs);
|
||||
break;
|
||||
case N_TBANG:
|
||||
fputc('!', of);
|
||||
wwi_type(of, t->lhs);
|
||||
break;
|
||||
case N_TCHAN:
|
||||
fputs("chan ", of);
|
||||
wwi_type(of, t->lhs);
|
||||
break;
|
||||
case N_TFN:
|
||||
fputs("fn(", of);
|
||||
for (Node *p = t->list; p; p = p->next) {
|
||||
if (p != t->list) fputs(", ", of);
|
||||
wwi_param(of, p);
|
||||
}
|
||||
fputs(") ", of);
|
||||
wwi_type(of, t->lhs);
|
||||
break;
|
||||
case N_TSTRUCT:
|
||||
fputs("struct { ", of);
|
||||
for (Node *f = t->list; f; f = f->next) {
|
||||
if (f != t->list) fputs(", ", of);
|
||||
if (f->str && f->str[0]) {
|
||||
fputs(f->str, of);
|
||||
fputs(": ", of);
|
||||
}
|
||||
wwi_type(of, f->lhs);
|
||||
}
|
||||
fputs(" }", of);
|
||||
break;
|
||||
case N_TTUPLE:
|
||||
fputc('(', of);
|
||||
for (Node *e = t->list; e; e = e->next) {
|
||||
if (e != t->list) fputs(", ", of);
|
||||
wwi_type(of, e);
|
||||
}
|
||||
fputc(')', of);
|
||||
break;
|
||||
case N_TTAGGED:
|
||||
fputc('(', of);
|
||||
for (Node *e = t->list; e; e = e->next) {
|
||||
if (e != t->list) fputs(" | ", of);
|
||||
wwi_type(of, e);
|
||||
}
|
||||
fputc(')', of);
|
||||
break;
|
||||
case N_TENUM:
|
||||
fputs("enum ", of);
|
||||
if (t->lhs) {
|
||||
wwi_type(of, t->lhs);
|
||||
fputc(' ', of);
|
||||
}
|
||||
fputs("{ ", of);
|
||||
for (Node *m = t->list; m; m = m->next) {
|
||||
if (m != t->list) fputs(", ", of);
|
||||
fputs(m->str, of);
|
||||
if (m->lhs) {
|
||||
fputs(" = ", of);
|
||||
wwi_expr(of, m->lhs);
|
||||
}
|
||||
}
|
||||
fputs(" }", of);
|
||||
break;
|
||||
default:
|
||||
fatal("wwi: unhandled type node kind %d", t->kind);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
wwi_expr(FILE *of, Node *e)
|
||||
{
|
||||
if (e == NULL)
|
||||
return;
|
||||
switch (e->kind) {
|
||||
case N_INTLIT:
|
||||
fprintf(of, "%llu", (unsigned long long)e->uval);
|
||||
if (e->tsuffix) fputs(e->tsuffix, of);
|
||||
break;
|
||||
case N_IDENT:
|
||||
case N_TNAME:
|
||||
fputs(e->str, of);
|
||||
break;
|
||||
case N_DOT:
|
||||
wwi_expr(of, e->lhs);
|
||||
fputc('.', of);
|
||||
fputs(e->str, of);
|
||||
break;
|
||||
case N_TRUE: fputs("true", of); break;
|
||||
case N_FALSE: fputs("false", of); break;
|
||||
case N_NIL: fputs("nil", of); break;
|
||||
case N_STRLIT: wwi_quote(of, e->str, e->strlen); break;
|
||||
case N_BIN:
|
||||
wwi_expr(of, e->lhs);
|
||||
fprintf(of, " %s ", tokname(e->op));
|
||||
wwi_expr(of, e->rhs);
|
||||
break;
|
||||
case N_UN:
|
||||
fputs(tokname(e->op), of);
|
||||
wwi_expr(of, e->lhs);
|
||||
break;
|
||||
default:
|
||||
fatal("wwi: unhandled const-expr node kind %d", e->kind);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
wwi_decl(FILE *of, Node *d)
|
||||
{
|
||||
switch (d->kind) {
|
||||
case N_FNDECL:
|
||||
fputs("export fn ", of);
|
||||
fputs(d->str, of);
|
||||
fputc('(', of);
|
||||
for (Node *p = d->list; p; p = p->next) {
|
||||
if (p != d->list) fputs(", ", of);
|
||||
wwi_param(of, p);
|
||||
}
|
||||
fputs(") ", of);
|
||||
wwi_type(of, d->lhs);
|
||||
fputs(";\n", of);
|
||||
break;
|
||||
case N_TYPEDECL:
|
||||
fputs("export type ", of);
|
||||
fputs(d->str, of);
|
||||
fputs(" = ", of);
|
||||
wwi_type(of, d->lhs);
|
||||
fputs(";\n", of);
|
||||
break;
|
||||
case N_DEF:
|
||||
fputs("export def ", of);
|
||||
fputs(d->str, of);
|
||||
fputs(": ", of);
|
||||
wwi_type(of, d->lhs);
|
||||
fputs(" = ", of);
|
||||
wwi_expr(of, d->rhs);
|
||||
fputs(";\n", of);
|
||||
break;
|
||||
case N_LET:
|
||||
fputs("export let ", of);
|
||||
fputs(d->str, of);
|
||||
fputs(": ", of);
|
||||
if (d->lhs == NULL)
|
||||
fatal("wwi: exported let '%s' has no declared type",
|
||||
d->str);
|
||||
wwi_type(of, d->lhs);
|
||||
fputs(";\n", of);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- deterministic ordering (rob §3) -------------------------------- */
|
||||
|
||||
struct declent { Node *d; int idx; };
|
||||
struct useent { const char *path; int idx; };
|
||||
|
||||
static int
|
||||
declcmp(const void *a, const void *b)
|
||||
{
|
||||
const struct declent *x = a, *y = b;
|
||||
int r = strcmp(x->d->str, y->d->str); /* symbol name ONLY */
|
||||
if (r != 0) return r;
|
||||
return x->idx - y->idx; /* total order, stable */
|
||||
}
|
||||
|
||||
static int
|
||||
usecmp(const void *a, const void *b)
|
||||
{
|
||||
const struct useent *x = a, *y = b;
|
||||
int r = strcmp(x->path, y->path);
|
||||
if (r != 0) return r;
|
||||
return x->idx - y->idx;
|
||||
}
|
||||
|
||||
static int
|
||||
wwi_is_decl(Node *d)
|
||||
{
|
||||
return d->kind == N_FNDECL || d->kind == N_TYPEDECL
|
||||
|| d->kind == N_DEF || d->kind == N_LET;
|
||||
}
|
||||
|
||||
int
|
||||
wwi_emit(Checker *c, FILE *of, Node *file)
|
||||
{
|
||||
/* §5: check_exported_type FIRST, before any byte is written — a
|
||||
* producer without it can emit a dangling `.wwi`. */
|
||||
int bad = 0;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (!wwi_primary(d) || !d->export || !wwi_is_decl(d))
|
||||
continue;
|
||||
bad |= wwi_check_decl(c, d);
|
||||
}
|
||||
if (bad)
|
||||
return 1;
|
||||
|
||||
/* package line: leaf of the first primary decl's module tag. */
|
||||
const char *pkg = "main";
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (wwi_primary(d) && d->module && d->module[0]) {
|
||||
const char *dot = strrchr(d->module, '.');
|
||||
pkg = dot ? dot + 1 : d->module;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fprintf(of, "package %s;\n", pkg);
|
||||
|
||||
/* imports — primary N_USE, byte-sorted by import path. */
|
||||
int nuse = 0;
|
||||
for (Node *u = file->list; u; u = u->next)
|
||||
if (u->kind == N_USE && wwi_primary(u))
|
||||
nuse++;
|
||||
if (nuse > 0) {
|
||||
struct useent *us = malloc((size_t)nuse * sizeof *us);
|
||||
int k = 0;
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || !wwi_primary(u))
|
||||
continue;
|
||||
us[k].path = u->usepath ? u->usepath : u->str;
|
||||
us[k].idx = k;
|
||||
k++;
|
||||
}
|
||||
qsort(us, (size_t)nuse, sizeof *us, usecmp);
|
||||
for (int i = 0; i < nuse; i++)
|
||||
fprintf(of, "import %s;\n", us[i].path);
|
||||
free(us);
|
||||
}
|
||||
|
||||
/* decls — exported primary, byte-sorted by symbol name. */
|
||||
int ndecl = 0;
|
||||
for (Node *d = file->list; d; d = d->next)
|
||||
if (wwi_primary(d) && d->export && wwi_is_decl(d))
|
||||
ndecl++;
|
||||
if (ndecl > 0) {
|
||||
struct declent *ds = malloc((size_t)ndecl * sizeof *ds);
|
||||
int k = 0;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (!wwi_primary(d) || !d->export || !wwi_is_decl(d))
|
||||
continue;
|
||||
ds[k].d = d;
|
||||
ds[k].idx = k;
|
||||
k++;
|
||||
}
|
||||
qsort(ds, (size_t)ndecl, sizeof *ds, declcmp);
|
||||
for (int i = 0; i < ndecl; i++)
|
||||
wwi_decl(of, ds[i].d);
|
||||
free(ds);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -45288,6 +45288,564 @@ export fn fargregname(i: i32) str = {
|
||||
return "?";
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// wwi.ww — `.wwi` export-data producer (w6c_ww -I). M2 DEAD-CODE: writes
|
||||
// a re-parseable ww-prototype rendering of a package's EXPORTED surface.
|
||||
// Nothing consumes `.wwi` yet (combined.ww stays the live path); the only
|
||||
// caller is the new -I flag, off on every existing invocation.
|
||||
//
|
||||
// wwstage twin of cmd/w6c/wwi.c — byte-identical output is a rule-10
|
||||
// requirement (`.wwi` is a new cross-stage byte-id substrate). Specs:
|
||||
// .ai/rob-M2-spec.md + .ai/drew-M2-checkexported.md.
|
||||
//
|
||||
// - Unparse walks the AST type-expr subtree (the N_T* nodes), NOT the
|
||||
// tinfo — tinfo collapses nominal pkg.Name identity.
|
||||
// - check_exported_type rides the producer entry (flag-gated), so M2
|
||||
// stays dead on the normal `.s` path. It rejects exactly one thing:
|
||||
// an exported signature naming a non-exported nominal type.
|
||||
// - A `.wwi` is ONE package's interface; the M2 gate feeds a driver-
|
||||
// combined unit, so the emit filters to PRIMARY decls (imported==0).
|
||||
|
||||
// Imports mirror check.ww (os/tok/strconv only): node/nkind/sym/skind/
|
||||
// scopelookuptype/streq/checker/tkind resolve bare in the selfhost's flat
|
||||
// bundled scope, exactly as check.ww references them.
|
||||
package wcc;
|
||||
|
||||
import os;
|
||||
import tok;
|
||||
import strconv;
|
||||
|
||||
// --- byte writers ------------------------------------------------------
|
||||
|
||||
fn wputs(fd: i32, s: str) void = {
|
||||
os.write(fd, s.ptr, s.len: u64);
|
||||
};
|
||||
|
||||
fn wputb(fd: i32, b: u8) void = {
|
||||
let buf: [1]u8;
|
||||
buf[0] = b;
|
||||
os.write(fd, buf.ptr, 1u64);
|
||||
};
|
||||
|
||||
fn wquote(fd: i32, s: str) void = {
|
||||
wputb(fd, '"');
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
if (c == '"') {
|
||||
wputs(fd, "\\\"");
|
||||
} else { if (c == '\\') {
|
||||
wputs(fd, "\\\\");
|
||||
} else { if (c == '\n') {
|
||||
wputs(fd, "\\n");
|
||||
} else { if (c == '\t') {
|
||||
wputs(fd, "\\t");
|
||||
} else { if (c < 32u8) {
|
||||
let hi: u8 = c >> 4u8;
|
||||
let lo: u8 = c & 15u8;
|
||||
let h: u8 = 0u8;
|
||||
let l: u8 = 0u8;
|
||||
if (hi < 10u8) { h = hi + 48u8; } else { h = (hi - 10u8) + 97u8; };
|
||||
if (lo < 10u8) { l = lo + 48u8; } else { l = (lo - 10u8) + 97u8; };
|
||||
let buf: [4]u8;
|
||||
buf[0] = 92u8;
|
||||
buf[1] = 120u8;
|
||||
buf[2] = h;
|
||||
buf[3] = l;
|
||||
os.write(fd, buf.ptr, 4u64);
|
||||
} else {
|
||||
wputb(fd, c);
|
||||
};};};};};
|
||||
i += 1;
|
||||
};
|
||||
wputb(fd, '"');
|
||||
};
|
||||
|
||||
// --- check_exported_type (drew) ---------------------------------------
|
||||
//
|
||||
// Resolve an N_TNAME to its type sym via the public sym helpers (mirror
|
||||
// of cstage wwi_typesym / scope_lookup_type) WITHOUT the side effects of
|
||||
// the checker's aliassym (no on-demand resolve, no double error). A
|
||||
// primitive/keyword resolves to no SK_TYPE → leaf. By producer time
|
||||
// checkfile has finished and c.cur == c.top.
|
||||
|
||||
fn wwitypesym(c: *checker, nm: str) *sym = {
|
||||
let empty: str;
|
||||
let s: *sym = scopelookuptype(c.cur, empty, nm);
|
||||
if (s == nil) {
|
||||
let dotidx: i32 = -1;
|
||||
let i: i32 = 0;
|
||||
for (i < nm.len) {
|
||||
if (nm[i] == 46u8) { dotidx = i; };
|
||||
i += 1;
|
||||
};
|
||||
if (dotidx >= 0) {
|
||||
let leaf: str;
|
||||
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
|
||||
leaf.len = nm.len - dotidx - 1;
|
||||
s = scopelookuptype(c.cur, empty, leaf);
|
||||
};
|
||||
};
|
||||
if (s == nil) { return nil; };
|
||||
if (s.skind != skind.SK_TYPE) { return nil; };
|
||||
return s;
|
||||
};
|
||||
|
||||
fn wwireject(d: *node, nm: str) void = {
|
||||
wputs(2, d.file);
|
||||
wputs(2, ":");
|
||||
wputs(2, strconv.u64tos(d.line: u64, strconv.base.DEC));
|
||||
wputs(2, ":");
|
||||
wputs(2, strconv.u64tos(d.col: u64, strconv.base.DEC));
|
||||
wputs(2, ": error: exported declaration references unexported type '");
|
||||
wputs(2, nm);
|
||||
wputs(2, "'\n");
|
||||
};
|
||||
|
||||
// Returns 1 if a non-exported nominal was named (loud), else 0.
|
||||
fn wwichecktype(c: *checker, d: *node, t: *node) i32 = {
|
||||
if (t == nil) { return 0; };
|
||||
// rule-10: the wwstage checker wraps N_TTUPLE.list elements in
|
||||
// N_TPARAM (ast.ww:101); cstage keeps the type-AST pristine. Unwrap
|
||||
// transparently so the recursion sees the same shape cstage walks.
|
||||
if (t.kind == nkind.N_TPARAM) { return wwichecktype(c, d, t.lhs); };
|
||||
let bad: i32 = 0;
|
||||
if (t.kind == nkind.N_TNAME) {
|
||||
let s: *sym = wwitypesym(c, t.str);
|
||||
// sym.exported is vestigial (never set); the nominal's export
|
||||
// status lives on its decl node, parser-set.
|
||||
if (s != nil) {
|
||||
if (s.decl != nil) {
|
||||
if (s.decl.kind == nkind.N_TYPEDECL) {
|
||||
// file.len == 0 ⇒ a checkinit-synthesized
|
||||
// predeclared builtin (the ONLY empty-source
|
||||
// decls: nomemdecl check.ww:126, the `void`
|
||||
// TNAME check.ww:122), not a user nominal — ww's
|
||||
// analogue of harec's STORAGE_NOMEM leaf-arm, and
|
||||
// why cstage (lookup_builtin, no scope SK_TYPE)
|
||||
// needs no such guard.
|
||||
if (s.decl.file.len > 0) {
|
||||
if (s.decl.exported == 0) {
|
||||
wwireject(d, t.str);
|
||||
bad = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} else { if (
|
||||
t.kind == nkind.N_TPTR ||
|
||||
t.kind == nkind.N_TSLICE ||
|
||||
t.kind == nkind.N_TBANG ||
|
||||
t.kind == nkind.N_TCHAN
|
||||
) {
|
||||
bad = bad | wwichecktype(c, d, t.lhs);
|
||||
} else { if (t.kind == nkind.N_TARRAY) {
|
||||
// element only; the length is a const-expr, not a type.
|
||||
bad = bad | wwichecktype(c, d, t.lhs);
|
||||
} else { if (t.kind == nkind.N_TFN) {
|
||||
let p: *node = t.list;
|
||||
for (p != nil) {
|
||||
bad = bad | wwichecktype(c, d, p.lhs);
|
||||
p = p.next;
|
||||
};
|
||||
bad = bad | wwichecktype(c, d, t.lhs);
|
||||
} else { if (t.kind == nkind.N_TSTRUCT) {
|
||||
let f: *node = t.list;
|
||||
for (f != nil) {
|
||||
bad = bad | wwichecktype(c, d, f.lhs);
|
||||
f = f.next;
|
||||
};
|
||||
} else { if (t.kind == nkind.N_TTAGGED || t.kind == nkind.N_TTUPLE) {
|
||||
let e: *node = t.list;
|
||||
for (e != nil) {
|
||||
bad = bad | wwichecktype(c, d, e);
|
||||
e = e.next;
|
||||
};
|
||||
} else { if (t.kind == nkind.N_TENUM) {
|
||||
// the inline enum body is the definition, not a reference;
|
||||
// recurse only its storage type (members are values).
|
||||
bad = bad | wwichecktype(c, d, t.lhs);
|
||||
};};};};};};};
|
||||
return bad;
|
||||
};
|
||||
|
||||
fn wwicheckdecl(c: *checker, d: *node) i32 = {
|
||||
let bad: i32 = 0;
|
||||
if (d.kind == nkind.N_FNDECL) {
|
||||
let p: *node = d.list;
|
||||
for (p != nil) {
|
||||
bad = bad | wwichecktype(c, d, p.lhs);
|
||||
p = p.next;
|
||||
};
|
||||
bad = bad | wwichecktype(c, d, d.lhs); // ret
|
||||
} else { if (d.kind == nkind.N_TYPEDECL) {
|
||||
bad = bad | wwichecktype(c, d, d.lhs);
|
||||
} else { if (d.kind == nkind.N_DEF) {
|
||||
// the declared type; the rhs const value is not a type.
|
||||
bad = bad | wwichecktype(c, d, d.lhs);
|
||||
} else { if (d.kind == nkind.N_LET) {
|
||||
bad = bad | wwichecktype(c, d, d.lhs);
|
||||
};};};};
|
||||
return bad;
|
||||
};
|
||||
|
||||
// --- type-expr + const-expr unparser (rob §2.2/§2.4) ------------------
|
||||
|
||||
fn wwiexpr(fd: i32, e: *node) void = {
|
||||
if (e == nil) { return; };
|
||||
if (e.kind == nkind.N_INTLIT) {
|
||||
wputs(fd, strconv.u64tos(e.uval, strconv.base.DEC));
|
||||
if (e.tsuffix.len > 0) { wputs(fd, e.tsuffix); };
|
||||
} else { if (e.kind == nkind.N_IDENT || e.kind == nkind.N_TNAME) {
|
||||
wputs(fd, e.str);
|
||||
} else { if (e.kind == nkind.N_DOT) {
|
||||
wwiexpr(fd, e.lhs);
|
||||
wputb(fd, '.');
|
||||
wputs(fd, e.str);
|
||||
} else { if (e.kind == nkind.N_TRUE) {
|
||||
wputs(fd, "true");
|
||||
} else { if (e.kind == nkind.N_FALSE) {
|
||||
wputs(fd, "false");
|
||||
} else { if (e.kind == nkind.N_NIL) {
|
||||
wputs(fd, "nil");
|
||||
} else { if (e.kind == nkind.N_STRLIT) {
|
||||
wquote(fd, e.str);
|
||||
} else { if (e.kind == nkind.N_BIN) {
|
||||
wwiexpr(fd, e.lhs);
|
||||
wputb(fd, 32u8);
|
||||
wputs(fd, tokname(e.op));
|
||||
wputb(fd, 32u8);
|
||||
wwiexpr(fd, e.rhs);
|
||||
} else { if (e.kind == nkind.N_UN) {
|
||||
wputs(fd, tokname(e.op));
|
||||
wwiexpr(fd, e.lhs);
|
||||
} else {
|
||||
wputs(2, "wwi: unhandled const-expr node kind\n");
|
||||
os.exit(1);
|
||||
};};};};};};};};};
|
||||
};
|
||||
|
||||
fn wwiparam(fd: i32, p: *node) void = {
|
||||
if (streq(p.str, "...")) { // C-style FFI `...`
|
||||
wputs(fd, "...");
|
||||
return;
|
||||
};
|
||||
if (p.str.len > 0) {
|
||||
wputs(fd, p.str);
|
||||
wputs(fd, ": ");
|
||||
};
|
||||
// rule-10: the wwstage checker desugars a Hare variadic `T...` param
|
||||
// in place to `[]T` (lhs becomes N_TSLICE); cstage leaves lhs == T.
|
||||
// Peel the inserted slice so both stages emit the surface `T...`.
|
||||
if (p.op == tkind.TK_ELLIPSIS && p.lhs != nil && p.lhs.kind == nkind.N_TSLICE) {
|
||||
wwitype(fd, p.lhs.lhs);
|
||||
} else {
|
||||
wwitype(fd, p.lhs);
|
||||
};
|
||||
if (p.op == tkind.TK_ELLIPSIS) { // Hare `T...` variadic
|
||||
wputs(fd, "...");
|
||||
};
|
||||
};
|
||||
|
||||
fn wwitype(fd: i32, t: *node) void = {
|
||||
if (t == nil) { // absent return type spells void
|
||||
wputs(fd, "void");
|
||||
return;
|
||||
};
|
||||
// rule-10: unwrap the wwstage-only N_TPARAM tuple-element wrapper
|
||||
// (ast.ww:101) so the unparse matches cstage's pristine type-AST.
|
||||
if (t.kind == nkind.N_TPARAM) { wwitype(fd, t.lhs); return; };
|
||||
if (t.kind == nkind.N_TNAME) {
|
||||
if (t.str.len > 0) { wputs(fd, t.str); } else { wputs(fd, "void"); };
|
||||
} else { if (t.kind == nkind.N_TPTR) {
|
||||
wputb(fd, '*');
|
||||
wwitype(fd, t.lhs);
|
||||
} else { if (t.kind == nkind.N_TSLICE) {
|
||||
wputs(fd, "[]");
|
||||
wwitype(fd, t.lhs);
|
||||
} else { if (t.kind == nkind.N_TARRAY) {
|
||||
wputb(fd, '[');
|
||||
if (t.rhs != nil) { wwiexpr(fd, t.rhs); } else { wputb(fd, '_'); };
|
||||
wputb(fd, ']');
|
||||
wwitype(fd, t.lhs);
|
||||
} else { if (t.kind == nkind.N_TBANG) {
|
||||
wputb(fd, '!');
|
||||
wwitype(fd, t.lhs);
|
||||
} else { if (t.kind == nkind.N_TCHAN) {
|
||||
wputs(fd, "chan ");
|
||||
wwitype(fd, t.lhs);
|
||||
} else { if (t.kind == nkind.N_TFN) {
|
||||
wputs(fd, "fn(");
|
||||
let p: *node = t.list;
|
||||
for (p != nil) {
|
||||
if (p != t.list) { wputs(fd, ", "); };
|
||||
wwiparam(fd, p);
|
||||
p = p.next;
|
||||
};
|
||||
wputs(fd, ") ");
|
||||
wwitype(fd, t.lhs);
|
||||
} else { if (t.kind == nkind.N_TSTRUCT) {
|
||||
wputs(fd, "struct { ");
|
||||
let f: *node = t.list;
|
||||
for (f != nil) {
|
||||
if (f != t.list) { wputs(fd, ", "); };
|
||||
if (f.str.len > 0) {
|
||||
wputs(fd, f.str);
|
||||
wputs(fd, ": ");
|
||||
};
|
||||
wwitype(fd, f.lhs);
|
||||
f = f.next;
|
||||
};
|
||||
wputs(fd, " }");
|
||||
} else { if (t.kind == nkind.N_TTUPLE) {
|
||||
wputb(fd, '(');
|
||||
let e: *node = t.list;
|
||||
for (e != nil) {
|
||||
if (e != t.list) { wputs(fd, ", "); };
|
||||
wwitype(fd, e);
|
||||
e = e.next;
|
||||
};
|
||||
wputb(fd, ')');
|
||||
} else { if (t.kind == nkind.N_TTAGGED) {
|
||||
wputb(fd, '(');
|
||||
let e: *node = t.list;
|
||||
for (e != nil) {
|
||||
if (e != t.list) { wputs(fd, " | "); };
|
||||
wwitype(fd, e);
|
||||
e = e.next;
|
||||
};
|
||||
wputb(fd, ')');
|
||||
} else { if (t.kind == nkind.N_TENUM) {
|
||||
wputs(fd, "enum ");
|
||||
if (t.lhs != nil) {
|
||||
wwitype(fd, t.lhs);
|
||||
wputb(fd, 32u8);
|
||||
};
|
||||
wputs(fd, "{ ");
|
||||
let m: *node = t.list;
|
||||
for (m != nil) {
|
||||
if (m != t.list) { wputs(fd, ", "); };
|
||||
wputs(fd, m.str);
|
||||
if (m.lhs != nil) {
|
||||
wputs(fd, " = ");
|
||||
wwiexpr(fd, m.lhs);
|
||||
};
|
||||
m = m.next;
|
||||
};
|
||||
wputs(fd, " }");
|
||||
} else {
|
||||
wputs(2, "wwi: unhandled type node kind\n");
|
||||
os.exit(1);
|
||||
};};};};};};};};};};};
|
||||
};
|
||||
|
||||
fn wwidecl(fd: i32, d: *node) void = {
|
||||
if (d.kind == nkind.N_FNDECL) {
|
||||
wputs(fd, "export fn ");
|
||||
wputs(fd, d.str);
|
||||
wputb(fd, '(');
|
||||
let p: *node = d.list;
|
||||
for (p != nil) {
|
||||
if (p != d.list) { wputs(fd, ", "); };
|
||||
wwiparam(fd, p);
|
||||
p = p.next;
|
||||
};
|
||||
wputs(fd, ") ");
|
||||
wwitype(fd, d.lhs);
|
||||
wputs(fd, ";\n");
|
||||
} else { if (d.kind == nkind.N_TYPEDECL) {
|
||||
wputs(fd, "export type ");
|
||||
wputs(fd, d.str);
|
||||
wputs(fd, " = ");
|
||||
wwitype(fd, d.lhs);
|
||||
wputs(fd, ";\n");
|
||||
} else { if (d.kind == nkind.N_DEF) {
|
||||
wputs(fd, "export def ");
|
||||
wputs(fd, d.str);
|
||||
wputs(fd, ": ");
|
||||
wwitype(fd, d.lhs);
|
||||
wputs(fd, " = ");
|
||||
wwiexpr(fd, d.rhs);
|
||||
wputs(fd, ";\n");
|
||||
} else { if (d.kind == nkind.N_LET) {
|
||||
wputs(fd, "export let ");
|
||||
wputs(fd, d.str);
|
||||
wputs(fd, ": ");
|
||||
if (d.lhs == nil) {
|
||||
wputs(2, "wwi: exported let has no declared type\n");
|
||||
os.exit(1);
|
||||
};
|
||||
wwitype(fd, d.lhs);
|
||||
wputs(fd, ";\n");
|
||||
};};};};
|
||||
};
|
||||
|
||||
// --- deterministic ordering (rob §3) ----------------------------------
|
||||
|
||||
fn wwiprimary(n: *node) bool = {
|
||||
// imported==1 marks a decl reached through a `//ww:module <path>`
|
||||
// boundary (an imported module's concatenated section).
|
||||
if (n == nil) { return false; };
|
||||
return n.imported == 0;
|
||||
};
|
||||
|
||||
fn wwiisdecl(d: *node) bool = {
|
||||
return d.kind == nkind.N_FNDECL || d.kind == nkind.N_TYPEDECL ||
|
||||
d.kind == nkind.N_DEF || d.kind == nkind.N_LET;
|
||||
};
|
||||
|
||||
// strcmp — byte lexicographic, mirror C strcmp sign (<0/0/>0). Both
|
||||
// stages key the sort identically, so the `.wwi` order is deterministic.
|
||||
fn wwistrcmp(a: str, b: str) i32 = {
|
||||
let i: i32 = 0;
|
||||
for (i < a.len && i < b.len) {
|
||||
let ca: i32 = a[i]: i32;
|
||||
let cb: i32 = b[i]: i32;
|
||||
if (ca != cb) { return ca - cb; };
|
||||
i += 1;
|
||||
};
|
||||
return a.len - b.len;
|
||||
};
|
||||
|
||||
// Selection sort over parallel (key, node) arrays. Total order keyed on
|
||||
// the symbol name; ties broken by original index — so the result is
|
||||
// stable regardless of any same-name collision, matching cstage's qsort
|
||||
// + idx tiebreak.
|
||||
fn wwisortdecls(keys: []str, nodes: []*node, n: i32) void = {
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
let best: i32 = i;
|
||||
let j: i32 = i + 1;
|
||||
for (j < n) {
|
||||
let r: i32 = wwistrcmp(keys[j], keys[best]);
|
||||
if (r < 0) { best = j; };
|
||||
j += 1;
|
||||
};
|
||||
if (best != i) {
|
||||
let tk: str = keys[i]; keys[i] = keys[best]; keys[best] = tk;
|
||||
let tn: *node = nodes[i]; nodes[i] = nodes[best]; nodes[best] = tn;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
|
||||
export fn wwiemit(c: *checker, file: *node, path: str) i32 = {
|
||||
// §5: check_exported_type FIRST, before any byte — a producer
|
||||
// without it can emit a dangling `.wwi`.
|
||||
let bad: i32 = 0;
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
if (wwiprimary(d) && d.exported != 0 && wwiisdecl(d)) {
|
||||
bad = bad | wwicheckdecl(c, d);
|
||||
};
|
||||
d = d.next;
|
||||
};
|
||||
if (bad != 0) { return 1i32; };
|
||||
|
||||
let fd: i32 = os.open(path,
|
||||
os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
if (fd < 0) {
|
||||
wputs(2, "w6c: cannot open ");
|
||||
wputs(2, path);
|
||||
wputs(2, "\n");
|
||||
return 1i32;
|
||||
};
|
||||
|
||||
// package line: leaf of the first primary decl's module tag.
|
||||
let pkg: str = "main";
|
||||
let pd: *node = file.list;
|
||||
for (pd != nil) {
|
||||
if (wwiprimary(pd) && pd.nmod.len > 0) {
|
||||
let dotidx: i32 = -1;
|
||||
let i: i32 = 0;
|
||||
for (i < pd.nmod.len) {
|
||||
if (pd.nmod[i] == 46u8) { dotidx = i; };
|
||||
i += 1;
|
||||
};
|
||||
if (dotidx >= 0) {
|
||||
let leaf: str;
|
||||
leaf.ptr = pd.nmod.ptr + ((dotidx + 1): u64);
|
||||
leaf.len = pd.nmod.len - dotidx - 1;
|
||||
pkg = leaf;
|
||||
} else {
|
||||
pkg = pd.nmod;
|
||||
};
|
||||
pd = nil;
|
||||
} else {
|
||||
pd = pd.next;
|
||||
};
|
||||
};
|
||||
wputs(fd, "package ");
|
||||
wputs(fd, pkg);
|
||||
wputs(fd, ";\n");
|
||||
|
||||
// imports — primary N_USE, byte-sorted by import path.
|
||||
let nuse: i32 = 0;
|
||||
let u: *node = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == nkind.N_USE && wwiprimary(u)) { nuse += 1; };
|
||||
u = u.next;
|
||||
};
|
||||
if (nuse > 0) {
|
||||
let upaths: []str = alloc([], nuse: u64)!;
|
||||
upaths.len = nuse;
|
||||
let unodes: []*node = alloc([], nuse: u64)!;
|
||||
unodes.len = nuse;
|
||||
let k: i32 = 0;
|
||||
u = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == nkind.N_USE && wwiprimary(u)) {
|
||||
if (u.usepath.len > 0) { upaths[k] = u.usepath; } else { upaths[k] = u.str; };
|
||||
unodes[k] = u;
|
||||
k += 1;
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
wwisortdecls(upaths, unodes, nuse);
|
||||
let i: i32 = 0;
|
||||
for (i < nuse) {
|
||||
wputs(fd, "import ");
|
||||
wputs(fd, upaths[i]);
|
||||
wputs(fd, ";\n");
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
|
||||
// decls — exported primary, byte-sorted by symbol name.
|
||||
let ndecl: i32 = 0;
|
||||
d = file.list;
|
||||
for (d != nil) {
|
||||
if (wwiprimary(d) && d.exported != 0 && wwiisdecl(d)) { ndecl += 1; };
|
||||
d = d.next;
|
||||
};
|
||||
if (ndecl > 0) {
|
||||
let dkeys: []str = alloc([], ndecl: u64)!;
|
||||
dkeys.len = ndecl;
|
||||
let dnodes: []*node = alloc([], ndecl: u64)!;
|
||||
dnodes.len = ndecl;
|
||||
let k: i32 = 0;
|
||||
d = file.list;
|
||||
for (d != nil) {
|
||||
if (wwiprimary(d) && d.exported != 0 && wwiisdecl(d)) {
|
||||
dkeys[k] = d.str;
|
||||
dnodes[k] = d;
|
||||
k += 1;
|
||||
};
|
||||
d = d.next;
|
||||
};
|
||||
wwisortdecls(dkeys, dnodes, ndecl);
|
||||
let i: i32 = 0;
|
||||
for (i < ndecl) {
|
||||
wwidecl(fd, dnodes[i]);
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
|
||||
os.close(fd);
|
||||
return 0i32;
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/w6c/main.ww — port of cmd/w6c/main.c.
|
||||
//
|
||||
@@ -45314,6 +45872,7 @@ import typ;
|
||||
import sym;
|
||||
import check;
|
||||
import cgen;
|
||||
import wwi;
|
||||
|
||||
fn cstreq(a: *u8, lit: str) bool = {
|
||||
let n: u64 = lit.len: u64;
|
||||
@@ -45370,6 +45929,7 @@ fn slurp(path: *u8) (*u8, u64) = {
|
||||
export fn main(argc: i32, argv: **u8) i32 = {
|
||||
let src: *u8 = nil;
|
||||
let out: *u8 = nil;
|
||||
let wwiout: *u8 = nil; // -I <out.wwi>: M2 export-data producer
|
||||
let testmode: i32 = 0i32; // #15: `-T` test-mode
|
||||
|
||||
let i: i32 = 1;
|
||||
@@ -45383,6 +45943,14 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
return 2;
|
||||
};
|
||||
out = argv[i];
|
||||
} else { if (cstreq(a, "-I")) {
|
||||
i += 1;
|
||||
if (i >= argc) {
|
||||
let m: str = "w6c: -I requires arg\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
return 2;
|
||||
};
|
||||
wwiout = argv[i];
|
||||
} else { if (cstreq(a, "-T")) {
|
||||
testmode = 1i32;
|
||||
} else { if (a[0u64] == 45u8) {
|
||||
@@ -45396,7 +45964,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
return 2;
|
||||
};
|
||||
src = a;
|
||||
}; }; };
|
||||
}; }; }; };
|
||||
i += 1;
|
||||
};
|
||||
|
||||
@@ -45466,6 +46034,13 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
checkfile(&ck, f);
|
||||
if (ck.errs > 0) { return 1; };
|
||||
|
||||
// M2 export-data: write the `.wwi` after a clean check, before cgen.
|
||||
// Dead on the live path (no existing invocation passes -I); the
|
||||
// producer's check_exported_type may reject a dangling export.
|
||||
if (wwiout != nil) {
|
||||
if (wwiemit(&ck, f, pathstr(wwiout)) != 0) { return 1; };
|
||||
};
|
||||
|
||||
let cg: cgen;
|
||||
cgeninit(&cg);
|
||||
cgfile(&cg, f);
|
||||
|
||||
@@ -23,6 +23,7 @@ import typ;
|
||||
import sym;
|
||||
import check;
|
||||
import cgen;
|
||||
import wwi;
|
||||
|
||||
fn cstreq(a: *u8, lit: str) bool = {
|
||||
let n: u64 = lit.len: u64;
|
||||
@@ -79,6 +80,7 @@ fn slurp(path: *u8) (*u8, u64) = {
|
||||
export fn main(argc: i32, argv: **u8) i32 = {
|
||||
let src: *u8 = nil;
|
||||
let out: *u8 = nil;
|
||||
let wwiout: *u8 = nil; // -I <out.wwi>: M2 export-data producer
|
||||
let testmode: i32 = 0i32; // #15: `-T` test-mode
|
||||
|
||||
let i: i32 = 1;
|
||||
@@ -92,6 +94,14 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
return 2;
|
||||
};
|
||||
out = argv[i];
|
||||
} else { if (cstreq(a, "-I")) {
|
||||
i += 1;
|
||||
if (i >= argc) {
|
||||
let m: str = "w6c: -I requires arg\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
return 2;
|
||||
};
|
||||
wwiout = argv[i];
|
||||
} else { if (cstreq(a, "-T")) {
|
||||
testmode = 1i32;
|
||||
} else { if (a[0u64] == 45u8) {
|
||||
@@ -105,7 +115,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
return 2;
|
||||
};
|
||||
src = a;
|
||||
}; }; };
|
||||
}; }; }; };
|
||||
i += 1;
|
||||
};
|
||||
|
||||
@@ -175,6 +185,13 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
checkfile(&ck, f);
|
||||
if (ck.errs > 0) { return 1; };
|
||||
|
||||
// M2 export-data: write the `.wwi` after a clean check, before cgen.
|
||||
// Dead on the live path (no existing invocation passes -I); the
|
||||
// producer's check_exported_type may reject a dangling export.
|
||||
if (wwiout != nil) {
|
||||
if (wwiemit(&ck, f, pathstr(wwiout)) != 0) { return 1; };
|
||||
};
|
||||
|
||||
let cg: cgen;
|
||||
cgeninit(&cg);
|
||||
cgfile(&cg, f);
|
||||
|
||||
556
selfhost/cmd/wcc/wwi.ww
Normal file
556
selfhost/cmd/wcc/wwi.ww
Normal file
@@ -0,0 +1,556 @@
|
||||
// wwi.ww — `.wwi` export-data producer (w6c_ww -I). M2 DEAD-CODE: writes
|
||||
// a re-parseable ww-prototype rendering of a package's EXPORTED surface.
|
||||
// Nothing consumes `.wwi` yet (combined.ww stays the live path); the only
|
||||
// caller is the new -I flag, off on every existing invocation.
|
||||
//
|
||||
// wwstage twin of cmd/w6c/wwi.c — byte-identical output is a rule-10
|
||||
// requirement (`.wwi` is a new cross-stage byte-id substrate). Specs:
|
||||
// .ai/rob-M2-spec.md + .ai/drew-M2-checkexported.md.
|
||||
//
|
||||
// - Unparse walks the AST type-expr subtree (the N_T* nodes), NOT the
|
||||
// tinfo — tinfo collapses nominal pkg.Name identity.
|
||||
// - check_exported_type rides the producer entry (flag-gated), so M2
|
||||
// stays dead on the normal `.s` path. It rejects exactly one thing:
|
||||
// an exported signature naming a non-exported nominal type.
|
||||
// - A `.wwi` is ONE package's interface; the M2 gate feeds a driver-
|
||||
// combined unit, so the emit filters to PRIMARY decls (imported==0).
|
||||
|
||||
// Imports mirror check.ww (os/tok/strconv only): node/nkind/sym/skind/
|
||||
// scopelookuptype/streq/checker/tkind resolve bare in the selfhost's flat
|
||||
// bundled scope, exactly as check.ww references them.
|
||||
package wcc;
|
||||
|
||||
import os;
|
||||
import tok;
|
||||
import strconv;
|
||||
|
||||
// --- byte writers ------------------------------------------------------
|
||||
|
||||
fn wputs(fd: i32, s: str) void = {
|
||||
os.write(fd, s.ptr, s.len: u64);
|
||||
};
|
||||
|
||||
fn wputb(fd: i32, b: u8) void = {
|
||||
let buf: [1]u8;
|
||||
buf[0] = b;
|
||||
os.write(fd, buf.ptr, 1u64);
|
||||
};
|
||||
|
||||
fn wquote(fd: i32, s: str) void = {
|
||||
wputb(fd, '"');
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
if (c == '"') {
|
||||
wputs(fd, "\\\"");
|
||||
} else { if (c == '\\') {
|
||||
wputs(fd, "\\\\");
|
||||
} else { if (c == '\n') {
|
||||
wputs(fd, "\\n");
|
||||
} else { if (c == '\t') {
|
||||
wputs(fd, "\\t");
|
||||
} else { if (c < 32u8) {
|
||||
let hi: u8 = c >> 4u8;
|
||||
let lo: u8 = c & 15u8;
|
||||
let h: u8 = 0u8;
|
||||
let l: u8 = 0u8;
|
||||
if (hi < 10u8) { h = hi + 48u8; } else { h = (hi - 10u8) + 97u8; };
|
||||
if (lo < 10u8) { l = lo + 48u8; } else { l = (lo - 10u8) + 97u8; };
|
||||
let buf: [4]u8;
|
||||
buf[0] = 92u8;
|
||||
buf[1] = 120u8;
|
||||
buf[2] = h;
|
||||
buf[3] = l;
|
||||
os.write(fd, buf.ptr, 4u64);
|
||||
} else {
|
||||
wputb(fd, c);
|
||||
};};};};};
|
||||
i += 1;
|
||||
};
|
||||
wputb(fd, '"');
|
||||
};
|
||||
|
||||
// --- check_exported_type (drew) ---------------------------------------
|
||||
//
|
||||
// Resolve an N_TNAME to its type sym via the public sym helpers (mirror
|
||||
// of cstage wwi_typesym / scope_lookup_type) WITHOUT the side effects of
|
||||
// the checker's aliassym (no on-demand resolve, no double error). A
|
||||
// primitive/keyword resolves to no SK_TYPE → leaf. By producer time
|
||||
// checkfile has finished and c.cur == c.top.
|
||||
|
||||
fn wwitypesym(c: *checker, nm: str) *sym = {
|
||||
let empty: str;
|
||||
let s: *sym = scopelookuptype(c.cur, empty, nm);
|
||||
if (s == nil) {
|
||||
let dotidx: i32 = -1;
|
||||
let i: i32 = 0;
|
||||
for (i < nm.len) {
|
||||
if (nm[i] == 46u8) { dotidx = i; };
|
||||
i += 1;
|
||||
};
|
||||
if (dotidx >= 0) {
|
||||
let leaf: str;
|
||||
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
|
||||
leaf.len = nm.len - dotidx - 1;
|
||||
s = scopelookuptype(c.cur, empty, leaf);
|
||||
};
|
||||
};
|
||||
if (s == nil) { return nil; };
|
||||
if (s.skind != skind.SK_TYPE) { return nil; };
|
||||
return s;
|
||||
};
|
||||
|
||||
fn wwireject(d: *node, nm: str) void = {
|
||||
wputs(2, d.file);
|
||||
wputs(2, ":");
|
||||
wputs(2, strconv.u64tos(d.line: u64, strconv.base.DEC));
|
||||
wputs(2, ":");
|
||||
wputs(2, strconv.u64tos(d.col: u64, strconv.base.DEC));
|
||||
wputs(2, ": error: exported declaration references unexported type '");
|
||||
wputs(2, nm);
|
||||
wputs(2, "'\n");
|
||||
};
|
||||
|
||||
// Returns 1 if a non-exported nominal was named (loud), else 0.
|
||||
fn wwichecktype(c: *checker, d: *node, t: *node) i32 = {
|
||||
if (t == nil) { return 0; };
|
||||
// rule-10: the wwstage checker wraps N_TTUPLE.list elements in
|
||||
// N_TPARAM (ast.ww:101); cstage keeps the type-AST pristine. Unwrap
|
||||
// transparently so the recursion sees the same shape cstage walks.
|
||||
if (t.kind == nkind.N_TPARAM) { return wwichecktype(c, d, t.lhs); };
|
||||
let bad: i32 = 0;
|
||||
if (t.kind == nkind.N_TNAME) {
|
||||
let s: *sym = wwitypesym(c, t.str);
|
||||
// sym.exported is vestigial (never set); the nominal's export
|
||||
// status lives on its decl node, parser-set.
|
||||
if (s != nil) {
|
||||
if (s.decl != nil) {
|
||||
if (s.decl.kind == nkind.N_TYPEDECL) {
|
||||
// file.len == 0 ⇒ a checkinit-synthesized
|
||||
// predeclared builtin (the ONLY empty-source
|
||||
// decls: nomemdecl check.ww:126, the `void`
|
||||
// TNAME check.ww:122), not a user nominal — ww's
|
||||
// analogue of harec's STORAGE_NOMEM leaf-arm, and
|
||||
// why cstage (lookup_builtin, no scope SK_TYPE)
|
||||
// needs no such guard.
|
||||
if (s.decl.file.len > 0) {
|
||||
if (s.decl.exported == 0) {
|
||||
wwireject(d, t.str);
|
||||
bad = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} else { if (
|
||||
t.kind == nkind.N_TPTR ||
|
||||
t.kind == nkind.N_TSLICE ||
|
||||
t.kind == nkind.N_TBANG ||
|
||||
t.kind == nkind.N_TCHAN
|
||||
) {
|
||||
bad = bad | wwichecktype(c, d, t.lhs);
|
||||
} else { if (t.kind == nkind.N_TARRAY) {
|
||||
// element only; the length is a const-expr, not a type.
|
||||
bad = bad | wwichecktype(c, d, t.lhs);
|
||||
} else { if (t.kind == nkind.N_TFN) {
|
||||
let p: *node = t.list;
|
||||
for (p != nil) {
|
||||
bad = bad | wwichecktype(c, d, p.lhs);
|
||||
p = p.next;
|
||||
};
|
||||
bad = bad | wwichecktype(c, d, t.lhs);
|
||||
} else { if (t.kind == nkind.N_TSTRUCT) {
|
||||
let f: *node = t.list;
|
||||
for (f != nil) {
|
||||
bad = bad | wwichecktype(c, d, f.lhs);
|
||||
f = f.next;
|
||||
};
|
||||
} else { if (t.kind == nkind.N_TTAGGED || t.kind == nkind.N_TTUPLE) {
|
||||
let e: *node = t.list;
|
||||
for (e != nil) {
|
||||
bad = bad | wwichecktype(c, d, e);
|
||||
e = e.next;
|
||||
};
|
||||
} else { if (t.kind == nkind.N_TENUM) {
|
||||
// the inline enum body is the definition, not a reference;
|
||||
// recurse only its storage type (members are values).
|
||||
bad = bad | wwichecktype(c, d, t.lhs);
|
||||
};};};};};};};
|
||||
return bad;
|
||||
};
|
||||
|
||||
fn wwicheckdecl(c: *checker, d: *node) i32 = {
|
||||
let bad: i32 = 0;
|
||||
if (d.kind == nkind.N_FNDECL) {
|
||||
let p: *node = d.list;
|
||||
for (p != nil) {
|
||||
bad = bad | wwichecktype(c, d, p.lhs);
|
||||
p = p.next;
|
||||
};
|
||||
bad = bad | wwichecktype(c, d, d.lhs); // ret
|
||||
} else { if (d.kind == nkind.N_TYPEDECL) {
|
||||
bad = bad | wwichecktype(c, d, d.lhs);
|
||||
} else { if (d.kind == nkind.N_DEF) {
|
||||
// the declared type; the rhs const value is not a type.
|
||||
bad = bad | wwichecktype(c, d, d.lhs);
|
||||
} else { if (d.kind == nkind.N_LET) {
|
||||
bad = bad | wwichecktype(c, d, d.lhs);
|
||||
};};};};
|
||||
return bad;
|
||||
};
|
||||
|
||||
// --- type-expr + const-expr unparser (rob §2.2/§2.4) ------------------
|
||||
|
||||
fn wwiexpr(fd: i32, e: *node) void = {
|
||||
if (e == nil) { return; };
|
||||
if (e.kind == nkind.N_INTLIT) {
|
||||
wputs(fd, strconv.u64tos(e.uval, strconv.base.DEC));
|
||||
if (e.tsuffix.len > 0) { wputs(fd, e.tsuffix); };
|
||||
} else { if (e.kind == nkind.N_IDENT || e.kind == nkind.N_TNAME) {
|
||||
wputs(fd, e.str);
|
||||
} else { if (e.kind == nkind.N_DOT) {
|
||||
wwiexpr(fd, e.lhs);
|
||||
wputb(fd, '.');
|
||||
wputs(fd, e.str);
|
||||
} else { if (e.kind == nkind.N_TRUE) {
|
||||
wputs(fd, "true");
|
||||
} else { if (e.kind == nkind.N_FALSE) {
|
||||
wputs(fd, "false");
|
||||
} else { if (e.kind == nkind.N_NIL) {
|
||||
wputs(fd, "nil");
|
||||
} else { if (e.kind == nkind.N_STRLIT) {
|
||||
wquote(fd, e.str);
|
||||
} else { if (e.kind == nkind.N_BIN) {
|
||||
wwiexpr(fd, e.lhs);
|
||||
wputb(fd, 32u8);
|
||||
wputs(fd, tokname(e.op));
|
||||
wputb(fd, 32u8);
|
||||
wwiexpr(fd, e.rhs);
|
||||
} else { if (e.kind == nkind.N_UN) {
|
||||
wputs(fd, tokname(e.op));
|
||||
wwiexpr(fd, e.lhs);
|
||||
} else {
|
||||
wputs(2, "wwi: unhandled const-expr node kind\n");
|
||||
os.exit(1);
|
||||
};};};};};};};};};
|
||||
};
|
||||
|
||||
fn wwiparam(fd: i32, p: *node) void = {
|
||||
if (streq(p.str, "...")) { // C-style FFI `...`
|
||||
wputs(fd, "...");
|
||||
return;
|
||||
};
|
||||
if (p.str.len > 0) {
|
||||
wputs(fd, p.str);
|
||||
wputs(fd, ": ");
|
||||
};
|
||||
// rule-10: the wwstage checker desugars a Hare variadic `T...` param
|
||||
// in place to `[]T` (lhs becomes N_TSLICE); cstage leaves lhs == T.
|
||||
// Peel the inserted slice so both stages emit the surface `T...`.
|
||||
if (p.op == tkind.TK_ELLIPSIS && p.lhs != nil && p.lhs.kind == nkind.N_TSLICE) {
|
||||
wwitype(fd, p.lhs.lhs);
|
||||
} else {
|
||||
wwitype(fd, p.lhs);
|
||||
};
|
||||
if (p.op == tkind.TK_ELLIPSIS) { // Hare `T...` variadic
|
||||
wputs(fd, "...");
|
||||
};
|
||||
};
|
||||
|
||||
fn wwitype(fd: i32, t: *node) void = {
|
||||
if (t == nil) { // absent return type spells void
|
||||
wputs(fd, "void");
|
||||
return;
|
||||
};
|
||||
// rule-10: unwrap the wwstage-only N_TPARAM tuple-element wrapper
|
||||
// (ast.ww:101) so the unparse matches cstage's pristine type-AST.
|
||||
if (t.kind == nkind.N_TPARAM) { wwitype(fd, t.lhs); return; };
|
||||
if (t.kind == nkind.N_TNAME) {
|
||||
if (t.str.len > 0) { wputs(fd, t.str); } else { wputs(fd, "void"); };
|
||||
} else { if (t.kind == nkind.N_TPTR) {
|
||||
wputb(fd, '*');
|
||||
wwitype(fd, t.lhs);
|
||||
} else { if (t.kind == nkind.N_TSLICE) {
|
||||
wputs(fd, "[]");
|
||||
wwitype(fd, t.lhs);
|
||||
} else { if (t.kind == nkind.N_TARRAY) {
|
||||
wputb(fd, '[');
|
||||
if (t.rhs != nil) { wwiexpr(fd, t.rhs); } else { wputb(fd, '_'); };
|
||||
wputb(fd, ']');
|
||||
wwitype(fd, t.lhs);
|
||||
} else { if (t.kind == nkind.N_TBANG) {
|
||||
wputb(fd, '!');
|
||||
wwitype(fd, t.lhs);
|
||||
} else { if (t.kind == nkind.N_TCHAN) {
|
||||
wputs(fd, "chan ");
|
||||
wwitype(fd, t.lhs);
|
||||
} else { if (t.kind == nkind.N_TFN) {
|
||||
wputs(fd, "fn(");
|
||||
let p: *node = t.list;
|
||||
for (p != nil) {
|
||||
if (p != t.list) { wputs(fd, ", "); };
|
||||
wwiparam(fd, p);
|
||||
p = p.next;
|
||||
};
|
||||
wputs(fd, ") ");
|
||||
wwitype(fd, t.lhs);
|
||||
} else { if (t.kind == nkind.N_TSTRUCT) {
|
||||
wputs(fd, "struct { ");
|
||||
let f: *node = t.list;
|
||||
for (f != nil) {
|
||||
if (f != t.list) { wputs(fd, ", "); };
|
||||
if (f.str.len > 0) {
|
||||
wputs(fd, f.str);
|
||||
wputs(fd, ": ");
|
||||
};
|
||||
wwitype(fd, f.lhs);
|
||||
f = f.next;
|
||||
};
|
||||
wputs(fd, " }");
|
||||
} else { if (t.kind == nkind.N_TTUPLE) {
|
||||
wputb(fd, '(');
|
||||
let e: *node = t.list;
|
||||
for (e != nil) {
|
||||
if (e != t.list) { wputs(fd, ", "); };
|
||||
wwitype(fd, e);
|
||||
e = e.next;
|
||||
};
|
||||
wputb(fd, ')');
|
||||
} else { if (t.kind == nkind.N_TTAGGED) {
|
||||
wputb(fd, '(');
|
||||
let e: *node = t.list;
|
||||
for (e != nil) {
|
||||
if (e != t.list) { wputs(fd, " | "); };
|
||||
wwitype(fd, e);
|
||||
e = e.next;
|
||||
};
|
||||
wputb(fd, ')');
|
||||
} else { if (t.kind == nkind.N_TENUM) {
|
||||
wputs(fd, "enum ");
|
||||
if (t.lhs != nil) {
|
||||
wwitype(fd, t.lhs);
|
||||
wputb(fd, 32u8);
|
||||
};
|
||||
wputs(fd, "{ ");
|
||||
let m: *node = t.list;
|
||||
for (m != nil) {
|
||||
if (m != t.list) { wputs(fd, ", "); };
|
||||
wputs(fd, m.str);
|
||||
if (m.lhs != nil) {
|
||||
wputs(fd, " = ");
|
||||
wwiexpr(fd, m.lhs);
|
||||
};
|
||||
m = m.next;
|
||||
};
|
||||
wputs(fd, " }");
|
||||
} else {
|
||||
wputs(2, "wwi: unhandled type node kind\n");
|
||||
os.exit(1);
|
||||
};};};};};};};};};};};
|
||||
};
|
||||
|
||||
fn wwidecl(fd: i32, d: *node) void = {
|
||||
if (d.kind == nkind.N_FNDECL) {
|
||||
wputs(fd, "export fn ");
|
||||
wputs(fd, d.str);
|
||||
wputb(fd, '(');
|
||||
let p: *node = d.list;
|
||||
for (p != nil) {
|
||||
if (p != d.list) { wputs(fd, ", "); };
|
||||
wwiparam(fd, p);
|
||||
p = p.next;
|
||||
};
|
||||
wputs(fd, ") ");
|
||||
wwitype(fd, d.lhs);
|
||||
wputs(fd, ";\n");
|
||||
} else { if (d.kind == nkind.N_TYPEDECL) {
|
||||
wputs(fd, "export type ");
|
||||
wputs(fd, d.str);
|
||||
wputs(fd, " = ");
|
||||
wwitype(fd, d.lhs);
|
||||
wputs(fd, ";\n");
|
||||
} else { if (d.kind == nkind.N_DEF) {
|
||||
wputs(fd, "export def ");
|
||||
wputs(fd, d.str);
|
||||
wputs(fd, ": ");
|
||||
wwitype(fd, d.lhs);
|
||||
wputs(fd, " = ");
|
||||
wwiexpr(fd, d.rhs);
|
||||
wputs(fd, ";\n");
|
||||
} else { if (d.kind == nkind.N_LET) {
|
||||
wputs(fd, "export let ");
|
||||
wputs(fd, d.str);
|
||||
wputs(fd, ": ");
|
||||
if (d.lhs == nil) {
|
||||
wputs(2, "wwi: exported let has no declared type\n");
|
||||
os.exit(1);
|
||||
};
|
||||
wwitype(fd, d.lhs);
|
||||
wputs(fd, ";\n");
|
||||
};};};};
|
||||
};
|
||||
|
||||
// --- deterministic ordering (rob §3) ----------------------------------
|
||||
|
||||
fn wwiprimary(n: *node) bool = {
|
||||
// imported==1 marks a decl reached through a `//ww:module <path>`
|
||||
// boundary (an imported module's concatenated section).
|
||||
if (n == nil) { return false; };
|
||||
return n.imported == 0;
|
||||
};
|
||||
|
||||
fn wwiisdecl(d: *node) bool = {
|
||||
return d.kind == nkind.N_FNDECL || d.kind == nkind.N_TYPEDECL ||
|
||||
d.kind == nkind.N_DEF || d.kind == nkind.N_LET;
|
||||
};
|
||||
|
||||
// strcmp — byte lexicographic, mirror C strcmp sign (<0/0/>0). Both
|
||||
// stages key the sort identically, so the `.wwi` order is deterministic.
|
||||
fn wwistrcmp(a: str, b: str) i32 = {
|
||||
let i: i32 = 0;
|
||||
for (i < a.len && i < b.len) {
|
||||
let ca: i32 = a[i]: i32;
|
||||
let cb: i32 = b[i]: i32;
|
||||
if (ca != cb) { return ca - cb; };
|
||||
i += 1;
|
||||
};
|
||||
return a.len - b.len;
|
||||
};
|
||||
|
||||
// Selection sort over parallel (key, node) arrays. Total order keyed on
|
||||
// the symbol name; ties broken by original index — so the result is
|
||||
// stable regardless of any same-name collision, matching cstage's qsort
|
||||
// + idx tiebreak.
|
||||
fn wwisortdecls(keys: []str, nodes: []*node, n: i32) void = {
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
let best: i32 = i;
|
||||
let j: i32 = i + 1;
|
||||
for (j < n) {
|
||||
let r: i32 = wwistrcmp(keys[j], keys[best]);
|
||||
if (r < 0) { best = j; };
|
||||
j += 1;
|
||||
};
|
||||
if (best != i) {
|
||||
let tk: str = keys[i]; keys[i] = keys[best]; keys[best] = tk;
|
||||
let tn: *node = nodes[i]; nodes[i] = nodes[best]; nodes[best] = tn;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
|
||||
export fn wwiemit(c: *checker, file: *node, path: str) i32 = {
|
||||
// §5: check_exported_type FIRST, before any byte — a producer
|
||||
// without it can emit a dangling `.wwi`.
|
||||
let bad: i32 = 0;
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
if (wwiprimary(d) && d.exported != 0 && wwiisdecl(d)) {
|
||||
bad = bad | wwicheckdecl(c, d);
|
||||
};
|
||||
d = d.next;
|
||||
};
|
||||
if (bad != 0) { return 1i32; };
|
||||
|
||||
let fd: i32 = os.open(path,
|
||||
os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
if (fd < 0) {
|
||||
wputs(2, "w6c: cannot open ");
|
||||
wputs(2, path);
|
||||
wputs(2, "\n");
|
||||
return 1i32;
|
||||
};
|
||||
|
||||
// package line: leaf of the first primary decl's module tag.
|
||||
let pkg: str = "main";
|
||||
let pd: *node = file.list;
|
||||
for (pd != nil) {
|
||||
if (wwiprimary(pd) && pd.nmod.len > 0) {
|
||||
let dotidx: i32 = -1;
|
||||
let i: i32 = 0;
|
||||
for (i < pd.nmod.len) {
|
||||
if (pd.nmod[i] == 46u8) { dotidx = i; };
|
||||
i += 1;
|
||||
};
|
||||
if (dotidx >= 0) {
|
||||
let leaf: str;
|
||||
leaf.ptr = pd.nmod.ptr + ((dotidx + 1): u64);
|
||||
leaf.len = pd.nmod.len - dotidx - 1;
|
||||
pkg = leaf;
|
||||
} else {
|
||||
pkg = pd.nmod;
|
||||
};
|
||||
pd = nil;
|
||||
} else {
|
||||
pd = pd.next;
|
||||
};
|
||||
};
|
||||
wputs(fd, "package ");
|
||||
wputs(fd, pkg);
|
||||
wputs(fd, ";\n");
|
||||
|
||||
// imports — primary N_USE, byte-sorted by import path.
|
||||
let nuse: i32 = 0;
|
||||
let u: *node = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == nkind.N_USE && wwiprimary(u)) { nuse += 1; };
|
||||
u = u.next;
|
||||
};
|
||||
if (nuse > 0) {
|
||||
let upaths: []str = alloc([], nuse: u64)!;
|
||||
upaths.len = nuse;
|
||||
let unodes: []*node = alloc([], nuse: u64)!;
|
||||
unodes.len = nuse;
|
||||
let k: i32 = 0;
|
||||
u = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == nkind.N_USE && wwiprimary(u)) {
|
||||
if (u.usepath.len > 0) { upaths[k] = u.usepath; } else { upaths[k] = u.str; };
|
||||
unodes[k] = u;
|
||||
k += 1;
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
wwisortdecls(upaths, unodes, nuse);
|
||||
let i: i32 = 0;
|
||||
for (i < nuse) {
|
||||
wputs(fd, "import ");
|
||||
wputs(fd, upaths[i]);
|
||||
wputs(fd, ";\n");
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
|
||||
// decls — exported primary, byte-sorted by symbol name.
|
||||
let ndecl: i32 = 0;
|
||||
d = file.list;
|
||||
for (d != nil) {
|
||||
if (wwiprimary(d) && d.exported != 0 && wwiisdecl(d)) { ndecl += 1; };
|
||||
d = d.next;
|
||||
};
|
||||
if (ndecl > 0) {
|
||||
let dkeys: []str = alloc([], ndecl: u64)!;
|
||||
dkeys.len = ndecl;
|
||||
let dnodes: []*node = alloc([], ndecl: u64)!;
|
||||
dnodes.len = ndecl;
|
||||
let k: i32 = 0;
|
||||
d = file.list;
|
||||
for (d != nil) {
|
||||
if (wwiprimary(d) && d.exported != 0 && wwiisdecl(d)) {
|
||||
dkeys[k] = d.str;
|
||||
dnodes[k] = d;
|
||||
k += 1;
|
||||
};
|
||||
d = d.next;
|
||||
};
|
||||
wwisortdecls(dkeys, dnodes, ndecl);
|
||||
let i: i32 = 0;
|
||||
for (i < ndecl) {
|
||||
wwidecl(fd, dnodes[i]);
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
|
||||
os.close(fd);
|
||||
return 0i32;
|
||||
};
|
||||
330
test/wcc/989_m2wwi_run.c
Normal file
330
test/wcc/989_m2wwi_run.c
Normal file
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
* 989_m2wwi_run — M2 `.wwi` export-data producer gate (task #22 arc).
|
||||
* Both stages (rule-10): the new `w6c -I <out.wwi>` producer is DEAD on
|
||||
* the live path (combined.ww stays the compile substrate; nothing reads
|
||||
* `.wwi`), so this is purely a new cross-stage byte-id substrate.
|
||||
*
|
||||
* POSITIVE gate — for each package (ascii/strings/getopt, drew2-audited
|
||||
* leak-free), drive the target as the PRIMARY module of a driver-combined
|
||||
* unit, then:
|
||||
* 1. w6c -I cs.wwi and w6c_ww -I ww.wwi both succeed,
|
||||
* 2. cs.wwi == ww.wwi byte-for-byte (the new substrate's determinism),
|
||||
* 3. cs.wwi RE-PARSES under the existing parser (wwdump -a exits 0) —
|
||||
* proves the producer emits genuinely re-parseable ww prototype
|
||||
* source, de-risking the M3 consumer.
|
||||
* getopt is the recursion stressor (enum→struct→struct, ptr-to-nominal,
|
||||
* tagged-union return, qualified pkg.Name). A synth fixture then covers the
|
||||
* decl-kinds + type-nodes no lib package reaches: `def` (const-expr unparser
|
||||
* + fold parity), `let` global, `[N]T` array, `fn(..)R` fn-ptr, `!T`, tuple,
|
||||
* storage-less enum.
|
||||
*
|
||||
* NEGATIVE gate — a fixture whose exported fn names a PRIVATE (non-
|
||||
* exported) nominal must be LOUD-REJECTED by check_exported_type,
|
||||
* identically on BOTH stages (same non-zero exit, byte-identical
|
||||
* diagnostic). Without it a vacuous no-op check would pass the positive
|
||||
* gate silently.
|
||||
*
|
||||
* Compile/produce/cmp only — no driver run, everything under a getpid-
|
||||
* keyed /tmp dir (no source-tree writes), so this is phase-1 parallel-
|
||||
* safe. 9xx is full; shares the 989 prefix per the 989_lib_byteid
|
||||
* precedent (the `short` name keys the binary).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* one positive package: build the target as primary, produce the `.wwi`
|
||||
* on both stages, require byte-id + re-parse. Returns 0 on pass. */
|
||||
static int
|
||||
positive(const char *bin, const char *cwd, const char *pkg, int idx)
|
||||
{
|
||||
char td[64], cmd[4096], src[1024];
|
||||
int rc = -1;
|
||||
|
||||
snprintf(td, sizeof td, "/tmp/wwm2_%d_%d", getpid(), idx);
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
||||
runwait(cmd);
|
||||
mkdir(td, 0755);
|
||||
|
||||
snprintf(src, sizeof src, "%s/lib/%s/%s.ww", cwd, pkg, pkg);
|
||||
snprintf(cmd, sizeof cmd, "cp %s %s/%s.ww", src, td, pkg);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "m2wwi FAIL: %s — cp\n", pkg);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Drive the package file as the build target → its decls are the
|
||||
* PRIMARY module (imported==0) of the resolved unit; the producer's
|
||||
* primary filter then yields exactly this package's interface. The
|
||||
* library has no main, so the build's link step fails — the
|
||||
* `.combined.ww` is written before codegen (cf 989_lib_byteid), and
|
||||
* only it matters here, so the exit code is deliberately ignored. */
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && timeout 180 %s/ww build -I %s/lib %s.ww >/dev/null 2>&1",
|
||||
td, bin, cwd, pkg);
|
||||
runwait(cmd);
|
||||
|
||||
char comb[1100], cs[1100], ws[1100];
|
||||
snprintf(comb, sizeof comb, "%s/%s.combined.ww", td, pkg);
|
||||
snprintf(cs, sizeof cs, "%s/cs.wwi", td);
|
||||
snprintf(ws, sizeof ws, "%s/ww.wwi", td);
|
||||
if (access(comb, 0) != 0) {
|
||||
fprintf(stderr, "m2wwi FAIL: %s — no resolved unit\n", pkg);
|
||||
goto out;
|
||||
}
|
||||
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"timeout 180 %s/w6c -I %s %s >/dev/null 2>&1", bin, cs, comb);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "m2wwi FAIL: %s — w6c -I rejected\n", pkg);
|
||||
goto out;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"timeout 180 %s/w6c_ww -I %s %s >/dev/null 2>&1", bin, ws, comb);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "m2wwi FAIL: %s — w6c_ww -I rejected\n", pkg);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (files_eq(cs, ws) != 0) {
|
||||
fprintf(stderr, "m2wwi FAIL: %s — cs.wwi != ww.wwi (byte-id "
|
||||
"broken on the .wwi substrate)\n", pkg);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* re-parse: the emitted `.wwi` must be valid ww prototype source. */
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"timeout 180 %s/wwdump -a %s >/dev/null 2>&1", bin, cs);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "m2wwi FAIL: %s — emitted .wwi does not "
|
||||
"re-parse (wwdump -a)\n", pkg);
|
||||
goto out;
|
||||
}
|
||||
rc = 0;
|
||||
out:
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
||||
runwait(cmd);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* negative gate: an exported fn naming a private nominal must be rejected
|
||||
* identically (exit + diagnostic) by BOTH stages. Returns 0 on pass. */
|
||||
static const char *leak_src =
|
||||
"package leaktest;\n"
|
||||
"type secret = struct { x: i32 };\n"
|
||||
"export fn leaks(s: secret) i32 = { return s.x; };\n"
|
||||
"export fn clean(a: i32) i32 = { return a; };\n";
|
||||
|
||||
static int
|
||||
negative(const char *bin)
|
||||
{
|
||||
char td[64], cmd[4096], p[512];
|
||||
int rc = -1;
|
||||
|
||||
snprintf(td, sizeof td, "/tmp/wwm2neg_%d", getpid());
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
||||
runwait(cmd);
|
||||
mkdir(td, 0755);
|
||||
|
||||
snprintf(p, sizeof p, "%s/leak.ww", td);
|
||||
FILE *f = fopen(p, "wb");
|
||||
if (!f) goto out;
|
||||
fputs(leak_src, f);
|
||||
fclose(f);
|
||||
|
||||
char cse[1100], wwe[1100];
|
||||
snprintf(cse, sizeof cse, "%s/cs.err", td);
|
||||
snprintf(wwe, sizeof wwe, "%s/ww.err", td);
|
||||
|
||||
/* cd into td so both stages report the bare `leak.ww:L:C:` prefix. */
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && timeout 180 %s/w6c -I out.wwi leak.ww >/dev/null 2>cs.err",
|
||||
td, bin);
|
||||
int crc = runwait(cmd);
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && timeout 180 %s/w6c_ww -I out.wwi leak.ww >/dev/null 2>ww.err",
|
||||
td, bin);
|
||||
int wrc = runwait(cmd);
|
||||
|
||||
if (crc == 0 || wrc == 0) {
|
||||
fprintf(stderr, "m2wwi FAIL: negative — check_exported_type is "
|
||||
"vacuous (cstage exit=%d wwstage exit=%d; both must reject "
|
||||
"the private-type leak)\n", crc, wrc);
|
||||
goto out;
|
||||
}
|
||||
if (files_eq(cse, wwe) != 0) {
|
||||
fprintf(stderr, "m2wwi FAIL: negative — reject diagnostics "
|
||||
"differ across stages (rule-10)\n");
|
||||
goto out;
|
||||
}
|
||||
rc = 0;
|
||||
out:
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
||||
runwait(cmd);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* synth gate: a single self-contained package exercising the decl-kinds +
|
||||
* type-AST nodes the lib packages above do NOT cover — `export def` (the
|
||||
* const-expr unparser, incl a folded `10+2*3` and a unary `-7`), `export
|
||||
* let` (global), `[N]T` array (decl + array-dim ident-ref), an `fn(...) R`
|
||||
* fn-pointer type, an `!T` error type, a `(a, b)` tuple return, and a
|
||||
* storage-less `enum {...}`. Asserts both stages produce a byte-identical,
|
||||
* re-parseable `.wwi` (the const-expr fold + tokname spelling must agree
|
||||
* cross-stage — paths the lib gate never reaches). `chan T` is omitted: the
|
||||
* wwstage parser does not yet accept it in a return position (pre-existing,
|
||||
* unrelated to M2), so the producer's N_TCHAN arm is unreachable there.
|
||||
* Returns 0 on pass. */
|
||||
static const char *synth_src =
|
||||
"package synth;\n"
|
||||
"export type color = enum { RED, GREEN = 5, BLUE };\n"
|
||||
"export def LIMIT: i32 = 10 + 2 * 3;\n"
|
||||
"export def NAME: str = \"hi\\n\";\n"
|
||||
"export def FLAG: bool = true;\n"
|
||||
"export def NEG: i32 = -7;\n"
|
||||
"export let counter: i32;\n"
|
||||
"export let grid: [4]i32;\n"
|
||||
"export fn apply(f: fn(x: i32) i32, n: i32) i32;\n"
|
||||
"export fn risky() !i32;\n"
|
||||
"export fn matrix() [LIMIT]u8;\n"
|
||||
"export fn pair() (i32, i32);\n"
|
||||
"export fn opt(p: *color, b: []u8) (i32 | void);\n";
|
||||
|
||||
static int
|
||||
synth(const char *bin)
|
||||
{
|
||||
char td[64], cmd[4096], p[512];
|
||||
int rc = -1;
|
||||
|
||||
snprintf(td, sizeof td, "/tmp/wwm2syn_%d", getpid());
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
||||
runwait(cmd);
|
||||
mkdir(td, 0755);
|
||||
|
||||
snprintf(p, sizeof p, "%s/synth.ww", td);
|
||||
FILE *f = fopen(p, "wb");
|
||||
if (!f) goto out;
|
||||
fputs(synth_src, f);
|
||||
fclose(f);
|
||||
|
||||
char cs[1100], ws[1100];
|
||||
snprintf(cs, sizeof cs, "%s/cs.wwi", td);
|
||||
snprintf(ws, sizeof ws, "%s/ww.wwi", td);
|
||||
|
||||
/* self-contained package (no imports) → it is its own primary unit,
|
||||
* so w6c -I runs directly on the source (no driver-combined step). */
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"timeout 180 %s/w6c -I %s %s/synth.ww >/dev/null 2>&1", bin, cs, td);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "m2wwi FAIL: synth — w6c -I rejected\n");
|
||||
goto out;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"timeout 180 %s/w6c_ww -I %s %s/synth.ww >/dev/null 2>&1", bin, ws, td);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "m2wwi FAIL: synth — w6c_ww -I rejected\n");
|
||||
goto out;
|
||||
}
|
||||
if (files_eq(cs, ws) != 0) {
|
||||
fprintf(stderr, "m2wwi FAIL: synth — cs.wwi != ww.wwi (const-expr "
|
||||
"/ decl-kind unparse diverges across stages)\n");
|
||||
goto out;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"timeout 180 %s/wwdump -a %s >/dev/null 2>&1", bin, cs);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "m2wwi FAIL: synth — emitted .wwi does not "
|
||||
"re-parse (wwdump -a)\n");
|
||||
goto out;
|
||||
}
|
||||
rc = 0;
|
||||
out:
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
||||
runwait(cmd);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = absbin();
|
||||
if (!bin) return 1;
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *pkgs[] = { "ascii", "strings", "getopt", NULL };
|
||||
int fail = 0, npos = 0;
|
||||
for (int i = 0; pkgs[i]; i++) {
|
||||
if (positive(bin, cwd, pkgs[i], i) != 0) fail++;
|
||||
else npos++;
|
||||
}
|
||||
if (synth(bin) != 0) fail++;
|
||||
else npos++;
|
||||
if (negative(bin) != 0) fail++;
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "m2wwi: %d check(s) failed\n", fail);
|
||||
return 1;
|
||||
}
|
||||
printf("m2wwi: %d package(s) byte-identical + re-parse; negative "
|
||||
"gate rejects (both stages)\n", npos);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user