From dc8405429e433719389ceb38b486c683e3c808ef Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 12 May 2026 02:45:27 +0900 Subject: [PATCH] selfhost: ?/! postfix in parser + cgen; use ! in lex.ww escape path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- lib/ww/lex/lex.ww | 19 ++-- lib/ww/parse/expr.ww | 16 ++++ selfhost/cmd/w6a/main.combined.ww | 25 ++--- selfhost/cmd/w6c/main.combined.ww | 131 ++++++++++++++++++++++++--- selfhost/cmd/w6l/main.combined.ww | 25 ++--- selfhost/cmd/wcc/cgenexpr.ww | 96 ++++++++++++++++++++ selfhost/cmd/ww/main.combined.ww | 25 ++--- selfhost/cmd/wwdump/main.combined.ww | 131 ++++++++++++++++++++++++--- 8 files changed, 396 insertions(+), 72 deletions(-) diff --git a/lib/ww/lex/lex.ww b/lib/ww/lex/lex.ww index c7358c5c..3614f9db 100644 --- a/lib/ww/lex/lex.ww +++ b/lib/ww/lex/lex.ww @@ -240,18 +240,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; }; diff --git a/lib/ww/parse/expr.ww b/lib/ww/parse/expr.ww index efe00fca..36af7066 100644 --- a/lib/ww/parse/expr.ww +++ b/lib/ww/parse/expr.ww @@ -330,6 +330,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; diff --git a/selfhost/cmd/w6a/main.combined.ww b/selfhost/cmd/w6a/main.combined.ww index 87354108..aec6ca50 100644 --- a/selfhost/cmd/w6a/main.combined.ww +++ b/selfhost/cmd/w6a/main.combined.ww @@ -74,18 +74,18 @@ export fn dup2(oldfd: i32, newfd: i32) i32 = { return syscall2(SYS_DUP2, oldfd: i64, newfd: i64): i32; }; -// Fallible wrappers. The error variant is a plain str (Plan 9 errstr -// model, see lib/errors); the sum type makes success/failure explicit -// without overloading length-zero. -export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | str) = { +// Fallible wrappers. The error variant is `oserror` (an i64 carrying +// -errno). The sum type makes success/failure explicit and lets +// callers `?` the result up the stack. +export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { let r: i64 = read(fd, buf, n); - if (r < 0) { return "read failed"; }; + if (r < 0) { return r: oserror; }; return r; }; -export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | str) = { +export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { let r: i64 = write(fd, buf, n); - if (r < 0) { return "write failed"; }; + if (r < 0) { return r: oserror; }; return r; }; @@ -97,9 +97,9 @@ export fn open(path: *u8, flags: i32, mode: i32) i32 = { return syscall3(SYS_OPEN, path: i64, flags: i64, mode: i64): i32; }; -export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | str) = { +export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | oserror) = { let fd: i32 = open(path, flags, mode); - if (fd < 0) { return "open failed"; }; + if (fd < 0) { return fd: i64: oserror; }; return fd; }; @@ -112,9 +112,10 @@ export fn lseek(fd: i32, off: i64, whence: i32) i64 = { // oserror — the underlying errno from a failed syscall, as a // negative i64 (Linux's int convention; e.g. -2 = ENOENT). The -// NAMED-i64 alias makes it a distinct variant tag from a "good" -// i64 byte count. Hare's analogue is errors::errno. -export type oserror = i64; +// `!`-flagged alias makes ?-propagation pick this variant as the +// error half of any (T | oserror) shape. Hare's analogue is +// errors::errno carried inside io::error. +export type oserror = !i64; // filesize — byte length of an open fd via lseek-to-end-and-back. export fn filesize(fd: i32) (i64 | oserror) = { diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index a12ba3d4..dcddb6ed 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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. diff --git a/selfhost/cmd/w6l/main.combined.ww b/selfhost/cmd/w6l/main.combined.ww index f0c01619..253705f8 100644 --- a/selfhost/cmd/w6l/main.combined.ww +++ b/selfhost/cmd/w6l/main.combined.ww @@ -74,18 +74,18 @@ export fn dup2(oldfd: i32, newfd: i32) i32 = { return syscall2(SYS_DUP2, oldfd: i64, newfd: i64): i32; }; -// Fallible wrappers. The error variant is a plain str (Plan 9 errstr -// model, see lib/errors); the sum type makes success/failure explicit -// without overloading length-zero. -export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | str) = { +// Fallible wrappers. The error variant is `oserror` (an i64 carrying +// -errno). The sum type makes success/failure explicit and lets +// callers `?` the result up the stack. +export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { let r: i64 = read(fd, buf, n); - if (r < 0) { return "read failed"; }; + if (r < 0) { return r: oserror; }; return r; }; -export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | str) = { +export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { let r: i64 = write(fd, buf, n); - if (r < 0) { return "write failed"; }; + if (r < 0) { return r: oserror; }; return r; }; @@ -97,9 +97,9 @@ export fn open(path: *u8, flags: i32, mode: i32) i32 = { return syscall3(SYS_OPEN, path: i64, flags: i64, mode: i64): i32; }; -export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | str) = { +export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | oserror) = { let fd: i32 = open(path, flags, mode); - if (fd < 0) { return "open failed"; }; + if (fd < 0) { return fd: i64: oserror; }; return fd; }; @@ -112,9 +112,10 @@ export fn lseek(fd: i32, off: i64, whence: i32) i64 = { // oserror — the underlying errno from a failed syscall, as a // negative i64 (Linux's int convention; e.g. -2 = ENOENT). The -// NAMED-i64 alias makes it a distinct variant tag from a "good" -// i64 byte count. Hare's analogue is errors::errno. -export type oserror = i64; +// `!`-flagged alias makes ?-propagation pick this variant as the +// error half of any (T | oserror) shape. Hare's analogue is +// errors::errno carried inside io::error. +export type oserror = !i64; // filesize — byte length of an open fd via lseek-to-end-and-back. export fn filesize(fd: i32) (i64 | oserror) = { diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index b3b3825c..84b6a5de 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -86,6 +86,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; }; }; @@ -114,6 +116,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. diff --git a/selfhost/cmd/ww/main.combined.ww b/selfhost/cmd/ww/main.combined.ww index 2f0950f9..84edc88b 100644 --- a/selfhost/cmd/ww/main.combined.ww +++ b/selfhost/cmd/ww/main.combined.ww @@ -74,18 +74,18 @@ export fn dup2(oldfd: i32, newfd: i32) i32 = { return syscall2(SYS_DUP2, oldfd: i64, newfd: i64): i32; }; -// Fallible wrappers. The error variant is a plain str (Plan 9 errstr -// model, see lib/errors); the sum type makes success/failure explicit -// without overloading length-zero. -export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | str) = { +// Fallible wrappers. The error variant is `oserror` (an i64 carrying +// -errno). The sum type makes success/failure explicit and lets +// callers `?` the result up the stack. +export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { let r: i64 = read(fd, buf, n); - if (r < 0) { return "read failed"; }; + if (r < 0) { return r: oserror; }; return r; }; -export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | str) = { +export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { let r: i64 = write(fd, buf, n); - if (r < 0) { return "write failed"; }; + if (r < 0) { return r: oserror; }; return r; }; @@ -97,9 +97,9 @@ export fn open(path: *u8, flags: i32, mode: i32) i32 = { return syscall3(SYS_OPEN, path: i64, flags: i64, mode: i64): i32; }; -export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | str) = { +export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | oserror) = { let fd: i32 = open(path, flags, mode); - if (fd < 0) { return "open failed"; }; + if (fd < 0) { return fd: i64: oserror; }; return fd; }; @@ -112,9 +112,10 @@ export fn lseek(fd: i32, off: i64, whence: i32) i64 = { // oserror — the underlying errno from a failed syscall, as a // negative i64 (Linux's int convention; e.g. -2 = ENOENT). The -// NAMED-i64 alias makes it a distinct variant tag from a "good" -// i64 byte count. Hare's analogue is errors::errno. -export type oserror = i64; +// `!`-flagged alias makes ?-propagation pick this variant as the +// error half of any (T | oserror) shape. Hare's analogue is +// errors::errno carried inside io::error. +export type oserror = !i64; // filesize — byte length of an open fd via lseek-to-end-and-back. export fn filesize(fd: i32) (i64 | oserror) = { diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index a60ce817..ea6597f1 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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.