selfhost: port forrange — N_FORRANGE parser + cgen + tuple destructure

This commit is contained in:
2026-05-12 16:21:13 +09:00
parent ce5d66e18a
commit e087c843e9
8 changed files with 1280 additions and 66 deletions

View File

@@ -140,31 +140,100 @@ fn parsefor(p: *parser) *node = {
let pc: i32 = p.curcol;
advance(p); // past `for`
expecttok(p, tkind.TK_LPAREN, "expected '(' after for");
let n: *node = newnode(p.a, nkind.N_FOR, pf, pl, pc);
// Three forms (matching C parser):
// for (cond) — only cond
// for (init; cond; post) — full
// for (true) — infinite (cond is nkind.N_TRUE)
// Distinguish by counting ';'. Look at first chunk: if it's a
// `let` stmt that's the init. Otherwise, parse expr; if next is
// ';' it was cond. If we see two ';' total after init, post is
// next. Simpler: peek for `let` to decide init form.
// Four forms (matching C parser):
// 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
// 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) {
n.lhs = parseletlocal(p); // init (consumes its own ';')
n.cond = parseexpr(p);
expecttok(p, tkind.TK_SEMI, "expected ';' after for cond");
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: str = p.curfile;
let npl: i32 = p.curline;
let npc: i32 = p.curcol;
let e: *node = newnode(p.a, 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: *node = newnode(p.a, 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: bool = (p.curkind == tkind.TK_UNDER);
let nm: str;
nm.ptr = nil; nm.len = 0;
if (!isunder) { nm = p.curtext; };
let lpf: str = p.curfile;
let lpl: i32 = p.curline;
let lpc: i32 = p.curcol;
advance(p); // consume IDENT/UNDER
if (p.curkind == tkind.TK_DOTDOT) {
advance(p);
let rng: *node = newnode(p.a, 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: *node = newnode(p.a, 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: *node = newnode(p.a, 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);
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) or for (cond; post)
let n: *node = newnode(p.a, nkind.N_FOR, pf, pl, pc);
let first: *node = parseexpr(p);
if (accepttok(p, tkind.TK_SEMI)) {
n.cond = first;
n.rhs = parseexpr(p);
} else {
// Parse one expr. If next is ';', it's a 3-clause without init.
let first: *node = parseexpr(p);
if (accepttok(p, tkind.TK_SEMI)) {
// cond ; post
n.cond = first;
n.rhs = parseexpr(p);
} else {
// just (cond)
n.cond = first;
};
n.cond = first;
};
expecttok(p, tkind.TK_RPAREN, "expected ')' after for");
n.body = parseblock(p);