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:
2026-05-12 03:08:00 +09:00
parent 67e27589fd
commit f267f99a2b
15 changed files with 231 additions and 14 deletions

View File

@@ -90,8 +90,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 -------------------------------------------------------------
@@ -194,6 +195,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 "?";
};

View File

@@ -108,10 +108,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 --------------------------------------------------------
//
@@ -183,6 +184,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;
};
@@ -225,6 +227,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"; };

View File

@@ -233,6 +233,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");