Files
ww/lib/ww/ast.ww
Hojun-Cho 045c49e398 selfhost/cmd/wcc: enable asserttyped + close A.6.2 (#4, #15)
Final closer for the A.6.2 sequence. Mirror of harec's
`assert(expr->result)` at ref/harec/src/check.c:3810: every value-
producing nkind dispatched by resolvewalk (L475-488 + N_DOT at L391)
reaches a stamping arm in exprtype that sets e.type_ before
returning. asserttyped is the post-checker invariant gate; it walks
checkfile's decls in pass 3 (same curmod context exprtype saw in
pass 2) and writes a one-line stderr diagnostic for any dispatched
node whose type_ remained nil.

Three residual gates encode bails that aren't true gaps until #19
(Drew's δ: dedicated AST kinds for alloc/size/etc.) retires the
seeded-SK_FN-with-nil-decl + SK_USE-as-value shapes:
  1. N_IDENT resolving to SK_USE (module ref like `os` in `os.write`)
  2. N_IDENT whose sym.decl == nil (pseudo-builtin callee — len,
     append, free, alloc, size, align, offset seeded at L86-98)
  3. N_IDENT in LHS-of-N_DOT syntactic position (member-access
     lookup target, not value-producing) — tracked via `indot` param

ZERO fires across all 5 selfhost combined.ww corpora (wcc, w6c, w6a,
w6l, wwdump). asserttyped IS the regression catch — future commits
that drop a type_ stamp will fire it during the 990_selfhost probes;
no standalone table-driven test is bundled.

Accreted folds:
- 5-lite-a (#33): dispatcher-invariant docstring at exprtype L1535.
- 5-lite-b (#34): WHY comments at 9 helper bail sites
  (unifyarith/binoptype/unoptype/indexresult) classifying each as
  unreachable-for-valid-input, propagation-from-callee, or
  invalid-input (cstage errors at the matching cite). Cites
  ref/harec/src/types.c type_promote on the function-doc updates.
- A.6.2.1c (#24): three propagation pointers re-cite the
  inherent-IDENT bail at exprtype N_IDENT arm L1596-1599.
- unoptype TK_STAR dead `if (u == nil) { return nil; }` removed —
  resolvealias(unwrapbang(non-nil)) is non-nil by parser invariant
  (parsetype L148 always sets N_TBANG.lhs; resolvealias L514-569
  every exit returns non-nil for non-nil input).

lib/ww/ast.ww: nkname becomes export so asserttyped's diagnostic can
format the offending node's kind without duplicating the table.

Closes #4 (A.6.2 umbrella) and #15 (A.6.2.1e).
2026-05-22 04:10:24 +09:00

379 lines
11 KiB
Plaintext

// lib/ww/ast.ww — port of cmd/wcc/ast.c (Node defs + printer).
//
// Status: AST printer is fully ported. Constructor `newnode` is here.
// The parser (parse.ww) is currently minimal — see its file header.
//
// Calling-convention shim: same as tok/lex — `node` is too big to pass
// by value (8 *node pointers + 2 strs + a few ints), so callers always
// hand around `*node`. Only `newnode` allocates and returns a *node.
package ww;
import os;
import strconv;
import tok;
// ---- Nkind ------------------------------------------------------------
//
// Mirror of cmd/wcc/ww.h Nkind. Values must stay numerically equal so
// the AST diff probe in 990_selfhost works.
// Mirror of the C `Nkind` enum in cmd/wcc/ww.h. Numeric values are
// explicit and must stay in sync — the 990_selfhost test diffs
// astprint against the C side byte-for-byte. Tail-appended entries
// (TYPETEST onward) preserve every prior N_* value.
type nkind = enum i32 {
N_NONE = 0,
N_INTLIT = 1,
N_FLOATLIT = 2,
N_STRLIT = 3,
N_RUNELIT = 4,
N_TRUE = 5,
N_FALSE = 6,
N_NIL = 7,
N_IDENT = 8,
N_BIN = 9,
N_UN = 10,
N_CALL = 11,
N_INDEX = 12,
N_DOT = 13,
N_CAST = 14,
N_STRUCTLIT = 15,
N_ARRLIT = 16,
N_FIELD = 17,
N_ASSIGN = 18,
N_ALLOC = 19,
N_FREE = 20,
N_RECV = 21,
N_SLICE = 22,
N_SPREAD = 23,
N_BLOCK = 24,
N_EXPRSTMT = 25,
N_LET = 26,
N_RETURN = 27,
N_IF = 28,
N_FOR = 29,
N_FORRANGE = 30,
N_DEFER = 31,
N_BREAK = 32,
N_CONTINUE = 33,
N_SWITCH = 34,
N_CASE = 35,
N_FILE = 36,
N_USE = 37,
N_DEF = 38,
N_TYPEDECL = 39,
N_FNDECL = 40,
N_PARAM = 41,
N_TNAME = 42,
N_TPTR = 43,
N_TSLICE = 44,
N_TARRAY = 45,
N_TFN = 46,
N_TSTRUCT = 47,
N_TFIELD = 48,
N_TCHAN = 49,
N_ATTR = 50,
N_TTUPLE = 51,
N_TTAGGED = 52,
N_TUPLE = 53,
N_MATCH = 54,
N_MCASE = 55,
N_TRYPROP = 56,
N_TRYUNW = 57,
N_MLET = 58,
N_MASSIGN = 59,
N_TYPETEST = 60,
N_TYPEASSERT = 61,
N_VOIDLIT = 62,
N_TBANG = 63,
N_YIELD = 64,
N_TENUM = 65,
N_TENUMMEMBER = 66,
// N_TPARAM — chain wrapper for N_TTUPLE.list elements. Mirror of
// cstage's Tparam (cmd/wcc/check.c:1437-1451) lifted to the AST so
// `exprtype` can return shared element-type nodes (sym.decl.lhs,
// struct field's .lhs, another N_TTUPLE's .list element) without
// corrupting source ASTs by reusing their .next. .lhs holds the
// element type AST (possibly shared); .next chains within the
// parent N_TTUPLE.list. Cstage keeps Tparam at the Type-layer; ww
// has no separate type layer for tuple chains, so the wrapper sits
// at the AST layer. Other node fields are unused. Never appears
// outside an N_TTUPLE.list; astprint unwraps transparently to keep
// the 990 -a byte-diff against cstage.
N_TPARAM = 67,
N_LAST = 68,
};
// ---- Node -------------------------------------------------------------
type node = struct {
kind: nkind,
file: str,
line: i32,
col: i32,
op: tkind, // for nkind.N_BIN / nkind.N_UN / nkind.N_ASSIGN
str: str,
uval: u64,
fval: f64,
lhs: *node,
rhs: *node,
cond: *node,
body: *node,
els: *node,
list: *node,
next: *node,
attr: *node,
exported: i32, // bool — `export` keyword present
type_: *void, // filled in by checker; type.ww treats it as *tinfo
tsuffix: str, // typed numeric literal suffix ("i32", "u64", ...)
nmod: str, // originating module from `// MODULE: foo`; "" if none
};
export fn newnode(k: nkind, file: str, line: i32, col: i32) *node = {
// fval cast-init: 990's wwdump TK_FLOAT diff requires this file
// to tokenise identically through C and ww (lex.ww:382 has the
// same workaround for the cstage %g-formats vs ww-skips divergence).
let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, type_=nil, tsuffix="", nmod=""})!;
return n;
};
// ---- printer ----------------------------------------------------------
export fn nkname(k: nkind) str = {
if (k == nkind.N_NONE) { return "none"; };
if (k == nkind.N_INTLIT) { return "int"; };
if (k == nkind.N_FLOATLIT) { return "float"; };
if (k == nkind.N_STRLIT) { return "str"; };
if (k == nkind.N_RUNELIT) { return "rune"; };
if (k == nkind.N_TRUE) { return "true"; };
if (k == nkind.N_FALSE) { return "false"; };
if (k == nkind.N_NIL) { return "nil"; };
if (k == nkind.N_IDENT) { return "id"; };
if (k == nkind.N_BIN) { return "bin"; };
if (k == nkind.N_UN) { return "un"; };
if (k == nkind.N_CALL) { return "call"; };
if (k == nkind.N_INDEX) { return "index"; };
if (k == nkind.N_DOT) { return "dot"; };
if (k == nkind.N_CAST) { return "cast"; };
if (k == nkind.N_STRUCTLIT) { return "structlit"; };
if (k == nkind.N_ARRLIT) { return "arrlit"; };
if (k == nkind.N_FIELD) { return "field"; };
if (k == nkind.N_ASSIGN) { return "assign"; };
if (k == nkind.N_ALLOC) { return "alloc"; };
if (k == nkind.N_FREE) { return "free"; };
if (k == nkind.N_RECV) { return "recv"; };
if (k == nkind.N_SLICE) { return "slice"; };
if (k == nkind.N_SPREAD) { return "spread"; };
if (k == nkind.N_BLOCK) { return "block"; };
if (k == nkind.N_EXPRSTMT) { return "exprstmt"; };
if (k == nkind.N_LET) { return "let"; };
if (k == nkind.N_RETURN) { return "return"; };
if (k == nkind.N_IF) { return "if"; };
if (k == nkind.N_FOR) { return "for"; };
if (k == nkind.N_FORRANGE) { return "forrange"; };
if (k == nkind.N_DEFER) { return "defer"; };
if (k == nkind.N_BREAK) { return "break"; };
if (k == nkind.N_CONTINUE) { return "continue"; };
if (k == nkind.N_SWITCH) { return "switch"; };
if (k == nkind.N_CASE) { return "case"; };
if (k == nkind.N_FILE) { return "file"; };
if (k == nkind.N_USE) { return "use"; };
if (k == nkind.N_DEF) { return "def"; };
if (k == nkind.N_TYPEDECL) { return "typedecl"; };
if (k == nkind.N_FNDECL) { return "fn"; };
if (k == nkind.N_PARAM) { return "param"; };
if (k == nkind.N_TNAME) { return "tname"; };
if (k == nkind.N_TPTR) { return "tptr"; };
if (k == nkind.N_TSLICE) { return "tslice"; };
if (k == nkind.N_TARRAY) { return "tarray"; };
if (k == nkind.N_TFN) { return "tfn"; };
if (k == nkind.N_TSTRUCT) { return "tstruct"; };
if (k == nkind.N_TFIELD) { return "tfield"; };
if (k == nkind.N_TCHAN) { return "tchan"; };
if (k == nkind.N_ATTR) { return "attr"; };
if (k == nkind.N_TTUPLE) { return "ttuple"; };
if (k == nkind.N_TTAGGED) { return "ttagged"; };
if (k == nkind.N_TUPLE) { return "tuple"; };
if (k == nkind.N_MATCH) { return "match"; };
if (k == nkind.N_MCASE) { return "mcase"; };
if (k == nkind.N_TRYPROP) { return "tryprop"; };
if (k == nkind.N_TRYUNW) { return "tryunw"; };
if (k == nkind.N_MLET) { return "mlet"; };
if (k == nkind.N_MASSIGN) { return "massign"; };
if (k == nkind.N_TYPETEST) { return "typetest"; };
if (k == nkind.N_TYPEASSERT) { return "typeassert"; };
if (k == nkind.N_VOIDLIT) { return "voidlit"; };
if (k == nkind.N_TBANG) { return "tbang"; };
if (k == nkind.N_YIELD) { return "yield"; };
if (k == nkind.N_TENUM) { return "tenum"; };
if (k == nkind.N_TENUMMEMBER) { return "tenummember"; };
if (k == nkind.N_TPARAM) { return "tparam"; };
if (k == nkind.N_LAST) { return "last"; };
return "?";
};
fn ind(fd: i32, d: i32) void = {
let i: i32 = 0;
for (i < d) {
os.write(fd, " ".ptr, 2u64);
i += 1;
};
};
fn putc1(fd: i32, b: u8) void = {
let buf: [1]u8;
buf[0] = b;
os.write(fd, buf.ptr, 1u64);
};
fn putq(fd: i32, s: str) void = {
putc1(fd, 34u8); // '"'
let i: i32 = 0;
for (i < s.len) {
let c: u8 = s[i];
if (c == 34u8) { // '"'
os.write(fd, "\\\"".ptr, 2u64);
} else { if (c == 92u8) { // '\\'
os.write(fd, "\\\\".ptr, 2u64);
} else { if (c == 10u8) { // '\n'
os.write(fd, "\\n".ptr, 2u64);
} else { if (c == 9u8) { // '\t'
os.write(fd, "\\t".ptr, 2u64);
} 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 {
putc1(fd, c);
};};};};};
i += 1;
};
putc1(fd, 34u8);
};
fn pr(fd: i32, n: *node, d: i32) void = {
if (n == nil) {
ind(fd, d);
os.write(fd, "()\n".ptr, 3u64);
return;
};
// N_TPARAM wraps an N_TTUPLE.list element so exprtype can return
// shared element-type nodes without corrupting their .next chain.
// Cstage has no AST-level wrapper, so unwrap here to keep the 990
// -a byte-diff with cstage's astprint.
if (n.kind == nkind.N_TPARAM) {
pr(fd, n.lhs, d);
return;
};
ind(fd, d);
putc1(fd, 40u8); // '('
let nm: str = nkname(n.kind);
os.write(fd, nm.ptr, nm.len: u64);
if (n.kind == nkind.N_INTLIT) {
putc1(fd, 32u8);
let s: str = strconv.u64tos(n.uval, strconv.base.DEC);
os.write(fd, s.ptr, s.len: u64);
} else { if (n.kind == nkind.N_RUNELIT) {
putc1(fd, 32u8);
let s: str = strconv.u64tos(n.uval, strconv.base.DEC);
os.write(fd, s.ptr, s.len: u64);
} else { if (
n.kind == nkind.N_STRLIT ||
n.kind == nkind.N_IDENT ||
n.kind == nkind.N_USE ||
n.kind == nkind.N_DOT ||
n.kind == nkind.N_DEF ||
n.kind == nkind.N_TYPEDECL ||
n.kind == nkind.N_FNDECL ||
n.kind == nkind.N_PARAM ||
n.kind == nkind.N_LET ||
n.kind == nkind.N_TNAME ||
n.kind == nkind.N_TFIELD ||
n.kind == nkind.N_TENUMMEMBER ||
n.kind == nkind.N_FIELD ||
n.kind == nkind.N_ATTR
) {
// Match C ast.c: print the str field whenever it's non-nil,
// even if its length is zero (e.g. an empty STRLIT prints
// `(str ""`).
let s: str = n.str;
if (s.ptr != nil) {
putc1(fd, 32u8);
putq(fd, s);
};
} else { if (
n.kind == nkind.N_BIN ||
n.kind == nkind.N_UN ||
n.kind == nkind.N_ASSIGN
) {
putc1(fd, 32u8);
let on: str = tokname(n.op);
os.write(fd, on.ptr, on.len: u64);
};};};};
if (n.kind == nkind.N_FNDECL) {
if (n.exported != 0) { os.write(fd, " export".ptr, 7u64); };
};
if (n.kind == nkind.N_DEF) {
if (n.exported != 0) { os.write(fd, " export".ptr, 7u64); };
};
if (n.kind == nkind.N_TYPEDECL) {
if (n.exported != 0) { os.write(fd, " export".ptr, 7u64); };
};
putc1(fd, 10u8); // '\n'
if (n.attr != nil) {
ind(fd, d + 1);
os.write(fd, "(@\n".ptr, 3u64);
let m: *node = n.attr;
for (m != nil) {
pr(fd, m, d + 2);
m = m.next;
};
ind(fd, d + 1);
os.write(fd, ")\n".ptr, 2u64);
};
if (n.lhs != nil) { pr(fd, n.lhs, d + 1); };
if (n.rhs != nil) { pr(fd, n.rhs, d + 1); };
if (n.cond != nil) { pr(fd, n.cond, d + 1); };
if (n.body != nil) { pr(fd, n.body, d + 1); };
if (n.els != nil) { pr(fd, n.els, d + 1); };
if (n.list != nil) {
ind(fd, d + 1);
os.write(fd, "(list\n".ptr, 6u64);
let m: *node = n.list;
for (m != nil) {
pr(fd, m, d + 2);
m = m.next;
};
ind(fd, d + 1);
os.write(fd, ")\n".ptr, 2u64);
};
ind(fd, d);
os.write(fd, ")\n".ptr, 2u64);
};
export fn astprint(fd: i32, n: *node) void = {
pr(fd, n, 0);
};