After the frontend consolidated into one syntax package (#74), wcc still referenced syntax symbols unqualified — residue of the old flat combined namespace, where bare refs resolved by accident. Under separate compilation Hare and Go both require the package qualifier, so those bare refs would not sep-resolve. Qualify every wcc reference to a syntax type, function, or enum member as syntax.X across the seven syntax-importing files. Resolution-only: the resolved symbol and emitted code are unchanged, so the two combined.ww regenerate textually but all five _ww binaries hold byte-for-byte. The struct-literal sites resolve via #76. This makes w6c fully separate-compilable.
649 lines
19 KiB
Plaintext
649 lines
19 KiB
Plaintext
// 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 syntax;
|
|
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) *syntax.sym = {
|
|
let empty: str;
|
|
let s: *syntax.sym = syntax.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 = syntax.scopelookuptype(c.cur, empty, leaf);
|
|
};
|
|
};
|
|
if (s == nil) { return nil; };
|
|
if (s.skind != syntax.skind.SK_TYPE) { return nil; };
|
|
return s;
|
|
};
|
|
|
|
fn wwireject(d: *syntax.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: *syntax.node, t: *syntax.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 == syntax.nkind.N_TPARAM) { return wwichecktype(c, d, t.lhs); };
|
|
let bad: i32 = 0;
|
|
if (t.kind == syntax.nkind.N_TNAME) {
|
|
let s: *syntax.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 == syntax.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 == syntax.nkind.N_TPTR ||
|
|
t.kind == syntax.nkind.N_TSLICE ||
|
|
t.kind == syntax.nkind.N_TBANG ||
|
|
t.kind == syntax.nkind.N_TCHAN
|
|
) {
|
|
bad = bad | wwichecktype(c, d, t.lhs);
|
|
} else { if (t.kind == syntax.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 == syntax.nkind.N_TFN) {
|
|
let p: *syntax.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 == syntax.nkind.N_TSTRUCT) {
|
|
let f: *syntax.node = t.list;
|
|
for (f != nil) {
|
|
bad = bad | wwichecktype(c, d, f.lhs);
|
|
f = f.next;
|
|
};
|
|
} else { if (t.kind == syntax.nkind.N_TTAGGED || t.kind == syntax.nkind.N_TTUPLE) {
|
|
let e: *syntax.node = t.list;
|
|
for (e != nil) {
|
|
bad = bad | wwichecktype(c, d, e);
|
|
e = e.next;
|
|
};
|
|
} else { if (t.kind == syntax.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: *syntax.node) i32 = {
|
|
let bad: i32 = 0;
|
|
if (d.kind == syntax.nkind.N_FNDECL) {
|
|
let p: *syntax.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 == syntax.nkind.N_TYPEDECL) {
|
|
bad = bad | wwichecktype(c, d, d.lhs);
|
|
} else { if (d.kind == syntax.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 == syntax.nkind.N_LET) {
|
|
bad = bad | wwichecktype(c, d, d.lhs);
|
|
};};};};
|
|
return bad;
|
|
};
|
|
|
|
// --- type-expr + const-expr unparser (rob §2.2/§2.4) ------------------
|
|
|
|
// The lexer's rune escapes stop at \xHH (no \u/\U — task #50), so a
|
|
// codepoint above 0xff can't render as a re-parseable rune literal; fail
|
|
// loud rather than emit a malformed one. No exported def names such a rune
|
|
// today (RUNE_MAX is written as an int-cast for the same reason).
|
|
fn wwirune(fd: i32, cp: u64) void = {
|
|
if (cp > 255u64) {
|
|
wputs(2, "wwi: rune codepoint exceeds \\xHH (task #50)\n");
|
|
os.exit(1);
|
|
};
|
|
let c: u8 = cp: u8;
|
|
wputb(fd, '\'');
|
|
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 || c >= 127u8) {
|
|
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);
|
|
};};};};};
|
|
wputb(fd, '\'');
|
|
};
|
|
|
|
fn wwiexpr(fd: i32, e: *syntax.node) void = {
|
|
if (e == nil) { return; };
|
|
if (e.kind == syntax.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 == syntax.nkind.N_IDENT || e.kind == syntax.nkind.N_TNAME) {
|
|
wputs(fd, e.str);
|
|
} else { if (e.kind == syntax.nkind.N_DOT) {
|
|
wwiexpr(fd, e.lhs);
|
|
wputb(fd, '.');
|
|
wputs(fd, e.str);
|
|
} else { if (e.kind == syntax.nkind.N_TRUE) {
|
|
wputs(fd, "true");
|
|
} else { if (e.kind == syntax.nkind.N_FALSE) {
|
|
wputs(fd, "false");
|
|
} else { if (e.kind == syntax.nkind.N_NIL) {
|
|
wputs(fd, "nil");
|
|
} else { if (e.kind == syntax.nkind.N_STRLIT) {
|
|
wquote(fd, e.str);
|
|
} else { if (e.kind == syntax.nkind.N_RUNELIT) {
|
|
// A bare rune literal in a const-expr (types.RUNE_MIN = '\0');
|
|
// casts never reach here — the checker folds a const cast to an
|
|
// integer literal before the producer runs (RUNE_MAX
|
|
// `0x10ffff: rune` emits as the plain int 1114111).
|
|
wwirune(fd, e.uval);
|
|
} else { if (e.kind == syntax.nkind.N_BIN) {
|
|
wwiexpr(fd, e.lhs);
|
|
wputb(fd, 32u8);
|
|
wputs(fd, syntax.tokname(e.op));
|
|
wputb(fd, 32u8);
|
|
wwiexpr(fd, e.rhs);
|
|
} else { if (e.kind == syntax.nkind.N_UN) {
|
|
wputs(fd, syntax.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: *syntax.node) void = {
|
|
if (syntax.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 == syntax.tkind.TK_ELLIPSIS && p.lhs != nil && p.lhs.kind == syntax.nkind.N_TSLICE) {
|
|
wwitype(fd, p.lhs.lhs);
|
|
} else {
|
|
wwitype(fd, p.lhs);
|
|
};
|
|
if (p.op == syntax.tkind.TK_ELLIPSIS) { // Hare `T...` variadic
|
|
wputs(fd, "...");
|
|
};
|
|
};
|
|
|
|
fn wwitype(fd: i32, t: *syntax.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 == syntax.nkind.N_TPARAM) { wwitype(fd, t.lhs); return; };
|
|
if (t.kind == syntax.nkind.N_TNAME) {
|
|
if (t.str.len > 0) { wputs(fd, t.str); } else { wputs(fd, "void"); };
|
|
} else { if (t.kind == syntax.nkind.N_TPTR) {
|
|
wputb(fd, '*');
|
|
wwitype(fd, t.lhs);
|
|
} else { if (t.kind == syntax.nkind.N_TSLICE) {
|
|
wputs(fd, "[]");
|
|
wwitype(fd, t.lhs);
|
|
} else { if (t.kind == syntax.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 == syntax.nkind.N_TBANG) {
|
|
wputb(fd, '!');
|
|
wwitype(fd, t.lhs);
|
|
} else { if (t.kind == syntax.nkind.N_TCHAN) {
|
|
wputs(fd, "chan ");
|
|
wwitype(fd, t.lhs);
|
|
} else { if (t.kind == syntax.nkind.N_TFN) {
|
|
wputs(fd, "fn(");
|
|
let p: *syntax.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 == syntax.nkind.N_TSTRUCT) {
|
|
wputs(fd, "struct { ");
|
|
let f: *syntax.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 == syntax.nkind.N_TTUPLE) {
|
|
wputb(fd, '(');
|
|
let e: *syntax.node = t.list;
|
|
for (e != nil) {
|
|
if (e != t.list) { wputs(fd, ", "); };
|
|
wwitype(fd, e);
|
|
e = e.next;
|
|
};
|
|
wputb(fd, ')');
|
|
} else { if (t.kind == syntax.nkind.N_TTAGGED) {
|
|
wputb(fd, '(');
|
|
let e: *syntax.node = t.list;
|
|
for (e != nil) {
|
|
if (e != t.list) { wputs(fd, " | "); };
|
|
wwitype(fd, e);
|
|
e = e.next;
|
|
};
|
|
wputb(fd, ')');
|
|
} else { if (t.kind == syntax.nkind.N_TENUM) {
|
|
wputs(fd, "enum ");
|
|
if (t.lhs != nil) {
|
|
wwitype(fd, t.lhs);
|
|
wputb(fd, 32u8);
|
|
};
|
|
wputs(fd, "{ ");
|
|
let m: *syntax.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);
|
|
};};};};};};};};};};};
|
|
};
|
|
|
|
// Only codegen/link-relevant attributes round-trip into the `.wwi`. Today
|
|
// that is exactly @symbol (the FFI link-symbol override, read back at cgen
|
|
// fficollect — dropping it makes sep-compile emit `CALL malloc` for a
|
|
// `@symbol("rt_malloc")` fn). Named for the class so @align/@offset would
|
|
// slot in here IF ww ever grows field-layout attributes — it has none
|
|
// today (task #47 report). @test never reaches a `.wwi` (test fns are not
|
|
// export-marked), so it needs no exclusion arm.
|
|
fn wwiattrrelevant(nm: str) bool = {
|
|
return syntax.streq(nm, "symbol");
|
|
};
|
|
|
|
fn wwiattrs(fd: i32, d: *syntax.node) void = {
|
|
let a: *syntax.node = d.attr;
|
|
for (a != nil) {
|
|
if (a.kind == syntax.nkind.N_ATTR && wwiattrrelevant(a.str)) {
|
|
wputb(fd, '@');
|
|
wputs(fd, a.str);
|
|
if (a.list != nil) {
|
|
wputb(fd, '(');
|
|
let arg: *syntax.node = a.list;
|
|
for (arg != nil) {
|
|
if (arg != a.list) { wputs(fd, ", "); };
|
|
wwiexpr(fd, arg);
|
|
arg = arg.next;
|
|
};
|
|
wputb(fd, ')');
|
|
};
|
|
wputb(fd, 32u8);
|
|
};
|
|
a = a.next;
|
|
};
|
|
};
|
|
|
|
fn wwidecl(fd: i32, d: *syntax.node) void = {
|
|
if (d.kind == syntax.nkind.N_FNDECL) {
|
|
wwiattrs(fd, d);
|
|
wputs(fd, "export fn ");
|
|
wputs(fd, d.str);
|
|
wputb(fd, '(');
|
|
let p: *syntax.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 == syntax.nkind.N_TYPEDECL) {
|
|
wputs(fd, "export type ");
|
|
wputs(fd, d.str);
|
|
wputs(fd, " = ");
|
|
wwitype(fd, d.lhs);
|
|
wputs(fd, ";\n");
|
|
} else { if (d.kind == syntax.nkind.N_DEF) {
|
|
wputs(fd, "export def ");
|
|
wputs(fd, d.str);
|
|
wputs(fd, ": ");
|
|
wwitype(fd, d.lhs);
|
|
// An aggregate initializer (N_STRUCTLIT/N_ARRLIT) is a DATA-global
|
|
// (#52): emit a value-LESS prototype `export def X: T;`. The
|
|
// defining package's own .o emits the struct/array DATA; the
|
|
// importer registers the def by TYPE only (DATA-suppression
|
|
// already in cgen) and field/element reads become external refs
|
|
// the linker fills. Boundary (rule 7): this gives up importer-
|
|
// side const-fold of an aggregate def's FIELDS — a no-op, ww
|
|
// never folds struct-literal field access, and an aggregate-def
|
|
// field demanded in a const-fold context stays a LOUD error (#71).
|
|
if (d.rhs != nil && (d.rhs.kind == syntax.nkind.N_STRUCTLIT ||
|
|
d.rhs.kind == syntax.nkind.N_ARRLIT)) {
|
|
wputs(fd, ";\n");
|
|
} else {
|
|
wputs(fd, " = ");
|
|
wwiexpr(fd, d.rhs);
|
|
wputs(fd, ";\n");
|
|
};
|
|
} else { if (d.kind == syntax.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: *syntax.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: *syntax.node) bool = {
|
|
return d.kind == syntax.nkind.N_FNDECL || d.kind == syntax.nkind.N_TYPEDECL ||
|
|
d.kind == syntax.nkind.N_DEF || d.kind == syntax.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: []*syntax.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: *syntax.node = nodes[i]; nodes[i] = nodes[best]; nodes[best] = tn;
|
|
};
|
|
i += 1;
|
|
};
|
|
};
|
|
|
|
export fn wwiemit(c: *checker, file: *syntax.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: *syntax.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: *syntax.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: *syntax.node = file.list;
|
|
for (u != nil) {
|
|
if (u.kind == syntax.nkind.N_USE && wwiprimary(u)) { nuse += 1; };
|
|
u = u.next;
|
|
};
|
|
if (nuse > 0) {
|
|
let upaths: []str = alloc([], nuse: u64)!;
|
|
upaths.len = nuse;
|
|
let unodes: []*syntax.node = alloc([], nuse: u64)!;
|
|
unodes.len = nuse;
|
|
let k: i32 = 0;
|
|
u = file.list;
|
|
for (u != nil) {
|
|
if (u.kind == syntax.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: []*syntax.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;
|
|
};
|