From 8ffe6dbee6cb831dbdcaf09d29192dc3f04528db Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Mon, 11 May 2026 16:48:58 +0900 Subject: [PATCH] ww: split parse.ww into parse/{parse,expr,stmt,decl}.ww submodule --- Makefile | 7 +- lib/ww/parse.ww | 986 --------------------------- lib/ww/parse/decl.ww | 153 +++++ lib/ww/parse/expr.ww | 323 +++++++++ lib/ww/parse/parse.ww | 317 +++++++++ lib/ww/parse/stmt.ww | 198 ++++++ selfhost/cmd/w6c/main.combined.ww | 537 ++++++++------- selfhost/cmd/wwdump/main.combined.ww | 537 ++++++++------- test/wcc/990_selfhost.c | 8 +- test/wcc/993_ww_ww.c | 54 +- test/wcc/995_self_rebuild.c | 8 +- 11 files changed, 1573 insertions(+), 1555 deletions(-) delete mode 100644 lib/ww/parse.ww create mode 100644 lib/ww/parse/decl.ww create mode 100644 lib/ww/parse/expr.ww create mode 100644 lib/ww/parse/parse.ww create mode 100644 lib/ww/parse/stmt.ww diff --git a/Makefile b/Makefile index 62649a2d..6da86e5a 100644 --- a/Makefile +++ b/Makefile @@ -107,7 +107,7 @@ $(OBJ)/w6l/%.o: cmd/w6l/%.c cmd/w6l/l.h | $(OBJ)/w6l # to avoid colliding with the C-side wwdump in $(BIN). $(BIN)/wwdump_ww: selfhost/cmd/wwdump/main.ww \ lib/ww/lex/lex.ww lib/ww/lex/tok.ww lib/ww/ast.ww \ - lib/ww/parse.ww lib/ww/typ.ww lib/ww/sym.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/mem.ww selfhost/cmd/wcc/check.ww \ selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \ selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \ @@ -118,6 +118,7 @@ $(BIN)/wwdump_ww: selfhost/cmd/wwdump/main.ww \ cd $(BIN) && ./ww build \ -I $$PWD/../../lib/ww \ -I $$PWD/../../lib/ww/lex \ + -I $$PWD/../../lib/ww/parse \ -I $$PWD/../../selfhost/cmd/wcc \ $$PWD/../../selfhost/cmd/wwdump/main.ww mv $(BIN)/main $@ @@ -127,7 +128,7 @@ $(BIN)/wwdump_ww: selfhost/cmd/wwdump/main.ww \ # lib/ww/; cgen + check live in selfhost/cmd/wcc/. $(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.ww lib/ww/typ.ww lib/ww/sym.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/mem.ww selfhost/cmd/wcc/check.ww \ selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \ selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \ @@ -138,6 +139,7 @@ $(BIN)/w6c_ww: selfhost/cmd/w6c/main.ww \ cd $(BIN) && ./ww build \ -I $$PWD/../../lib/ww \ -I $$PWD/../../lib/ww/lex \ + -I $$PWD/../../lib/ww/parse \ -I $$PWD/../../selfhost/cmd/wcc \ $$PWD/../../selfhost/cmd/w6c/main.ww mv $(BIN)/main $@ @@ -400,6 +402,7 @@ nocc: @cd $(NOCC_BIN) && ./ww build \ -I $(CURDIR)/lib/ww \ -I $(CURDIR)/lib/ww/lex \ + -I $(CURDIR)/lib/ww/parse \ -I $(CURDIR)/selfhost/cmd/wcc \ -I $(CURDIR)/lib $(CURDIR)/selfhost/cmd/w6c/main.ww && mv main w6c_ww1 @cd $(NOCC_BIN) && ./ww build -I $(CURDIR)/selfhost/cmd/w6a \ diff --git a/lib/ww/parse.ww b/lib/ww/parse.ww deleted file mode 100644 index 98bd2df0..00000000 --- a/lib/ww/parse.ww +++ /dev/null @@ -1,986 +0,0 @@ -// lib/ww/parse.ww — port of cmd/wcc/parse.c. -// -// Status: GROWING stub. Currently handles top-level `use IDENT;`, -// `def NAME: TYPE = LIT;`, `type NAME = TYPE;`, and `fn NAME(params) -// RET;` (header-only — bodies are recovered past). Unknown decls are -// chewed token-by-token until the next ';' so the diff probe can -// still anchor on partial fixtures. -// -// The full port is multi-session work — parse.c is 1,183 lines of -// hand-rolled recursive descent + Pratt expression parser. Each -// surface form lands here gradually so the AST diff in 990_selfhost -// grows toward whole-language coverage one increment at a time. -// -// Calling-convention shim: w6c can't yet pass a sub-struct field -// (e.g. p.cur.line where p.cur is a `tok` of size 76). The parser -// stores the current token as flat primitive fields rather than a -// nested `tok` struct; `refill` copies a freshly lexed token in. - -use os; -use mem; -use tok; - -type parser = struct { - l: *lex, - a: *arena, - errs: i32, - // nocast: while inside `[...]` we treat ':' as the slice - // separator, not the cast operator. Mirrors parse.c's flag. - nocast: i32, - curkind: i32, - curfile: str, - curline: i32, - curcol: i32, - curtext: str, - curuval: u64, -}; - -fn refill(p: *parser) void = { - let t: tok; - lexnext(p.l, &t); - p.curkind = t.kind; - p.curfile = t.file; - p.curline = t.line; - p.curcol = t.col; - p.curtext = t.text; - p.curuval = t.uval; -}; - -export fn parserinit(p: *parser, a: *arena, l: *lex) void = { - p.l = l; - p.a = a; - p.errs = 0; - p.nocast = 0; - refill(p); -}; - -fn advance(p: *parser) void = { refill(p); }; - -fn accepttok(p: *parser, k: i32) bool = { - if (p.curkind == k) { advance(p); return true; }; - return false; -}; - -fn errmsg(p: *parser, msg: str) void = { - let pre: str = "parse: "; - os.write(2, pre.ptr, pre.len: u64); - os.write(2, msg.ptr, msg.len: u64); - os.write(2, "\n".ptr, 1u64); - p.errs += 1; -}; - -fn expecttok(p: *parser, k: i32, what: str) bool = { - if (p.curkind == k) { advance(p); return true; }; - errmsg(p, what); - return false; -}; - -// expectident — consume the current TK_IDENT and return its text. -// Returns the empty str on error (and advances to make progress). -fn expectident(p: *parser, into: *str) bool = { - if (p.curkind != TK_IDENT) { - errmsg(p, "expected identifier"); - advance(p); - return false; - }; - *into = p.curtext; - advance(p); - return true; -}; - -// ---- type expressions ------------------------------------------------ -// -// Currently: TNAME (single ident, no dotted path yet) and TPTR (`*T`). -// Other forms (slice, array, struct, fn, chan, tuple, tagged) will -// land in subsequent commits. - -fn parsetype(p: *parser) *node = { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - - if (p.curkind == TK_STAR) { - advance(p); - let n: *node = newnode(p.a, N_TPTR, pf, pl, pc); - n.lhs = parsetype(p); - return n; - }; - - if (p.curkind == TK_LBRACK) { - advance(p); - if (p.curkind == TK_RBRACK) { - advance(p); - let n: *node = newnode(p.a, N_TSLICE, pf, pl, pc); - n.lhs = parsetype(p); - return n; - }; - let n: *node = newnode(p.a, N_TARRAY, pf, pl, pc); - n.rhs = parseexpr(p); - expecttok(p, TK_RBRACK, "expected ']' in array type"); - n.lhs = parsetype(p); - return n; - }; - - if (p.curkind == TK_STRUCT) { - advance(p); - expecttok(p, TK_LBRACE, "expected '{' after struct"); - let n: *node = newnode(p.a, N_TSTRUCT, pf, pl, pc); - let fhead: *node = nil; - let ftail: *node = nil; - for (p.curkind != TK_RBRACE) { - if (p.curkind == TK_EOF) { break; }; - let fpf: str = p.curfile; - let fpl: i32 = p.curline; - let fpc: i32 = p.curcol; - let f: *node = newnode(p.a, N_TFIELD, fpf, fpl, fpc); - let fid: str; - expectident(p, &fid); - f.str = fid; - expecttok(p, TK_COLON, "expected ':' in field"); - f.lhs = parsetype(p); - if (fhead == nil) { fhead = f; ftail = f; } - else { ftail.next = f; ftail = f; }; - if (!accepttok(p, TK_COMMA)) { break; }; - }; - expecttok(p, TK_RBRACE, "expected '}' after struct fields"); - n.list = fhead; - return n; - }; - - if (p.curkind == TK_IDENT) { - let n: *node = newnode(p.a, N_TNAME, pf, pl, pc); - n.str = p.curtext; - advance(p); - // Dotted path collapse (pkg.Type) deferred — fixtures don't - // need it yet. - return n; - }; - - if (p.curkind == TK_LPAREN) { - // (T) or (T, T, ...) or (T | T | ...) - advance(p); - let first: *node = parsetype(p); - if (accepttok(p, TK_PIPE)) { - let n: *node = newnode(p.a, N_TTAGGED, pf, pl, pc); - let head: *node = first; - let tail: *node = first; - for (true) { - let e: *node = parsetype(p); - tail.next = e; - tail = e; - if (!accepttok(p, TK_PIPE)) { break; }; - }; - expecttok(p, TK_RPAREN, "expected ')' in tagged-union type"); - n.list = head; - return n; - }; - if (!accepttok(p, TK_COMMA)) { - expecttok(p, TK_RPAREN, "expected ')' after parenthesised type"); - return first; - }; - let n: *node = newnode(p.a, N_TTUPLE, pf, pl, pc); - let head: *node = first; - let tail: *node = first; - for (true) { - let e: *node = parsetype(p); - tail.next = e; - tail = e; - if (!accepttok(p, TK_COMMA)) { break; }; - if (p.curkind == TK_RPAREN) { break; }; - }; - expecttok(p, TK_RPAREN, "expected ')' in tuple type"); - n.list = head; - return n; - }; - - if (p.curkind == TK_FN) { - advance(p); - expecttok(p, TK_LPAREN, "expected '(' after fn in type"); - let n: *node = newnode(p.a, N_TFN, pf, pl, pc); - // Anonymous-or-named params: parseparams handles named only; - // for fn-type expressions the C parser allows IDENT-less - // (anonymous) params. Stub: only named params for now. - n.list = parseparams(p); - expecttok(p, TK_RPAREN, "expected ')' after fn type params"); - n.lhs = parsetype(p); - return n; - }; - - errmsg(p, "expected type"); - advance(p); - return newnode(p.a, N_TNAME, pf, pl, pc); -}; - -// ---- expressions (Pratt) --------------------------------------------- -// -// Forwards: parseexpr → parsebin → parseunary → parsepostfix(parseprimary). -// Tuple literals, match expressions, struct literals, slice [lo:hi], -// and the ?/! try operators are not yet wired — they'll arrive as the -// AST diff fixture grows to need them. - -fn bprec(k: i32) i32 = { - if (k == TK_OR) { return 1; }; - if (k == TK_AND) { return 2; }; - if (k == TK_EQ) { return 3; }; - if (k == TK_NEQ) { return 3; }; - if (k == TK_LT) { return 4; }; - if (k == TK_LE) { return 4; }; - if (k == TK_GT) { return 4; }; - if (k == TK_GE) { return 4; }; - if (k == TK_PIPE) { return 5; }; - if (k == TK_CARET) { return 6; }; - if (k == TK_AMP) { return 7; }; - if (k == TK_LSHIFT) { return 8; }; - if (k == TK_RSHIFT) { return 8; }; - if (k == TK_PLUS) { return 9; }; - if (k == TK_MINUS) { return 9; }; - if (k == TK_STAR) { return 10; }; - if (k == TK_SLASH) { return 10; }; - if (k == TK_PERCENT) { return 10; }; - return 0; -}; - -fn isassignop(k: i32) bool = { - if (k == TK_ASSIGN) { return true; }; - if (k == TK_PLUSEQ) { return true; }; - if (k == TK_MINUSEQ) { return true; }; - if (k == TK_STAREQ) { return true; }; - if (k == TK_SLASHEQ) { return true; }; - if (k == TK_PERCENTEQ) { return true; }; - if (k == TK_AMPEQ) { return true; }; - if (k == TK_PIPEEQ) { return true; }; - if (k == TK_CARETEQ) { return true; }; - if (k == TK_LSHIFTEQ) { return true; }; - if (k == TK_RSHIFTEQ) { return true; }; - return false; -}; - -// Forward references between parseunary/parseexpr/parsebin/parsepostfix -// are resolved by the two-pass checker — no body-less prototypes needed. - -fn parseprimary(p: *parser) *node = { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - - if (p.curkind == TK_INT) { - let n: *node = newnode(p.a, N_INTLIT, pf, pl, pc); - n.uval = p.curuval; - n.str = p.curtext; - advance(p); - return n; - }; - if (p.curkind == TK_STR) { - let n: *node = newnode(p.a, N_STRLIT, pf, pl, pc); - n.str = p.curtext; - advance(p); - return n; - }; - if (p.curkind == TK_RUNE) { - let n: *node = newnode(p.a, N_RUNELIT, pf, pl, pc); - n.uval = p.curuval; - advance(p); - return n; - }; - if (p.curkind == TK_TRUE) { - advance(p); - return newnode(p.a, N_TRUE, pf, pl, pc); - }; - if (p.curkind == TK_FALSE) { - advance(p); - return newnode(p.a, N_FALSE, pf, pl, pc); - }; - if (p.curkind == TK_NIL) { - advance(p); - return newnode(p.a, N_NIL, pf, pl, pc); - }; - if (p.curkind == TK_LPAREN) { - advance(p); - let e: *node = parseexpr(p); - // Tuple literal: (a, b, ...) - if (accepttok(p, TK_COMMA)) { - let t: *node = newnode(p.a, N_TUPLE, pf, pl, pc); - t.list = e; - let tail: *node = e; - for (true) { - if (p.curkind == TK_RPAREN) { break; }; - let en: *node = parseexpr(p); - tail.next = en; - tail = en; - if (!accepttok(p, TK_COMMA)) { break; }; - }; - expecttok(p, TK_RPAREN, "expected ')' in tuple"); - return t; - }; - expecttok(p, TK_RPAREN, "expected ')'"); - return e; - }; - if (p.curkind == TK_IDENT) { - let n: *node = newnode(p.a, N_IDENT, pf, pl, pc); - n.str = p.curtext; - advance(p); - // `IDENT {` — struct literal. Disambiguate: only consume as a - // struct lit when we're not in a context where '{' starts a - // block (e.g. `if (cond) {`). The parser is called from - // expressions, never directly from cond contexts that need a - // block; in stmt parsing, the for/if drivers consume their - // own paren/cond, so this is safe. - if (p.curkind == TK_LBRACE) { - advance(p); - let s: *node = newnode(p.a, N_STRUCTLIT, pf, pl, pc); - s.lhs = n; - let head: *node = nil; - let tail: *node = nil; - for (p.curkind != TK_RBRACE) { - if (p.curkind == TK_EOF) { break; }; - let fpf: str = p.curfile; - let fpl: i32 = p.curline; - let fpc: i32 = p.curcol; - let id: str; - expectident(p, &id); - expecttok(p, TK_ASSIGN, "expected '=' in struct lit field"); - let v: *node = parseexpr(p); - let f: *node = newnode(p.a, N_FIELD, fpf, fpl, fpc); - f.str = id; - f.lhs = v; - if (head == nil) { head = f; tail = f; } - else { tail.next = f; tail = f; }; - if (!accepttok(p, TK_COMMA)) { break; }; - }; - expecttok(p, TK_RBRACE, "expected '}' after struct literal"); - s.list = head; - return s; - }; - return n; - }; - if (p.curkind == TK_MATCH) { - // match (e) { case let v: T => stmt; case T => stmt; case => stmt; }; - advance(p); - expecttok(p, TK_LPAREN, "expected '(' after match"); - let m: *node = newnode(p.a, N_MATCH, pf, pl, pc); - m.lhs = parseexpr(p); - expecttok(p, TK_RPAREN, "expected ')' after match scrutinee"); - expecttok(p, TK_LBRACE, "expected '{' to open match body"); - let head: *node = nil; - let tail: *node = nil; - for (p.curkind == TK_CASE) { - let cf: str = p.curfile; - let cl: i32 = p.curline; - let cc: i32 = p.curcol; - advance(p); // past `case` - let mc: *node = newnode(p.a, N_MCASE, cf, cl, cc); - if (p.curkind == TK_LET) { - advance(p); - let id: str; - expectident(p, &id); - mc.str = id; - expecttok(p, TK_COLON, "expected ':' after match binding"); - mc.lhs = parsetype(p); - } else { if (p.curkind != TK_FATARROW) { - mc.lhs = parsetype(p); - };}; - expecttok(p, TK_FATARROW, "expected '=>' in match arm"); - mc.body = parsestmt(p); - if (head == nil) { head = mc; tail = mc; } - else { tail.next = mc; tail = mc; }; - }; - expecttok(p, TK_RBRACE, "expected '}' after match body"); - m.list = head; - return m; - }; - errmsg(p, "expected expression"); - advance(p); - return newnode(p.a, N_NONE, pf, pl, pc); -}; - -fn parsearglist(p: *parser, closekind: i32, headout: **node) void = { - *headout = nil; - if (p.curkind == closekind) { return; }; - let head: *node = nil; - let tail: *node = nil; - for (true) { - let e: *node = parseexpr(p); - if (head == nil) { head = e; tail = e; } - else { tail.next = e; tail = e; }; - if (!accepttok(p, TK_COMMA)) { break; }; - if (p.curkind == closekind) { break; }; - }; - *headout = head; -}; - -fn parsepostfix(p: *parser, lhs: *node) *node = { - let cur: *node = lhs; - for (true) { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - if (p.curkind == TK_LPAREN) { - advance(p); - let n: *node = newnode(p.a, N_CALL, pf, pl, pc); - n.lhs = cur; - let arghead: *node = nil; - parsearglist(p, TK_RPAREN, &arghead); - n.list = arghead; - expecttok(p, TK_RPAREN, "expected ')' after args"); - cur = n; - continue; - }; - if (p.curkind == TK_LBRACK) { - advance(p); - // `[ : hi ]` — slice with implicit lo = 0. - if (p.curkind == TK_COLON) { - advance(p); - let n: *node = newnode(p.a, N_SLICE, pf, pl, pc); - n.lhs = cur; - if (p.curkind != TK_RBRACK) { - n.cond = parseexpr(p); - }; - expecttok(p, TK_RBRACK, "expected ']' in slice"); - cur = n; - continue; - }; - // Suppress cast inside `[...]` so ':' parses as slice - // separator rather than the postfix cast operator. - let prev: i32 = p.nocast; - p.nocast = 1; - let e: *node = parseexpr(p); - p.nocast = prev; - if (p.curkind == TK_COLON) { - advance(p); - let n: *node = newnode(p.a, N_SLICE, pf, pl, pc); - n.lhs = cur; - n.rhs = e; - if (p.curkind != TK_RBRACK) { - n.cond = parseexpr(p); - }; - expecttok(p, TK_RBRACK, "expected ']' in slice"); - cur = n; - continue; - }; - let n: *node = newnode(p.a, N_INDEX, pf, pl, pc); - n.lhs = cur; - n.rhs = e; - expecttok(p, TK_RBRACK, "expected ']' after index"); - cur = n; - continue; - }; - if (p.curkind == TK_DOT) { - advance(p); - let n: *node = newnode(p.a, N_DOT, pf, pl, pc); - n.lhs = cur; - let id: str; - expectident(p, &id); - n.str = id; - cur = n; - continue; - }; - if (p.curkind == TK_COLON) { - if (p.nocast != 0) { - return cur; - }; - advance(p); - let n: *node = newnode(p.a, N_CAST, pf, pl, pc); - n.lhs = cur; - n.rhs = parsetype(p); - cur = n; - continue; - }; - break; - }; - return cur; -}; - -fn parseunary(p: *parser) *node = { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - let k: i32 = p.curkind; - if (k == TK_MINUS) { - advance(p); - let n: *node = newnode(p.a, N_UN, pf, pl, pc); - n.op = TK_MINUS; n.lhs = parseunary(p); - return n; - }; - if (k == TK_PLUS) { - advance(p); - let n: *node = newnode(p.a, N_UN, pf, pl, pc); - n.op = TK_PLUS; n.lhs = parseunary(p); - return n; - }; - if (k == TK_NOT) { - advance(p); - let n: *node = newnode(p.a, N_UN, pf, pl, pc); - n.op = TK_NOT; n.lhs = parseunary(p); - return n; - }; - if (k == TK_TILDE) { - advance(p); - let n: *node = newnode(p.a, N_UN, pf, pl, pc); - n.op = TK_TILDE; n.lhs = parseunary(p); - return n; - }; - if (k == TK_STAR) { - advance(p); - let n: *node = newnode(p.a, N_UN, pf, pl, pc); - n.op = TK_STAR; n.lhs = parseunary(p); - return n; - }; - if (k == TK_AMP) { - advance(p); - let n: *node = newnode(p.a, N_UN, pf, pl, pc); - n.op = TK_AMP; n.lhs = parseunary(p); - return n; - }; - return parsepostfix(p, parseprimary(p)); -}; - -fn parsebin(p: *parser, lhs: *node, minp: i32) *node = { - let cur: *node = lhs; - for (true) { - let op: i32 = p.curkind; - let pr: i32 = bprec(op); - if (pr == 0) { return cur; }; - if (pr < minp) { return cur; }; - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - advance(p); - let rhs: *node = parseunary(p); - for (true) { - let np: i32 = bprec(p.curkind); - if (np <= pr) { break; }; - rhs = parsebin(p, rhs, np); - }; - let n: *node = newnode(p.a, N_BIN, pf, pl, pc); - n.op = op; n.lhs = cur; n.rhs = rhs; - cur = n; - }; - return cur; -}; - -fn parseexpr(p: *parser) *node = { - let e: *node = parsebin(p, parseunary(p), 1); - if (isassignop(p.curkind)) { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - let op: i32 = p.curkind; - advance(p); - let n: *node = newnode(p.a, N_ASSIGN, pf, pl, pc); - n.op = op; - n.lhs = e; - n.rhs = parseexpr(p); // right-associative - return n; - }; - return e; -}; - -// ---- statements ------------------------------------------------------ -// -// Subset wired today: block, let, return, if (no else-if chain), for -// (single-cond C-style), expr-stmt, defer, break, continue. Switch -// and match arms are not yet wired; tuple-let / multi-let neither. - -fn parseletlocal(p: *parser) *node = { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - advance(p); // past `let` - let n: *node = newnode(p.a, N_LET, pf, pl, pc); - let id: str; - expectident(p, &id); - n.str = id; - if (accepttok(p, TK_COLON)) { - n.lhs = parsetype(p); - }; - if (accepttok(p, TK_ASSIGN)) { - n.rhs = parseexpr(p); - }; - expecttok(p, TK_SEMI, "expected ';' after let"); - return n; -}; - -fn parseblock(p: *parser) *node = { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - expecttok(p, TK_LBRACE, "expected '{' to open block"); - let blk: *node = newnode(p.a, N_BLOCK, pf, pl, pc); - let head: *node = nil; - let tail: *node = nil; - for (p.curkind != TK_RBRACE) { - if (p.curkind == TK_EOF) { break; }; - let s: *node = parsestmt(p); - if (s != nil) { - if (head == nil) { head = s; tail = s; } - else { tail.next = s; tail = s; }; - }; - }; - expecttok(p, TK_RBRACE, "expected '}' to close block"); - blk.list = head; - return blk; -}; - -fn parseif(p: *parser) *node = { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - advance(p); // past `if` - expecttok(p, TK_LPAREN, "expected '(' after if"); - let n: *node = newnode(p.a, N_IF, pf, pl, pc); - n.cond = parseexpr(p); - expecttok(p, TK_RPAREN, "expected ')' after if condition"); - n.body = parseblock(p); - if (accepttok(p, TK_ELSE)) { - if (p.curkind == TK_IF) { - n.els = parseif(p); - } else { - n.els = parseblock(p); - }; - }; - return n; -}; - -fn parsefor(p: *parser) *node = { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - advance(p); // past `for` - expecttok(p, TK_LPAREN, "expected '(' after for"); - let n: *node = newnode(p.a, N_FOR, pf, pl, pc); - // Three forms (matching C parser): - // for (cond) — only cond - // for (init; cond; post) — full - // for (true) — infinite (cond is N_TRUE) - // Distinguish by counting ';'. Look at first chunk: if it's a - // `let` stmt that's the init. Otherwise, parse expr; if next is - // ';' it was cond. If we see two ';' total after init, post is - // next. Simpler: peek for `let` to decide init form. - if (p.curkind == TK_LET) { - n.lhs = parseletlocal(p); // init (consumes its own ';') - n.cond = parseexpr(p); - expecttok(p, TK_SEMI, "expected ';' after for cond"); - n.rhs = parseexpr(p); - } else { - // Parse one expr. If next is ';', it's a 3-clause without init. - let first: *node = parseexpr(p); - if (accepttok(p, TK_SEMI)) { - // cond ; post - n.cond = first; - n.rhs = parseexpr(p); - } else { - // just (cond) - n.cond = first; - }; - }; - expecttok(p, TK_RPAREN, "expected ')' after for"); - n.body = parseblock(p); - return n; -}; - -fn parsestmt(p: *parser) *node = { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - - // `static` is allowed on local lets per Hare; we accept and skip - // it (it doesn't change the AST shape). - if (p.curkind == TK_STATIC) { advance(p); }; - - if (p.curkind == TK_LBRACE) { - let b: *node = parseblock(p); - expecttok(p, TK_SEMI, "expected ';' after block"); - return b; - }; - if (p.curkind == TK_LET) { return parseletlocal(p); }; - if (p.curkind == TK_IF) { - let n: *node = parseif(p); - expecttok(p, TK_SEMI, "expected ';' after if"); - return n; - }; - if (p.curkind == TK_FOR) { - let n: *node = parsefor(p); - expecttok(p, TK_SEMI, "expected ';' after for"); - return n; - }; - if (p.curkind == TK_RETURN) { - advance(p); - let n: *node = newnode(p.a, N_RETURN, pf, pl, pc); - if (p.curkind != TK_SEMI) { - let first: *node = parseexpr(p); - // Hare-style multi-value: `return a, b;` becomes a - // tuple expression so codegen sees one rvalue. - if (p.curkind == TK_COMMA) { - let t: *node = newnode(p.a, N_TUPLE, pf, pl, pc); - t.list = first; - let tail: *node = first; - for (accepttok(p, TK_COMMA)) { - let e: *node = parseexpr(p); - tail.next = e; - tail = e; - }; - n.lhs = t; - } else { - n.lhs = first; - }; - }; - expecttok(p, TK_SEMI, "expected ';' after return"); - return n; - }; - if (p.curkind == TK_DEFER) { - advance(p); - let n: *node = newnode(p.a, N_DEFER, pf, pl, pc); - n.lhs = parseexpr(p); - expecttok(p, TK_SEMI, "expected ';' after defer"); - return n; - }; - if (p.curkind == TK_BREAK) { - advance(p); - expecttok(p, TK_SEMI, "expected ';' after break"); - return newnode(p.a, N_BREAK, pf, pl, pc); - }; - if (p.curkind == TK_CONTINUE) { - advance(p); - expecttok(p, TK_SEMI, "expected ';' after continue"); - return newnode(p.a, N_CONTINUE, pf, pl, pc); - }; - // expression statement, or tuple-destructure multi-assign: - // a, b = expr; - // Mirrors cmd/wcc/parse.c:1015-1031. We parse the first lvalue - // with parseexpr (matches the C side); subsequent lvalues go - // through parsebin(parseunary, 1) so the `=` stays for us to - // consume — parseexpr would absorb it. - let e: *node = parseexpr(p); - if (p.curkind == TK_COMMA) { - let m: *node = newnode(p.a, N_MASSIGN, pf, pl, pc); - let head: *node = e; - let tail: *node = e; - for (p.curkind == TK_COMMA) { - advance(p); - let lv: *node = parsebin(p, parseunary(p), 1); - tail.next = lv; - tail = lv; - }; - expecttok(p, TK_ASSIGN, "expected '=' after multi-assign lvalues"); - m.rhs = parseexpr(p); - m.list = head; - expecttok(p, TK_SEMI, "expected ';' after multi-assign"); - return m; - }; - let n: *node = newnode(p.a, N_EXPRSTMT, pf, pl, pc); - n.lhs = e; - expecttok(p, TK_SEMI, "expected ';' after expression statement"); - return n; -}; - -// ---- top-level decl parsers ------------------------------------------ - -fn parseuse(p: *parser) *node = { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - advance(p); // past `use` - let n: *node = newnode(p.a, N_USE, pf, pl, pc); - let id: str; - expectident(p, &id); - n.str = id; - expecttok(p, TK_SEMI, "expected ';' after use"); - return n; -}; - -fn parsedef(p: *parser, exported: i32) *node = { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - advance(p); // past `def` - let n: *node = newnode(p.a, N_DEF, pf, pl, pc); - n.module = p.l.module; - let id: str; - expectident(p, &id); - n.str = id; - expecttok(p, TK_COLON, "expected ':' in def"); - n.lhs = parsetype(p); - expecttok(p, TK_ASSIGN, "expected '=' in def"); - n.rhs = parseexpr(p); - expecttok(p, TK_SEMI, "expected ';' after def"); - n.exported = exported; - return n; -}; - -fn parselet(p: *parser, exported: i32) *node = { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - advance(p); // past `let` - let n: *node = newnode(p.a, N_LET, pf, pl, pc); - n.module = p.l.module; - let id: str; - expectident(p, &id); - n.str = id; - if (accepttok(p, TK_COLON)) { - n.lhs = parsetype(p); - }; - if (accepttok(p, TK_ASSIGN)) { - n.rhs = parseexpr(p); - }; - expecttok(p, TK_SEMI, "expected ';' after let"); - n.exported = exported; - return n; -}; - -fn parseattrs(p: *parser) *node = { - let head: *node = nil; - let tail: *node = nil; - for (p.curkind == TK_AT) { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - advance(p); - let a: *node = newnode(p.a, N_ATTR, pf, pl, pc); - let id: str; - expectident(p, &id); - a.str = id; - expecttok(p, TK_LPAREN, "expected '(' after attribute name"); - let arghead: *node = nil; - parsearglist(p, TK_RPAREN, &arghead); - a.list = arghead; - expecttok(p, TK_RPAREN, "expected ')' after attribute args"); - if (head == nil) { head = a; tail = a; } - else { tail.next = a; tail = a; }; - }; - return head; -}; - -fn parseparams(p: *parser) *node = { - if (p.curkind == TK_RPAREN) { return nil; }; - let head: *node = nil; - let tail: *node = nil; - for (true) { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - let n: *node = newnode(p.a, N_PARAM, pf, pl, pc); - // Param form: IDENT ':' type. Anonymous-type-only params (used - // in fn type expressions) aren't yet wired here. - let id: str; - expectident(p, &id); - n.str = id; - expecttok(p, TK_COLON, "expected ':' in parameter"); - n.lhs = parsetype(p); - if (head == nil) { head = n; tail = n; } - else { tail.next = n; tail = n; }; - if (!accepttok(p, TK_COMMA)) { break; }; - if (p.curkind == TK_RPAREN) { break; }; - }; - return head; -}; - -fn parsefn(p: *parser, exported: i32, attrs: *node) *node = { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - advance(p); // past `fn` - let n: *node = newnode(p.a, N_FNDECL, pf, pl, pc); - n.module = p.l.module; - let id: str; - expectident(p, &id); - n.str = id; - expecttok(p, TK_LPAREN, "expected '(' after fn name"); - n.list = parseparams(p); - expecttok(p, TK_RPAREN, "expected ')' after params"); - if (p.curkind != TK_ASSIGN) { - if (p.curkind != TK_SEMI) { - n.lhs = parsetype(p); - }; - }; - if (accepttok(p, TK_ASSIGN)) { - n.body = parseblock(p); - expecttok(p, TK_SEMI, "expected ';' after fn body"); - } else { - // Body-less fn: FFI declaration (`fn name(args) ret;`). - expecttok(p, TK_SEMI, "expected ';' after fn header"); - }; - n.exported = exported; - n.attr = attrs; - return n; -}; - -fn parsetypedecl(p: *parser, exported: i32) *node = { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - advance(p); // past `type` - let n: *node = newnode(p.a, N_TYPEDECL, pf, pl, pc); - n.module = p.l.module; - let id: str; - expectident(p, &id); - n.str = id; - expecttok(p, TK_ASSIGN, "expected '=' in type decl"); - n.lhs = parsetype(p); - expecttok(p, TK_SEMI, "expected ';' after type decl"); - n.exported = exported; - return n; -}; - -// ---- file-level loop ------------------------------------------------- - -export fn parsefile(p: *parser) *node = { - let f: *node = newnode(p.a, N_FILE, p.curfile, p.curline, p.curcol); - let head: *node = nil; - let tail: *node = nil; - - for (p.curkind != TK_EOF) { - let attrs: *node = parseattrs(p); - let exported: i32 = 0; - if (p.curkind == TK_EXPORT) { exported = 1; advance(p); }; - - let d: *node = nil; - if (p.curkind == TK_USE) { - d = parseuse(p); - } else { if (p.curkind == TK_DEF) { - d = parsedef(p, exported); - } else { if (p.curkind == TK_TYPE) { - d = parsetypedecl(p, exported); - } else { if (p.curkind == TK_LET) { - d = parselet(p, exported); - } else { if (p.curkind == TK_FN) { - d = parsefn(p, exported, attrs); - } else { - // Recovery: chew tokens until next ';' or EOF, balancing - // '{' '}' pairs so internal ';'s in unfamiliar forms don't - // derail us. - for (p.curkind != TK_SEMI) { - if (p.curkind == TK_EOF) { break; }; - if (p.curkind == TK_LBRACE) { - let depth: i32 = 0; - for (true) { - if (p.curkind == TK_EOF) { break; }; - if (p.curkind == TK_LBRACE) { depth += 1; advance(p); continue; }; - if (p.curkind == TK_RBRACE) { - depth -= 1; - advance(p); - if (depth == 0) { break; }; - continue; - }; - advance(p); - }; - continue; - }; - advance(p); - }; - if (p.curkind == TK_SEMI) { advance(p); }; - };};};};}; - - if (d != nil) { - if (head == nil) { - head = d; - tail = d; - } else { - tail.next = d; - tail = d; - }; - }; - }; - f.list = head; - return f; -}; diff --git a/lib/ww/parse/decl.ww b/lib/ww/parse/decl.ww new file mode 100644 index 00000000..352964c2 --- /dev/null +++ b/lib/ww/parse/decl.ww @@ -0,0 +1,153 @@ +// lib/ww/parse/decl.ww — declaration parsing, split out of parse.ww. + +use os; +use mem; +use tok; + +fn parseuse(p: *parser) *node = { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + advance(p); // past `use` + let n: *node = newnode(p.a, N_USE, pf, pl, pc); + let id: str; + expectident(p, &id); + n.str = id; + expecttok(p, TK_SEMI, "expected ';' after use"); + return n; +}; + +fn parsedef(p: *parser, exported: i32) *node = { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + advance(p); // past `def` + let n: *node = newnode(p.a, N_DEF, pf, pl, pc); + n.module = p.l.module; + let id: str; + expectident(p, &id); + n.str = id; + expecttok(p, TK_COLON, "expected ':' in def"); + n.lhs = parsetype(p); + expecttok(p, TK_ASSIGN, "expected '=' in def"); + n.rhs = parseexpr(p); + expecttok(p, TK_SEMI, "expected ';' after def"); + n.exported = exported; + return n; +}; + +fn parselet(p: *parser, exported: i32) *node = { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + advance(p); // past `let` + let n: *node = newnode(p.a, N_LET, pf, pl, pc); + n.module = p.l.module; + let id: str; + expectident(p, &id); + n.str = id; + if (accepttok(p, TK_COLON)) { + n.lhs = parsetype(p); + }; + if (accepttok(p, TK_ASSIGN)) { + n.rhs = parseexpr(p); + }; + expecttok(p, TK_SEMI, "expected ';' after let"); + n.exported = exported; + return n; +}; + +fn parseattrs(p: *parser) *node = { + let head: *node = nil; + let tail: *node = nil; + for (p.curkind == TK_AT) { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + advance(p); + let a: *node = newnode(p.a, N_ATTR, pf, pl, pc); + let id: str; + expectident(p, &id); + a.str = id; + expecttok(p, TK_LPAREN, "expected '(' after attribute name"); + let arghead: *node = nil; + parsearglist(p, TK_RPAREN, &arghead); + a.list = arghead; + expecttok(p, TK_RPAREN, "expected ')' after attribute args"); + if (head == nil) { head = a; tail = a; } + else { tail.next = a; tail = a; }; + }; + return head; +}; + +fn parseparams(p: *parser) *node = { + if (p.curkind == TK_RPAREN) { return nil; }; + let head: *node = nil; + let tail: *node = nil; + for (true) { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + let n: *node = newnode(p.a, N_PARAM, pf, pl, pc); + // Param form: IDENT ':' type. Anonymous-type-only params (used + // in fn type expressions) aren't yet wired here. + let id: str; + expectident(p, &id); + n.str = id; + expecttok(p, TK_COLON, "expected ':' in parameter"); + n.lhs = parsetype(p); + if (head == nil) { head = n; tail = n; } + else { tail.next = n; tail = n; }; + if (!accepttok(p, TK_COMMA)) { break; }; + if (p.curkind == TK_RPAREN) { break; }; + }; + return head; +}; + +fn parsefn(p: *parser, exported: i32, attrs: *node) *node = { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + advance(p); // past `fn` + let n: *node = newnode(p.a, N_FNDECL, pf, pl, pc); + n.module = p.l.module; + let id: str; + expectident(p, &id); + n.str = id; + expecttok(p, TK_LPAREN, "expected '(' after fn name"); + n.list = parseparams(p); + expecttok(p, TK_RPAREN, "expected ')' after params"); + if (p.curkind != TK_ASSIGN) { + if (p.curkind != TK_SEMI) { + n.lhs = parsetype(p); + }; + }; + if (accepttok(p, TK_ASSIGN)) { + n.body = parseblock(p); + expecttok(p, TK_SEMI, "expected ';' after fn body"); + } else { + // Body-less fn: FFI declaration (`fn name(args) ret;`). + expecttok(p, TK_SEMI, "expected ';' after fn header"); + }; + n.exported = exported; + n.attr = attrs; + return n; +}; + +fn parsetypedecl(p: *parser, exported: i32) *node = { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + advance(p); // past `type` + let n: *node = newnode(p.a, N_TYPEDECL, pf, pl, pc); + n.module = p.l.module; + let id: str; + expectident(p, &id); + n.str = id; + expecttok(p, TK_ASSIGN, "expected '=' in type decl"); + n.lhs = parsetype(p); + expecttok(p, TK_SEMI, "expected ';' after type decl"); + n.exported = exported; + return n; +}; + diff --git a/lib/ww/parse/expr.ww b/lib/ww/parse/expr.ww new file mode 100644 index 00000000..e88c222c --- /dev/null +++ b/lib/ww/parse/expr.ww @@ -0,0 +1,323 @@ +// lib/ww/parse/expr.ww — expression parsing, split out of parse.ww. + +use os; +use mem; +use tok; + +fn parseprimary(p: *parser) *node = { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + + if (p.curkind == TK_INT) { + let n: *node = newnode(p.a, N_INTLIT, pf, pl, pc); + n.uval = p.curuval; + n.str = p.curtext; + advance(p); + return n; + }; + if (p.curkind == TK_STR) { + let n: *node = newnode(p.a, N_STRLIT, pf, pl, pc); + n.str = p.curtext; + advance(p); + return n; + }; + if (p.curkind == TK_RUNE) { + let n: *node = newnode(p.a, N_RUNELIT, pf, pl, pc); + n.uval = p.curuval; + advance(p); + return n; + }; + if (p.curkind == TK_TRUE) { + advance(p); + return newnode(p.a, N_TRUE, pf, pl, pc); + }; + if (p.curkind == TK_FALSE) { + advance(p); + return newnode(p.a, N_FALSE, pf, pl, pc); + }; + if (p.curkind == TK_NIL) { + advance(p); + return newnode(p.a, N_NIL, pf, pl, pc); + }; + if (p.curkind == TK_LPAREN) { + advance(p); + let e: *node = parseexpr(p); + // Tuple literal: (a, b, ...) + if (accepttok(p, TK_COMMA)) { + let t: *node = newnode(p.a, N_TUPLE, pf, pl, pc); + t.list = e; + let tail: *node = e; + for (true) { + if (p.curkind == TK_RPAREN) { break; }; + let en: *node = parseexpr(p); + tail.next = en; + tail = en; + if (!accepttok(p, TK_COMMA)) { break; }; + }; + expecttok(p, TK_RPAREN, "expected ')' in tuple"); + return t; + }; + expecttok(p, TK_RPAREN, "expected ')'"); + return e; + }; + if (p.curkind == TK_IDENT) { + let n: *node = newnode(p.a, N_IDENT, pf, pl, pc); + n.str = p.curtext; + advance(p); + // `IDENT {` — struct literal. Disambiguate: only consume as a + // struct lit when we're not in a context where '{' starts a + // block (e.g. `if (cond) {`). The parser is called from + // expressions, never directly from cond contexts that need a + // block; in stmt parsing, the for/if drivers consume their + // own paren/cond, so this is safe. + if (p.curkind == TK_LBRACE) { + advance(p); + let s: *node = newnode(p.a, N_STRUCTLIT, pf, pl, pc); + s.lhs = n; + let head: *node = nil; + let tail: *node = nil; + for (p.curkind != TK_RBRACE) { + if (p.curkind == TK_EOF) { break; }; + let fpf: str = p.curfile; + let fpl: i32 = p.curline; + let fpc: i32 = p.curcol; + let id: str; + expectident(p, &id); + expecttok(p, TK_ASSIGN, "expected '=' in struct lit field"); + let v: *node = parseexpr(p); + let f: *node = newnode(p.a, N_FIELD, fpf, fpl, fpc); + f.str = id; + f.lhs = v; + if (head == nil) { head = f; tail = f; } + else { tail.next = f; tail = f; }; + if (!accepttok(p, TK_COMMA)) { break; }; + }; + expecttok(p, TK_RBRACE, "expected '}' after struct literal"); + s.list = head; + return s; + }; + return n; + }; + if (p.curkind == TK_MATCH) { + // match (e) { case let v: T => stmt; case T => stmt; case => stmt; }; + advance(p); + expecttok(p, TK_LPAREN, "expected '(' after match"); + let m: *node = newnode(p.a, N_MATCH, pf, pl, pc); + m.lhs = parseexpr(p); + expecttok(p, TK_RPAREN, "expected ')' after match scrutinee"); + expecttok(p, TK_LBRACE, "expected '{' to open match body"); + let head: *node = nil; + let tail: *node = nil; + for (p.curkind == TK_CASE) { + let cf: str = p.curfile; + let cl: i32 = p.curline; + let cc: i32 = p.curcol; + advance(p); // past `case` + let mc: *node = newnode(p.a, N_MCASE, cf, cl, cc); + if (p.curkind == TK_LET) { + advance(p); + let id: str; + expectident(p, &id); + mc.str = id; + expecttok(p, TK_COLON, "expected ':' after match binding"); + mc.lhs = parsetype(p); + } else { if (p.curkind != TK_FATARROW) { + mc.lhs = parsetype(p); + };}; + expecttok(p, TK_FATARROW, "expected '=>' in match arm"); + mc.body = parsestmt(p); + if (head == nil) { head = mc; tail = mc; } + else { tail.next = mc; tail = mc; }; + }; + expecttok(p, TK_RBRACE, "expected '}' after match body"); + m.list = head; + return m; + }; + errmsg(p, "expected expression"); + advance(p); + return newnode(p.a, N_NONE, pf, pl, pc); +}; + +fn parsearglist(p: *parser, closekind: i32, headout: **node) void = { + *headout = nil; + if (p.curkind == closekind) { return; }; + let head: *node = nil; + let tail: *node = nil; + for (true) { + let e: *node = parseexpr(p); + if (head == nil) { head = e; tail = e; } + else { tail.next = e; tail = e; }; + if (!accepttok(p, TK_COMMA)) { break; }; + if (p.curkind == closekind) { break; }; + }; + *headout = head; +}; + +fn parsepostfix(p: *parser, lhs: *node) *node = { + let cur: *node = lhs; + for (true) { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + if (p.curkind == TK_LPAREN) { + advance(p); + let n: *node = newnode(p.a, N_CALL, pf, pl, pc); + n.lhs = cur; + let arghead: *node = nil; + parsearglist(p, TK_RPAREN, &arghead); + n.list = arghead; + expecttok(p, TK_RPAREN, "expected ')' after args"); + cur = n; + continue; + }; + if (p.curkind == TK_LBRACK) { + advance(p); + // `[ : hi ]` — slice with implicit lo = 0. + if (p.curkind == TK_COLON) { + advance(p); + let n: *node = newnode(p.a, N_SLICE, pf, pl, pc); + n.lhs = cur; + if (p.curkind != TK_RBRACK) { + n.cond = parseexpr(p); + }; + expecttok(p, TK_RBRACK, "expected ']' in slice"); + cur = n; + continue; + }; + // Suppress cast inside `[...]` so ':' parses as slice + // separator rather than the postfix cast operator. + let prev: i32 = p.nocast; + p.nocast = 1; + let e: *node = parseexpr(p); + p.nocast = prev; + if (p.curkind == TK_COLON) { + advance(p); + let n: *node = newnode(p.a, N_SLICE, pf, pl, pc); + n.lhs = cur; + n.rhs = e; + if (p.curkind != TK_RBRACK) { + n.cond = parseexpr(p); + }; + expecttok(p, TK_RBRACK, "expected ']' in slice"); + cur = n; + continue; + }; + let n: *node = newnode(p.a, N_INDEX, pf, pl, pc); + n.lhs = cur; + n.rhs = e; + expecttok(p, TK_RBRACK, "expected ']' after index"); + cur = n; + continue; + }; + if (p.curkind == TK_DOT) { + advance(p); + let n: *node = newnode(p.a, N_DOT, pf, pl, pc); + n.lhs = cur; + let id: str; + expectident(p, &id); + n.str = id; + cur = n; + continue; + }; + if (p.curkind == TK_COLON) { + if (p.nocast != 0) { + return cur; + }; + advance(p); + let n: *node = newnode(p.a, N_CAST, pf, pl, pc); + n.lhs = cur; + n.rhs = parsetype(p); + cur = n; + continue; + }; + break; + }; + return cur; +}; + +fn parseunary(p: *parser) *node = { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + let k: i32 = p.curkind; + if (k == TK_MINUS) { + advance(p); + let n: *node = newnode(p.a, N_UN, pf, pl, pc); + n.op = TK_MINUS; n.lhs = parseunary(p); + return n; + }; + if (k == TK_PLUS) { + advance(p); + let n: *node = newnode(p.a, N_UN, pf, pl, pc); + n.op = TK_PLUS; n.lhs = parseunary(p); + return n; + }; + if (k == TK_NOT) { + advance(p); + let n: *node = newnode(p.a, N_UN, pf, pl, pc); + n.op = TK_NOT; n.lhs = parseunary(p); + return n; + }; + if (k == TK_TILDE) { + advance(p); + let n: *node = newnode(p.a, N_UN, pf, pl, pc); + n.op = TK_TILDE; n.lhs = parseunary(p); + return n; + }; + if (k == TK_STAR) { + advance(p); + let n: *node = newnode(p.a, N_UN, pf, pl, pc); + n.op = TK_STAR; n.lhs = parseunary(p); + return n; + }; + if (k == TK_AMP) { + advance(p); + let n: *node = newnode(p.a, N_UN, pf, pl, pc); + n.op = TK_AMP; n.lhs = parseunary(p); + return n; + }; + return parsepostfix(p, parseprimary(p)); +}; + +fn parsebin(p: *parser, lhs: *node, minp: i32) *node = { + let cur: *node = lhs; + for (true) { + let op: i32 = p.curkind; + let pr: i32 = bprec(op); + if (pr == 0) { return cur; }; + if (pr < minp) { return cur; }; + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + advance(p); + let rhs: *node = parseunary(p); + for (true) { + let np: i32 = bprec(p.curkind); + if (np <= pr) { break; }; + rhs = parsebin(p, rhs, np); + }; + let n: *node = newnode(p.a, N_BIN, pf, pl, pc); + n.op = op; n.lhs = cur; n.rhs = rhs; + cur = n; + }; + return cur; +}; + +fn parseexpr(p: *parser) *node = { + let e: *node = parsebin(p, parseunary(p), 1); + if (isassignop(p.curkind)) { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + let op: i32 = p.curkind; + advance(p); + let n: *node = newnode(p.a, N_ASSIGN, pf, pl, pc); + n.op = op; + n.lhs = e; + n.rhs = parseexpr(p); // right-associative + return n; + }; + return e; +}; + diff --git a/lib/ww/parse/parse.ww b/lib/ww/parse/parse.ww new file mode 100644 index 00000000..a30761e2 --- /dev/null +++ b/lib/ww/parse/parse.ww @@ -0,0 +1,317 @@ +// lib/ww/parse/parse.ww — port of cmd/wcc/parse.c (entry + plumbing). +// +// Split into Hare-style submodule: parse.ww (here) holds the parser +// struct, lexer plumbing, parsetype, parsefile (entry). Expression, +// statement, and declaration parsers live in expr.ww, stmt.ww, +// decl.ww respectively — all in the same `parse` module. +// +// Calling-convention shim: w6c can't yet pass a sub-struct field +// (e.g. p.cur.line where p.cur is a `tok` of size 76). The parser +// stores the current token as flat primitive fields rather than a +// nested `tok` struct; `refill` copies a freshly lexed token in. + +use os; +use mem; +use tok; +use expr; +use stmt; +use decl; + +type parser = struct { + l: *lex, + a: *arena, + errs: i32, + // nocast: while inside `[...]` we treat ':' as the slice + // separator, not the cast operator. Mirrors parse.c's flag. + nocast: i32, + curkind: i32, + curfile: str, + curline: i32, + curcol: i32, + curtext: str, + curuval: u64, +}; + +fn refill(p: *parser) void = { + let t: tok; + lexnext(p.l, &t); + p.curkind = t.kind; + p.curfile = t.file; + p.curline = t.line; + p.curcol = t.col; + p.curtext = t.text; + p.curuval = t.uval; +}; + +export fn parserinit(p: *parser, a: *arena, l: *lex) void = { + p.l = l; + p.a = a; + p.errs = 0; + p.nocast = 0; + refill(p); +}; + +fn advance(p: *parser) void = { refill(p); }; + +fn accepttok(p: *parser, k: i32) bool = { + if (p.curkind == k) { advance(p); return true; }; + return false; +}; + +fn errmsg(p: *parser, msg: str) void = { + let pre: str = "parse: "; + os.write(2, pre.ptr, pre.len: u64); + os.write(2, msg.ptr, msg.len: u64); + os.write(2, "\n".ptr, 1u64); + p.errs += 1; +}; + +fn expecttok(p: *parser, k: i32, what: str) bool = { + if (p.curkind == k) { advance(p); return true; }; + errmsg(p, what); + return false; +}; + +// expectident — consume the current TK_IDENT and return its text. +// Returns the empty str on error (and advances to make progress). +fn expectident(p: *parser, into: *str) bool = { + if (p.curkind != TK_IDENT) { + errmsg(p, "expected identifier"); + advance(p); + return false; + }; + *into = p.curtext; + advance(p); + return true; +}; + +// ---- type expressions ------------------------------------------------ +// +// Currently: TNAME (single ident, no dotted path yet) and TPTR (`*T`). +// Other forms (slice, array, struct, fn, chan, tuple, tagged) will +// land in subsequent commits. + +fn parsetype(p: *parser) *node = { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + + if (p.curkind == TK_STAR) { + advance(p); + let n: *node = newnode(p.a, N_TPTR, pf, pl, pc); + n.lhs = parsetype(p); + return n; + }; + + if (p.curkind == TK_LBRACK) { + advance(p); + if (p.curkind == TK_RBRACK) { + advance(p); + let n: *node = newnode(p.a, N_TSLICE, pf, pl, pc); + n.lhs = parsetype(p); + return n; + }; + let n: *node = newnode(p.a, N_TARRAY, pf, pl, pc); + n.rhs = parseexpr(p); + expecttok(p, TK_RBRACK, "expected ']' in array type"); + n.lhs = parsetype(p); + return n; + }; + + if (p.curkind == TK_STRUCT) { + advance(p); + expecttok(p, TK_LBRACE, "expected '{' after struct"); + let n: *node = newnode(p.a, N_TSTRUCT, pf, pl, pc); + let fhead: *node = nil; + let ftail: *node = nil; + for (p.curkind != TK_RBRACE) { + if (p.curkind == TK_EOF) { break; }; + let fpf: str = p.curfile; + let fpl: i32 = p.curline; + let fpc: i32 = p.curcol; + let f: *node = newnode(p.a, N_TFIELD, fpf, fpl, fpc); + let fid: str; + expectident(p, &fid); + f.str = fid; + expecttok(p, TK_COLON, "expected ':' in field"); + f.lhs = parsetype(p); + if (fhead == nil) { fhead = f; ftail = f; } + else { ftail.next = f; ftail = f; }; + if (!accepttok(p, TK_COMMA)) { break; }; + }; + expecttok(p, TK_RBRACE, "expected '}' after struct fields"); + n.list = fhead; + return n; + }; + + if (p.curkind == TK_IDENT) { + let n: *node = newnode(p.a, N_TNAME, pf, pl, pc); + n.str = p.curtext; + advance(p); + // Dotted path collapse (pkg.Type) deferred — fixtures don't + // need it yet. + return n; + }; + + if (p.curkind == TK_LPAREN) { + // (T) or (T, T, ...) or (T | T | ...) + advance(p); + let first: *node = parsetype(p); + if (accepttok(p, TK_PIPE)) { + let n: *node = newnode(p.a, N_TTAGGED, pf, pl, pc); + let head: *node = first; + let tail: *node = first; + for (true) { + let e: *node = parsetype(p); + tail.next = e; + tail = e; + if (!accepttok(p, TK_PIPE)) { break; }; + }; + expecttok(p, TK_RPAREN, "expected ')' in tagged-union type"); + n.list = head; + return n; + }; + if (!accepttok(p, TK_COMMA)) { + expecttok(p, TK_RPAREN, "expected ')' after parenthesised type"); + return first; + }; + let n: *node = newnode(p.a, N_TTUPLE, pf, pl, pc); + let head: *node = first; + let tail: *node = first; + for (true) { + let e: *node = parsetype(p); + tail.next = e; + tail = e; + if (!accepttok(p, TK_COMMA)) { break; }; + if (p.curkind == TK_RPAREN) { break; }; + }; + expecttok(p, TK_RPAREN, "expected ')' in tuple type"); + n.list = head; + return n; + }; + + if (p.curkind == TK_FN) { + advance(p); + expecttok(p, TK_LPAREN, "expected '(' after fn in type"); + let n: *node = newnode(p.a, N_TFN, pf, pl, pc); + // Anonymous-or-named params: parseparams handles named only; + // for fn-type expressions the C parser allows IDENT-less + // (anonymous) params. Stub: only named params for now. + n.list = parseparams(p); + expecttok(p, TK_RPAREN, "expected ')' after fn type params"); + n.lhs = parsetype(p); + return n; + }; + + errmsg(p, "expected type"); + advance(p); + return newnode(p.a, N_TNAME, pf, pl, pc); +}; + +// ---- expressions (Pratt) --------------------------------------------- +// +// Forwards: parseexpr → parsebin → parseunary → parsepostfix(parseprimary). +// Tuple literals, match expressions, struct literals, slice [lo:hi], +// and the ?/! try operators are not yet wired — they'll arrive as the +// AST diff fixture grows to need them. + +fn bprec(k: i32) i32 = { + if (k == TK_OR) { return 1; }; + if (k == TK_AND) { return 2; }; + if (k == TK_EQ) { return 3; }; + if (k == TK_NEQ) { return 3; }; + if (k == TK_LT) { return 4; }; + if (k == TK_LE) { return 4; }; + if (k == TK_GT) { return 4; }; + if (k == TK_GE) { return 4; }; + if (k == TK_PIPE) { return 5; }; + if (k == TK_CARET) { return 6; }; + if (k == TK_AMP) { return 7; }; + if (k == TK_LSHIFT) { return 8; }; + if (k == TK_RSHIFT) { return 8; }; + if (k == TK_PLUS) { return 9; }; + if (k == TK_MINUS) { return 9; }; + if (k == TK_STAR) { return 10; }; + if (k == TK_SLASH) { return 10; }; + if (k == TK_PERCENT) { return 10; }; + return 0; +}; + +fn isassignop(k: i32) bool = { + if (k == TK_ASSIGN) { return true; }; + if (k == TK_PLUSEQ) { return true; }; + if (k == TK_MINUSEQ) { return true; }; + if (k == TK_STAREQ) { return true; }; + if (k == TK_SLASHEQ) { return true; }; + if (k == TK_PERCENTEQ) { return true; }; + if (k == TK_AMPEQ) { return true; }; + if (k == TK_PIPEEQ) { return true; }; + if (k == TK_CARETEQ) { return true; }; + if (k == TK_LSHIFTEQ) { return true; }; + if (k == TK_RSHIFTEQ) { return true; }; + return false; +}; + +// Forward references between parseunary/parseexpr/parsebin/parsepostfix +// are resolved by the two-pass checker — no body-less prototypes needed. + +export fn parsefile(p: *parser) *node = { + let f: *node = newnode(p.a, N_FILE, p.curfile, p.curline, p.curcol); + let head: *node = nil; + let tail: *node = nil; + + for (p.curkind != TK_EOF) { + let attrs: *node = parseattrs(p); + let exported: i32 = 0; + if (p.curkind == TK_EXPORT) { exported = 1; advance(p); }; + + let d: *node = nil; + if (p.curkind == TK_USE) { + d = parseuse(p); + } else { if (p.curkind == TK_DEF) { + d = parsedef(p, exported); + } else { if (p.curkind == TK_TYPE) { + d = parsetypedecl(p, exported); + } else { if (p.curkind == TK_LET) { + d = parselet(p, exported); + } else { if (p.curkind == TK_FN) { + d = parsefn(p, exported, attrs); + } else { + // Recovery: chew tokens until next ';' or EOF, balancing + // '{' '}' pairs so internal ';'s in unfamiliar forms don't + // derail us. + for (p.curkind != TK_SEMI) { + if (p.curkind == TK_EOF) { break; }; + if (p.curkind == TK_LBRACE) { + let depth: i32 = 0; + for (true) { + if (p.curkind == TK_EOF) { break; }; + if (p.curkind == TK_LBRACE) { depth += 1; advance(p); continue; }; + if (p.curkind == TK_RBRACE) { + depth -= 1; + advance(p); + if (depth == 0) { break; }; + continue; + }; + advance(p); + }; + continue; + }; + advance(p); + }; + if (p.curkind == TK_SEMI) { advance(p); }; + };};};};}; + + if (d != nil) { + if (head == nil) { + head = d; + tail = d; + } else { + tail.next = d; + tail = d; + }; + }; + }; + f.list = head; + return f; +}; diff --git a/lib/ww/parse/stmt.ww b/lib/ww/parse/stmt.ww new file mode 100644 index 00000000..2aa98f56 --- /dev/null +++ b/lib/ww/parse/stmt.ww @@ -0,0 +1,198 @@ +// lib/ww/parse/stmt.ww — statement parsing, split out of parse.ww. + +use os; +use mem; +use tok; + +fn parseletlocal(p: *parser) *node = { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + advance(p); // past `let` + let n: *node = newnode(p.a, N_LET, pf, pl, pc); + let id: str; + expectident(p, &id); + n.str = id; + if (accepttok(p, TK_COLON)) { + n.lhs = parsetype(p); + }; + if (accepttok(p, TK_ASSIGN)) { + n.rhs = parseexpr(p); + }; + expecttok(p, TK_SEMI, "expected ';' after let"); + return n; +}; + +fn parseblock(p: *parser) *node = { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + expecttok(p, TK_LBRACE, "expected '{' to open block"); + let blk: *node = newnode(p.a, N_BLOCK, pf, pl, pc); + let head: *node = nil; + let tail: *node = nil; + for (p.curkind != TK_RBRACE) { + if (p.curkind == TK_EOF) { break; }; + let s: *node = parsestmt(p); + if (s != nil) { + if (head == nil) { head = s; tail = s; } + else { tail.next = s; tail = s; }; + }; + }; + expecttok(p, TK_RBRACE, "expected '}' to close block"); + blk.list = head; + return blk; +}; + +fn parseif(p: *parser) *node = { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + advance(p); // past `if` + expecttok(p, TK_LPAREN, "expected '(' after if"); + let n: *node = newnode(p.a, N_IF, pf, pl, pc); + n.cond = parseexpr(p); + expecttok(p, TK_RPAREN, "expected ')' after if condition"); + n.body = parseblock(p); + if (accepttok(p, TK_ELSE)) { + if (p.curkind == TK_IF) { + n.els = parseif(p); + } else { + n.els = parseblock(p); + }; + }; + return n; +}; + +fn parsefor(p: *parser) *node = { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + advance(p); // past `for` + expecttok(p, TK_LPAREN, "expected '(' after for"); + let n: *node = newnode(p.a, N_FOR, pf, pl, pc); + // Three forms (matching C parser): + // for (cond) — only cond + // for (init; cond; post) — full + // for (true) — infinite (cond is N_TRUE) + // Distinguish by counting ';'. Look at first chunk: if it's a + // `let` stmt that's the init. Otherwise, parse expr; if next is + // ';' it was cond. If we see two ';' total after init, post is + // next. Simpler: peek for `let` to decide init form. + if (p.curkind == TK_LET) { + n.lhs = parseletlocal(p); // init (consumes its own ';') + n.cond = parseexpr(p); + expecttok(p, TK_SEMI, "expected ';' after for cond"); + n.rhs = parseexpr(p); + } else { + // Parse one expr. If next is ';', it's a 3-clause without init. + let first: *node = parseexpr(p); + if (accepttok(p, TK_SEMI)) { + // cond ; post + n.cond = first; + n.rhs = parseexpr(p); + } else { + // just (cond) + n.cond = first; + }; + }; + expecttok(p, TK_RPAREN, "expected ')' after for"); + n.body = parseblock(p); + return n; +}; + +fn parsestmt(p: *parser) *node = { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + + // `static` is allowed on local lets per Hare; we accept and skip + // it (it doesn't change the AST shape). + if (p.curkind == TK_STATIC) { advance(p); }; + + if (p.curkind == TK_LBRACE) { + let b: *node = parseblock(p); + expecttok(p, TK_SEMI, "expected ';' after block"); + return b; + }; + if (p.curkind == TK_LET) { return parseletlocal(p); }; + if (p.curkind == TK_IF) { + let n: *node = parseif(p); + expecttok(p, TK_SEMI, "expected ';' after if"); + return n; + }; + if (p.curkind == TK_FOR) { + let n: *node = parsefor(p); + expecttok(p, TK_SEMI, "expected ';' after for"); + return n; + }; + if (p.curkind == TK_RETURN) { + advance(p); + let n: *node = newnode(p.a, N_RETURN, pf, pl, pc); + if (p.curkind != TK_SEMI) { + let first: *node = parseexpr(p); + // Hare-style multi-value: `return a, b;` becomes a + // tuple expression so codegen sees one rvalue. + if (p.curkind == TK_COMMA) { + let t: *node = newnode(p.a, N_TUPLE, pf, pl, pc); + t.list = first; + let tail: *node = first; + for (accepttok(p, TK_COMMA)) { + let e: *node = parseexpr(p); + tail.next = e; + tail = e; + }; + n.lhs = t; + } else { + n.lhs = first; + }; + }; + expecttok(p, TK_SEMI, "expected ';' after return"); + return n; + }; + if (p.curkind == TK_DEFER) { + advance(p); + let n: *node = newnode(p.a, N_DEFER, pf, pl, pc); + n.lhs = parseexpr(p); + expecttok(p, TK_SEMI, "expected ';' after defer"); + return n; + }; + if (p.curkind == TK_BREAK) { + advance(p); + expecttok(p, TK_SEMI, "expected ';' after break"); + return newnode(p.a, N_BREAK, pf, pl, pc); + }; + if (p.curkind == TK_CONTINUE) { + advance(p); + expecttok(p, TK_SEMI, "expected ';' after continue"); + return newnode(p.a, N_CONTINUE, pf, pl, pc); + }; + // expression statement, or tuple-destructure multi-assign: + // a, b = expr; + // Mirrors cmd/wcc/parse.c:1015-1031. We parse the first lvalue + // with parseexpr (matches the C side); subsequent lvalues go + // through parsebin(parseunary, 1) so the `=` stays for us to + // consume — parseexpr would absorb it. + let e: *node = parseexpr(p); + if (p.curkind == TK_COMMA) { + let m: *node = newnode(p.a, N_MASSIGN, pf, pl, pc); + let head: *node = e; + let tail: *node = e; + for (p.curkind == TK_COMMA) { + advance(p); + let lv: *node = parsebin(p, parseunary(p), 1); + tail.next = lv; + tail = lv; + }; + expecttok(p, TK_ASSIGN, "expected '=' after multi-assign lvalues"); + m.rhs = parseexpr(p); + m.list = head; + expecttok(p, TK_SEMI, "expected ';' after multi-assign"); + return m; + }; + let n: *node = newnode(p.a, N_EXPRSTMT, pf, pl, pc); + n.lhs = e; + expecttok(p, TK_SEMI, "expected ';' after expression statement"); + return n; +}; + diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index bb1c8272..ae5460ee 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -1954,267 +1954,13 @@ export fn astprint(fd: i32, n: *node) void = { pr(fd, n, 0); }; -// MODULE: ww -// lib/ww/parse.ww — port of cmd/wcc/parse.c. -// -// Status: GROWING stub. Currently handles top-level `use IDENT;`, -// `def NAME: TYPE = LIT;`, `type NAME = TYPE;`, and `fn NAME(params) -// RET;` (header-only — bodies are recovered past). Unknown decls are -// chewed token-by-token until the next ';' so the diff probe can -// still anchor on partial fixtures. -// -// The full port is multi-session work — parse.c is 1,183 lines of -// hand-rolled recursive descent + Pratt expression parser. Each -// surface form lands here gradually so the AST diff in 990_selfhost -// grows toward whole-language coverage one increment at a time. -// -// Calling-convention shim: w6c can't yet pass a sub-struct field -// (e.g. p.cur.line where p.cur is a `tok` of size 76). The parser -// stores the current token as flat primitive fields rather than a -// nested `tok` struct; `refill` copies a freshly lexed token in. +// MODULE: parse +// lib/ww/parse/expr.ww — expression parsing, split out of parse.ww. use os; use mem; use tok; -type parser = struct { - l: *lex, - a: *arena, - errs: i32, - // nocast: while inside `[...]` we treat ':' as the slice - // separator, not the cast operator. Mirrors parse.c's flag. - nocast: i32, - curkind: i32, - curfile: str, - curline: i32, - curcol: i32, - curtext: str, - curuval: u64, -}; - -fn refill(p: *parser) void = { - let t: tok; - lexnext(p.l, &t); - p.curkind = t.kind; - p.curfile = t.file; - p.curline = t.line; - p.curcol = t.col; - p.curtext = t.text; - p.curuval = t.uval; -}; - -export fn parserinit(p: *parser, a: *arena, l: *lex) void = { - p.l = l; - p.a = a; - p.errs = 0; - p.nocast = 0; - refill(p); -}; - -fn advance(p: *parser) void = { refill(p); }; - -fn accepttok(p: *parser, k: i32) bool = { - if (p.curkind == k) { advance(p); return true; }; - return false; -}; - -fn errmsg(p: *parser, msg: str) void = { - let pre: str = "parse: "; - os.write(2, pre.ptr, pre.len: u64); - os.write(2, msg.ptr, msg.len: u64); - os.write(2, "\n".ptr, 1u64); - p.errs += 1; -}; - -fn expecttok(p: *parser, k: i32, what: str) bool = { - if (p.curkind == k) { advance(p); return true; }; - errmsg(p, what); - return false; -}; - -// expectident — consume the current TK_IDENT and return its text. -// Returns the empty str on error (and advances to make progress). -fn expectident(p: *parser, into: *str) bool = { - if (p.curkind != TK_IDENT) { - errmsg(p, "expected identifier"); - advance(p); - return false; - }; - *into = p.curtext; - advance(p); - return true; -}; - -// ---- type expressions ------------------------------------------------ -// -// Currently: TNAME (single ident, no dotted path yet) and TPTR (`*T`). -// Other forms (slice, array, struct, fn, chan, tuple, tagged) will -// land in subsequent commits. - -fn parsetype(p: *parser) *node = { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - - if (p.curkind == TK_STAR) { - advance(p); - let n: *node = newnode(p.a, N_TPTR, pf, pl, pc); - n.lhs = parsetype(p); - return n; - }; - - if (p.curkind == TK_LBRACK) { - advance(p); - if (p.curkind == TK_RBRACK) { - advance(p); - let n: *node = newnode(p.a, N_TSLICE, pf, pl, pc); - n.lhs = parsetype(p); - return n; - }; - let n: *node = newnode(p.a, N_TARRAY, pf, pl, pc); - n.rhs = parseexpr(p); - expecttok(p, TK_RBRACK, "expected ']' in array type"); - n.lhs = parsetype(p); - return n; - }; - - if (p.curkind == TK_STRUCT) { - advance(p); - expecttok(p, TK_LBRACE, "expected '{' after struct"); - let n: *node = newnode(p.a, N_TSTRUCT, pf, pl, pc); - let fhead: *node = nil; - let ftail: *node = nil; - for (p.curkind != TK_RBRACE) { - if (p.curkind == TK_EOF) { break; }; - let fpf: str = p.curfile; - let fpl: i32 = p.curline; - let fpc: i32 = p.curcol; - let f: *node = newnode(p.a, N_TFIELD, fpf, fpl, fpc); - let fid: str; - expectident(p, &fid); - f.str = fid; - expecttok(p, TK_COLON, "expected ':' in field"); - f.lhs = parsetype(p); - if (fhead == nil) { fhead = f; ftail = f; } - else { ftail.next = f; ftail = f; }; - if (!accepttok(p, TK_COMMA)) { break; }; - }; - expecttok(p, TK_RBRACE, "expected '}' after struct fields"); - n.list = fhead; - return n; - }; - - if (p.curkind == TK_IDENT) { - let n: *node = newnode(p.a, N_TNAME, pf, pl, pc); - n.str = p.curtext; - advance(p); - // Dotted path collapse (pkg.Type) deferred — fixtures don't - // need it yet. - return n; - }; - - if (p.curkind == TK_LPAREN) { - // (T) or (T, T, ...) or (T | T | ...) - advance(p); - let first: *node = parsetype(p); - if (accepttok(p, TK_PIPE)) { - let n: *node = newnode(p.a, N_TTAGGED, pf, pl, pc); - let head: *node = first; - let tail: *node = first; - for (true) { - let e: *node = parsetype(p); - tail.next = e; - tail = e; - if (!accepttok(p, TK_PIPE)) { break; }; - }; - expecttok(p, TK_RPAREN, "expected ')' in tagged-union type"); - n.list = head; - return n; - }; - if (!accepttok(p, TK_COMMA)) { - expecttok(p, TK_RPAREN, "expected ')' after parenthesised type"); - return first; - }; - let n: *node = newnode(p.a, N_TTUPLE, pf, pl, pc); - let head: *node = first; - let tail: *node = first; - for (true) { - let e: *node = parsetype(p); - tail.next = e; - tail = e; - if (!accepttok(p, TK_COMMA)) { break; }; - if (p.curkind == TK_RPAREN) { break; }; - }; - expecttok(p, TK_RPAREN, "expected ')' in tuple type"); - n.list = head; - return n; - }; - - if (p.curkind == TK_FN) { - advance(p); - expecttok(p, TK_LPAREN, "expected '(' after fn in type"); - let n: *node = newnode(p.a, N_TFN, pf, pl, pc); - // Anonymous-or-named params: parseparams handles named only; - // for fn-type expressions the C parser allows IDENT-less - // (anonymous) params. Stub: only named params for now. - n.list = parseparams(p); - expecttok(p, TK_RPAREN, "expected ')' after fn type params"); - n.lhs = parsetype(p); - return n; - }; - - errmsg(p, "expected type"); - advance(p); - return newnode(p.a, N_TNAME, pf, pl, pc); -}; - -// ---- expressions (Pratt) --------------------------------------------- -// -// Forwards: parseexpr → parsebin → parseunary → parsepostfix(parseprimary). -// Tuple literals, match expressions, struct literals, slice [lo:hi], -// and the ?/! try operators are not yet wired — they'll arrive as the -// AST diff fixture grows to need them. - -fn bprec(k: i32) i32 = { - if (k == TK_OR) { return 1; }; - if (k == TK_AND) { return 2; }; - if (k == TK_EQ) { return 3; }; - if (k == TK_NEQ) { return 3; }; - if (k == TK_LT) { return 4; }; - if (k == TK_LE) { return 4; }; - if (k == TK_GT) { return 4; }; - if (k == TK_GE) { return 4; }; - if (k == TK_PIPE) { return 5; }; - if (k == TK_CARET) { return 6; }; - if (k == TK_AMP) { return 7; }; - if (k == TK_LSHIFT) { return 8; }; - if (k == TK_RSHIFT) { return 8; }; - if (k == TK_PLUS) { return 9; }; - if (k == TK_MINUS) { return 9; }; - if (k == TK_STAR) { return 10; }; - if (k == TK_SLASH) { return 10; }; - if (k == TK_PERCENT) { return 10; }; - return 0; -}; - -fn isassignop(k: i32) bool = { - if (k == TK_ASSIGN) { return true; }; - if (k == TK_PLUSEQ) { return true; }; - if (k == TK_MINUSEQ) { return true; }; - if (k == TK_STAREQ) { return true; }; - if (k == TK_SLASHEQ) { return true; }; - if (k == TK_PERCENTEQ) { return true; }; - if (k == TK_AMPEQ) { return true; }; - if (k == TK_PIPEEQ) { return true; }; - if (k == TK_CARETEQ) { return true; }; - if (k == TK_LSHIFTEQ) { return true; }; - if (k == TK_RSHIFTEQ) { return true; }; - return false; -}; - -// Forward references between parseunary/parseexpr/parsebin/parsepostfix -// are resolved by the two-pass checker — no body-less prototypes needed. - fn parseprimary(p: *parser) *node = { let pf: str = p.curfile; let pl: i32 = p.curline; @@ -2532,11 +2278,13 @@ fn parseexpr(p: *parser) *node = { return e; }; -// ---- statements ------------------------------------------------------ -// -// Subset wired today: block, let, return, if (no else-if chain), for -// (single-cond C-style), expr-stmt, defer, break, continue. Switch -// and match arms are not yet wired; tuple-let / multi-let neither. + +// MODULE: parse +// lib/ww/parse/stmt.ww — statement parsing, split out of parse.ww. + +use os; +use mem; +use tok; fn parseletlocal(p: *parser) *node = { let pf: str = p.curfile; @@ -2730,7 +2478,13 @@ fn parsestmt(p: *parser) *node = { return n; }; -// ---- top-level decl parsers ------------------------------------------ + +// MODULE: parse +// lib/ww/parse/decl.ww — declaration parsing, split out of parse.ww. + +use os; +use mem; +use tok; fn parseuse(p: *parser) *node = { let pf: str = p.curfile; @@ -2879,7 +2633,264 @@ fn parsetypedecl(p: *parser, exported: i32) *node = { return n; }; -// ---- file-level loop ------------------------------------------------- + +// MODULE: parse +// lib/ww/parse/parse.ww — port of cmd/wcc/parse.c (entry + plumbing). +// +// Split into Hare-style submodule: parse.ww (here) holds the parser +// struct, lexer plumbing, parsetype, parsefile (entry). Expression, +// statement, and declaration parsers live in expr.ww, stmt.ww, +// decl.ww respectively — all in the same `parse` module. +// +// Calling-convention shim: w6c can't yet pass a sub-struct field +// (e.g. p.cur.line where p.cur is a `tok` of size 76). The parser +// stores the current token as flat primitive fields rather than a +// nested `tok` struct; `refill` copies a freshly lexed token in. + +use os; +use mem; +use tok; +use expr; +use stmt; +use decl; + +type parser = struct { + l: *lex, + a: *arena, + errs: i32, + // nocast: while inside `[...]` we treat ':' as the slice + // separator, not the cast operator. Mirrors parse.c's flag. + nocast: i32, + curkind: i32, + curfile: str, + curline: i32, + curcol: i32, + curtext: str, + curuval: u64, +}; + +fn refill(p: *parser) void = { + let t: tok; + lexnext(p.l, &t); + p.curkind = t.kind; + p.curfile = t.file; + p.curline = t.line; + p.curcol = t.col; + p.curtext = t.text; + p.curuval = t.uval; +}; + +export fn parserinit(p: *parser, a: *arena, l: *lex) void = { + p.l = l; + p.a = a; + p.errs = 0; + p.nocast = 0; + refill(p); +}; + +fn advance(p: *parser) void = { refill(p); }; + +fn accepttok(p: *parser, k: i32) bool = { + if (p.curkind == k) { advance(p); return true; }; + return false; +}; + +fn errmsg(p: *parser, msg: str) void = { + let pre: str = "parse: "; + os.write(2, pre.ptr, pre.len: u64); + os.write(2, msg.ptr, msg.len: u64); + os.write(2, "\n".ptr, 1u64); + p.errs += 1; +}; + +fn expecttok(p: *parser, k: i32, what: str) bool = { + if (p.curkind == k) { advance(p); return true; }; + errmsg(p, what); + return false; +}; + +// expectident — consume the current TK_IDENT and return its text. +// Returns the empty str on error (and advances to make progress). +fn expectident(p: *parser, into: *str) bool = { + if (p.curkind != TK_IDENT) { + errmsg(p, "expected identifier"); + advance(p); + return false; + }; + *into = p.curtext; + advance(p); + return true; +}; + +// ---- type expressions ------------------------------------------------ +// +// Currently: TNAME (single ident, no dotted path yet) and TPTR (`*T`). +// Other forms (slice, array, struct, fn, chan, tuple, tagged) will +// land in subsequent commits. + +fn parsetype(p: *parser) *node = { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + + if (p.curkind == TK_STAR) { + advance(p); + let n: *node = newnode(p.a, N_TPTR, pf, pl, pc); + n.lhs = parsetype(p); + return n; + }; + + if (p.curkind == TK_LBRACK) { + advance(p); + if (p.curkind == TK_RBRACK) { + advance(p); + let n: *node = newnode(p.a, N_TSLICE, pf, pl, pc); + n.lhs = parsetype(p); + return n; + }; + let n: *node = newnode(p.a, N_TARRAY, pf, pl, pc); + n.rhs = parseexpr(p); + expecttok(p, TK_RBRACK, "expected ']' in array type"); + n.lhs = parsetype(p); + return n; + }; + + if (p.curkind == TK_STRUCT) { + advance(p); + expecttok(p, TK_LBRACE, "expected '{' after struct"); + let n: *node = newnode(p.a, N_TSTRUCT, pf, pl, pc); + let fhead: *node = nil; + let ftail: *node = nil; + for (p.curkind != TK_RBRACE) { + if (p.curkind == TK_EOF) { break; }; + let fpf: str = p.curfile; + let fpl: i32 = p.curline; + let fpc: i32 = p.curcol; + let f: *node = newnode(p.a, N_TFIELD, fpf, fpl, fpc); + let fid: str; + expectident(p, &fid); + f.str = fid; + expecttok(p, TK_COLON, "expected ':' in field"); + f.lhs = parsetype(p); + if (fhead == nil) { fhead = f; ftail = f; } + else { ftail.next = f; ftail = f; }; + if (!accepttok(p, TK_COMMA)) { break; }; + }; + expecttok(p, TK_RBRACE, "expected '}' after struct fields"); + n.list = fhead; + return n; + }; + + if (p.curkind == TK_IDENT) { + let n: *node = newnode(p.a, N_TNAME, pf, pl, pc); + n.str = p.curtext; + advance(p); + // Dotted path collapse (pkg.Type) deferred — fixtures don't + // need it yet. + return n; + }; + + if (p.curkind == TK_LPAREN) { + // (T) or (T, T, ...) or (T | T | ...) + advance(p); + let first: *node = parsetype(p); + if (accepttok(p, TK_PIPE)) { + let n: *node = newnode(p.a, N_TTAGGED, pf, pl, pc); + let head: *node = first; + let tail: *node = first; + for (true) { + let e: *node = parsetype(p); + tail.next = e; + tail = e; + if (!accepttok(p, TK_PIPE)) { break; }; + }; + expecttok(p, TK_RPAREN, "expected ')' in tagged-union type"); + n.list = head; + return n; + }; + if (!accepttok(p, TK_COMMA)) { + expecttok(p, TK_RPAREN, "expected ')' after parenthesised type"); + return first; + }; + let n: *node = newnode(p.a, N_TTUPLE, pf, pl, pc); + let head: *node = first; + let tail: *node = first; + for (true) { + let e: *node = parsetype(p); + tail.next = e; + tail = e; + if (!accepttok(p, TK_COMMA)) { break; }; + if (p.curkind == TK_RPAREN) { break; }; + }; + expecttok(p, TK_RPAREN, "expected ')' in tuple type"); + n.list = head; + return n; + }; + + if (p.curkind == TK_FN) { + advance(p); + expecttok(p, TK_LPAREN, "expected '(' after fn in type"); + let n: *node = newnode(p.a, N_TFN, pf, pl, pc); + // Anonymous-or-named params: parseparams handles named only; + // for fn-type expressions the C parser allows IDENT-less + // (anonymous) params. Stub: only named params for now. + n.list = parseparams(p); + expecttok(p, TK_RPAREN, "expected ')' after fn type params"); + n.lhs = parsetype(p); + return n; + }; + + errmsg(p, "expected type"); + advance(p); + return newnode(p.a, N_TNAME, pf, pl, pc); +}; + +// ---- expressions (Pratt) --------------------------------------------- +// +// Forwards: parseexpr → parsebin → parseunary → parsepostfix(parseprimary). +// Tuple literals, match expressions, struct literals, slice [lo:hi], +// and the ?/! try operators are not yet wired — they'll arrive as the +// AST diff fixture grows to need them. + +fn bprec(k: i32) i32 = { + if (k == TK_OR) { return 1; }; + if (k == TK_AND) { return 2; }; + if (k == TK_EQ) { return 3; }; + if (k == TK_NEQ) { return 3; }; + if (k == TK_LT) { return 4; }; + if (k == TK_LE) { return 4; }; + if (k == TK_GT) { return 4; }; + if (k == TK_GE) { return 4; }; + if (k == TK_PIPE) { return 5; }; + if (k == TK_CARET) { return 6; }; + if (k == TK_AMP) { return 7; }; + if (k == TK_LSHIFT) { return 8; }; + if (k == TK_RSHIFT) { return 8; }; + if (k == TK_PLUS) { return 9; }; + if (k == TK_MINUS) { return 9; }; + if (k == TK_STAR) { return 10; }; + if (k == TK_SLASH) { return 10; }; + if (k == TK_PERCENT) { return 10; }; + return 0; +}; + +fn isassignop(k: i32) bool = { + if (k == TK_ASSIGN) { return true; }; + if (k == TK_PLUSEQ) { return true; }; + if (k == TK_MINUSEQ) { return true; }; + if (k == TK_STAREQ) { return true; }; + if (k == TK_SLASHEQ) { return true; }; + if (k == TK_PERCENTEQ) { return true; }; + if (k == TK_AMPEQ) { return true; }; + if (k == TK_PIPEEQ) { return true; }; + if (k == TK_CARETEQ) { return true; }; + if (k == TK_LSHIFTEQ) { return true; }; + if (k == TK_RSHIFTEQ) { return true; }; + return false; +}; + +// Forward references between parseunary/parseexpr/parsebin/parsepostfix +// are resolved by the two-pass checker — no body-less prototypes needed. export fn parsefile(p: *parser) *node = { let f: *node = newnode(p.a, N_FILE, p.curfile, p.curline, p.curcol); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 8064ab09..946ad956 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -1954,267 +1954,13 @@ export fn astprint(fd: i32, n: *node) void = { pr(fd, n, 0); }; -// MODULE: ww -// lib/ww/parse.ww — port of cmd/wcc/parse.c. -// -// Status: GROWING stub. Currently handles top-level `use IDENT;`, -// `def NAME: TYPE = LIT;`, `type NAME = TYPE;`, and `fn NAME(params) -// RET;` (header-only — bodies are recovered past). Unknown decls are -// chewed token-by-token until the next ';' so the diff probe can -// still anchor on partial fixtures. -// -// The full port is multi-session work — parse.c is 1,183 lines of -// hand-rolled recursive descent + Pratt expression parser. Each -// surface form lands here gradually so the AST diff in 990_selfhost -// grows toward whole-language coverage one increment at a time. -// -// Calling-convention shim: w6c can't yet pass a sub-struct field -// (e.g. p.cur.line where p.cur is a `tok` of size 76). The parser -// stores the current token as flat primitive fields rather than a -// nested `tok` struct; `refill` copies a freshly lexed token in. +// MODULE: parse +// lib/ww/parse/expr.ww — expression parsing, split out of parse.ww. use os; use mem; use tok; -type parser = struct { - l: *lex, - a: *arena, - errs: i32, - // nocast: while inside `[...]` we treat ':' as the slice - // separator, not the cast operator. Mirrors parse.c's flag. - nocast: i32, - curkind: i32, - curfile: str, - curline: i32, - curcol: i32, - curtext: str, - curuval: u64, -}; - -fn refill(p: *parser) void = { - let t: tok; - lexnext(p.l, &t); - p.curkind = t.kind; - p.curfile = t.file; - p.curline = t.line; - p.curcol = t.col; - p.curtext = t.text; - p.curuval = t.uval; -}; - -export fn parserinit(p: *parser, a: *arena, l: *lex) void = { - p.l = l; - p.a = a; - p.errs = 0; - p.nocast = 0; - refill(p); -}; - -fn advance(p: *parser) void = { refill(p); }; - -fn accepttok(p: *parser, k: i32) bool = { - if (p.curkind == k) { advance(p); return true; }; - return false; -}; - -fn errmsg(p: *parser, msg: str) void = { - let pre: str = "parse: "; - os.write(2, pre.ptr, pre.len: u64); - os.write(2, msg.ptr, msg.len: u64); - os.write(2, "\n".ptr, 1u64); - p.errs += 1; -}; - -fn expecttok(p: *parser, k: i32, what: str) bool = { - if (p.curkind == k) { advance(p); return true; }; - errmsg(p, what); - return false; -}; - -// expectident — consume the current TK_IDENT and return its text. -// Returns the empty str on error (and advances to make progress). -fn expectident(p: *parser, into: *str) bool = { - if (p.curkind != TK_IDENT) { - errmsg(p, "expected identifier"); - advance(p); - return false; - }; - *into = p.curtext; - advance(p); - return true; -}; - -// ---- type expressions ------------------------------------------------ -// -// Currently: TNAME (single ident, no dotted path yet) and TPTR (`*T`). -// Other forms (slice, array, struct, fn, chan, tuple, tagged) will -// land in subsequent commits. - -fn parsetype(p: *parser) *node = { - let pf: str = p.curfile; - let pl: i32 = p.curline; - let pc: i32 = p.curcol; - - if (p.curkind == TK_STAR) { - advance(p); - let n: *node = newnode(p.a, N_TPTR, pf, pl, pc); - n.lhs = parsetype(p); - return n; - }; - - if (p.curkind == TK_LBRACK) { - advance(p); - if (p.curkind == TK_RBRACK) { - advance(p); - let n: *node = newnode(p.a, N_TSLICE, pf, pl, pc); - n.lhs = parsetype(p); - return n; - }; - let n: *node = newnode(p.a, N_TARRAY, pf, pl, pc); - n.rhs = parseexpr(p); - expecttok(p, TK_RBRACK, "expected ']' in array type"); - n.lhs = parsetype(p); - return n; - }; - - if (p.curkind == TK_STRUCT) { - advance(p); - expecttok(p, TK_LBRACE, "expected '{' after struct"); - let n: *node = newnode(p.a, N_TSTRUCT, pf, pl, pc); - let fhead: *node = nil; - let ftail: *node = nil; - for (p.curkind != TK_RBRACE) { - if (p.curkind == TK_EOF) { break; }; - let fpf: str = p.curfile; - let fpl: i32 = p.curline; - let fpc: i32 = p.curcol; - let f: *node = newnode(p.a, N_TFIELD, fpf, fpl, fpc); - let fid: str; - expectident(p, &fid); - f.str = fid; - expecttok(p, TK_COLON, "expected ':' in field"); - f.lhs = parsetype(p); - if (fhead == nil) { fhead = f; ftail = f; } - else { ftail.next = f; ftail = f; }; - if (!accepttok(p, TK_COMMA)) { break; }; - }; - expecttok(p, TK_RBRACE, "expected '}' after struct fields"); - n.list = fhead; - return n; - }; - - if (p.curkind == TK_IDENT) { - let n: *node = newnode(p.a, N_TNAME, pf, pl, pc); - n.str = p.curtext; - advance(p); - // Dotted path collapse (pkg.Type) deferred — fixtures don't - // need it yet. - return n; - }; - - if (p.curkind == TK_LPAREN) { - // (T) or (T, T, ...) or (T | T | ...) - advance(p); - let first: *node = parsetype(p); - if (accepttok(p, TK_PIPE)) { - let n: *node = newnode(p.a, N_TTAGGED, pf, pl, pc); - let head: *node = first; - let tail: *node = first; - for (true) { - let e: *node = parsetype(p); - tail.next = e; - tail = e; - if (!accepttok(p, TK_PIPE)) { break; }; - }; - expecttok(p, TK_RPAREN, "expected ')' in tagged-union type"); - n.list = head; - return n; - }; - if (!accepttok(p, TK_COMMA)) { - expecttok(p, TK_RPAREN, "expected ')' after parenthesised type"); - return first; - }; - let n: *node = newnode(p.a, N_TTUPLE, pf, pl, pc); - let head: *node = first; - let tail: *node = first; - for (true) { - let e: *node = parsetype(p); - tail.next = e; - tail = e; - if (!accepttok(p, TK_COMMA)) { break; }; - if (p.curkind == TK_RPAREN) { break; }; - }; - expecttok(p, TK_RPAREN, "expected ')' in tuple type"); - n.list = head; - return n; - }; - - if (p.curkind == TK_FN) { - advance(p); - expecttok(p, TK_LPAREN, "expected '(' after fn in type"); - let n: *node = newnode(p.a, N_TFN, pf, pl, pc); - // Anonymous-or-named params: parseparams handles named only; - // for fn-type expressions the C parser allows IDENT-less - // (anonymous) params. Stub: only named params for now. - n.list = parseparams(p); - expecttok(p, TK_RPAREN, "expected ')' after fn type params"); - n.lhs = parsetype(p); - return n; - }; - - errmsg(p, "expected type"); - advance(p); - return newnode(p.a, N_TNAME, pf, pl, pc); -}; - -// ---- expressions (Pratt) --------------------------------------------- -// -// Forwards: parseexpr → parsebin → parseunary → parsepostfix(parseprimary). -// Tuple literals, match expressions, struct literals, slice [lo:hi], -// and the ?/! try operators are not yet wired — they'll arrive as the -// AST diff fixture grows to need them. - -fn bprec(k: i32) i32 = { - if (k == TK_OR) { return 1; }; - if (k == TK_AND) { return 2; }; - if (k == TK_EQ) { return 3; }; - if (k == TK_NEQ) { return 3; }; - if (k == TK_LT) { return 4; }; - if (k == TK_LE) { return 4; }; - if (k == TK_GT) { return 4; }; - if (k == TK_GE) { return 4; }; - if (k == TK_PIPE) { return 5; }; - if (k == TK_CARET) { return 6; }; - if (k == TK_AMP) { return 7; }; - if (k == TK_LSHIFT) { return 8; }; - if (k == TK_RSHIFT) { return 8; }; - if (k == TK_PLUS) { return 9; }; - if (k == TK_MINUS) { return 9; }; - if (k == TK_STAR) { return 10; }; - if (k == TK_SLASH) { return 10; }; - if (k == TK_PERCENT) { return 10; }; - return 0; -}; - -fn isassignop(k: i32) bool = { - if (k == TK_ASSIGN) { return true; }; - if (k == TK_PLUSEQ) { return true; }; - if (k == TK_MINUSEQ) { return true; }; - if (k == TK_STAREQ) { return true; }; - if (k == TK_SLASHEQ) { return true; }; - if (k == TK_PERCENTEQ) { return true; }; - if (k == TK_AMPEQ) { return true; }; - if (k == TK_PIPEEQ) { return true; }; - if (k == TK_CARETEQ) { return true; }; - if (k == TK_LSHIFTEQ) { return true; }; - if (k == TK_RSHIFTEQ) { return true; }; - return false; -}; - -// Forward references between parseunary/parseexpr/parsebin/parsepostfix -// are resolved by the two-pass checker — no body-less prototypes needed. - fn parseprimary(p: *parser) *node = { let pf: str = p.curfile; let pl: i32 = p.curline; @@ -2532,11 +2278,13 @@ fn parseexpr(p: *parser) *node = { return e; }; -// ---- statements ------------------------------------------------------ -// -// Subset wired today: block, let, return, if (no else-if chain), for -// (single-cond C-style), expr-stmt, defer, break, continue. Switch -// and match arms are not yet wired; tuple-let / multi-let neither. + +// MODULE: parse +// lib/ww/parse/stmt.ww — statement parsing, split out of parse.ww. + +use os; +use mem; +use tok; fn parseletlocal(p: *parser) *node = { let pf: str = p.curfile; @@ -2730,7 +2478,13 @@ fn parsestmt(p: *parser) *node = { return n; }; -// ---- top-level decl parsers ------------------------------------------ + +// MODULE: parse +// lib/ww/parse/decl.ww — declaration parsing, split out of parse.ww. + +use os; +use mem; +use tok; fn parseuse(p: *parser) *node = { let pf: str = p.curfile; @@ -2879,7 +2633,264 @@ fn parsetypedecl(p: *parser, exported: i32) *node = { return n; }; -// ---- file-level loop ------------------------------------------------- + +// MODULE: parse +// lib/ww/parse/parse.ww — port of cmd/wcc/parse.c (entry + plumbing). +// +// Split into Hare-style submodule: parse.ww (here) holds the parser +// struct, lexer plumbing, parsetype, parsefile (entry). Expression, +// statement, and declaration parsers live in expr.ww, stmt.ww, +// decl.ww respectively — all in the same `parse` module. +// +// Calling-convention shim: w6c can't yet pass a sub-struct field +// (e.g. p.cur.line where p.cur is a `tok` of size 76). The parser +// stores the current token as flat primitive fields rather than a +// nested `tok` struct; `refill` copies a freshly lexed token in. + +use os; +use mem; +use tok; +use expr; +use stmt; +use decl; + +type parser = struct { + l: *lex, + a: *arena, + errs: i32, + // nocast: while inside `[...]` we treat ':' as the slice + // separator, not the cast operator. Mirrors parse.c's flag. + nocast: i32, + curkind: i32, + curfile: str, + curline: i32, + curcol: i32, + curtext: str, + curuval: u64, +}; + +fn refill(p: *parser) void = { + let t: tok; + lexnext(p.l, &t); + p.curkind = t.kind; + p.curfile = t.file; + p.curline = t.line; + p.curcol = t.col; + p.curtext = t.text; + p.curuval = t.uval; +}; + +export fn parserinit(p: *parser, a: *arena, l: *lex) void = { + p.l = l; + p.a = a; + p.errs = 0; + p.nocast = 0; + refill(p); +}; + +fn advance(p: *parser) void = { refill(p); }; + +fn accepttok(p: *parser, k: i32) bool = { + if (p.curkind == k) { advance(p); return true; }; + return false; +}; + +fn errmsg(p: *parser, msg: str) void = { + let pre: str = "parse: "; + os.write(2, pre.ptr, pre.len: u64); + os.write(2, msg.ptr, msg.len: u64); + os.write(2, "\n".ptr, 1u64); + p.errs += 1; +}; + +fn expecttok(p: *parser, k: i32, what: str) bool = { + if (p.curkind == k) { advance(p); return true; }; + errmsg(p, what); + return false; +}; + +// expectident — consume the current TK_IDENT and return its text. +// Returns the empty str on error (and advances to make progress). +fn expectident(p: *parser, into: *str) bool = { + if (p.curkind != TK_IDENT) { + errmsg(p, "expected identifier"); + advance(p); + return false; + }; + *into = p.curtext; + advance(p); + return true; +}; + +// ---- type expressions ------------------------------------------------ +// +// Currently: TNAME (single ident, no dotted path yet) and TPTR (`*T`). +// Other forms (slice, array, struct, fn, chan, tuple, tagged) will +// land in subsequent commits. + +fn parsetype(p: *parser) *node = { + let pf: str = p.curfile; + let pl: i32 = p.curline; + let pc: i32 = p.curcol; + + if (p.curkind == TK_STAR) { + advance(p); + let n: *node = newnode(p.a, N_TPTR, pf, pl, pc); + n.lhs = parsetype(p); + return n; + }; + + if (p.curkind == TK_LBRACK) { + advance(p); + if (p.curkind == TK_RBRACK) { + advance(p); + let n: *node = newnode(p.a, N_TSLICE, pf, pl, pc); + n.lhs = parsetype(p); + return n; + }; + let n: *node = newnode(p.a, N_TARRAY, pf, pl, pc); + n.rhs = parseexpr(p); + expecttok(p, TK_RBRACK, "expected ']' in array type"); + n.lhs = parsetype(p); + return n; + }; + + if (p.curkind == TK_STRUCT) { + advance(p); + expecttok(p, TK_LBRACE, "expected '{' after struct"); + let n: *node = newnode(p.a, N_TSTRUCT, pf, pl, pc); + let fhead: *node = nil; + let ftail: *node = nil; + for (p.curkind != TK_RBRACE) { + if (p.curkind == TK_EOF) { break; }; + let fpf: str = p.curfile; + let fpl: i32 = p.curline; + let fpc: i32 = p.curcol; + let f: *node = newnode(p.a, N_TFIELD, fpf, fpl, fpc); + let fid: str; + expectident(p, &fid); + f.str = fid; + expecttok(p, TK_COLON, "expected ':' in field"); + f.lhs = parsetype(p); + if (fhead == nil) { fhead = f; ftail = f; } + else { ftail.next = f; ftail = f; }; + if (!accepttok(p, TK_COMMA)) { break; }; + }; + expecttok(p, TK_RBRACE, "expected '}' after struct fields"); + n.list = fhead; + return n; + }; + + if (p.curkind == TK_IDENT) { + let n: *node = newnode(p.a, N_TNAME, pf, pl, pc); + n.str = p.curtext; + advance(p); + // Dotted path collapse (pkg.Type) deferred — fixtures don't + // need it yet. + return n; + }; + + if (p.curkind == TK_LPAREN) { + // (T) or (T, T, ...) or (T | T | ...) + advance(p); + let first: *node = parsetype(p); + if (accepttok(p, TK_PIPE)) { + let n: *node = newnode(p.a, N_TTAGGED, pf, pl, pc); + let head: *node = first; + let tail: *node = first; + for (true) { + let e: *node = parsetype(p); + tail.next = e; + tail = e; + if (!accepttok(p, TK_PIPE)) { break; }; + }; + expecttok(p, TK_RPAREN, "expected ')' in tagged-union type"); + n.list = head; + return n; + }; + if (!accepttok(p, TK_COMMA)) { + expecttok(p, TK_RPAREN, "expected ')' after parenthesised type"); + return first; + }; + let n: *node = newnode(p.a, N_TTUPLE, pf, pl, pc); + let head: *node = first; + let tail: *node = first; + for (true) { + let e: *node = parsetype(p); + tail.next = e; + tail = e; + if (!accepttok(p, TK_COMMA)) { break; }; + if (p.curkind == TK_RPAREN) { break; }; + }; + expecttok(p, TK_RPAREN, "expected ')' in tuple type"); + n.list = head; + return n; + }; + + if (p.curkind == TK_FN) { + advance(p); + expecttok(p, TK_LPAREN, "expected '(' after fn in type"); + let n: *node = newnode(p.a, N_TFN, pf, pl, pc); + // Anonymous-or-named params: parseparams handles named only; + // for fn-type expressions the C parser allows IDENT-less + // (anonymous) params. Stub: only named params for now. + n.list = parseparams(p); + expecttok(p, TK_RPAREN, "expected ')' after fn type params"); + n.lhs = parsetype(p); + return n; + }; + + errmsg(p, "expected type"); + advance(p); + return newnode(p.a, N_TNAME, pf, pl, pc); +}; + +// ---- expressions (Pratt) --------------------------------------------- +// +// Forwards: parseexpr → parsebin → parseunary → parsepostfix(parseprimary). +// Tuple literals, match expressions, struct literals, slice [lo:hi], +// and the ?/! try operators are not yet wired — they'll arrive as the +// AST diff fixture grows to need them. + +fn bprec(k: i32) i32 = { + if (k == TK_OR) { return 1; }; + if (k == TK_AND) { return 2; }; + if (k == TK_EQ) { return 3; }; + if (k == TK_NEQ) { return 3; }; + if (k == TK_LT) { return 4; }; + if (k == TK_LE) { return 4; }; + if (k == TK_GT) { return 4; }; + if (k == TK_GE) { return 4; }; + if (k == TK_PIPE) { return 5; }; + if (k == TK_CARET) { return 6; }; + if (k == TK_AMP) { return 7; }; + if (k == TK_LSHIFT) { return 8; }; + if (k == TK_RSHIFT) { return 8; }; + if (k == TK_PLUS) { return 9; }; + if (k == TK_MINUS) { return 9; }; + if (k == TK_STAR) { return 10; }; + if (k == TK_SLASH) { return 10; }; + if (k == TK_PERCENT) { return 10; }; + return 0; +}; + +fn isassignop(k: i32) bool = { + if (k == TK_ASSIGN) { return true; }; + if (k == TK_PLUSEQ) { return true; }; + if (k == TK_MINUSEQ) { return true; }; + if (k == TK_STAREQ) { return true; }; + if (k == TK_SLASHEQ) { return true; }; + if (k == TK_PERCENTEQ) { return true; }; + if (k == TK_AMPEQ) { return true; }; + if (k == TK_PIPEEQ) { return true; }; + if (k == TK_CARETEQ) { return true; }; + if (k == TK_LSHIFTEQ) { return true; }; + if (k == TK_RSHIFTEQ) { return true; }; + return false; +}; + +// Forward references between parseunary/parseexpr/parsebin/parsepostfix +// are resolved by the two-pass checker — no body-less prototypes needed. export fn parsefile(p: *parser) *node = { let f: *node = newnode(p.a, N_FILE, p.curfile, p.curline, p.curcol); diff --git a/test/wcc/990_selfhost.c b/test/wcc/990_selfhost.c index 564cebb0..46191916 100644 --- a/test/wcc/990_selfhost.c +++ b/test/wcc/990_selfhost.c @@ -174,7 +174,7 @@ probe_dump_diff(const char *bin) "lib/ww/lex/lex.ww", "lib/ww/lex/tok.ww", "lib/ww/ast.ww", - "lib/ww/parse.ww", + "lib/ww/parse/parse.ww", "lib/ww/parse/expr.ww", "lib/ww/parse/stmt.ww", "lib/ww/parse/decl.ww", "selfhost/cmd/wwdump/main.ww", "selfhost/test/smoke.ww", NULL, @@ -191,7 +191,7 @@ probe_dump_diff(const char *bin) "lib/ww/lex/lex.ww", "lib/ww/lex/tok.ww", "lib/ww/ast.ww", - "lib/ww/parse.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", @@ -656,8 +656,8 @@ probe_ww_links(const char *bin) runwait(cmd); /* ww build to get the .combined.ww as a side effect. */ snprintf(cmd, sizeof cmd, - "cd %s && %s/ww build -I %s/lib/ww -I %s/lib/ww/lex -I %s/selfhost/cmd/wcc %s >/dev/null 2>&1", - tmpdir, bin, cwd, cwd, cwd, tmpsrc); + "cd %s && %s/ww build -I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse -I %s/selfhost/cmd/wcc %s >/dev/null 2>&1", + tmpdir, bin, cwd, cwd, cwd, cwd, tmpsrc); if (runwait(cmd) != 0) { fprintf(stderr, "ww-links FAIL: ww build %s\n", fix); fail++; diff --git a/test/wcc/993_ww_ww.c b/test/wcc/993_ww_ww.c index 13589a1d..cb39eeea 100644 --- a/test/wcc/993_ww_ww.c +++ b/test/wcc/993_ww_ww.c @@ -56,25 +56,18 @@ slurp_eq(const char *a, const char *b) } /* Build `src` via the named driver, expecting output binary `out` in - * the build directory. Returns 0 on success. */ + * the build directory. `incs` may be a colon-separated list of include + * dirs (the ww driver accepts -I path1:path2:path3). Returns 0 on + * success. */ static int build_via(const char *bin, const char *driver, const char *src, - const char *workdir, const char *includes_a, const char *includes_b, - const char *includes_c) + const char *workdir, const char *incs) { char cmd[4096]; - if (includes_c && includes_c[0]) { - snprintf(cmd, sizeof cmd, - "cd %s && %s/%s build -I %s -I %s -I %s %s 2>/dev/null", - workdir, bin, driver, includes_a, includes_b, includes_c, src); - } else if (includes_b && includes_b[0]) { - snprintf(cmd, sizeof cmd, - "cd %s && %s/%s build -I %s -I %s %s 2>/dev/null", - workdir, bin, driver, includes_a, includes_b, src); - } else if (includes_a && includes_a[0]) { + if (incs && incs[0]) { snprintf(cmd, sizeof cmd, "cd %s && %s/%s build -I %s %s 2>/dev/null", - workdir, bin, driver, includes_a, src); + workdir, bin, driver, incs, src); } else { snprintf(cmd, sizeof cmd, "cd %s && %s/%s build %s 2>/dev/null", @@ -86,7 +79,7 @@ build_via(const char *bin, const char *driver, const char *src, static int diff_one(const char *bin, const char *cwd, const char *label, const char *src, const char *out_basename, - const char *inc_a, const char *inc_b, const char *inc_c) + const char *incs) { char dc[64], dw[64]; snprintf(dc, sizeof dc, "/tmp/ww_d_%d_c", getpid()); @@ -96,11 +89,11 @@ diff_one(const char *bin, const char *cwd, const char *label, snprintf(cmd, sizeof cmd, "rm -rf %s %s && mkdir -p %s %s", dc, dw, dc, dw); if (runwait(cmd) != 0) return -1; - if (build_via(bin, "ww", src, dc, inc_a, inc_b, inc_c) != 0) { + if (build_via(bin, "ww", src, dc, incs) != 0) { fprintf(stderr, "ww_ww FAIL: C ww errored on %s\n", label); return -1; } - if (build_via(bin, "ww_ww", src, dw, inc_a, inc_b, inc_c) != 0) { + if (build_via(bin, "ww_ww", src, dw, incs) != 0) { fprintf(stderr, "ww_ww FAIL: ww ww errored on %s\n", label); return -1; } @@ -145,33 +138,28 @@ main(void) const char *label; const char *src; /* may be relative to cwd */ const char *out; /* basename of expected output */ - const char *inc_a; - const char *inc_b; - const char *inc_c; + const char *incs; /* colon-separated -I list, may be NULL */ } cases[] = { - { "hello", "/tmp/ww_d_hello.ww", "ww_d_hello", "", "", "" }, - { "wwdump", NULL, "main", NULL, NULL, NULL }, /* filled in below */ - { NULL, NULL, NULL, NULL, NULL, NULL }, + { "hello", "/tmp/ww_d_hello.ww", "ww_d_hello", "" }, + { "wwdump", NULL, "main", NULL }, /* filled in below */ + { NULL, NULL, NULL, NULL }, }; /* wwdump case: absolute paths so the driver finds the imports * regardless of the per-driver workdir. */ - static char wwdump_src[2048], wwdump_inc_a[2048], wwdump_inc_b[2048], wwdump_inc_c[2048]; - snprintf(wwdump_src, sizeof wwdump_src, "%s/selfhost/cmd/wwdump/main.ww", cwd); - snprintf(wwdump_inc_a, sizeof wwdump_inc_a, "%s/lib/ww", cwd); - snprintf(wwdump_inc_b, sizeof wwdump_inc_b, "%s/lib/ww/lex", cwd); - snprintf(wwdump_inc_c, sizeof wwdump_inc_c, "%s/selfhost/cmd/wcc", cwd); - cases[1].src = wwdump_src; - cases[1].inc_a = wwdump_inc_a; - cases[1].inc_b = wwdump_inc_b; - cases[1].inc_c = wwdump_inc_c; + static char wwdump_src[2048], wwdump_incs[4096]; + snprintf(wwdump_src, sizeof wwdump_src, "%s/selfhost/cmd/wwdump/main.ww", cwd); + snprintf(wwdump_incs, sizeof wwdump_incs, + "%s/lib/ww:%s/lib/ww/lex:%s/lib/ww/parse:%s/selfhost/cmd/wcc", + cwd, cwd, cwd, cwd); + cases[1].src = wwdump_src; + cases[1].incs = wwdump_incs; int fail = 0; int n = 0; for (int i = 0; cases[i].label; i++) { if (diff_one(bin, cwd, cases[i].label, cases[i].src, - cases[i].out, cases[i].inc_a, cases[i].inc_b, - cases[i].inc_c) != 0) + cases[i].out, cases[i].incs) != 0) fail++; n++; } diff --git a/test/wcc/995_self_rebuild.c b/test/wcc/995_self_rebuild.c index b66abddf..4e7dcb71 100644 --- a/test/wcc/995_self_rebuild.c +++ b/test/wcc/995_self_rebuild.c @@ -77,14 +77,14 @@ rebuild_one(const char *bin, const char *cwd, const char *tool, if (inc_local && inc_local[0]) { snprintf(cmd, sizeof cmd, - "cd %s && %s/ww_ww build -I %s/%s -I %s/lib/ww -I %s/lib/ww/lex -I %s/selfhost/cmd/wcc " + "cd %s && %s/ww_ww build -I %s/%s -I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse -I %s/selfhost/cmd/wcc " "%s/%s >/dev/null 2>&1", - workdir, bin, cwd, inc_local, cwd, cwd, cwd, cwd, src_rel); + workdir, bin, cwd, inc_local, cwd, cwd, cwd, cwd, cwd, src_rel); } else { snprintf(cmd, sizeof cmd, - "cd %s && %s/ww_ww build -I %s/lib/ww -I %s/lib/ww/lex -I %s/selfhost/cmd/wcc " + "cd %s && %s/ww_ww build -I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse -I %s/selfhost/cmd/wcc " "%s/%s >/dev/null 2>&1", - workdir, bin, cwd, cwd, cwd, cwd, src_rel); + workdir, bin, cwd, cwd, cwd, cwd, cwd, src_rel); } if (runwait(cmd) != 0) { fprintf(stderr, "self-rebuild FAIL: ww_ww build errored on %s\n", tool);