Files
ww/lib/ww/syntax/stmt.ww
Hojun-Cho 241f28c96b syntax: align for-header grammar to cstage (Go's three forms)
The bbdd8bed residue, ruled by the Go derivation (ww's for IS the Go
for; the cstage grammar is the language definition and already the
Go-shaped one):
- bare `for { }` accepted (align UP to parse.c:1023, incl. for-else);
- 3-clause cond and post each omissible — `for (init;; post)`,
  `for (init; cond)`, `for (init;)` (align UP, parse.c:1092-1097);
- 2-clause `for (cond; post)` REJECTED (align DOWN): the form exists
  in neither Go nor cstage (parse.c:1101 eats a stray ';' then
  requires ')'), so the old wwstage accept was a parser bug. The
  reject wording mirrors cstage's failure mode per stage.

forhdr_{brace,emptymid,partial} run fixtures pin the accepted forms
byte-identically; forhdr_twoclause_reject pins both reject fragments.
Corpus pin 1745/346/21/209/1169/3490.
2026-08-08 19:19:42 +09:00

441 lines
13 KiB
Plaintext

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