syntax: accept the empty for () condition (align up to cstage)

cstage parse.c:1027 accepts `for ()` — N_FOR with a nil cond, no
for-else arm, no test emitted (an unconditional loop; a deliberate
Go-flavored ww divergence from Hare, original to the toolchain
import). The wwstage parser had no RPAREN early-out and rejected it.
Selfhost checker and cgen already nil-guard n.cond, so the parser arm
is the whole gap. Graduates the held e2e row (pin 1731/1157/3462).

Adjacent asymmetries left filed, not fixed here (rule 11): the brace
form `for { }`, the empty middle clause of the 3-clause form, and
the REVERSE case (wwstage accepts 2-clause `for (cond; post)` which
cstage rejects).
This commit is contained in:
2026-08-08 17:15:20 +09:00
parent d78137a051
commit bbdd8bede6
3 changed files with 27 additions and 5 deletions

View File

@@ -141,11 +141,22 @@ fn parsefor(p: *parser) *node = {
advance(p); // past `for`
expecttok(p, tkind.TK_LPAREN, "expected '(' after for");
// Four forms (matching C parser):
// Five forms (matching C parser):
// for () — empty cond, unconditional loop
// for (cond) — only cond
// for (init; cond; post) — C-style 3-clause
// 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) {