From f267f99a2bba509b38f3889859e17b16016d8be6 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 12 May 2026 03:08:00 +0900 Subject: [PATCH] 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. --- cmd/w6c/cgen.c | 20 ++++++++++++ cmd/wcc/ast.c | 1 + cmd/wcc/check.c | 43 ++++++++++++++++++++++++- cmd/wcc/parse.c | 7 +++++ cmd/wcc/tok.c | 4 ++- cmd/wcc/ww.h | 3 ++ lib/ww/ast.ww | 4 ++- lib/ww/lex/tok.ww | 9 ++++-- lib/ww/parse/stmt.ww | 7 +++++ selfhost/cmd/w6c/main.combined.ww | 47 +++++++++++++++++++++++++--- selfhost/cmd/wcc/cgen.ww | 4 +++ selfhost/cmd/wcc/cgenexpr.ww | 6 ++++ selfhost/cmd/wcc/cgenstmt.ww | 17 ++++++++++ selfhost/cmd/wwdump/main.combined.ww | 47 +++++++++++++++++++++++++--- test/wcc/700_e2e.c | 26 +++++++++++++++ 15 files changed, 231 insertions(+), 14 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index e5d09325..945cc0ea 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -47,6 +47,13 @@ static const char *loop_cont[LOOP_MAX]; static const char *loop_brk[LOOP_MAX]; static int nloops; +/* Yield-target stack. Each entry is the end label of an enclosing + * match-as-expression; `yield expr;` evaluates expr (AX) and JMPs + * to the topmost entry. */ +#define YIELD_MAX 16 +static const char *yield_target[YIELD_MAX]; +static int nyields; + static int cg_isfloat(Type *t) { @@ -1928,6 +1935,10 @@ cgexpr(Cg *c, Node *n, Local *locals) } } char *end = mklabel(c, "match_end"); + /* Push the end label as the yield target for arm bodies. */ + if (nyields < YIELD_MAX) { + yield_target[nyields++] = end; + } for (Node *cs = n->list; cs; cs = cs->next) { char *next = mklabel(c, "match_next"); if (cs->type != NULL) { @@ -2009,6 +2020,7 @@ cgexpr(Cg *c, Node *n, Local *locals) label(c, next); } label(c, end); + if (nyields > 0) nyields--; break; } case N_TRYPROP: { @@ -3254,6 +3266,14 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) defers[ndefers++] = n->lhs; } break; + case N_YIELD: + /* Evaluate the value into AX, then jump to the enclosing + * match's end label. str-typed yields land in (AX, BX); + * the consumer's let-init or call-arg site reads both. */ + if (n->lhs) cgexpr(c, n->lhs, *locals); + if (nyields > 0) + ins1(c, A_JMP, abranch(yield_target[nyields - 1])); + break; case N_BREAK: if (nloops > 0) ins1(c, A_JMP, abranch(loop_brk[nloops - 1])); diff --git a/cmd/wcc/ast.c b/cmd/wcc/ast.c index 8c39aaf5..dd569c14 100644 --- a/cmd/wcc/ast.c +++ b/cmd/wcc/ast.c @@ -85,6 +85,7 @@ nkname(Nkind k) case N_TYPEASSERT: return "typeassert"; case N_VOIDLIT: return "voidlit"; case N_TBANG: return "tbang"; + case N_YIELD: return "yield"; case N_LAST: return "last"; } return "?"; diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index c8cf0bb8..579eee83 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -99,6 +99,33 @@ variant_present(Tparam *head, Type *vt) return 0; } +/* match_yield_type — walk a match arm's body looking for the type + * of its first `yield expr;` statement. Returns NULL if no yield + * was found. Doesn't descend into nested match bodies — each match + * is its own yield scope. */ +static Type * +match_yield_type(Node *body) +{ + if (body == NULL) return NULL; + if (body->kind == N_YIELD) return body->lhs ? body->lhs->type : NULL; + if (body->kind == N_MATCH) return NULL; /* inner match: own scope */ + if (body->kind == N_BLOCK) { + for (Node *s = body->list; s; s = s->next) { + Type *t = match_yield_type(s); + if (t) return t; + } + return NULL; + } + if (body->kind == N_IF) { + Type *t = match_yield_type(body->body); + if (t) return t; + return match_yield_type(body->els); + } + if (body->kind == N_FOR || body->kind == N_FORRANGE) + return match_yield_type(body->body); + return NULL; +} + /* tagged_has_errflag — true iff any variant is `!`-marked. Determines * whether the union uses Hare's explicit error subset or the legacy * "first variant = success" convention. */ @@ -913,7 +940,20 @@ cexpr(Checker *c, Node *n) type_name(c->a, p->type)); } } - n->type = ty_void; + /* match-as-expression: the type is the common yield type + * across arms. If no arm yields, the match is a statement + * and its type is void. */ + Type *yt = NULL; + for (Node *cs = n->list; cs; cs = cs->next) { + Type *t = match_yield_type(cs->body); + if (t == NULL) continue; + if (yt == NULL) yt = t; + else if (!type_eq(yt, t) && !type_assignable(yt, t)) + err(c, cs->pos, + "match arm yields %s, expected %s", + type_name(c->a, t), type_name(c->a, yt)); + } + n->type = yt ? yt : ty_void; return n->type; } case N_TYPETEST: case N_TYPEASSERT: { @@ -1221,6 +1261,7 @@ cstmt(Checker *c, Node *n) break; } case N_DEFER: (void)cexpr(c, n->lhs); break; + case N_YIELD: if (n->lhs) (void)cexpr(c, n->lhs); break; case N_BREAK: case N_CONTINUE: if (c->loops == 0) diff --git a/cmd/wcc/parse.c b/cmd/wcc/parse.c index cebd789c..156c8f31 100644 --- a/cmd/wcc/parse.c +++ b/cmd/wcc/parse.c @@ -1121,6 +1121,13 @@ parsestmt(Parser *p) expect(p, TK_SEMI); return n; } + case TK_YIELD: { + advance(p); + Node *n = newnode(p->a, N_YIELD, pp); + n->lhs = parseexpr(p); + expect(p, TK_SEMI); + return n; + } case TK_BREAK: { advance(p); Node *n = newnode(p->a, N_BREAK, pp); diff --git a/cmd/wcc/tok.c b/cmd/wcc/tok.c index 1fb97773..bcf005cd 100644 --- a/cmd/wcc/tok.c +++ b/cmd/wcc/tok.c @@ -41,7 +41,8 @@ static const struct kwent kwtab[] = { { "true", TK_TRUE }, { "type", TK_TYPE }, { "use", TK_USE }, - { "void", TK_VOID } + { "void", TK_VOID }, + { "yield", TK_YIELD } }; Tkind @@ -93,6 +94,7 @@ tokname(Tkind k) case TK_AS: return "as"; case TK_IS: return "is"; case TK_VOID: return "void"; + case TK_YIELD: return "yield"; case TK_STATIC: return "static"; case TK_MATCH: return "match"; case TK_CONST: return "const"; diff --git a/cmd/wcc/ww.h b/cmd/wcc/ww.h index 62c2a30f..32a7a5fb 100644 --- a/cmd/wcc/ww.h +++ b/cmd/wcc/ww.h @@ -177,6 +177,7 @@ typedef enum { * the selfhost wwdump-diff test (990) is byte-sensitive. */ TK_IS, /* Hare-style type test: e is T */ TK_VOID, /* `void` — both a type name and a zero-size value */ + TK_YIELD, /* `yield expr;` — value-return from a match arm */ TK_LAST /* sentinel for tables */ } Tkind; @@ -297,6 +298,8 @@ typedef enum { N_TYPEASSERT, /* lhs as T → T (abort if tag mismatch) */ N_VOIDLIT, /* `void` as expression — zero-size void value */ N_TBANG, /* `!T` — error-flagged type. lhs = inner type. */ + N_YIELD, /* `yield expr;` — set the enclosing match's value- + * return and jump to its end label. lhs = value. */ N_LAST } Nkind; diff --git a/lib/ww/ast.ww b/lib/ww/ast.ww index bdef1ad4..3fa295f2 100644 --- a/lib/ww/ast.ww +++ b/lib/ww/ast.ww @@ -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 "?"; }; diff --git a/lib/ww/lex/tok.ww b/lib/ww/lex/tok.ww index 2b240ed7..0c5542d2 100644 --- a/lib/ww/lex/tok.ww +++ b/lib/ww/lex/tok.ww @@ -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"; }; diff --git a/lib/ww/parse/stmt.ww b/lib/ww/parse/stmt.ww index 2b8e7386..ddba0929 100644 --- a/lib/ww/parse/stmt.ww +++ b/lib/ww/parse/stmt.ww @@ -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"); diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 3e6b3798..19464afc 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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 diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index 1e527d5e..82fe3393 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -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 diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 56ed730c..110e47ed 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -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; }; diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index 0ddef7ce..6a0db10e 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -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) { diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index cefbf3a7..d3280381 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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 diff --git a/test/wcc/700_e2e.c b/test/wcc/700_e2e.c index 0aef6401..318963d0 100644 --- a/test/wcc/700_e2e.c +++ b/test/wcc/700_e2e.c @@ -538,6 +538,32 @@ static const struct row rows[] = { " };\n" " return 0;\n" "};", 3 }, + /* yield from match-as-expression: each arm yields a value; + * the match itself is bound to a let. */ + { "fn pick(b: bool) (i32 | str) = {\n" + " if (b) { return 7; };\n" + " return \"abc\";\n" + "};\n" + "fn main() i32 = {\n" + " let r: (i32 | str) = pick(true);\n" + " let v: i32 = match (r) {\n" + " case let n: i32 => yield n + 1;\n" + " case let s: str => yield s.len: i32 + 100;\n" + " };\n" + " return v;\n" + "};", 8 }, + { "fn pick(b: bool) (i32 | str) = {\n" + " if (b) { return 7; };\n" + " return \"abc\";\n" + "};\n" + "fn main() i32 = {\n" + " let r: (i32 | str) = pick(false);\n" + " let v: i32 = match (r) {\n" + " case let n: i32 => yield n + 1;\n" + " case let s: str => yield s.len: i32 + 100;\n" + " };\n" + " return v;\n" + "};", 103 }, /* Nullable pointer folding: `(*T | void)` is one 8-byte word * where null = void variant. match/is/as/?/! all key off the * pointer-vs-null discriminator instead of a separate tag. */