ww: add Hare-style is/as postfix ops on tagged unions
`e is T` returns bool (variant tag == T's index); `e as T` unwraps to T or exit(1) on mismatch. Postfix, same precedence as `:` cast. TK_IS / N_TYPETEST / N_TYPEASSERT appended at the tail of their enums so every prior numeric value stays unchanged — the 990_selfhost wwdump-diff stays byte-clean. Cgen mirrors the match-case slot-based load (tag at +0, value at +8/+16), so an N_IDENT tagged-union local works just like a match scrutinee. Selfhost cgen inlines the slot resolution because the wwstage cgen drops sign bits on `*i32` output parameters in this position. Renames `errors.is` -> `errors.equal` (the only naming collision; the existing comment already noted it shared shape with strings.equal/bytes.equal).
This commit is contained in:
@@ -1936,6 +1936,71 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
ins2(c, A_MOVQ, areg(D_DX), areg(D_AX));
|
ins2(c, A_MOVQ, areg(D_DX), areg(D_AX));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case N_TYPETEST: {
|
||||||
|
/* `e is T` — Compare scrutinee tag against T's variant index.
|
||||||
|
* Result is bool (0/1) in AX. We only need the tag, not the
|
||||||
|
* value words, so cgexpr's AX-only result is enough for both
|
||||||
|
* register-returning callers (AX=tag) and N_IDENT locals
|
||||||
|
* (cgexpr loads slot+0 → AX, which is the tag). */
|
||||||
|
cgexpr(c, n->lhs, locals);
|
||||||
|
Type *u = n->lhs ? n->lhs->type : NULL;
|
||||||
|
if (u && u->kind == TY_NAMED) u = u->under;
|
||||||
|
Type *vt = n->rhs ? n->rhs->type : NULL;
|
||||||
|
int tag = cg_tag_for_variant(u, vt);
|
||||||
|
char *ne = mklabel(c, "is_ne");
|
||||||
|
char *done = mklabel(c, "is_done");
|
||||||
|
ins2(c, A_CMPQ, aimm(tag < 0 ? 0 : tag), areg(D_AX));
|
||||||
|
ins1(c, A_JNE, abranch(ne));
|
||||||
|
ins2(c, A_MOVQ, aimm(1), areg(D_AX));
|
||||||
|
ins1(c, A_JMP, abranch(done));
|
||||||
|
label(c, ne);
|
||||||
|
ins2(c, A_MOVQ, aimm(0), areg(D_AX));
|
||||||
|
label(c, done);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case N_TYPEASSERT: {
|
||||||
|
/* `e as T` — abort if tag != T's variant index; otherwise
|
||||||
|
* unwrap value to T's ABI: scalar/ptr variants land in AX;
|
||||||
|
* 16B str variants in AX:BX.
|
||||||
|
*
|
||||||
|
* We need both tag *and* value words. For an N_IDENT local
|
||||||
|
* the value lives at slot+8/+16 — cgexpr's single-MOVQ path
|
||||||
|
* does not load it. Mirror match's pattern: resolve a slot
|
||||||
|
* offset (existing local or a fresh @asrt_spill) and index
|
||||||
|
* out tag/value from memory. */
|
||||||
|
Node *s = n->lhs;
|
||||||
|
Type *st = s ? s->type : NULL;
|
||||||
|
Type *u = (st && st->kind == TY_NAMED) ? st->under : st;
|
||||||
|
Type *vt = n->type;
|
||||||
|
int tag = cg_tag_for_variant(u, vt);
|
||||||
|
int slot_size = (u && u->kind == TY_TAGGED) ? (int)u->size : 16;
|
||||||
|
int sl_off = 0;
|
||||||
|
if (s && s->kind == N_IDENT && s->str) {
|
||||||
|
sl_off = localfind(locals, s->str);
|
||||||
|
}
|
||||||
|
if (sl_off == 0) {
|
||||||
|
sl_off = localoff(c, &locals, "@asrt_spill",
|
||||||
|
slot_size, cg_frame);
|
||||||
|
cgexpr(c, s, locals);
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sl_off + 0));
|
||||||
|
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, sl_off + 8));
|
||||||
|
if (slot_size > 16)
|
||||||
|
ins2(c, A_MOVQ, areg(D_CX),
|
||||||
|
amem(D_BP, sl_off + 16));
|
||||||
|
}
|
||||||
|
char *ok = mklabel(c, "asrt_ok");
|
||||||
|
ins2(c, A_MOVQ, amem(D_BP, sl_off + 0), areg(D_AX));
|
||||||
|
ins2(c, A_CMPQ, aimm(tag < 0 ? 0 : tag), areg(D_AX));
|
||||||
|
ins1(c, A_JE, abranch(ok));
|
||||||
|
ins2(c, A_MOVQ, aimm(1), areg(D_DI));
|
||||||
|
ins2(c, A_MOVQ, aimm(60), areg(D_AX));
|
||||||
|
ins0(c, A_SYSCALL);
|
||||||
|
label(c, ok);
|
||||||
|
ins2(c, A_MOVQ, amem(D_BP, sl_off + 8), areg(D_AX));
|
||||||
|
if (type_isstr(vt))
|
||||||
|
ins2(c, A_MOVQ, amem(D_BP, sl_off + 16), areg(D_BX));
|
||||||
|
break;
|
||||||
|
}
|
||||||
case N_CAST: {
|
case N_CAST: {
|
||||||
int from_f = node_isfloat(n->lhs);
|
int from_f = node_isfloat(n->lhs);
|
||||||
int to_f = cg_isfloat(n->type);
|
int to_f = cg_isfloat(n->type);
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ nkname(Nkind k)
|
|||||||
case N_TRYUNW: return "tryunw";
|
case N_TRYUNW: return "tryunw";
|
||||||
case N_MLET: return "mlet";
|
case N_MLET: return "mlet";
|
||||||
case N_MASSIGN: return "massign";
|
case N_MASSIGN: return "massign";
|
||||||
|
case N_TYPETEST: return "typetest";
|
||||||
|
case N_TYPEASSERT: return "typeassert";
|
||||||
case N_LAST: return "last";
|
case N_LAST: return "last";
|
||||||
}
|
}
|
||||||
return "?";
|
return "?";
|
||||||
|
|||||||
@@ -710,6 +710,48 @@ cexpr(Checker *c, Node *n)
|
|||||||
n->type = ty_void;
|
n->type = ty_void;
|
||||||
return n->type;
|
return n->type;
|
||||||
}
|
}
|
||||||
|
case N_TYPETEST: case N_TYPEASSERT: {
|
||||||
|
/* `e is T` → bool; `e as T` → T.
|
||||||
|
* Requires lhs to be a tagged union and T to be one of its
|
||||||
|
* variants. The variant-index lookup lives in cgen (it knows
|
||||||
|
* NAMED-vs-structural matching for the success-variant rules);
|
||||||
|
* here we just check the LHS shape and resolve T. */
|
||||||
|
Type *t = cexpr(c, n->lhs);
|
||||||
|
Type *vt = resolve_type(c, n->rhs);
|
||||||
|
/* Stash the variant on rhs->type — cgen reads it uniformly
|
||||||
|
* whether the expression returns bool (is) or the variant
|
||||||
|
* itself (as). */
|
||||||
|
if (n->rhs) n->rhs->type = vt;
|
||||||
|
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||||
|
if (u == NULL || u->kind != TY_TAGGED) {
|
||||||
|
const char *op = (n->kind == N_TYPETEST) ? "is" : "as";
|
||||||
|
return n->type = err(c, n->pos,
|
||||||
|
"%s on non-tagged-union %s", op,
|
||||||
|
type_name(c->a, t));
|
||||||
|
}
|
||||||
|
/* Diagnostic-only: verify T appears as a variant. Mirrors
|
||||||
|
* cg_variant_match (NAMED ≡ pointer-identical, otherwise
|
||||||
|
* structural). Skipped silently if vt is ty_err. */
|
||||||
|
if (vt && vt != ty_err) {
|
||||||
|
int found = 0;
|
||||||
|
for (Tparam *p = u->params; p; p = p->next) {
|
||||||
|
if (p->type == NULL) continue;
|
||||||
|
if (p->type->kind == TY_NAMED &&
|
||||||
|
vt->kind == TY_NAMED) {
|
||||||
|
if (p->type == vt) { found = 1; break; }
|
||||||
|
} else if (p->type->kind == TY_NAMED ||
|
||||||
|
vt->kind == TY_NAMED) {
|
||||||
|
continue;
|
||||||
|
} else if (type_eq(p->type, vt)) {
|
||||||
|
found = 1; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found)
|
||||||
|
err(c, n->pos, "%s is not a variant of %s",
|
||||||
|
type_name(c->a, vt), type_name(c->a, t));
|
||||||
|
}
|
||||||
|
return n->type = (n->kind == N_TYPETEST) ? ty_bool : vt;
|
||||||
|
}
|
||||||
case N_TRYPROP: case N_TRYUNW: {
|
case N_TRYPROP: case N_TRYUNW: {
|
||||||
Type *t = cexpr(c, n->lhs);
|
Type *t = cexpr(c, n->lhs);
|
||||||
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||||
|
|||||||
@@ -670,6 +670,22 @@ parsepostfix(Parser *p, Node *lhs)
|
|||||||
lhs = n;
|
lhs = n;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case TK_AS:
|
||||||
|
case TK_IS: {
|
||||||
|
/* Hare-style:
|
||||||
|
* e as T — assert lhs is variant T of its tagged
|
||||||
|
* union; abort if not. Yields T.
|
||||||
|
* e is T — bool: does lhs currently hold variant T?
|
||||||
|
* Postfix, same level as `:` cast. */
|
||||||
|
Tkind k = p->cur.kind;
|
||||||
|
advance(p);
|
||||||
|
Node *n = newnode(p->a,
|
||||||
|
k == TK_AS ? N_TYPEASSERT : N_TYPETEST, pp);
|
||||||
|
n->lhs = lhs;
|
||||||
|
n->rhs = parsetype(p);
|
||||||
|
lhs = n;
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return lhs;
|
return lhs;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ static const struct kwent kwtab[] = {
|
|||||||
{ "fn", TK_FN },
|
{ "fn", TK_FN },
|
||||||
{ "for", TK_FOR },
|
{ "for", TK_FOR },
|
||||||
{ "if", TK_IF },
|
{ "if", TK_IF },
|
||||||
|
{ "is", TK_IS },
|
||||||
{ "let", TK_LET },
|
{ "let", TK_LET },
|
||||||
{ "match", TK_MATCH },
|
{ "match", TK_MATCH },
|
||||||
{ "nil", TK_NIL },
|
{ "nil", TK_NIL },
|
||||||
@@ -89,6 +90,7 @@ tokname(Tkind k)
|
|||||||
case TK_TRUE: return "true";
|
case TK_TRUE: return "true";
|
||||||
case TK_FALSE: return "false";
|
case TK_FALSE: return "false";
|
||||||
case TK_AS: return "as";
|
case TK_AS: return "as";
|
||||||
|
case TK_IS: return "is";
|
||||||
case TK_STATIC: return "static";
|
case TK_STATIC: return "static";
|
||||||
case TK_MATCH: return "match";
|
case TK_MATCH: return "match";
|
||||||
case TK_CONST: return "const";
|
case TK_CONST: return "const";
|
||||||
|
|||||||
13
cmd/wcc/ww.h
13
cmd/wcc/ww.h
@@ -111,7 +111,7 @@ typedef enum {
|
|||||||
TK_NIL,
|
TK_NIL,
|
||||||
TK_TRUE,
|
TK_TRUE,
|
||||||
TK_FALSE,
|
TK_FALSE,
|
||||||
TK_AS, /* reserved for future cast spelling, not active */
|
TK_AS, /* Hare-style type assertion: e as T */
|
||||||
TK_STATIC, /* Hare-style storage-class qualifier */
|
TK_STATIC, /* Hare-style storage-class qualifier */
|
||||||
TK_MATCH, /* match expression head */
|
TK_MATCH, /* match expression head */
|
||||||
TK_CONST, /* const binding */
|
TK_CONST, /* const binding */
|
||||||
@@ -172,6 +172,11 @@ typedef enum {
|
|||||||
TK_ARROW, /* -> (reserved) */
|
TK_ARROW, /* -> (reserved) */
|
||||||
TK_FATARROW, /* => (match arms) */
|
TK_FATARROW, /* => (match arms) */
|
||||||
|
|
||||||
|
/* Appended after TK_FATARROW (not grouped with the keyword block)
|
||||||
|
* to keep the numeric value of every existing kind unchanged —
|
||||||
|
* the selfhost wwdump-diff test (990) is byte-sensitive. */
|
||||||
|
TK_IS, /* Hare-style type test: e is T */
|
||||||
|
|
||||||
TK_LAST /* sentinel for tables */
|
TK_LAST /* sentinel for tables */
|
||||||
} Tkind;
|
} Tkind;
|
||||||
|
|
||||||
@@ -284,6 +289,12 @@ typedef enum {
|
|||||||
N_MLET, /* let a, b = expr; list = N_LET stubs (str, lhs=type), rhs = expr */
|
N_MLET, /* let a, b = expr; list = N_LET stubs (str, lhs=type), rhs = expr */
|
||||||
N_MASSIGN, /* a, b = expr; list = lvalue exprs, rhs = expr */
|
N_MASSIGN, /* a, b = expr; list = lvalue exprs, rhs = expr */
|
||||||
|
|
||||||
|
/* Appended after N_MASSIGN — keeps the numeric value of every
|
||||||
|
* existing kind unchanged, so the selfhost AST dump still diffs
|
||||||
|
* byte-for-byte. lhs=value, rhs=variant type expr. */
|
||||||
|
N_TYPETEST, /* lhs is T → bool */
|
||||||
|
N_TYPEASSERT, /* lhs as T → T (abort if tag mismatch) */
|
||||||
|
|
||||||
N_LAST
|
N_LAST
|
||||||
} Nkind;
|
} Nkind;
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export fn avail(b: *buf) i32 = {
|
|||||||
|
|
||||||
// Distinct alias so `(str | linerr)` has two variant types the
|
// Distinct alias so `(str | linerr)` has two variant types the
|
||||||
// tagged-union machinery can keep apart at the tag level. The error
|
// tagged-union machinery can keep apart at the tag level. The error
|
||||||
// variant carries a short description; callers compare with errors.is
|
// variant carries a short description; callers compare with errors.equal
|
||||||
// or just inspect by length.
|
// or just inspect by length.
|
||||||
type linerr = str;
|
type linerr = str;
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ export fn isnil(e: error) bool = {
|
|||||||
return e.len == 0;
|
return e.len == 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// is — compare an error against a sentinel (or any other error). Pure
|
// equal — compare an error against a sentinel (or any other error).
|
||||||
// byte equality; same shape as strings.equal but kept here so callers
|
// Pure byte equality. (Was `errors.is` before `is` became a keyword
|
||||||
// don't have to pull in strings just to compare.
|
// for tagged-union type-tests; rename matches bytes.equal / strings.equal.)
|
||||||
export fn is(e: error, want: error) bool = {
|
export fn equal(e: error, want: error) bool = {
|
||||||
if (e.len != want.len) { return false; };
|
if (e.len != want.len) { return false; };
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < e.len) {
|
for (i < e.len) {
|
||||||
|
|||||||
@@ -84,7 +84,12 @@ def N_TRYUNW: i32 = 57;
|
|||||||
def N_MLET: i32 = 58;
|
def N_MLET: i32 = 58;
|
||||||
def N_MASSIGN: i32 = 59;
|
def N_MASSIGN: i32 = 59;
|
||||||
|
|
||||||
def N_LAST: i32 = 60;
|
// Appended at the tail to keep all prior N_* values stable. The
|
||||||
|
// 990_selfhost test diffs astprint against the C side byte-for-byte.
|
||||||
|
def N_TYPETEST: i32 = 60;
|
||||||
|
def N_TYPEASSERT: i32 = 61;
|
||||||
|
|
||||||
|
def N_LAST: i32 = 62;
|
||||||
|
|
||||||
// ---- Node -------------------------------------------------------------
|
// ---- Node -------------------------------------------------------------
|
||||||
|
|
||||||
@@ -183,6 +188,8 @@ fn nkname(k: i32) str = {
|
|||||||
if (k == N_TRYUNW) { return "tryunw"; };
|
if (k == N_TRYUNW) { return "tryunw"; };
|
||||||
if (k == N_MLET) { return "mlet"; };
|
if (k == N_MLET) { return "mlet"; };
|
||||||
if (k == N_MASSIGN) { return "massign"; };
|
if (k == N_MASSIGN) { return "massign"; };
|
||||||
|
if (k == N_TYPETEST) { return "typetest"; };
|
||||||
|
if (k == N_TYPEASSERT) { return "typeassert"; };
|
||||||
if (k == N_LAST) { return "last"; };
|
if (k == N_LAST) { return "last"; };
|
||||||
return "?";
|
return "?";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -105,7 +105,12 @@ def TK_LARROW: i32 = 79;
|
|||||||
def TK_ARROW: i32 = 80;
|
def TK_ARROW: i32 = 80;
|
||||||
def TK_FATARROW: i32 = 81;
|
def TK_FATARROW: i32 = 81;
|
||||||
|
|
||||||
def TK_LAST: i32 = 82;
|
// 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_LAST: i32 = 83;
|
||||||
|
|
||||||
// ---- Pos / Tok --------------------------------------------------------
|
// ---- Pos / Tok --------------------------------------------------------
|
||||||
//
|
//
|
||||||
@@ -164,6 +169,7 @@ export fn kwlookup(p: *u8, n: i32) i32 = {
|
|||||||
if (streqn(p, "fn", n)) { return TK_FN; };
|
if (streqn(p, "fn", n)) { return TK_FN; };
|
||||||
if (streqn(p, "for", n)) { return TK_FOR; };
|
if (streqn(p, "for", n)) { return TK_FOR; };
|
||||||
if (streqn(p, "if", n)) { return TK_IF; };
|
if (streqn(p, "if", n)) { return TK_IF; };
|
||||||
|
if (streqn(p, "is", n)) { return TK_IS; };
|
||||||
if (streqn(p, "let", n)) { return TK_LET; };
|
if (streqn(p, "let", n)) { return TK_LET; };
|
||||||
if (streqn(p, "match", n)) { return TK_MATCH; };
|
if (streqn(p, "match", n)) { return TK_MATCH; };
|
||||||
if (streqn(p, "nil", n)) { return TK_NIL; };
|
if (streqn(p, "nil", n)) { return TK_NIL; };
|
||||||
@@ -215,6 +221,7 @@ export fn tokname(k: i32) str = {
|
|||||||
if (k == TK_TRUE) { return "true"; };
|
if (k == TK_TRUE) { return "true"; };
|
||||||
if (k == TK_FALSE) { return "false"; };
|
if (k == TK_FALSE) { return "false"; };
|
||||||
if (k == TK_AS) { return "as"; };
|
if (k == TK_AS) { return "as"; };
|
||||||
|
if (k == TK_IS) { return "is"; };
|
||||||
if (k == TK_STATIC) { return "static"; };
|
if (k == TK_STATIC) { return "static"; };
|
||||||
if (k == TK_MATCH) { return "match"; };
|
if (k == TK_MATCH) { return "match"; };
|
||||||
if (k == TK_CONST) { return "const"; };
|
if (k == TK_CONST) { return "const"; };
|
||||||
|
|||||||
@@ -306,6 +306,26 @@ fn parsepostfix(p: *parser, lhs: *node) *node = {
|
|||||||
cur = n;
|
cur = n;
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
// Hare-style postfix:
|
||||||
|
// `e as T` — assert lhs is variant T (abort otherwise) → T
|
||||||
|
// `e is T` — bool: does lhs currently hold variant T?
|
||||||
|
// Same precedence level as the `:` cast.
|
||||||
|
if (p.curkind == TK_AS) {
|
||||||
|
advance(p);
|
||||||
|
let n: *node = newnode(p.a, N_TYPEASSERT, pf, pl, pc);
|
||||||
|
n.lhs = cur;
|
||||||
|
n.rhs = parsetype(p);
|
||||||
|
cur = n;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if (p.curkind == TK_IS) {
|
||||||
|
advance(p);
|
||||||
|
let n: *node = newnode(p.a, N_TYPETEST, pf, pl, pc);
|
||||||
|
n.lhs = cur;
|
||||||
|
n.rhs = parsetype(p);
|
||||||
|
cur = n;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
return cur;
|
return cur;
|
||||||
|
|||||||
@@ -545,7 +545,12 @@ def TK_LARROW: i32 = 79;
|
|||||||
def TK_ARROW: i32 = 80;
|
def TK_ARROW: i32 = 80;
|
||||||
def TK_FATARROW: i32 = 81;
|
def TK_FATARROW: i32 = 81;
|
||||||
|
|
||||||
def TK_LAST: i32 = 82;
|
// 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_LAST: i32 = 83;
|
||||||
|
|
||||||
// ---- Pos / Tok --------------------------------------------------------
|
// ---- Pos / Tok --------------------------------------------------------
|
||||||
//
|
//
|
||||||
@@ -604,6 +609,7 @@ export fn kwlookup(p: *u8, n: i32) i32 = {
|
|||||||
if (streqn(p, "fn", n)) { return TK_FN; };
|
if (streqn(p, "fn", n)) { return TK_FN; };
|
||||||
if (streqn(p, "for", n)) { return TK_FOR; };
|
if (streqn(p, "for", n)) { return TK_FOR; };
|
||||||
if (streqn(p, "if", n)) { return TK_IF; };
|
if (streqn(p, "if", n)) { return TK_IF; };
|
||||||
|
if (streqn(p, "is", n)) { return TK_IS; };
|
||||||
if (streqn(p, "let", n)) { return TK_LET; };
|
if (streqn(p, "let", n)) { return TK_LET; };
|
||||||
if (streqn(p, "match", n)) { return TK_MATCH; };
|
if (streqn(p, "match", n)) { return TK_MATCH; };
|
||||||
if (streqn(p, "nil", n)) { return TK_NIL; };
|
if (streqn(p, "nil", n)) { return TK_NIL; };
|
||||||
@@ -655,6 +661,7 @@ export fn tokname(k: i32) str = {
|
|||||||
if (k == TK_TRUE) { return "true"; };
|
if (k == TK_TRUE) { return "true"; };
|
||||||
if (k == TK_FALSE) { return "false"; };
|
if (k == TK_FALSE) { return "false"; };
|
||||||
if (k == TK_AS) { return "as"; };
|
if (k == TK_AS) { return "as"; };
|
||||||
|
if (k == TK_IS) { return "is"; };
|
||||||
if (k == TK_STATIC) { return "static"; };
|
if (k == TK_STATIC) { return "static"; };
|
||||||
if (k == TK_MATCH) { return "match"; };
|
if (k == TK_MATCH) { return "match"; };
|
||||||
if (k == TK_CONST) { return "const"; };
|
if (k == TK_CONST) { return "const"; };
|
||||||
@@ -1716,7 +1723,12 @@ def N_TRYUNW: i32 = 57;
|
|||||||
def N_MLET: i32 = 58;
|
def N_MLET: i32 = 58;
|
||||||
def N_MASSIGN: i32 = 59;
|
def N_MASSIGN: i32 = 59;
|
||||||
|
|
||||||
def N_LAST: i32 = 60;
|
// Appended at the tail to keep all prior N_* values stable. The
|
||||||
|
// 990_selfhost test diffs astprint against the C side byte-for-byte.
|
||||||
|
def N_TYPETEST: i32 = 60;
|
||||||
|
def N_TYPEASSERT: i32 = 61;
|
||||||
|
|
||||||
|
def N_LAST: i32 = 62;
|
||||||
|
|
||||||
// ---- Node -------------------------------------------------------------
|
// ---- Node -------------------------------------------------------------
|
||||||
|
|
||||||
@@ -1815,6 +1827,8 @@ fn nkname(k: i32) str = {
|
|||||||
if (k == N_TRYUNW) { return "tryunw"; };
|
if (k == N_TRYUNW) { return "tryunw"; };
|
||||||
if (k == N_MLET) { return "mlet"; };
|
if (k == N_MLET) { return "mlet"; };
|
||||||
if (k == N_MASSIGN) { return "massign"; };
|
if (k == N_MASSIGN) { return "massign"; };
|
||||||
|
if (k == N_TYPETEST) { return "typetest"; };
|
||||||
|
if (k == N_TYPEASSERT) { return "typeassert"; };
|
||||||
if (k == N_LAST) { return "last"; };
|
if (k == N_LAST) { return "last"; };
|
||||||
return "?";
|
return "?";
|
||||||
};
|
};
|
||||||
@@ -2276,6 +2290,26 @@ fn parsepostfix(p: *parser, lhs: *node) *node = {
|
|||||||
cur = n;
|
cur = n;
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
// Hare-style postfix:
|
||||||
|
// `e as T` — assert lhs is variant T (abort otherwise) → T
|
||||||
|
// `e is T` — bool: does lhs currently hold variant T?
|
||||||
|
// Same precedence level as the `:` cast.
|
||||||
|
if (p.curkind == TK_AS) {
|
||||||
|
advance(p);
|
||||||
|
let n: *node = newnode(p.a, N_TYPEASSERT, pf, pl, pc);
|
||||||
|
n.lhs = cur;
|
||||||
|
n.rhs = parsetype(p);
|
||||||
|
cur = n;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if (p.curkind == TK_IS) {
|
||||||
|
advance(p);
|
||||||
|
let n: *node = newnode(p.a, N_TYPETEST, pf, pl, pc);
|
||||||
|
n.lhs = cur;
|
||||||
|
n.rhs = parsetype(p);
|
||||||
|
cur = n;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
return cur;
|
return cur;
|
||||||
@@ -4863,6 +4897,115 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
|||||||
if (k == N_CALL) { cgcall(c, n); return; };
|
if (k == N_CALL) { cgcall(c, n); return; };
|
||||||
|
|
||||||
if (k == N_ASSIGN) { cgassign(c, n); return; };
|
if (k == N_ASSIGN) { cgassign(c, n); return; };
|
||||||
|
|
||||||
|
if (k == N_TYPETEST) { cgtypetest(c, n); return; };
|
||||||
|
if (k == N_TYPEASSERT) { cgtypeassert(c, n); return; };
|
||||||
|
};
|
||||||
|
|
||||||
|
// cgtagvariantidx — find the 0-based variant index of `vt` inside the
|
||||||
|
// tagged-union type expression `tagged`. -1 if `tagged` isn't an
|
||||||
|
// N_TTAGGED or no variant matches. Mirrors the lookup that cgmatch
|
||||||
|
// does inline; pulled out so `is` / `as` can reuse it.
|
||||||
|
fn cgtagvariantidx(tagged: *node, vt: *node) i32 = {
|
||||||
|
if (tagged == nil) { return -1; };
|
||||||
|
if (vt == nil) { return -1; };
|
||||||
|
if (tagged.kind != N_TTAGGED) { return -1; };
|
||||||
|
let want: str;
|
||||||
|
want.ptr = nil; want.len = 0;
|
||||||
|
if (vt.kind == N_TNAME) { want = vt.str; };
|
||||||
|
if (want.len == 0) { return -1; };
|
||||||
|
let v: *node = tagged.list;
|
||||||
|
let idx: i32 = 0;
|
||||||
|
for (v != nil) {
|
||||||
|
if (v.kind == N_TNAME) {
|
||||||
|
if (streq(v.str, want)) { return idx; };
|
||||||
|
};
|
||||||
|
v = v.next;
|
||||||
|
idx += 1;
|
||||||
|
};
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn cgtypetest(c: *cgen, n: *node) void = {
|
||||||
|
// `e is T` — load the lhs's tag, compare against T's variant
|
||||||
|
// index, set AX = (tag == idx). Result type is bool.
|
||||||
|
//
|
||||||
|
// Slot resolution is inlined (rather than factored into a helper
|
||||||
|
// with output parameters): wwstage cgen has a trap with i32
|
||||||
|
// stored via *i32 in this context — direct assignment of the
|
||||||
|
// local works, indirection through &scrutoff drops sign bits.
|
||||||
|
let lhs: *node = n.lhs;
|
||||||
|
let scrutoff: i32 = 0;
|
||||||
|
let scrutt: *node = nil;
|
||||||
|
if (lhs != nil) {
|
||||||
|
if (lhs.kind == N_IDENT) {
|
||||||
|
let lc: *local = localfindnode(c, lhs.str);
|
||||||
|
if (lc != nil) {
|
||||||
|
scrutoff = lc.off;
|
||||||
|
scrutt = resolvetype(c, lc.tnode);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let want: i32 = cgtagvariantidx(scrutt, n.rhs);
|
||||||
|
if (want < 0) { want = 0; };
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(scrutoff: i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
let nel: str = mklabel(c, "is_ne");
|
||||||
|
let dnl: str = mklabel(c, "is_done");
|
||||||
|
emitline("\tCMPQ\t$");
|
||||||
|
emitint(want: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tJNE\t");
|
||||||
|
emitline(nel);
|
||||||
|
emitline("\n\tMOVQ\t$1, AX\n\tJMP\t");
|
||||||
|
emitline(dnl);
|
||||||
|
emitline("\n");
|
||||||
|
emitlabel(nel);
|
||||||
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
|
emitlabel(dnl);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn cgtypeassert(c: *cgen, n: *node) void = {
|
||||||
|
// `e as T` — load tag, abort (exit 1) if tag != T's variant
|
||||||
|
// index, otherwise unwrap to T's ABI: scalar/ptr → AX, 16B
|
||||||
|
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
|
||||||
|
// Slot resolution inlined; see cgtypetest comment.
|
||||||
|
let lhs: *node = n.lhs;
|
||||||
|
let scrutoff: i32 = 0;
|
||||||
|
let scrutt: *node = nil;
|
||||||
|
if (lhs != nil) {
|
||||||
|
if (lhs.kind == N_IDENT) {
|
||||||
|
let lc: *local = localfindnode(c, lhs.str);
|
||||||
|
if (lc != nil) {
|
||||||
|
scrutoff = lc.off;
|
||||||
|
scrutt = resolvetype(c, lc.tnode);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let want: i32 = cgtagvariantidx(scrutt, n.rhs);
|
||||||
|
if (want < 0) { want = 0; };
|
||||||
|
let okl: str = mklabel(c, "asrt_ok");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(scrutoff: i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tCMPQ\t$");
|
||||||
|
emitint(want: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tJE\t");
|
||||||
|
emitline(okl);
|
||||||
|
emitline("\n\tMOVQ\t$1, DI\n\tMOVQ\t$60, AX\n\tSYSCALL\n");
|
||||||
|
emitlabel(okl);
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((scrutoff + 8): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
if (isstrtype(c, n.rhs)) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((scrutoff + 16): i64);
|
||||||
|
emitline("(BP), BX\n");
|
||||||
|
};
|
||||||
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
fn cgstrlit(c: *cgen, n: *node) void = {
|
fn cgstrlit(c: *cgen, n: *node) void = {
|
||||||
|
|||||||
@@ -79,6 +79,115 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
|||||||
if (k == N_CALL) { cgcall(c, n); return; };
|
if (k == N_CALL) { cgcall(c, n); return; };
|
||||||
|
|
||||||
if (k == N_ASSIGN) { cgassign(c, n); return; };
|
if (k == N_ASSIGN) { cgassign(c, n); return; };
|
||||||
|
|
||||||
|
if (k == N_TYPETEST) { cgtypetest(c, n); return; };
|
||||||
|
if (k == N_TYPEASSERT) { cgtypeassert(c, n); return; };
|
||||||
|
};
|
||||||
|
|
||||||
|
// cgtagvariantidx — find the 0-based variant index of `vt` inside the
|
||||||
|
// tagged-union type expression `tagged`. -1 if `tagged` isn't an
|
||||||
|
// N_TTAGGED or no variant matches. Mirrors the lookup that cgmatch
|
||||||
|
// does inline; pulled out so `is` / `as` can reuse it.
|
||||||
|
fn cgtagvariantidx(tagged: *node, vt: *node) i32 = {
|
||||||
|
if (tagged == nil) { return -1; };
|
||||||
|
if (vt == nil) { return -1; };
|
||||||
|
if (tagged.kind != N_TTAGGED) { return -1; };
|
||||||
|
let want: str;
|
||||||
|
want.ptr = nil; want.len = 0;
|
||||||
|
if (vt.kind == N_TNAME) { want = vt.str; };
|
||||||
|
if (want.len == 0) { return -1; };
|
||||||
|
let v: *node = tagged.list;
|
||||||
|
let idx: i32 = 0;
|
||||||
|
for (v != nil) {
|
||||||
|
if (v.kind == N_TNAME) {
|
||||||
|
if (streq(v.str, want)) { return idx; };
|
||||||
|
};
|
||||||
|
v = v.next;
|
||||||
|
idx += 1;
|
||||||
|
};
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn cgtypetest(c: *cgen, n: *node) void = {
|
||||||
|
// `e is T` — load the lhs's tag, compare against T's variant
|
||||||
|
// index, set AX = (tag == idx). Result type is bool.
|
||||||
|
//
|
||||||
|
// Slot resolution is inlined (rather than factored into a helper
|
||||||
|
// with output parameters): wwstage cgen has a trap with i32
|
||||||
|
// stored via *i32 in this context — direct assignment of the
|
||||||
|
// local works, indirection through &scrutoff drops sign bits.
|
||||||
|
let lhs: *node = n.lhs;
|
||||||
|
let scrutoff: i32 = 0;
|
||||||
|
let scrutt: *node = nil;
|
||||||
|
if (lhs != nil) {
|
||||||
|
if (lhs.kind == N_IDENT) {
|
||||||
|
let lc: *local = localfindnode(c, lhs.str);
|
||||||
|
if (lc != nil) {
|
||||||
|
scrutoff = lc.off;
|
||||||
|
scrutt = resolvetype(c, lc.tnode);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let want: i32 = cgtagvariantidx(scrutt, n.rhs);
|
||||||
|
if (want < 0) { want = 0; };
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(scrutoff: i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
let nel: str = mklabel(c, "is_ne");
|
||||||
|
let dnl: str = mklabel(c, "is_done");
|
||||||
|
emitline("\tCMPQ\t$");
|
||||||
|
emitint(want: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tJNE\t");
|
||||||
|
emitline(nel);
|
||||||
|
emitline("\n\tMOVQ\t$1, AX\n\tJMP\t");
|
||||||
|
emitline(dnl);
|
||||||
|
emitline("\n");
|
||||||
|
emitlabel(nel);
|
||||||
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
|
emitlabel(dnl);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn cgtypeassert(c: *cgen, n: *node) void = {
|
||||||
|
// `e as T` — load tag, abort (exit 1) if tag != T's variant
|
||||||
|
// index, otherwise unwrap to T's ABI: scalar/ptr → AX, 16B
|
||||||
|
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
|
||||||
|
// Slot resolution inlined; see cgtypetest comment.
|
||||||
|
let lhs: *node = n.lhs;
|
||||||
|
let scrutoff: i32 = 0;
|
||||||
|
let scrutt: *node = nil;
|
||||||
|
if (lhs != nil) {
|
||||||
|
if (lhs.kind == N_IDENT) {
|
||||||
|
let lc: *local = localfindnode(c, lhs.str);
|
||||||
|
if (lc != nil) {
|
||||||
|
scrutoff = lc.off;
|
||||||
|
scrutt = resolvetype(c, lc.tnode);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let want: i32 = cgtagvariantidx(scrutt, n.rhs);
|
||||||
|
if (want < 0) { want = 0; };
|
||||||
|
let okl: str = mklabel(c, "asrt_ok");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(scrutoff: i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tCMPQ\t$");
|
||||||
|
emitint(want: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tJE\t");
|
||||||
|
emitline(okl);
|
||||||
|
emitline("\n\tMOVQ\t$1, DI\n\tMOVQ\t$60, AX\n\tSYSCALL\n");
|
||||||
|
emitlabel(okl);
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((scrutoff + 8): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
if (isstrtype(c, n.rhs)) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((scrutoff + 16): i64);
|
||||||
|
emitline("(BP), BX\n");
|
||||||
|
};
|
||||||
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
fn cgstrlit(c: *cgen, n: *node) void = {
|
fn cgstrlit(c: *cgen, n: *node) void = {
|
||||||
|
|||||||
@@ -545,7 +545,12 @@ def TK_LARROW: i32 = 79;
|
|||||||
def TK_ARROW: i32 = 80;
|
def TK_ARROW: i32 = 80;
|
||||||
def TK_FATARROW: i32 = 81;
|
def TK_FATARROW: i32 = 81;
|
||||||
|
|
||||||
def TK_LAST: i32 = 82;
|
// 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_LAST: i32 = 83;
|
||||||
|
|
||||||
// ---- Pos / Tok --------------------------------------------------------
|
// ---- Pos / Tok --------------------------------------------------------
|
||||||
//
|
//
|
||||||
@@ -604,6 +609,7 @@ export fn kwlookup(p: *u8, n: i32) i32 = {
|
|||||||
if (streqn(p, "fn", n)) { return TK_FN; };
|
if (streqn(p, "fn", n)) { return TK_FN; };
|
||||||
if (streqn(p, "for", n)) { return TK_FOR; };
|
if (streqn(p, "for", n)) { return TK_FOR; };
|
||||||
if (streqn(p, "if", n)) { return TK_IF; };
|
if (streqn(p, "if", n)) { return TK_IF; };
|
||||||
|
if (streqn(p, "is", n)) { return TK_IS; };
|
||||||
if (streqn(p, "let", n)) { return TK_LET; };
|
if (streqn(p, "let", n)) { return TK_LET; };
|
||||||
if (streqn(p, "match", n)) { return TK_MATCH; };
|
if (streqn(p, "match", n)) { return TK_MATCH; };
|
||||||
if (streqn(p, "nil", n)) { return TK_NIL; };
|
if (streqn(p, "nil", n)) { return TK_NIL; };
|
||||||
@@ -655,6 +661,7 @@ export fn tokname(k: i32) str = {
|
|||||||
if (k == TK_TRUE) { return "true"; };
|
if (k == TK_TRUE) { return "true"; };
|
||||||
if (k == TK_FALSE) { return "false"; };
|
if (k == TK_FALSE) { return "false"; };
|
||||||
if (k == TK_AS) { return "as"; };
|
if (k == TK_AS) { return "as"; };
|
||||||
|
if (k == TK_IS) { return "is"; };
|
||||||
if (k == TK_STATIC) { return "static"; };
|
if (k == TK_STATIC) { return "static"; };
|
||||||
if (k == TK_MATCH) { return "match"; };
|
if (k == TK_MATCH) { return "match"; };
|
||||||
if (k == TK_CONST) { return "const"; };
|
if (k == TK_CONST) { return "const"; };
|
||||||
@@ -1716,7 +1723,12 @@ def N_TRYUNW: i32 = 57;
|
|||||||
def N_MLET: i32 = 58;
|
def N_MLET: i32 = 58;
|
||||||
def N_MASSIGN: i32 = 59;
|
def N_MASSIGN: i32 = 59;
|
||||||
|
|
||||||
def N_LAST: i32 = 60;
|
// Appended at the tail to keep all prior N_* values stable. The
|
||||||
|
// 990_selfhost test diffs astprint against the C side byte-for-byte.
|
||||||
|
def N_TYPETEST: i32 = 60;
|
||||||
|
def N_TYPEASSERT: i32 = 61;
|
||||||
|
|
||||||
|
def N_LAST: i32 = 62;
|
||||||
|
|
||||||
// ---- Node -------------------------------------------------------------
|
// ---- Node -------------------------------------------------------------
|
||||||
|
|
||||||
@@ -1815,6 +1827,8 @@ fn nkname(k: i32) str = {
|
|||||||
if (k == N_TRYUNW) { return "tryunw"; };
|
if (k == N_TRYUNW) { return "tryunw"; };
|
||||||
if (k == N_MLET) { return "mlet"; };
|
if (k == N_MLET) { return "mlet"; };
|
||||||
if (k == N_MASSIGN) { return "massign"; };
|
if (k == N_MASSIGN) { return "massign"; };
|
||||||
|
if (k == N_TYPETEST) { return "typetest"; };
|
||||||
|
if (k == N_TYPEASSERT) { return "typeassert"; };
|
||||||
if (k == N_LAST) { return "last"; };
|
if (k == N_LAST) { return "last"; };
|
||||||
return "?";
|
return "?";
|
||||||
};
|
};
|
||||||
@@ -2276,6 +2290,26 @@ fn parsepostfix(p: *parser, lhs: *node) *node = {
|
|||||||
cur = n;
|
cur = n;
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
// Hare-style postfix:
|
||||||
|
// `e as T` — assert lhs is variant T (abort otherwise) → T
|
||||||
|
// `e is T` — bool: does lhs currently hold variant T?
|
||||||
|
// Same precedence level as the `:` cast.
|
||||||
|
if (p.curkind == TK_AS) {
|
||||||
|
advance(p);
|
||||||
|
let n: *node = newnode(p.a, N_TYPEASSERT, pf, pl, pc);
|
||||||
|
n.lhs = cur;
|
||||||
|
n.rhs = parsetype(p);
|
||||||
|
cur = n;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if (p.curkind == TK_IS) {
|
||||||
|
advance(p);
|
||||||
|
let n: *node = newnode(p.a, N_TYPETEST, pf, pl, pc);
|
||||||
|
n.lhs = cur;
|
||||||
|
n.rhs = parsetype(p);
|
||||||
|
cur = n;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
return cur;
|
return cur;
|
||||||
@@ -4863,6 +4897,115 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
|||||||
if (k == N_CALL) { cgcall(c, n); return; };
|
if (k == N_CALL) { cgcall(c, n); return; };
|
||||||
|
|
||||||
if (k == N_ASSIGN) { cgassign(c, n); return; };
|
if (k == N_ASSIGN) { cgassign(c, n); return; };
|
||||||
|
|
||||||
|
if (k == N_TYPETEST) { cgtypetest(c, n); return; };
|
||||||
|
if (k == N_TYPEASSERT) { cgtypeassert(c, n); return; };
|
||||||
|
};
|
||||||
|
|
||||||
|
// cgtagvariantidx — find the 0-based variant index of `vt` inside the
|
||||||
|
// tagged-union type expression `tagged`. -1 if `tagged` isn't an
|
||||||
|
// N_TTAGGED or no variant matches. Mirrors the lookup that cgmatch
|
||||||
|
// does inline; pulled out so `is` / `as` can reuse it.
|
||||||
|
fn cgtagvariantidx(tagged: *node, vt: *node) i32 = {
|
||||||
|
if (tagged == nil) { return -1; };
|
||||||
|
if (vt == nil) { return -1; };
|
||||||
|
if (tagged.kind != N_TTAGGED) { return -1; };
|
||||||
|
let want: str;
|
||||||
|
want.ptr = nil; want.len = 0;
|
||||||
|
if (vt.kind == N_TNAME) { want = vt.str; };
|
||||||
|
if (want.len == 0) { return -1; };
|
||||||
|
let v: *node = tagged.list;
|
||||||
|
let idx: i32 = 0;
|
||||||
|
for (v != nil) {
|
||||||
|
if (v.kind == N_TNAME) {
|
||||||
|
if (streq(v.str, want)) { return idx; };
|
||||||
|
};
|
||||||
|
v = v.next;
|
||||||
|
idx += 1;
|
||||||
|
};
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn cgtypetest(c: *cgen, n: *node) void = {
|
||||||
|
// `e is T` — load the lhs's tag, compare against T's variant
|
||||||
|
// index, set AX = (tag == idx). Result type is bool.
|
||||||
|
//
|
||||||
|
// Slot resolution is inlined (rather than factored into a helper
|
||||||
|
// with output parameters): wwstage cgen has a trap with i32
|
||||||
|
// stored via *i32 in this context — direct assignment of the
|
||||||
|
// local works, indirection through &scrutoff drops sign bits.
|
||||||
|
let lhs: *node = n.lhs;
|
||||||
|
let scrutoff: i32 = 0;
|
||||||
|
let scrutt: *node = nil;
|
||||||
|
if (lhs != nil) {
|
||||||
|
if (lhs.kind == N_IDENT) {
|
||||||
|
let lc: *local = localfindnode(c, lhs.str);
|
||||||
|
if (lc != nil) {
|
||||||
|
scrutoff = lc.off;
|
||||||
|
scrutt = resolvetype(c, lc.tnode);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let want: i32 = cgtagvariantidx(scrutt, n.rhs);
|
||||||
|
if (want < 0) { want = 0; };
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(scrutoff: i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
let nel: str = mklabel(c, "is_ne");
|
||||||
|
let dnl: str = mklabel(c, "is_done");
|
||||||
|
emitline("\tCMPQ\t$");
|
||||||
|
emitint(want: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tJNE\t");
|
||||||
|
emitline(nel);
|
||||||
|
emitline("\n\tMOVQ\t$1, AX\n\tJMP\t");
|
||||||
|
emitline(dnl);
|
||||||
|
emitline("\n");
|
||||||
|
emitlabel(nel);
|
||||||
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
|
emitlabel(dnl);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn cgtypeassert(c: *cgen, n: *node) void = {
|
||||||
|
// `e as T` — load tag, abort (exit 1) if tag != T's variant
|
||||||
|
// index, otherwise unwrap to T's ABI: scalar/ptr → AX, 16B
|
||||||
|
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
|
||||||
|
// Slot resolution inlined; see cgtypetest comment.
|
||||||
|
let lhs: *node = n.lhs;
|
||||||
|
let scrutoff: i32 = 0;
|
||||||
|
let scrutt: *node = nil;
|
||||||
|
if (lhs != nil) {
|
||||||
|
if (lhs.kind == N_IDENT) {
|
||||||
|
let lc: *local = localfindnode(c, lhs.str);
|
||||||
|
if (lc != nil) {
|
||||||
|
scrutoff = lc.off;
|
||||||
|
scrutt = resolvetype(c, lc.tnode);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let want: i32 = cgtagvariantidx(scrutt, n.rhs);
|
||||||
|
if (want < 0) { want = 0; };
|
||||||
|
let okl: str = mklabel(c, "asrt_ok");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(scrutoff: i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tCMPQ\t$");
|
||||||
|
emitint(want: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tJE\t");
|
||||||
|
emitline(okl);
|
||||||
|
emitline("\n\tMOVQ\t$1, DI\n\tMOVQ\t$60, AX\n\tSYSCALL\n");
|
||||||
|
emitlabel(okl);
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((scrutoff + 8): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
if (isstrtype(c, n.rhs)) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((scrutoff + 16): i64);
|
||||||
|
emitline("(BP), BX\n");
|
||||||
|
};
|
||||||
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
fn cgstrlit(c: *cgen, n: *node) void = {
|
fn cgstrlit(c: *cgen, n: *node) void = {
|
||||||
|
|||||||
@@ -162,6 +162,11 @@ main(void)
|
|||||||
"fn try() (i32, str) = { return 0, \"\"; };",
|
"fn try() (i32, str) = { return 0, \"\"; };",
|
||||||
"fn use_tuple() void = { let q, r = divmod(10, 3); };",
|
"fn use_tuple() void = { let q, r = divmod(10, 3); };",
|
||||||
"fn assign_tuple() void = { q, r = divmod(10, 3); };",
|
"fn assign_tuple() void = { q, r = divmod(10, 3); };",
|
||||||
|
|
||||||
|
/* Hare-style type test / type assertion (postfix) */
|
||||||
|
"fn ti(r: (i64 | i32)) bool = { return r is i64; };",
|
||||||
|
"fn ai(r: (i64 | i32)) i64 = { return r as i64; };",
|
||||||
|
"fn br(r: (i64 | str)) i32 = { if (r is i64) { return 1; }; return 0; };",
|
||||||
};
|
};
|
||||||
int n = sizeof parses / sizeof parses[0];
|
int n = sizeof parses / sizeof parses[0];
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
|
|||||||
@@ -750,7 +750,7 @@ static const struct row rows[] = {
|
|||||||
" needle[0] = 119u8; needle[1] = 111u8; needle[2] = 114u8;\n"
|
" needle[0] = 119u8; needle[1] = 111u8; needle[2] = 114u8;\n"
|
||||||
" return bytes.indexsub(buf[0:12], needle[0:3]);\n"
|
" return bytes.indexsub(buf[0:12], needle[0:3]);\n"
|
||||||
"};", 7 },
|
"};", 7 },
|
||||||
/* errors.is — sentinel comparison through a (T | error) union.
|
/* errors.equal — sentinel comparison through a (T | error) union.
|
||||||
* Sets up two errors, dispatches each, and confirms the matching
|
* Sets up two errors, dispatches each, and confirms the matching
|
||||||
* sentinel detection. */
|
* sentinel detection. */
|
||||||
{ "use errors;\n"
|
{ "use errors;\n"
|
||||||
@@ -766,13 +766,13 @@ static const struct row rows[] = {
|
|||||||
" match (r1) {\n"
|
" match (r1) {\n"
|
||||||
" case let v: i64 => acc += -100;\n"
|
" case let v: i64 => acc += -100;\n"
|
||||||
" case let e: errors.error =>\n"
|
" case let e: errors.error =>\n"
|
||||||
" if (errors.is(e, errors.eEOF)) { acc += 1; }\n"
|
" if (errors.equal(e, errors.eEOF)) { acc += 1; }\n"
|
||||||
" else { acc += -100; };\n"
|
" else { acc += -100; };\n"
|
||||||
" };\n"
|
" };\n"
|
||||||
" match (r2) {\n"
|
" match (r2) {\n"
|
||||||
" case let v: i64 => acc += -100;\n"
|
" case let v: i64 => acc += -100;\n"
|
||||||
" case let e: errors.error =>\n"
|
" case let e: errors.error =>\n"
|
||||||
" if (errors.is(e, errors.eShortRead)) { acc += 10; }\n"
|
" if (errors.equal(e, errors.eShortRead)) { acc += 10; }\n"
|
||||||
" else { acc += -100; };\n"
|
" else { acc += -100; };\n"
|
||||||
" };\n"
|
" };\n"
|
||||||
" return acc;\n"
|
" return acc;\n"
|
||||||
@@ -869,6 +869,33 @@ static const struct row rows[] = {
|
|||||||
" os.write(1, GREETING.ptr, GREETING.len: u64);\n"
|
" os.write(1, GREETING.ptr, GREETING.len: u64);\n"
|
||||||
" return GREETING.len: i32;\n"
|
" return GREETING.len: i32;\n"
|
||||||
"};", 3 },
|
"};", 3 },
|
||||||
|
/* Hare-style `is` / `as`: type test returns bool, type assertion
|
||||||
|
* unwraps to the variant's value (success path only — abort path
|
||||||
|
* exit(1) is exercised manually). Covers i64/i32 scalar variants
|
||||||
|
* and str (16B variant via .len pseudo-field). */
|
||||||
|
{ "fn classify(n: i64) (i64 | i32) = {\n"
|
||||||
|
" if (n < 0) { return 7: i32; };\n"
|
||||||
|
" return n;\n"
|
||||||
|
"};\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let ok: (i64 | i32) = classify(40);\n"
|
||||||
|
" let bad: (i64 | i32) = classify(-1);\n"
|
||||||
|
" let s: i32 = 0;\n"
|
||||||
|
" if (ok is i64) { s += 1; };\n"
|
||||||
|
" if (bad is i32) { s += 1; };\n"
|
||||||
|
" if (ok is i32) { s += 100; };\n"
|
||||||
|
" if (bad is i64) { s += 100; };\n"
|
||||||
|
" let v: i64 = ok as i64;\n"
|
||||||
|
" let e: i32 = bad as i32;\n"
|
||||||
|
" return (v: i32) + s + e;\n"
|
||||||
|
"};", 49 },
|
||||||
|
/* `as` on a 16B str variant — unwrap loads (ptr, len). */
|
||||||
|
{ "fn fail() (i64 | str) = { return \"bad\"; };\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let r: (i64 | str) = fail();\n"
|
||||||
|
" let e: str = r as str;\n"
|
||||||
|
" return e.len: i32;\n"
|
||||||
|
"};", 3 },
|
||||||
{ NULL, 0 }
|
{ NULL, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user