wcc: match-as-expression with yield
`match (e) { ... }` can now sit in expression position, with each
arm using `yield expr;` to produce the match's value:
let v = match (r) {
case let n: i32 => yield n + 1;
case let s: str => yield s.len: i32 + 100;
};
TK_YIELD keyword + N_YIELD AST node, both appended at the tail of
their enums to keep prior numeric values byte-stable for the
wwdump-diff gates.
Checker: cexpr for N_MATCH walks each arm's body looking for the
first N_YIELD; the match's type is the unified yield type (or
ty_void if no yield, preserving the statement-form semantics).
Mismatched arm yields are flagged.
Cgen: a yield-target stack (separate from the loop break stack)
holds each enclosing match's end label. N_YIELD evaluates its
expression into AX (and BX for str) and JMPs to the topmost entry.
cgmatch pushes its end label on entry and pops on exit.
Selfhost mirror: lib/ww/lex/tok.ww kwtab+name, lib/ww/ast.ww
N_YIELD def+print, lib/ww/parse/stmt.ww yield-stmt; selfhost cgen
adds a yieldbuf to the cgen struct and a cgyield helper. Verified
end-to-end: a yield-using program compiled via the wwstage cgen
matches the C-cgen build's exit code.
This commit is contained in:
@@ -543,10 +543,11 @@ def TK_FATARROW: i32 = 81;
|
||||
// Appended at the tail (not grouped with the keyword block) so every
|
||||
// pre-existing TK_* value stays unchanged — the 990_selfhost test
|
||||
// diffs wwdump output against the C side, byte for byte.
|
||||
def TK_IS: i32 = 82;
|
||||
def TK_VOID: i32 = 83;
|
||||
def TK_IS: i32 = 82;
|
||||
def TK_VOID: i32 = 83;
|
||||
def TK_YIELD: i32 = 84;
|
||||
|
||||
def TK_LAST: i32 = 84;
|
||||
def TK_LAST: i32 = 85;
|
||||
|
||||
// ---- Pos / Tok --------------------------------------------------------
|
||||
//
|
||||
@@ -618,6 +619,7 @@ export fn kwlookup(p: *u8, n: i32) i32 = {
|
||||
if (streqn(p, "type", n)) { return TK_TYPE; };
|
||||
if (streqn(p, "use", n)) { return TK_USE; };
|
||||
if (streqn(p, "void", n)) { return TK_VOID; };
|
||||
if (streqn(p, "yield", n)) { return TK_YIELD; };
|
||||
return TK_NONE;
|
||||
};
|
||||
|
||||
@@ -660,6 +662,7 @@ export fn tokname(k: i32) str = {
|
||||
if (k == TK_AS) { return "as"; };
|
||||
if (k == TK_IS) { return "is"; };
|
||||
if (k == TK_VOID) { return "void"; };
|
||||
if (k == TK_YIELD) { return "yield"; };
|
||||
if (k == TK_STATIC) { return "static"; };
|
||||
if (k == TK_MATCH) { return "match"; };
|
||||
if (k == TK_CONST) { return "const"; };
|
||||
@@ -1732,8 +1735,9 @@ def N_TYPETEST: i32 = 60;
|
||||
def N_TYPEASSERT: i32 = 61;
|
||||
def N_VOIDLIT: i32 = 62;
|
||||
def N_TBANG: i32 = 63;
|
||||
def N_YIELD: i32 = 64;
|
||||
|
||||
def N_LAST: i32 = 64;
|
||||
def N_LAST: i32 = 65;
|
||||
|
||||
// ---- Node -------------------------------------------------------------
|
||||
|
||||
@@ -1836,6 +1840,7 @@ fn nkname(k: i32) str = {
|
||||
if (k == N_TYPEASSERT) { return "typeassert"; };
|
||||
if (k == N_VOIDLIT) { return "voidlit"; };
|
||||
if (k == N_TBANG) { return "tbang"; };
|
||||
if (k == N_YIELD) { return "yield"; };
|
||||
if (k == N_LAST) { return "last"; };
|
||||
return "?";
|
||||
};
|
||||
@@ -2664,6 +2669,13 @@ fn parsestmt(p: *parser) *node = {
|
||||
expecttok(p, TK_SEMI, "expected ';' after defer");
|
||||
return n;
|
||||
};
|
||||
if (p.curkind == TK_YIELD) {
|
||||
advance(p);
|
||||
let n: *node = newnode(p.a, N_YIELD, pf, pl, pc);
|
||||
n.lhs = parseexpr(p);
|
||||
expecttok(p, TK_SEMI, "expected ';' after yield");
|
||||
return n;
|
||||
};
|
||||
if (p.curkind == TK_BREAK) {
|
||||
advance(p);
|
||||
expecttok(p, TK_SEMI, "expected ';' after break");
|
||||
@@ -5419,6 +5431,11 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
let endl: str = mklabel(c, "match_end");
|
||||
// Push end label as the yield target for this match's arm bodies.
|
||||
if (c.yieldtop < LOOP_MAX) {
|
||||
c.yieldbuf[c.yieldtop] = endl;
|
||||
c.yieldtop += 1;
|
||||
};
|
||||
let cs: *node = n.list;
|
||||
for (cs != nil) {
|
||||
let nxt: str = mklabel(c, "match_next");
|
||||
@@ -5534,6 +5551,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
cs = cs.next;
|
||||
};
|
||||
emitlabel(endl);
|
||||
if (c.yieldtop > 0) { c.yieldtop -= 1; };
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -6522,9 +6540,26 @@ fn cgstmt(c: *cgen, n: *node) void = {
|
||||
if (k == N_BREAK) { cgbreak(c, n); return; };
|
||||
if (k == N_CONTINUE) { cgcontinue(c, n); return; };
|
||||
|
||||
if (k == N_YIELD) { cgyield(c, n); return; };
|
||||
|
||||
c.lastwasreturn = 0;
|
||||
};
|
||||
|
||||
fn cgyield(c: *cgen, n: *node) void = {
|
||||
// Evaluate the value into AX (and BX for str), then JMP to the
|
||||
// enclosing match's end label. Falls through silently if there
|
||||
// is no active match — should be a checker error eventually.
|
||||
if (n.lhs != nil) { cgexpr(c, n.lhs); };
|
||||
if (c.yieldtop > 0) {
|
||||
let tgt: str = c.yieldbuf[c.yieldtop - 1];
|
||||
emitline("\tJMP\t");
|
||||
emitline(tgt);
|
||||
emitline("\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
|
||||
fn cgblock(c: *cgen, n: *node) void = {
|
||||
let s: *node = n.list;
|
||||
for (s != nil) {
|
||||
@@ -7631,6 +7666,8 @@ type cgen = struct {
|
||||
looptop: i32,
|
||||
loopendbuf: *str, // stack of end labels for break
|
||||
loopcontbuf: *str, // stack of cont labels for continue
|
||||
yieldtop: i32,
|
||||
yieldbuf: *str, // stack of match end labels for yield
|
||||
};
|
||||
|
||||
fn cgeninit(c: *cgen, a: *arena) void = {
|
||||
@@ -7645,6 +7682,8 @@ fn cgeninit(c: *cgen, a: *arena) void = {
|
||||
c.looptop = 0;
|
||||
c.loopendbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str;
|
||||
c.loopcontbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str;
|
||||
c.yieldtop = 0;
|
||||
c.yieldbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str;
|
||||
};
|
||||
|
||||
// localalloc — append a slot for `name` without dedup. Used for
|
||||
|
||||
@@ -165,6 +165,8 @@ type cgen = struct {
|
||||
looptop: i32,
|
||||
loopendbuf: *str, // stack of end labels for break
|
||||
loopcontbuf: *str, // stack of cont labels for continue
|
||||
yieldtop: i32,
|
||||
yieldbuf: *str, // stack of match end labels for yield
|
||||
};
|
||||
|
||||
fn cgeninit(c: *cgen, a: *arena) void = {
|
||||
@@ -179,6 +181,8 @@ fn cgeninit(c: *cgen, a: *arena) void = {
|
||||
c.looptop = 0;
|
||||
c.loopendbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str;
|
||||
c.loopcontbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str;
|
||||
c.yieldtop = 0;
|
||||
c.yieldbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str;
|
||||
};
|
||||
|
||||
// localalloc — append a slot for `name` without dedup. Used for
|
||||
|
||||
@@ -438,6 +438,11 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
let endl: str = mklabel(c, "match_end");
|
||||
// Push end label as the yield target for this match's arm bodies.
|
||||
if (c.yieldtop < LOOP_MAX) {
|
||||
c.yieldbuf[c.yieldtop] = endl;
|
||||
c.yieldtop += 1;
|
||||
};
|
||||
let cs: *node = n.list;
|
||||
for (cs != nil) {
|
||||
let nxt: str = mklabel(c, "match_next");
|
||||
@@ -553,6 +558,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
cs = cs.next;
|
||||
};
|
||||
emitlabel(endl);
|
||||
if (c.yieldtop > 0) { c.yieldtop -= 1; };
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -41,9 +41,26 @@ fn cgstmt(c: *cgen, n: *node) void = {
|
||||
if (k == N_BREAK) { cgbreak(c, n); return; };
|
||||
if (k == N_CONTINUE) { cgcontinue(c, n); return; };
|
||||
|
||||
if (k == N_YIELD) { cgyield(c, n); return; };
|
||||
|
||||
c.lastwasreturn = 0;
|
||||
};
|
||||
|
||||
fn cgyield(c: *cgen, n: *node) void = {
|
||||
// Evaluate the value into AX (and BX for str), then JMP to the
|
||||
// enclosing match's end label. Falls through silently if there
|
||||
// is no active match — should be a checker error eventually.
|
||||
if (n.lhs != nil) { cgexpr(c, n.lhs); };
|
||||
if (c.yieldtop > 0) {
|
||||
let tgt: str = c.yieldbuf[c.yieldtop - 1];
|
||||
emitline("\tJMP\t");
|
||||
emitline(tgt);
|
||||
emitline("\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
|
||||
fn cgblock(c: *cgen, n: *node) void = {
|
||||
let s: *node = n.list;
|
||||
for (s != nil) {
|
||||
|
||||
@@ -543,10 +543,11 @@ def TK_FATARROW: i32 = 81;
|
||||
// Appended at the tail (not grouped with the keyword block) so every
|
||||
// pre-existing TK_* value stays unchanged — the 990_selfhost test
|
||||
// diffs wwdump output against the C side, byte for byte.
|
||||
def TK_IS: i32 = 82;
|
||||
def TK_VOID: i32 = 83;
|
||||
def TK_IS: i32 = 82;
|
||||
def TK_VOID: i32 = 83;
|
||||
def TK_YIELD: i32 = 84;
|
||||
|
||||
def TK_LAST: i32 = 84;
|
||||
def TK_LAST: i32 = 85;
|
||||
|
||||
// ---- Pos / Tok --------------------------------------------------------
|
||||
//
|
||||
@@ -618,6 +619,7 @@ export fn kwlookup(p: *u8, n: i32) i32 = {
|
||||
if (streqn(p, "type", n)) { return TK_TYPE; };
|
||||
if (streqn(p, "use", n)) { return TK_USE; };
|
||||
if (streqn(p, "void", n)) { return TK_VOID; };
|
||||
if (streqn(p, "yield", n)) { return TK_YIELD; };
|
||||
return TK_NONE;
|
||||
};
|
||||
|
||||
@@ -660,6 +662,7 @@ export fn tokname(k: i32) str = {
|
||||
if (k == TK_AS) { return "as"; };
|
||||
if (k == TK_IS) { return "is"; };
|
||||
if (k == TK_VOID) { return "void"; };
|
||||
if (k == TK_YIELD) { return "yield"; };
|
||||
if (k == TK_STATIC) { return "static"; };
|
||||
if (k == TK_MATCH) { return "match"; };
|
||||
if (k == TK_CONST) { return "const"; };
|
||||
@@ -1732,8 +1735,9 @@ def N_TYPETEST: i32 = 60;
|
||||
def N_TYPEASSERT: i32 = 61;
|
||||
def N_VOIDLIT: i32 = 62;
|
||||
def N_TBANG: i32 = 63;
|
||||
def N_YIELD: i32 = 64;
|
||||
|
||||
def N_LAST: i32 = 64;
|
||||
def N_LAST: i32 = 65;
|
||||
|
||||
// ---- Node -------------------------------------------------------------
|
||||
|
||||
@@ -1836,6 +1840,7 @@ fn nkname(k: i32) str = {
|
||||
if (k == N_TYPEASSERT) { return "typeassert"; };
|
||||
if (k == N_VOIDLIT) { return "voidlit"; };
|
||||
if (k == N_TBANG) { return "tbang"; };
|
||||
if (k == N_YIELD) { return "yield"; };
|
||||
if (k == N_LAST) { return "last"; };
|
||||
return "?";
|
||||
};
|
||||
@@ -2664,6 +2669,13 @@ fn parsestmt(p: *parser) *node = {
|
||||
expecttok(p, TK_SEMI, "expected ';' after defer");
|
||||
return n;
|
||||
};
|
||||
if (p.curkind == TK_YIELD) {
|
||||
advance(p);
|
||||
let n: *node = newnode(p.a, N_YIELD, pf, pl, pc);
|
||||
n.lhs = parseexpr(p);
|
||||
expecttok(p, TK_SEMI, "expected ';' after yield");
|
||||
return n;
|
||||
};
|
||||
if (p.curkind == TK_BREAK) {
|
||||
advance(p);
|
||||
expecttok(p, TK_SEMI, "expected ';' after break");
|
||||
@@ -5419,6 +5431,11 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
let endl: str = mklabel(c, "match_end");
|
||||
// Push end label as the yield target for this match's arm bodies.
|
||||
if (c.yieldtop < LOOP_MAX) {
|
||||
c.yieldbuf[c.yieldtop] = endl;
|
||||
c.yieldtop += 1;
|
||||
};
|
||||
let cs: *node = n.list;
|
||||
for (cs != nil) {
|
||||
let nxt: str = mklabel(c, "match_next");
|
||||
@@ -5534,6 +5551,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
cs = cs.next;
|
||||
};
|
||||
emitlabel(endl);
|
||||
if (c.yieldtop > 0) { c.yieldtop -= 1; };
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -6522,9 +6540,26 @@ fn cgstmt(c: *cgen, n: *node) void = {
|
||||
if (k == N_BREAK) { cgbreak(c, n); return; };
|
||||
if (k == N_CONTINUE) { cgcontinue(c, n); return; };
|
||||
|
||||
if (k == N_YIELD) { cgyield(c, n); return; };
|
||||
|
||||
c.lastwasreturn = 0;
|
||||
};
|
||||
|
||||
fn cgyield(c: *cgen, n: *node) void = {
|
||||
// Evaluate the value into AX (and BX for str), then JMP to the
|
||||
// enclosing match's end label. Falls through silently if there
|
||||
// is no active match — should be a checker error eventually.
|
||||
if (n.lhs != nil) { cgexpr(c, n.lhs); };
|
||||
if (c.yieldtop > 0) {
|
||||
let tgt: str = c.yieldbuf[c.yieldtop - 1];
|
||||
emitline("\tJMP\t");
|
||||
emitline(tgt);
|
||||
emitline("\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
|
||||
fn cgblock(c: *cgen, n: *node) void = {
|
||||
let s: *node = n.list;
|
||||
for (s != nil) {
|
||||
@@ -7631,6 +7666,8 @@ type cgen = struct {
|
||||
looptop: i32,
|
||||
loopendbuf: *str, // stack of end labels for break
|
||||
loopcontbuf: *str, // stack of cont labels for continue
|
||||
yieldtop: i32,
|
||||
yieldbuf: *str, // stack of match end labels for yield
|
||||
};
|
||||
|
||||
fn cgeninit(c: *cgen, a: *arena) void = {
|
||||
@@ -7645,6 +7682,8 @@ fn cgeninit(c: *cgen, a: *arena) void = {
|
||||
c.looptop = 0;
|
||||
c.loopendbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str;
|
||||
c.loopcontbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str;
|
||||
c.yieldtop = 0;
|
||||
c.yieldbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str;
|
||||
};
|
||||
|
||||
// localalloc — append a slot for `name` without dedup. Used for
|
||||
|
||||
Reference in New Issue
Block a user