// lib/ww/syntax/stmt.ww — statement parsing, split out of parse.ww. package syntax; import os; fn parseletlocal(p: *parser) *node = { let pf = p.curfile; let pl = p.curline; let pc = p.curcol; // `let` or `const`. Const-bound locals are marked via n.op = tkind.TK_CONST. let is_const: i32 = 0; if (p.curkind == tkind.TK_CONST) { is_const = 1; }; advance(p); // Hare-style tuple destructure: `let (a, b) = expr;`. // Types are optional per binding (matches C parser; Hare itself // doesn't allow types here, but cmd/wcc/parse.c does). if (p.curkind == tkind.TK_LPAREN) { advance(p); let m = newnode(nkind.N_MLET, pf, pl, pc); let head: *node = nil; let tail: *node = nil; for (true) { let lpf = p.curfile; let lpl = p.curline; let lpc = p.curcol; let l = newnode(nkind.N_LET, lpf, lpl, lpc); let id: str; expectbindname(p, &id); l.str = id; if (accepttok(p, tkind.TK_COLON)) { l.lhs = parsetype(p); }; if (head == nil) { head = l; } else { tail.next = l; }; tail = l; if (!accepttok(p, tkind.TK_COMMA)) { break; }; }; expecttok(p, tkind.TK_RPAREN, "expected ')' in let destructure"); expecttok(p, tkind.TK_ASSIGN, "expected '=' after let destructure"); m.rhs = parseexpr(p); expecttok(p, tkind.TK_SEMI, "expected ';' after let"); m.list = head; if (is_const != 0) { m.op = tkind.TK_CONST; let lc = head; for (lc != nil) { lc.op = tkind.TK_CONST; lc = lc.next; }; }; return m; }; let n = newnode(nkind.N_LET, pf, pl, pc); let id: str; expectbindname(p, &id); n.str = id; if (accepttok(p, tkind.TK_COLON)) { n.lhs = parsetype(p); }; // Comma-multi-let: `let n, s = call();` (ww extension over Hare). // Collects (name, type) pairs, then '=' rhs. Each binding gets // its own nkind.N_LET; the wrapping nkind.N_MLET carries the rhs. if (p.curkind == tkind.TK_COMMA) { let m = newnode(nkind.N_MLET, pf, pl, pc); let head = n; let tail = n; for (accepttok(p, tkind.TK_COMMA)) { let lpf = p.curfile; let lpl = p.curline; let lpc = p.curcol; let l = newnode(nkind.N_LET, lpf, lpl, lpc); let id2: str; expectbindname(p, &id2); l.str = id2; if (accepttok(p, tkind.TK_COLON)) { l.lhs = parsetype(p); }; tail.next = l; tail = l; }; expecttok(p, tkind.TK_ASSIGN, "expected '=' after let names"); m.rhs = parseexpr(p); expecttok(p, tkind.TK_SEMI, "expected ';' after let"); m.list = head; if (is_const != 0) { m.op = tkind.TK_CONST; let lc = head; for (lc != nil) { lc.op = tkind.TK_CONST; lc = lc.next; }; }; return m; }; if (accepttok(p, tkind.TK_ASSIGN)) { n.rhs = parseexpr(p); }; expecttok(p, tkind.TK_SEMI, "expected ';' after let"); if (is_const != 0) { n.op = tkind.TK_CONST; }; return n; }; fn parseblock(p: *parser) *node = { let pf = p.curfile; let pl = p.curline; let pc = p.curcol; expecttok(p, tkind.TK_LBRACE, "expected '{' to open block"); let blk = newnode(nkind.N_BLOCK, pf, pl, pc); let head: *node = nil; let tail: *node = nil; for (p.curkind != tkind.TK_RBRACE) { if (p.curkind == tkind.TK_EOF) { break; }; let s = parsestmt(p); if (s != nil) { if (head == nil) { head = s; tail = s; } else { tail.next = s; tail = s; }; }; }; expecttok(p, tkind.TK_RBRACE, "expected '}' to close block"); blk.list = head; return blk; }; fn parseif(p: *parser) *node = { let pf = p.curfile; let pl = p.curline; let pc = p.curcol; advance(p); // past `if` expecttok(p, tkind.TK_LPAREN, "expected '(' after if"); let n = newnode(nkind.N_IF, pf, pl, pc); n.cond = parseexpr(p); expecttok(p, tkind.TK_RPAREN, "expected ')' after if condition"); n.body = parseblock(p); if (accepttok(p, tkind.TK_ELSE)) { if (p.curkind == tkind.TK_IF) { n.els = parseif(p); } else { n.els = parseblock(p); }; }; return n; }; fn parsefor(p: *parser) *node = { let pf = p.curfile; let pl = p.curline; let pc = p.curcol; advance(p); // past `for` // Go's bare `for { }` — no header at all (cmd/wcc/parse.c:1023; // Go's for has exactly three forms and this is the empty one). // Keeps the for-else arm exactly as the cstage brace form does. if (p.curkind == tkind.TK_LBRACE) { let n = newnode(nkind.N_FOR, pf, pl, pc); n.body = parseblock(p); if (accepttok(p, tkind.TK_ELSE)) { n.els = parseblock(p); }; return n; }; expecttok(p, tkind.TK_LPAREN, "expected '(' after for"); // Six forms (matching C parser): // for { } — bare Go form, no header // for () — empty cond, unconditional loop // for (cond) — only cond // for (init; cond; post) — C-style 3-clause (cond and // post each omissible, parse.c:1092) // for (let x .. expr) — Hare-style range, single binding // for (let (a, b) .. expr) — range with tuple destructure // Empty condition: cond stays nil (no test emitted downstream). // Mirrors cmd/wcc/parse.c:1027 — returns WITHOUT the for-else arm, // exactly as the cstage empty form does. if (p.curkind == tkind.TK_RPAREN) { advance(p); let n = newnode(nkind.N_FOR, pf, pl, pc); n.body = parseblock(p); return n; }; // Range and 3-clause both lead with `let`, so we commit to consuming // `let` then disambiguate by looking at what follows. if (p.curkind == tkind.TK_LET) { advance(p); // past `let` // Tuple destructure: `for (let (a, b) .. expr)`. if (p.curkind == tkind.TK_LPAREN) { advance(p); let names: *node = nil; let ntail: *node = nil; for (true) { let npf = p.curfile; let npl = p.curline; let npc = p.curcol; let e = newnode(nkind.N_IDENT, npf, npl, npc); let nm: str; expectbindname(p, &nm); e.str = nm; if (names == nil) { names = e; } else { ntail.next = e; }; ntail = e; if (!accepttok(p, tkind.TK_COMMA)) { break; }; }; expecttok(p, tkind.TK_RPAREN, "expected ')' in for-range names"); expecttok(p, tkind.TK_DOTDOT, "expected '..' after for-range names"); let rng = newnode(nkind.N_FORRANGE, pf, pl, pc); rng.list = names; rng.lhs = parseexpr(p); expecttok(p, tkind.TK_RPAREN, "expected ')' after for"); rng.body = parseblock(p); if (accepttok(p, tkind.TK_ELSE)) { rng.els = parseblock(p); }; return rng; }; // Single binding range or C-style let-init. We need to consume // the IDENT/UNDER to know which: if followed by '..' it's a // range; otherwise build a synthetic LET for the C-style for-init // with the consumed name baked in. if (p.curkind == tkind.TK_IDENT || p.curkind == tkind.TK_UNDER) { let isunder = (p.curkind == tkind.TK_UNDER); let nm: str; nm.ptr = nil; nm.len = 0; if (!isunder) { nm = p.curtext; }; let lpf = p.curfile; let lpl = p.curline; let lpc = p.curcol; advance(p); // consume IDENT/UNDER if (p.curkind == tkind.TK_DOTDOT) { advance(p); let rng = newnode(nkind.N_FORRANGE, pf, pl, pc); rng.str = nm; // "" for `_` rng.lhs = parseexpr(p); expecttok(p, tkind.TK_RPAREN, "expected ')' after for"); rng.body = parseblock(p); if (accepttok(p, tkind.TK_ELSE)) { rng.els = parseblock(p); }; return rng; }; // Not a range — finish the let manually and continue as // a 3-clause for-init. let first = newnode(nkind.N_LET, lpf, lpl, lpc); first.str = nm; if (accepttok(p, tkind.TK_COLON)) { first.lhs = parsetype(p); }; if (accepttok(p, tkind.TK_ASSIGN)) { first.rhs = parseexpr(p); }; expecttok(p, tkind.TK_SEMI, "expected ';' after for-init let"); let n = newnode(nkind.N_FOR, pf, pl, pc); n.lhs = first; // cond and post are each omissible (`for (init;; post)`, // `for (init; cond)`, `for (init;)`) — mirror cstage // parse.c:1092-1097 exactly. if (p.curkind != tkind.TK_SEMI && p.curkind != tkind.TK_RPAREN) { n.cond = parseexpr(p); }; if (accepttok(p, tkind.TK_SEMI)) { n.rhs = parseexpr(p); }; expecttok(p, tkind.TK_RPAREN, "expected ')' after for"); n.body = parseblock(p); if (accepttok(p, tkind.TK_ELSE)) { n.els = parseblock(p); }; return n; }; errmsg(p, "expected name after 'let' in for"); }; // for (cond) — a 2-clause `for (cond; post)` is NOT a form: it // exists in neither Go's grammar nor the cstage one (parse.c:1101 // consumes a stray ';' then requires ')'), so the old wwstage // accept was a parser bug, not a feature. Mirror cstage: eat the // stray ';' and demand ')' — a post expression then fails loud. let n = newnode(nkind.N_FOR, pf, pl, pc); n.cond = parseexpr(p); if (accepttok(p, tkind.TK_SEMI)) { }; expecttok(p, tkind.TK_RPAREN, "expected ')' after for"); n.body = parseblock(p); // Optional `else { ... }` — runs at normal cond-false exit; skipped // by break. Hare's "did the loop find it?" idiom. if (accepttok(p, tkind.TK_ELSE)) { n.els = parseblock(p); }; return n; }; fn parseswitch(p: *parser) *node = { let pf = p.curfile; let pl = p.curline; let pc = p.curcol; advance(p); // past `switch` expecttok(p, tkind.TK_LPAREN, "expected '(' after switch"); let n = newnode(nkind.N_SWITCH, pf, pl, pc); n.lhs = parseexpr(p); expecttok(p, tkind.TK_RPAREN, "expected ')' after switch expression"); expecttok(p, tkind.TK_LBRACE, "expected '{' to open switch body"); let head: *node = nil; let tail: *node = nil; for (p.curkind == tkind.TK_CASE) { let cpf = p.curfile; let cpl = p.curline; let cpc = p.curcol; advance(p); // past `case` let cs = newnode(nkind.N_CASE, cpf, cpl, cpc); let eh: *node = nil; let et: *node = nil; if (p.curkind != tkind.TK_COLON) { p.nocast = 1; for (true) { let e = parseexpr(p); if (eh == nil) { eh = e; } else { et.next = e; }; et = e; if (!accepttok(p, tkind.TK_COMMA)) { break; }; }; p.nocast = 0; }; cs.list = eh; expecttok(p, tkind.TK_COLON, "expected ':' after case label"); let bh: *node = nil; let bt: *node = nil; for (p.curkind != tkind.TK_CASE) { if (p.curkind == tkind.TK_RBRACE) { break; }; if (p.curkind == tkind.TK_EOF) { break; }; let s = parsestmt(p); if (s != nil) { if (bh == nil) { bh = s; } else { bt.next = s; }; bt = s; }; }; let blk = newnode(nkind.N_BLOCK, cpf, cpl, cpc); blk.list = bh; cs.body = blk; if (head == nil) { head = cs; } else { tail.next = cs; }; tail = cs; }; expecttok(p, tkind.TK_RBRACE, "expected '}' to close switch"); n.list = head; return n; }; fn parsestmt(p: *parser) *node = { let pf = p.curfile; let pl = p.curline; let pc = 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 == tkind.TK_STATIC) { advance(p); }; if (p.curkind == tkind.TK_LBRACE) { let b = parseblock(p); expecttok(p, tkind.TK_SEMI, "expected ';' after block"); return b; }; if (p.curkind == tkind.TK_LET) { return parseletlocal(p); }; if (p.curkind == tkind.TK_CONST) { return parseletlocal(p); }; if (p.curkind == tkind.TK_IF) { let n = parseif(p); expecttok(p, tkind.TK_SEMI, "expected ';' after if"); return n; }; if (p.curkind == tkind.TK_FOR) { let n = parsefor(p); expecttok(p, tkind.TK_SEMI, "expected ';' after for"); return n; }; if (p.curkind == tkind.TK_SWITCH) { let n = parseswitch(p); expecttok(p, tkind.TK_SEMI, "expected ';' after switch"); return n; }; if (p.curkind == tkind.TK_RETURN) { advance(p); let n = newnode(nkind.N_RETURN, pf, pl, pc); if (p.curkind != tkind.TK_SEMI) { let first = parseexpr(p); // Hare-style multi-value: `return a, b;` becomes a // tuple expression so codegen sees one rvalue. if (p.curkind == tkind.TK_COMMA) { let t = newnode(nkind.N_TUPLE, pf, pl, pc); t.list = first; let tail = first; for (accepttok(p, tkind.TK_COMMA)) { let e = parseexpr(p); tail.next = e; tail = e; }; n.lhs = t; } else { n.lhs = first; }; }; expecttok(p, tkind.TK_SEMI, "expected ';' after return"); return n; }; if (p.curkind == tkind.TK_DEFER) { advance(p); let n = newnode(nkind.N_DEFER, pf, pl, pc); n.lhs = parseexpr(p); expecttok(p, tkind.TK_SEMI, "expected ';' after defer"); return n; }; if (p.curkind == tkind.TK_YIELD) { advance(p); let n = newnode(nkind.N_YIELD, pf, pl, pc); n.lhs = parseexpr(p); expecttok(p, tkind.TK_SEMI, "expected ';' after yield"); return n; }; if (p.curkind == tkind.TK_BREAK) { advance(p); expecttok(p, tkind.TK_SEMI, "expected ';' after break"); return newnode(nkind.N_BREAK, pf, pl, pc); }; if (p.curkind == tkind.TK_CONTINUE) { advance(p); expecttok(p, tkind.TK_SEMI, "expected ';' after continue"); return newnode(nkind.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 = parseexpr(p); if (p.curkind == tkind.TK_COMMA) { let m = newnode(nkind.N_MASSIGN, pf, pl, pc); let head = e; let tail = e; for (p.curkind == tkind.TK_COMMA) { advance(p); let lv = parsebin(p, parseunary(p), 1); tail.next = lv; tail = lv; }; expecttok(p, tkind.TK_ASSIGN, "expected '=' after multi-assign lvalues"); m.rhs = parseexpr(p); m.list = head; expecttok(p, tkind.TK_SEMI, "expected ';' after multi-assign"); return m; }; let n = newnode(nkind.N_EXPRSTMT, pf, pl, pc); n.lhs = e; expecttok(p, tkind.TK_SEMI, "expected ';' after expression statement"); return n; };