1167 lines
36 KiB
Plaintext
1167 lines
36 KiB
Plaintext
// wwi.ww — `.wwi` export-data producer (w6c_ww -I): a re-parseable
|
|
// ww-prototype rendering of a package's exported surface and compiler-private
|
|
// closure. Since the
|
|
// sep-compile flip (epic #22) this is the LIVE import path — the driver
|
|
// runs one `w6c -c -I` per package and feeds each dep's `.wwi` to its
|
|
// importers through a separate canonical `--import` input.
|
|
//
|
|
// wwstage twin of cmd/w6c/wwi.c — byte-identical output is a rule-10
|
|
// requirement (`.wwi` is a 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.
|
|
// - Reachable owner-private nominal types are encoded without `export`.
|
|
// Consumers reconstruct public signatures from them, but source cannot
|
|
// qualify those private spellings.
|
|
// - A `.wwi` is ONE package's self-contained interface. Its primary
|
|
// section is followed by compiler-owned origin sections containing the
|
|
// exported foreign type/const facts reachable from the public surface.
|
|
|
|
package wcc;
|
|
|
|
import os;
|
|
import syntax;
|
|
import strconv;
|
|
|
|
let wwiwritefailed: i32 = 0;
|
|
|
|
fn wwrite(fd: i32, p: *u8, n: u64) void = {
|
|
if (wwiwritefailed != 0) { return; };
|
|
match (os.writeall(fd, p, n)) {
|
|
case let wrote: i64 => {
|
|
if (wrote < 0 || (wrote: u64) != n) { wwiwritefailed = 1; };
|
|
};
|
|
case let e: os.oserror => wwiwritefailed = 1;
|
|
};
|
|
};
|
|
|
|
fn wputs(fd: i32, s: str) void = {
|
|
wwrite(fd, s.ptr, s.len: u64);
|
|
};
|
|
|
|
fn wputb(fd: i32, b: u8) void = {
|
|
let buf: [1]u8;
|
|
buf[0] = b;
|
|
wwrite(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;
|
|
wwrite(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 wwimodeq(a: str, b: str) bool = {
|
|
if (a.len == 0 || b.len == 0) { return a.len == b.len; };
|
|
return syntax.streq(a, b);
|
|
};
|
|
|
|
// Map an import alias in the source package that owns the reference. The
|
|
// flattened parser file contains every imported interface's N_USE nodes, so
|
|
// this owner filter is what prevents cross-package alias capture.
|
|
fn wwiusepath(c: *checker, owner: str, source: i32, alias: str) str = {
|
|
let u: *syntax.node = c.file.list;
|
|
for (u != nil) {
|
|
if (u.kind == syntax.nkind.N_USE && u.useblank == 0
|
|
&& u.sourceid == source
|
|
&& syntax.streq(u.str, alias)) {
|
|
let same: bool = false;
|
|
if (owner.len == 0) {
|
|
same = u.imported == 0;
|
|
} else {
|
|
same = u.imported != 0 && syntax.streq(u.nmod, owner);
|
|
};
|
|
if (same) {
|
|
if (u.usepath.len > 0) { return u.usepath; };
|
|
return u.str;
|
|
};
|
|
};
|
|
u = u.next;
|
|
};
|
|
let empty: str;
|
|
return empty;
|
|
};
|
|
|
|
// Canonical export spelling depends only on path identity, never on the
|
|
// source alias or imported declared package name.
|
|
fn wwicanonicalalias(fd: i32, path: str) void = {
|
|
wputs(fd, "__wwi_");
|
|
let i: i32 = 0;
|
|
for (i < path.len) {
|
|
let hi: u8 = path[i] >> 4u8;
|
|
let lo: u8 = path[i] & 15u8;
|
|
if (hi < 10u8) { wputb(fd, hi + 48u8); }
|
|
else { wputb(fd, hi - 10u8 + 97u8); };
|
|
if (lo < 10u8) { wputb(fd, lo + 48u8); }
|
|
else { wputb(fd, lo - 10u8 + 97u8); };
|
|
i += 1;
|
|
};
|
|
};
|
|
|
|
fn wwiname(c: *checker, fd: i32, owner: str, source: i32, name: str) void = {
|
|
let dotidx: i32 = -1;
|
|
let i: i32 = 0;
|
|
for (i < name.len) { if (name[i] == 46u8) { dotidx = i; }; i += 1; };
|
|
if (dotidx >= 0) {
|
|
let head: str;
|
|
head.ptr = name.ptr;
|
|
head.len = dotidx;
|
|
let path: str = wwiusepath(c, owner, source, head);
|
|
if (path.len > 0) {
|
|
wwicanonicalalias(fd, path);
|
|
let tail: str;
|
|
tail.ptr = name.ptr + (dotidx: u64);
|
|
tail.len = name.len - dotidx;
|
|
wputs(fd, tail);
|
|
return;
|
|
};
|
|
};
|
|
wputs(fd, name);
|
|
};
|
|
|
|
fn wwitypesym(c: *checker, owner: str, source: i32, nm: str) *syntax.sym = {
|
|
let dotidx: i32 = -1;
|
|
let i: i32 = 0;
|
|
for (i < nm.len) {
|
|
if (nm[i] == 46u8) { dotidx = i; };
|
|
i += 1;
|
|
};
|
|
let s: *syntax.sym = nil;
|
|
if (dotidx >= 0) {
|
|
let head: str;
|
|
head.ptr = nm.ptr;
|
|
head.len = dotidx;
|
|
let leaf: str;
|
|
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
|
|
leaf.len = nm.len - dotidx - 1;
|
|
let mod: str = wwiusepath(c, owner, source, head);
|
|
if (mod.len > 0) {
|
|
s = syntax.scopelookupinmodule(c.cur, mod, leaf);
|
|
};
|
|
} else {
|
|
s = syntax.scopelookuptype(c.cur, owner, nm);
|
|
};
|
|
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, owner: str, 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, owner, d, t.lhs); };
|
|
let bad: i32 = 0;
|
|
if (t.kind == syntax.nkind.N_TNAME) {
|
|
// Nominal references, including private ones, are collected into
|
|
// the self-contained fact closure below.
|
|
} 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, owner, 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, owner, d, t.lhs);
|
|
} else { if (t.kind == syntax.nkind.N_TFN) {
|
|
let p: *syntax.node = t.list;
|
|
for (p != nil) {
|
|
bad = bad | wwichecktype(c, owner, d, p.lhs);
|
|
p = p.next;
|
|
};
|
|
bad = bad | wwichecktype(c, owner, d, t.lhs);
|
|
} else { if (t.kind == syntax.nkind.N_TSTRUCT) {
|
|
let f: *syntax.node = t.list;
|
|
for (f != nil) {
|
|
bad = bad | wwichecktype(c, owner, 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, owner, 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, owner, d, t.lhs);
|
|
};};};};};};};
|
|
return bad;
|
|
};
|
|
|
|
fn wwicheckdecl(c: *checker, d: *syntax.node) i32 = {
|
|
let owner: str;
|
|
let bad: i32 = 0;
|
|
if (d.kind == syntax.nkind.N_FNDECL) {
|
|
let p: *syntax.node = d.list;
|
|
for (p != nil) {
|
|
bad = bad | wwichecktype(c, owner, d, p.lhs);
|
|
p = p.next;
|
|
};
|
|
bad = bad | wwichecktype(c, owner, d, d.lhs);
|
|
} else { if (d.kind == syntax.nkind.N_TYPEDECL) {
|
|
bad = bad | wwichecktype(c, owner, 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, owner, d, d.lhs);
|
|
} else { if (d.kind == syntax.nkind.N_LET) {
|
|
bad = bad | wwichecktype(c, owner, d, d.lhs);
|
|
};};};};
|
|
return bad;
|
|
};
|
|
|
|
// Type-expr + const-expr unparser (rob §2.2/§2.4). wwihexdigits is
|
|
// byte-identical to cstage's fprintf("%0Nx").
|
|
fn wwihexdigits(fd: i32, v: u64, n: i32) void = {
|
|
let i: i32 = n - 1;
|
|
for (i >= 0) {
|
|
let d: u64 = (v >> ((i: u64) * 4u64)) & 15u64;
|
|
let c: u8 = 0u8;
|
|
if (d < 10u64) { c = (d: u8) + 48u8; } else { c = ((d: u8) - 10u8) + 97u8; };
|
|
wputb(fd, c);
|
|
i -= 1;
|
|
};
|
|
};
|
|
|
|
// #50: the lexer now reads \u/\U, so wide codepoints round-trip as those
|
|
// escapes (write-twin of lex.ww lexunicode). Codepoints <=0xff keep the
|
|
// \xHH spelling they already round-tripped as.
|
|
fn wwirune(fd: i32, cp: u64) void = {
|
|
if (cp > 65535u64) {
|
|
wputb(fd, '\'');
|
|
wputs(fd, "\\U");
|
|
wwihexdigits(fd, cp, 8);
|
|
wputb(fd, '\'');
|
|
return;
|
|
};
|
|
if (cp > 255u64) {
|
|
wputb(fd, '\'');
|
|
wputs(fd, "\\u");
|
|
wwihexdigits(fd, cp, 4);
|
|
wputb(fd, '\'');
|
|
return;
|
|
};
|
|
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;
|
|
wwrite(fd, buf.ptr, 4u64);
|
|
} else {
|
|
wputb(fd, c);
|
|
};};};};};
|
|
wputb(fd, '\'');
|
|
};
|
|
|
|
fn wwiexpr(c: *checker, fd: i32, owner: str, source: 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) {
|
|
wwiname(c, fd, owner, source, e.str);
|
|
} else { if (e.kind == syntax.nkind.N_DOT) {
|
|
if (e.lhs != nil && e.lhs.kind == syntax.nkind.N_IDENT) {
|
|
let path: str = wwiusepath(c, owner, source, e.lhs.str);
|
|
if (path.len > 0) { wwicanonicalalias(fd, path); }
|
|
else { wwiexpr(c, fd, owner, source, e.lhs); };
|
|
} else { wwiexpr(c, fd, owner, source, 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) {
|
|
wputb(fd, '(');
|
|
wwiexpr(c, fd, owner, source, e.lhs);
|
|
wputb(fd, 32u8);
|
|
wputs(fd, syntax.tokname(e.op));
|
|
wputb(fd, 32u8);
|
|
wwiexpr(c, fd, owner, source, e.rhs);
|
|
wputb(fd, ')');
|
|
} else { if (e.kind == syntax.nkind.N_UN) {
|
|
wputs(fd, syntax.tokname(e.op));
|
|
wwiexpr(c, fd, owner, source, e.lhs);
|
|
} else { if (e.kind == syntax.nkind.N_CAST) {
|
|
wputb(fd, '(');
|
|
wwiexpr(c, fd, owner, source, e.lhs);
|
|
wputs(fd, ": ");
|
|
wwitype(c, fd, owner, source, e.rhs);
|
|
wputb(fd, ')');
|
|
} else {
|
|
wputs(2, "wwi: unhandled const-expr node kind\n");
|
|
os.exit(1);
|
|
};};};};};};};};};};};
|
|
};
|
|
|
|
fn wwiparam(c: *checker, fd: i32, owner: str, source: 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(c, fd, owner, source, p.lhs.lhs);
|
|
} else {
|
|
wwitype(c, fd, owner, source, p.lhs);
|
|
};
|
|
if (p.op == syntax.tkind.TK_ELLIPSIS) { // Hare `T...` variadic
|
|
wputs(fd, "...");
|
|
};
|
|
};
|
|
|
|
fn wwitype(c: *checker, fd: i32, owner: str, source: 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(c, fd, owner, source, t.lhs); return;
|
|
};
|
|
if (t.kind == syntax.nkind.N_TNAME) {
|
|
if (t.str.len > 0) { wwiname(c, fd, owner, source, t.str); }
|
|
else { wputs(fd, "void"); };
|
|
} else { if (t.kind == syntax.nkind.N_TPTR) {
|
|
wputb(fd, '*');
|
|
wwitype(c, fd, owner, source, t.lhs);
|
|
} else { if (t.kind == syntax.nkind.N_TSLICE) {
|
|
wputs(fd, "[]");
|
|
wwitype(c, fd, owner, source, t.lhs);
|
|
} else { if (t.kind == syntax.nkind.N_TARRAY) {
|
|
wputb(fd, '[');
|
|
if (t.rhs != nil) { wwiexpr(c, fd, owner, source, t.rhs); }
|
|
else { wputb(fd, '_'); };
|
|
wputb(fd, ']');
|
|
wwitype(c, fd, owner, source, t.lhs);
|
|
} else { if (t.kind == syntax.nkind.N_TBANG) {
|
|
wputb(fd, '!');
|
|
wwitype(c, fd, owner, source, t.lhs);
|
|
} else { if (t.kind == syntax.nkind.N_TCHAN) {
|
|
wputs(fd, "chan ");
|
|
wwitype(c, fd, owner, source, 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(c, fd, owner, source, p);
|
|
p = p.next;
|
|
};
|
|
wputs(fd, ") ");
|
|
wwitype(c, fd, owner, source, t.lhs);
|
|
} else { if (t.kind == syntax.nkind.N_TSTRUCT) {
|
|
// re-emit `@packed` so the flag round-trips through
|
|
// sep-compile (harec unparse/type.ha:122-126).
|
|
if (t.packed != 0) {
|
|
wputs(fd, "struct @packed { ");
|
|
} else {
|
|
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(c, fd, owner, source, 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(c, fd, owner, source, 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, " | "); };
|
|
// #95: re-emit the spread marker purely syntactically;
|
|
// the consumer's type-store flattens
|
|
// (ref/hare/hare/unparse/type.ha:290-300).
|
|
if (e.op == syntax.tkind.TK_ELLIPSIS) { wputs(fd, "..."); };
|
|
wwitype(c, fd, owner, source, e);
|
|
e = e.next;
|
|
};
|
|
wputb(fd, ')');
|
|
} else { if (t.kind == syntax.nkind.N_TENUM) {
|
|
wputs(fd, "enum ");
|
|
if (t.lhs != nil) {
|
|
wwitype(c, fd, owner, source, 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(c, fd, owner, source, 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 is compiler-private package-test metadata.
|
|
fn wwiattrrelevant(nm: str) bool = {
|
|
return syntax.streq(nm, "symbol") || syntax.streq(nm, "test");
|
|
};
|
|
|
|
fn wwiattrs(c: *checker, fd: i32, owner: str, 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(c, fd, owner, d.sourceid, arg);
|
|
arg = arg.next;
|
|
};
|
|
wputb(fd, ')');
|
|
};
|
|
wputb(fd, 32u8);
|
|
};
|
|
a = a.next;
|
|
};
|
|
};
|
|
|
|
fn wwidecl(c: *checker, fd: i32, owner: str, d: *syntax.node) void = {
|
|
if (d.kind == syntax.nkind.N_FNDECL) {
|
|
wwiattrs(c, fd, owner, d);
|
|
if (d.exported != 0) { wputs(fd, "export fn "); }
|
|
else { wputs(fd, "fn "); };
|
|
wputs(fd, d.str);
|
|
wputb(fd, '(');
|
|
let p: *syntax.node = d.list;
|
|
for (p != nil) {
|
|
if (p != d.list) { wputs(fd, ", "); };
|
|
wwiparam(c, fd, owner, d.sourceid, p);
|
|
p = p.next;
|
|
};
|
|
wputs(fd, ") ");
|
|
wwitype(c, fd, owner, d.sourceid, d.lhs);
|
|
wputs(fd, ";\n");
|
|
} else { if (d.kind == syntax.nkind.N_TYPEDECL) {
|
|
if (d.exported != 0) { wputs(fd, "export type "); }
|
|
else { wputs(fd, "type "); };
|
|
wputs(fd, d.str);
|
|
wputs(fd, " = ");
|
|
wwitype(c, fd, owner, d.sourceid, d.lhs);
|
|
wputs(fd, ";\n");
|
|
} else { if (d.kind == syntax.nkind.N_DEF) {
|
|
if (d.exported != 0) { wputs(fd, "export def "); }
|
|
else { wputs(fd, "def "); };
|
|
wputs(fd, d.str);
|
|
wputs(fd, ": ");
|
|
wwitype(c, fd, owner, d.sourceid, 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(c, fd, owner, d.sourceid, d.rhs);
|
|
wputs(fd, ";\n");
|
|
};
|
|
} else { if (d.kind == syntax.nkind.N_LET) {
|
|
if (d.exported != 0) { wputs(fd, "export let "); }
|
|
else { wputs(fd, "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(c, fd, owner, d.sourceid, d.lhs);
|
|
wputs(fd, ";\n");
|
|
};};};};
|
|
};
|
|
|
|
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 = {
|
|
if (d.initfn != 0 || d.initsynthetic != 0) { return false; };
|
|
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;
|
|
};
|
|
|
|
fn wwihasattr(d: *syntax.node, name: str) bool = {
|
|
let a: *syntax.node = d.attr;
|
|
for (a != nil) {
|
|
if (a.kind == syntax.nkind.N_ATTR && syntax.streq(a.str, name)) {
|
|
return true;
|
|
};
|
|
a = a.next;
|
|
};
|
|
return false;
|
|
};
|
|
|
|
// Deterministic ordering (rob §3): 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;
|
|
};
|
|
|
|
// Total order keyed on the symbol name; ties broken by original index —
|
|
// 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;
|
|
};
|
|
};
|
|
|
|
// Compiler-owned reachable facts. Parallel arrays keep the self-hosted
|
|
// representation narrow and make the deterministic ordering explicit.
|
|
type wwifactset = struct {
|
|
c: *checker,
|
|
privatenodes: []*syntax.node,
|
|
privatekeys: []str,
|
|
nprivate: i32,
|
|
factnodes: []*syntax.node,
|
|
factmods: []str,
|
|
factranks: []i32,
|
|
nfacts: i32,
|
|
seennodes: []*syntax.node,
|
|
seenmods: []str,
|
|
seenranks: []i32,
|
|
nseen: i32,
|
|
bad: i32,
|
|
};
|
|
|
|
fn wwifactrank(d: *syntax.node) i32 = {
|
|
if (d.kind == syntax.nkind.N_TYPEDECL) { return 0i32; };
|
|
if (d.kind == syntax.nkind.N_DEF) { return 1i32; };
|
|
if (d.kind == syntax.nkind.N_FNDECL) { return 2i32; };
|
|
return 3i32;
|
|
};
|
|
|
|
fn wwifactsame(mod: str, rank: i32, d: *syntax.node,
|
|
smod: str, srank: i32, sd: *syntax.node) bool = {
|
|
return rank == srank && wwimodeq(mod, smod) && syntax.streq(d.str, sd.str);
|
|
};
|
|
|
|
fn wwifactvaluesym(c: *checker, owner: str, source: i32,
|
|
name: str) *syntax.sym = {
|
|
let p: *syntax.scope = c.top;
|
|
for (p != nil) {
|
|
let s: *syntax.sym = p.first;
|
|
for (s != nil) {
|
|
if (s.skind == syntax.skind.SK_DEF && syntax.streq(s.name, name)
|
|
&& wwimodeq(s.mod, owner)) { return s; };
|
|
s = s.snext;
|
|
};
|
|
p = p.parent;
|
|
};
|
|
return nil;
|
|
};
|
|
|
|
fn wwifactreject(d: *syntax.node, kind: str, name: 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 ");
|
|
wputs(2, kind);
|
|
wputs(2, " '");
|
|
wputs(2, name);
|
|
wputs(2, "'\n");
|
|
};
|
|
|
|
fn wwiencodearrayreject(d: *syntax.node) 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: cannot encode array dimension\n");
|
|
};
|
|
|
|
fn wwicollectdecl(fs: *wwifactset, owner: str, d: *syntax.node) void = {
|
|
let rank: i32 = wwifactrank(d);
|
|
let i: i32 = 0;
|
|
for (i < fs.nseen) {
|
|
if (wwifactsame(owner, rank, d, fs.seenmods[i],
|
|
fs.seenranks[i], fs.seennodes[i])) { return; };
|
|
i += 1;
|
|
};
|
|
fs.seennodes[fs.nseen] = d;
|
|
fs.seenmods[fs.nseen] = owner;
|
|
fs.seenranks[fs.nseen] = rank;
|
|
fs.nseen += 1;
|
|
|
|
if (owner.len == 0 && d.exported == 0) {
|
|
// checkinit's synthetic nomem/void declarations have no source file.
|
|
// They are builtins, not owner-private package facts; cstage resolves
|
|
// these through lookup_builtin and therefore never collects them.
|
|
if (d.file.len == 0) { return; };
|
|
if (d.kind != syntax.nkind.N_TYPEDECL) {
|
|
wwifactreject(d, "def", d.str);
|
|
fs.bad = 1;
|
|
return;
|
|
};
|
|
fs.privatenodes[fs.nprivate] = d;
|
|
fs.privatekeys[fs.nprivate] = d.str;
|
|
fs.nprivate += 1;
|
|
} else { if (owner.len > 0) {
|
|
if (d.exported == 0 && d.kind != syntax.nkind.N_TYPEDECL) {
|
|
let kind: str = "def";
|
|
if (d.kind == syntax.nkind.N_TYPEDECL) { kind = "type"; };
|
|
wwifactreject(d, kind, d.str);
|
|
fs.bad = 1;
|
|
return;
|
|
};
|
|
fs.factnodes[fs.nfacts] = d;
|
|
fs.factmods[fs.nfacts] = owner;
|
|
fs.factranks[fs.nfacts] = rank;
|
|
fs.nfacts += 1;
|
|
}; };
|
|
|
|
if (d.kind == syntax.nkind.N_FNDECL) {
|
|
let p: *syntax.node = d.list;
|
|
for (p != nil) { wwicollecttype(fs, owner, d.sourceid, p.lhs); p = p.next; };
|
|
wwicollecttype(fs, owner, d.sourceid, d.lhs);
|
|
} else { if (d.kind == syntax.nkind.N_TYPEDECL) {
|
|
wwicollecttype(fs, owner, d.sourceid, d.lhs);
|
|
} else { if (d.kind == syntax.nkind.N_DEF) {
|
|
wwicollecttype(fs, owner, d.sourceid, d.lhs);
|
|
wwicollectexpr(fs, owner, d.sourceid, d.rhs);
|
|
} else { if (d.kind == syntax.nkind.N_LET) {
|
|
wwicollecttype(fs, owner, d.sourceid, d.lhs);
|
|
};};};};
|
|
};
|
|
|
|
fn wwicollectexpr(fs: *wwifactset, owner: str, source: i32,
|
|
e: *syntax.node) void = {
|
|
if (e == nil) { return; };
|
|
let s: *syntax.sym = nil;
|
|
if (e.kind == syntax.nkind.N_IDENT) {
|
|
s = wwifactvaluesym(fs.c, owner, source, e.str);
|
|
} else { if (e.kind == syntax.nkind.N_DOT && e.lhs != nil) {
|
|
if (e.lhs.kind == syntax.nkind.N_IDENT) {
|
|
let mod: str = wwiusepath(fs.c, owner, source, e.lhs.str);
|
|
if (mod.len > 0) {
|
|
s = wwifactvaluesym(fs.c, mod, source, e.str);
|
|
};
|
|
};
|
|
}; };
|
|
if (s != nil && s.decl != nil) {
|
|
if (s.decl.exported == 0) {
|
|
wwifactreject(e, "def", e.str);
|
|
fs.bad = 1;
|
|
return;
|
|
};
|
|
wwicollectdecl(fs, s.mod, s.decl);
|
|
return;
|
|
};
|
|
if (e.kind == syntax.nkind.N_BIN) {
|
|
wwicollectexpr(fs, owner, source, e.lhs);
|
|
wwicollectexpr(fs, owner, source, e.rhs);
|
|
} else { if (e.kind == syntax.nkind.N_UN) {
|
|
wwicollectexpr(fs, owner, source, e.lhs);
|
|
} else { if (e.kind == syntax.nkind.N_CAST) {
|
|
wwicollectexpr(fs, owner, source, e.lhs);
|
|
wwicollecttype(fs, owner, source, e.rhs);
|
|
}; }; };
|
|
};
|
|
|
|
fn wwievalconst(fs: *wwifactset, owner: str, source: i32, e: *syntax.node,
|
|
out: *u64) bool = {
|
|
let saved: str = fs.c.curmod;
|
|
let savesource: i32 = fs.c.cursource;
|
|
fs.c.curmod = owner;
|
|
fs.c.cursource = source;
|
|
let ok: bool = evaldefconst(fs.c, e, out, 0);
|
|
fs.c.curmod = saved;
|
|
fs.c.cursource = savesource;
|
|
return ok;
|
|
};
|
|
|
|
fn wwicollecttype(fs: *wwifactset, owner: str, source: i32,
|
|
t: *syntax.node) void = {
|
|
if (t == nil) { return; };
|
|
if (t.kind == syntax.nkind.N_TPARAM) {
|
|
wwicollecttype(fs, owner, source, t.lhs);
|
|
return;
|
|
};
|
|
if (t.kind == syntax.nkind.N_TNAME) {
|
|
let s: *syntax.sym = wwitypesym(fs.c, owner, source, t.str);
|
|
if (s != nil && s.decl != nil) {
|
|
if (s.decl.kind == syntax.nkind.N_TYPEDECL) {
|
|
wwicollectdecl(fs, s.mod, s.decl);
|
|
};
|
|
};
|
|
} 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
|
|
) {
|
|
wwicollecttype(fs, owner, source, t.lhs);
|
|
} else { if (t.kind == syntax.nkind.N_TARRAY) {
|
|
// Array length is resolved type identity, not a source name
|
|
// dependency. Canonicalize it so private constants stay private
|
|
// and consumers never need implementation defs merely for layout.
|
|
if (t.rhs != nil && t.rhs.kind != syntax.nkind.N_INTLIT) {
|
|
let len: u64 = 0u64;
|
|
if (!wwievalconst(fs, owner, source, t.rhs, &len)) {
|
|
wwiencodearrayreject(t);
|
|
fs.bad = 1;
|
|
} else {
|
|
let e: *syntax.node = t.rhs;
|
|
e.kind = syntax.nkind.N_INTLIT;
|
|
e.uval = len;
|
|
e.lhs = nil; e.rhs = nil; e.list = nil;
|
|
let empty: str; e.tsuffix = empty;
|
|
};
|
|
};
|
|
wwicollecttype(fs, owner, source, t.lhs);
|
|
} else { if (t.kind == syntax.nkind.N_TFN) {
|
|
let p: *syntax.node = t.list;
|
|
for (p != nil) { wwicollecttype(fs, owner, source, p.lhs); p = p.next; };
|
|
wwicollecttype(fs, owner, source, t.lhs);
|
|
} else { if (t.kind == syntax.nkind.N_TSTRUCT) {
|
|
let f: *syntax.node = t.list;
|
|
for (f != nil) { wwicollecttype(fs, owner, source, 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) { wwicollecttype(fs, owner, source, e); e = e.next; };
|
|
} else { if (t.kind == syntax.nkind.N_TENUM) {
|
|
wwicollecttype(fs, owner, source, t.lhs);
|
|
// Member identifiers refer to prior siblings in this enum, not
|
|
// package defs; the declaration already carries the whole list.
|
|
};};};};};};};
|
|
};
|
|
|
|
fn wwisortfacts(fs: *wwifactset) void = {
|
|
let i: i32 = 0;
|
|
for (i < fs.nfacts) {
|
|
let best: i32 = i;
|
|
let j: i32 = i + 1;
|
|
for (j < fs.nfacts) {
|
|
let r: i32 = wwistrcmp(fs.factmods[j], fs.factmods[best]);
|
|
if (r == 0) {
|
|
r = fs.factnodes[j].sourceid - fs.factnodes[best].sourceid;
|
|
};
|
|
if (r == 0) { r = fs.factranks[j] - fs.factranks[best]; };
|
|
if (r == 0) { r = wwistrcmp(fs.factnodes[j].str, fs.factnodes[best].str); };
|
|
if (r < 0) { best = j; };
|
|
j += 1;
|
|
};
|
|
if (best != i) {
|
|
let tn: *syntax.node = fs.factnodes[i]; fs.factnodes[i] = fs.factnodes[best]; fs.factnodes[best] = tn;
|
|
let tm: str = fs.factmods[i]; fs.factmods[i] = fs.factmods[best]; fs.factmods[best] = tm;
|
|
let tr: i32 = fs.factranks[i]; fs.factranks[i] = fs.factranks[best]; fs.factranks[best] = tr;
|
|
};
|
|
i += 1;
|
|
};
|
|
};
|
|
|
|
fn wwiowneduse(u: *syntax.node, owner: str, source: i32) bool = {
|
|
return u.kind == syntax.nkind.N_USE && u.useblank == 0 && u.imported != 0
|
|
&& u.sourceid == source && wwimodeq(u.nmod, owner);
|
|
};
|
|
|
|
fn wwiemitimports(fd: i32, file: *syntax.node, owner: str, source: i32,
|
|
imported: bool) void = {
|
|
let nuse: i32 = 0;
|
|
let u: *syntax.node = file.list;
|
|
for (u != nil) {
|
|
let owned: bool = false;
|
|
if (imported) {
|
|
owned = wwiowneduse(u, owner, source);
|
|
} else {
|
|
owned = u.kind == syntax.nkind.N_USE && u.useblank == 0
|
|
&& u.imported == 0
|
|
&& u.sourceid == source;
|
|
};
|
|
if (owned) { nuse += 1; };
|
|
u = u.next;
|
|
};
|
|
if (nuse == 0) { return; };
|
|
let paths: []str = alloc([], nuse: u64)!; paths.len = nuse;
|
|
let nodes: []*syntax.node = alloc([], nuse: u64)!; nodes.len = nuse;
|
|
let k: i32 = 0;
|
|
u = file.list;
|
|
for (u != nil) {
|
|
let owned: bool = false;
|
|
if (imported) {
|
|
owned = wwiowneduse(u, owner, source);
|
|
} else {
|
|
owned = u.kind == syntax.nkind.N_USE && u.useblank == 0
|
|
&& u.imported == 0
|
|
&& u.sourceid == source;
|
|
};
|
|
if (owned) {
|
|
if (u.usepath.len > 0) { paths[k] = u.usepath; } else { paths[k] = u.str; };
|
|
nodes[k] = u;
|
|
k += 1;
|
|
};
|
|
u = u.next;
|
|
};
|
|
wwisortdecls(paths, nodes, nuse);
|
|
let previous: str;
|
|
let i: i32 = 0;
|
|
for (i < nuse) {
|
|
if (previous.len == 0 || !syntax.streq(previous, paths[i])) {
|
|
wputs(fd, "import "); wwicanonicalalias(fd, paths[i]);
|
|
wputb(fd, 32u8); wputs(fd, paths[i]); wputs(fd, ";\n");
|
|
previous = paths[i];
|
|
};
|
|
i += 1;
|
|
};
|
|
};
|
|
|
|
fn wwiprimarysectionhas(c: *checker, file: *syntax.node,
|
|
fs: *wwifactset, exports: []*syntax.node, nexports: i32,
|
|
source: i32) bool = {
|
|
let u: *syntax.node = file.list;
|
|
for (u != nil) {
|
|
if (u.kind == syntax.nkind.N_USE && u.useblank == 0 && u.imported == 0
|
|
&& u.sourceid == source) { return true; };
|
|
u = u.next;
|
|
};
|
|
let i: i32 = 0;
|
|
for (i < fs.nprivate) {
|
|
if (fs.privatenodes[i].sourceid == source) { return true; };
|
|
i += 1;
|
|
};
|
|
i = 0;
|
|
for (i < nexports) {
|
|
if (exports[i].sourceid == source) { return true; };
|
|
i += 1;
|
|
};
|
|
if (c.istestpackage != 0) {
|
|
let d: *syntax.node = file.list;
|
|
for (d != nil) {
|
|
if (wwiprimary(d) && d.sourceid == source
|
|
&& d.kind == syntax.nkind.N_FNDECL && d.exported == 0
|
|
&& wwihasattr(d, "test")) { return true; };
|
|
d = d.next;
|
|
};
|
|
};
|
|
return false;
|
|
};
|
|
|
|
fn wwifirstprimarysource(file: *syntax.node) i32 = {
|
|
let found: bool = false;
|
|
let source: i32 = 0;
|
|
let d: *syntax.node = file.list;
|
|
for (d != nil) {
|
|
if (wwiprimary(d) && (!found || d.sourceid < source)) {
|
|
source = d.sourceid;
|
|
found = true;
|
|
};
|
|
d = d.next;
|
|
};
|
|
if (found) { return source; };
|
|
return file.sourceid;
|
|
};
|
|
|
|
fn wwiemitprimarysection(c: *checker, fd: i32, file: *syntax.node,
|
|
fs: *wwifactset, exports: []*syntax.node, nexports: i32,
|
|
owner: str, pkgname: str, source: i32) void = {
|
|
if (owner.len > 0) {
|
|
wputs(fd, "//ww:module "); wputs(fd, owner); wputs(fd, "\n");
|
|
};
|
|
let pkg: str = pkgname;
|
|
if (pkg.len == 0) { pkg = "main"; };
|
|
wputs(fd, "package "); wputs(fd, pkg); wputs(fd, ";\n");
|
|
wwiemitimports(fd, file, owner, source, false);
|
|
let i: i32 = 0;
|
|
for (i < fs.nprivate) {
|
|
if (fs.privatenodes[i].sourceid == source) {
|
|
let primaryowner: str;
|
|
wwidecl(c, fd, primaryowner, fs.privatenodes[i]);
|
|
};
|
|
i += 1;
|
|
};
|
|
if (c.istestpackage != 0) {
|
|
let d: *syntax.node = file.list;
|
|
for (d != nil) {
|
|
if (wwiprimary(d) && d.sourceid == source
|
|
&& d.kind == syntax.nkind.N_FNDECL && d.exported == 0
|
|
&& wwihasattr(d, "test")) {
|
|
let primaryowner: str;
|
|
wwidecl(c, fd, primaryowner, d);
|
|
};
|
|
d = d.next;
|
|
};
|
|
};
|
|
i = 0;
|
|
for (i < nexports) {
|
|
if (exports[i].sourceid == source) {
|
|
let primaryowner: str;
|
|
wwidecl(c, fd, primaryowner, exports[i]);
|
|
};
|
|
i += 1;
|
|
};
|
|
};
|
|
|
|
fn wwiemitfd(c: *checker, file: *syntax.node, fd: i32) i32 = {
|
|
wwiwritefailed = 0;
|
|
// §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; };
|
|
|
|
// The complete closure cannot contain more declarations than the checked
|
|
// compilation unit. Allocate once; collection deduplicates by semantic
|
|
// (owner, kind, name), then sorting supplies the byte-stable order.
|
|
let nall: i32 = 0;
|
|
d = file.list;
|
|
for (d != nil) { if (wwiisdecl(d)) { nall += 1; }; d = d.next; };
|
|
if (nall == 0) { nall = 1; };
|
|
let fs: wwifactset;
|
|
fs.c = c;
|
|
let privatenodes: []*syntax.node = alloc([], nall: u64)!; privatenodes.len = nall;
|
|
let privatekeys: []str = alloc([], nall: u64)!; privatekeys.len = nall;
|
|
let factnodes: []*syntax.node = alloc([], nall: u64)!; factnodes.len = nall;
|
|
let factmods: []str = alloc([], nall: u64)!; factmods.len = nall;
|
|
let factranks: []i32 = alloc([], nall: u64)!; factranks.len = nall;
|
|
let seennodes: []*syntax.node = alloc([], nall: u64)!; seennodes.len = nall;
|
|
let seenmods: []str = alloc([], nall: u64)!; seenmods.len = nall;
|
|
let seenranks: []i32 = alloc([], nall: u64)!; seenranks.len = nall;
|
|
fs.privatenodes = privatenodes; fs.privatekeys = privatekeys;
|
|
fs.factnodes = factnodes; fs.factmods = factmods; fs.factranks = factranks;
|
|
fs.seennodes = seennodes; fs.seenmods = seenmods; fs.seenranks = seenranks;
|
|
let primary: str;
|
|
d = file.list;
|
|
for (d != nil) {
|
|
if (wwiprimary(d) && d.exported != 0 && wwiisdecl(d)) {
|
|
wwicollectdecl(&fs, primary, d);
|
|
};
|
|
d = d.next;
|
|
};
|
|
if (fs.bad != 0) { return 1i32; };
|
|
wwisortdecls(fs.privatekeys, fs.privatenodes, fs.nprivate);
|
|
wwisortfacts(&fs);
|
|
|
|
// Exported declarations are sorted within their source-file sections.
|
|
let ndecl: i32 = 0;
|
|
d = file.list;
|
|
for (d != nil) {
|
|
if (wwiprimary(d) && d.exported != 0 && wwiisdecl(d)) {
|
|
ndecl += 1;
|
|
};
|
|
d = d.next;
|
|
};
|
|
let dkeys: []str;
|
|
let dnodes: []*syntax.node;
|
|
if (ndecl > 0) {
|
|
let keys: []str = alloc([], ndecl: u64)!;
|
|
keys.len = ndecl;
|
|
dkeys = keys;
|
|
let nodes: []*syntax.node = alloc([], ndecl: u64)!;
|
|
nodes.len = ndecl;
|
|
dnodes = nodes;
|
|
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);
|
|
};
|
|
|
|
// Canonical ownership, declared name, and lexical source scope are
|
|
// independent export facts. Repeated owner sections preserve the file
|
|
// that owns each binding while every symbol/action remains keyed by owner.
|
|
let nsection: i32 = 0;
|
|
let p: *syntax.node = file.body;
|
|
for (p != nil) {
|
|
if (p.imported == 0 && wwiprimarysectionhas(c, file, &fs,
|
|
dnodes, ndecl, p.sourceid)) {
|
|
let owner: str = p.nmod;
|
|
if (owner.len == 0) { owner = file.nmod; };
|
|
let pkg: str = p.pkgname;
|
|
if (pkg.len == 0) { pkg = file.pkgname; };
|
|
wwiemitprimarysection(c, fd, file, &fs, dnodes, ndecl,
|
|
owner, pkg, p.sourceid);
|
|
nsection += 1;
|
|
};
|
|
p = p.next;
|
|
};
|
|
if (nsection == 0) {
|
|
wwiemitprimarysection(c, fd, file, &fs, dnodes, ndecl,
|
|
file.nmod, file.pkgname, wwifirstprimarysource(file));
|
|
};
|
|
|
|
// Compiler-owned public fact closure. Origin markers preserve nominal
|
|
// ownership but do not create source imports in the eventual consumer.
|
|
let lastmod: str;
|
|
let lastsource: i32 = -1;
|
|
let fi: i32 = 0;
|
|
for (fi < fs.nfacts) {
|
|
let mod: str = fs.factmods[fi];
|
|
let source: i32 = fs.factnodes[fi].sourceid;
|
|
if (lastmod.len == 0 || !syntax.streq(lastmod, mod)
|
|
|| lastsource != source) {
|
|
wputs(fd, "//ww:module "); wputs(fd, mod); wputs(fd, "\n");
|
|
wputs(fd, "package "); wwicanonicalalias(fd, mod);
|
|
wputs(fd, ";\n");
|
|
wwiemitimports(fd, file, mod, source, true);
|
|
lastmod = mod;
|
|
lastsource = source;
|
|
};
|
|
wwidecl(c, fd, mod, fs.factnodes[fi]);
|
|
fi += 1;
|
|
};
|
|
|
|
return wwiwritefailed;
|
|
};
|