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.
441 lines
13 KiB
Plaintext
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;
|
|
};
|
|
|