// 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); };