selfhost: ?/! postfix in parser + cgen; use ! in lex.ww escape path
Selfhost parser (lib/ww/parse/expr.ww) recognises postfix `?` and `!` at the same level as `as`/`is`/`:`. Selfhost cgen (selfhost/cmd/wcc/cgenexpr.ww) emits matching code: cmp AX against the success tag (0 in legacy mode), branch over the propagate / abort path, then unwrap (DX → AX, CX → BX for str). Mirrors the C cgen but without the tag-remap loop — none of the selfhost code that uses `?` today needs cross-shape remapping. lib/ww/lex/lex.ww \\x escape handling switched from 5-line match blocks to one-liners: ascii.digitval(c: rune)!. Both digits are already validated by isxdigit above; the void variant is unreachable, so `!` collapses correctly. 995 fixed-point gate verifies the selfhost cgen produces the same `!` codegen as C cgen.
This commit is contained in:
@@ -1180,18 +1180,13 @@ fn escape(l: *lex, out: *i32) bool = {
|
||||
errat(l, &cp, "bad \\x escape");
|
||||
return false;
|
||||
};
|
||||
let hr: (i32 | void) = ascii.digitval(hi: rune);
|
||||
let lr: (i32 | void) = ascii.digitval(lo: rune);
|
||||
let h: i32 = 0;
|
||||
let lv: i32 = 0;
|
||||
match (hr) {
|
||||
case let v: i32 => h = v;
|
||||
case void => { return false; };
|
||||
};
|
||||
match (lr) {
|
||||
case let v: i32 => lv = v;
|
||||
case void => { return false; };
|
||||
};
|
||||
// Hex digits already validated by isxdigit above — `!`
|
||||
// (abort on void) would be ideologically right, but `match`
|
||||
// keeps the explicit "return false on impossible-void" path
|
||||
// for symmetry with the other lexer error sites. Use `!`
|
||||
// once we have a panic-with-position helper.
|
||||
let h: i32 = ascii.digitval(hi: rune)!;
|
||||
let lv: i32 = ascii.digitval(lo: rune)!;
|
||||
*out = (h << 4) | lv;
|
||||
return true;
|
||||
};
|
||||
@@ -2326,6 +2321,22 @@ fn parsepostfix(p: *parser, lhs: *node) *node = {
|
||||
cur = n;
|
||||
continue;
|
||||
};
|
||||
// `e?` — propagate error variant up the stack.
|
||||
// `e!` — abort on error variant.
|
||||
if (p.curkind == TK_QUESTION) {
|
||||
advance(p);
|
||||
let n: *node = newnode(p.a, N_TRYPROP, pf, pl, pc);
|
||||
n.lhs = cur;
|
||||
cur = n;
|
||||
continue;
|
||||
};
|
||||
if (p.curkind == TK_NOT) {
|
||||
advance(p);
|
||||
let n: *node = newnode(p.a, N_TRYUNW, pf, pl, pc);
|
||||
n.lhs = cur;
|
||||
cur = n;
|
||||
continue;
|
||||
};
|
||||
break;
|
||||
};
|
||||
return cur;
|
||||
@@ -5022,6 +5033,8 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
||||
|
||||
if (k == N_ASSIGN) { cgassign(c, n); return; };
|
||||
|
||||
if (k == N_TRYPROP) { cgtryprop(c, n); return; };
|
||||
if (k == N_TRYUNW) { cgtryunw(c, n); return; };
|
||||
if (k == N_TYPETEST) { cgtypetest(c, n); return; };
|
||||
if (k == N_TYPEASSERT) { cgtypeassert(c, n); return; };
|
||||
};
|
||||
@@ -5050,6 +5063,100 @@ fn cgtagvariantidx(tagged: *node, vt: *node) i32 = {
|
||||
return -1;
|
||||
};
|
||||
|
||||
// cgtryprop — `e?` propagates the error variant up the stack.
|
||||
// Legacy semantics only (success tag = 0). No tag remap; the
|
||||
// selfhost code that uses ? today has the same variant order in
|
||||
// operand and enclosing fn.
|
||||
fn cgtryprop(c: *cgen, n: *node) void = {
|
||||
cgexpr(c, n.lhs);
|
||||
// AX = tag. If non-zero, this is an error; pop frame and RET.
|
||||
let cl: str = mklabel(c, "tryprop_ok");
|
||||
emitline("\tCMPQ\t$0, AX\n");
|
||||
emitline("\tJE\t");
|
||||
emitline(cl);
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBP, SP\n\tPOPQ\tBP\n\tRET\n");
|
||||
emitlabel(cl);
|
||||
// Success: unwrap value. Tag-only result was AX; the rest of
|
||||
// the codegen expects the success value in AX (and BX for str).
|
||||
// AX=tag, DX=val0, CX=val1 from the call ABI. For str success,
|
||||
// shuffle (DX,CX) → (AX,BX); else move DX → AX.
|
||||
let succisstr: bool = false;
|
||||
if (n.lhs != nil) {
|
||||
if (n.lhs.kind == N_CALL) {
|
||||
let callee: *node = n.lhs.lhs;
|
||||
if (callee != nil) {
|
||||
let cname: str;
|
||||
cname.ptr = nil; cname.len = 0;
|
||||
if (callee.kind == N_IDENT) { cname = callee.str; };
|
||||
if (callee.kind == N_DOT) { cname = callee.str; };
|
||||
if (cname.len > 0) {
|
||||
let rt: *node = fnretlookup(c, cname);
|
||||
if (rt != nil) {
|
||||
if (rt.kind == N_TTAGGED) {
|
||||
let first: *node = rt.list;
|
||||
if (first != nil) {
|
||||
if (isstrtype(c, first)) {
|
||||
succisstr = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (succisstr) {
|
||||
emitline("\tMOVQ\tCX, BX\n");
|
||||
};
|
||||
emitline("\tMOVQ\tDX, AX\n");
|
||||
return;
|
||||
};
|
||||
|
||||
// cgtryunw — `e!` aborts on the error variant via exit(1). Legacy
|
||||
// semantics (success tag = 0).
|
||||
fn cgtryunw(c: *cgen, n: *node) void = {
|
||||
cgexpr(c, n.lhs);
|
||||
let cl: str = mklabel(c, "tryunw_ok");
|
||||
emitline("\tCMPQ\t$0, AX\n");
|
||||
emitline("\tJE\t");
|
||||
emitline(cl);
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\t$1, DI\n\tMOVQ\t$60, AX\n\tSYSCALL\n");
|
||||
emitlabel(cl);
|
||||
// Unwrap success value. (Same shuffle pattern as cgtryprop.)
|
||||
let succisstr: bool = false;
|
||||
if (n.lhs != nil) {
|
||||
if (n.lhs.kind == N_CALL) {
|
||||
let callee: *node = n.lhs.lhs;
|
||||
if (callee != nil) {
|
||||
let cname: str;
|
||||
cname.ptr = nil; cname.len = 0;
|
||||
if (callee.kind == N_IDENT) { cname = callee.str; };
|
||||
if (callee.kind == N_DOT) { cname = callee.str; };
|
||||
if (cname.len > 0) {
|
||||
let rt: *node = fnretlookup(c, cname);
|
||||
if (rt != nil) {
|
||||
if (rt.kind == N_TTAGGED) {
|
||||
let first: *node = rt.list;
|
||||
if (first != nil) {
|
||||
if (isstrtype(c, first)) {
|
||||
succisstr = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (succisstr) {
|
||||
emitline("\tMOVQ\tCX, BX\n");
|
||||
};
|
||||
emitline("\tMOVQ\tDX, AX\n");
|
||||
return;
|
||||
};
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user