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:
2026-05-12 02:45:27 +09:00
parent d9041ab45e
commit dc8405429e
8 changed files with 396 additions and 72 deletions

View File

@@ -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) = {