From 241f28c96b5d64785f157c99149ef005106a47ae Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 19:19:27 +0900 Subject: [PATCH] syntax: align for-header grammar to cstage (Go's three forms) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- internal/wwfixture/types.ww | 10 ++--- lib/ww/syntax/stmt.ww | 43 +++++++++++++------ test/wcc/data/forhdr_brace/case.ww | 10 +++++ test/wcc/data/forhdr_emptymid/case.ww | 10 +++++ test/wcc/data/forhdr_partial/case.ww | 20 +++++++++ test/wcc/data/forhdr_twoclause_reject/case.ww | 12 ++++++ 6 files changed, 87 insertions(+), 18 deletions(-) create mode 100644 test/wcc/data/forhdr_brace/case.ww create mode 100644 test/wcc/data/forhdr_emptymid/case.ww create mode 100644 test/wcc/data/forhdr_partial/case.ww create mode 100644 test/wcc/data/forhdr_twoclause_reject/case.ww diff --git a/internal/wwfixture/types.ww b/internal/wwfixture/types.ww index 837b0694..f2e31b01 100644 --- a/internal/wwfixture/types.ww +++ b/internal/wwfixture/types.ww @@ -1,13 +1,13 @@ package wwfixture; def protocolversion: i32 = 1; -def corpuscount: i32 = 1741; -def errorcount: i32 = 345; +def corpuscount: i32 = 1745; +def errorcount: i32 = 346; def compilecount: i32 = 21; def runcount: i32 = 209; -def runexitcount: i32 = 1166; -def nativecount: i32 = 3482; -def corpushash: str = "bad5b37d555f3f4d742f7a6fcfa857ff46315757233819a19eb9bc1da2800ce7"; +def runexitcount: i32 = 1169; +def nativecount: i32 = 3490; +def corpushash: str = "472974a00552c6b3f5db1fc6b0c1ed128d7554e134880196c833f11f99370f21"; type directive = enum i32 { ERROR = 0, diff --git a/lib/ww/syntax/stmt.ww b/lib/ww/syntax/stmt.ww index c226c5f3..d9b4bba5 100644 --- a/lib/ww/syntax/stmt.ww +++ b/lib/ww/syntax/stmt.ww @@ -139,12 +139,24 @@ fn parsefor(p: *parser) *node = { 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"); - // Five forms (matching C parser): + // 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 + // 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 @@ -225,9 +237,15 @@ fn parsefor(p: *parser) *node = { expecttok(p, tkind.TK_SEMI, "expected ';' after for-init let"); let n = newnode(nkind.N_FOR, pf, pl, pc); n.lhs = first; - n.cond = parseexpr(p); - expecttok(p, tkind.TK_SEMI, "expected ';' after for cond"); - n.rhs = parseexpr(p); + // 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); }; @@ -237,15 +255,14 @@ fn parsefor(p: *parser) *node = { errmsg(p, "expected name after 'let' in for"); }; - // for (cond) or for (cond; post) + // 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); - let first = parseexpr(p); - if (accepttok(p, tkind.TK_SEMI)) { - n.cond = first; - n.rhs = parseexpr(p); - } else { - n.cond = first; - }; + 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 diff --git a/test/wcc/data/forhdr_brace/case.ww b/test/wcc/data/forhdr_brace/case.ww new file mode 100644 index 00000000..9fe913ef --- /dev/null +++ b/test/wcc/data/forhdr_brace/case.ww @@ -0,0 +1,10 @@ +//ww:run-exit 5 +package main; +export fn main() i32 = { + let i: i32 = 0; + for { + i += 1; + if (i >= 5) { break; }; + }; + return i; +}; diff --git a/test/wcc/data/forhdr_emptymid/case.ww b/test/wcc/data/forhdr_emptymid/case.ww new file mode 100644 index 00000000..272d94ca --- /dev/null +++ b/test/wcc/data/forhdr_emptymid/case.ww @@ -0,0 +1,10 @@ +//ww:run-exit 10 +package main; +export fn main() i32 = { + let s: i32 = 0; + for (let i: i32 = 0;; i += 1) { + if (i >= 4) { break; }; + s += i; + }; + return s + 4; +}; diff --git a/test/wcc/data/forhdr_partial/case.ww b/test/wcc/data/forhdr_partial/case.ww new file mode 100644 index 00000000..2d4efef1 --- /dev/null +++ b/test/wcc/data/forhdr_partial/case.ww @@ -0,0 +1,20 @@ +//ww:run-exit 12 +package main; +export fn main() i32 = { + let a: i32 = 0; + for (let i: i32 = 0; i < 5) { + i += 1; + a = i; + }; + let b: i32 = 0; + for (let j: i32 = 0;) { + j += 1; + if (j >= 3) { break; }; + }; + b = 3; + let k: i32 = 0; + for (k < 4;) { + k += 1; + }; + return a + b + k; +}; diff --git a/test/wcc/data/forhdr_twoclause_reject/case.ww b/test/wcc/data/forhdr_twoclause_reject/case.ww new file mode 100644 index 00000000..bbbb7a0a --- /dev/null +++ b/test/wcc/data/forhdr_twoclause_reject/case.ww @@ -0,0 +1,12 @@ +//ww:error c "expected ), got IDENT" ww "expected ')' after for" +// 2-clause `for (cond; post)` is a form in neither Go nor the cstage +// grammar — the old wwstage accept was a parser bug (aligned DOWN, +// rule 10 / the Go-derivation ruling). Both parsers consume the stray +// ';' and then demand ')'. +package main; +export fn main() i32 = { + let i: i32 = 0; + for (i < 5; i += 1) { + }; + return i; +};