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:
@@ -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]));
|
||||
|
||||
@@ -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 "?";
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user