selfhost: alias-aware istaggedtype for nested-union match
`type error = !(invalid | overflow)` miscompiled — istaggedtype only matched N_TTAGGED directly, so an `e: error` param spilled as 8B scalar and the match's slot+8 read trailed into saved BP. Mirror isstrtype's alias+bang unwrap; add resolvetagged() for is/as/match sites that need the inner N_TTAGGED. Frame scan counts via slotsize so wwstage stays byte-identical to cstage. Unblocks lib/strconv.strerror.
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
// today). Graduate to the static-buffer shape once that lands.
|
||||
|
||||
use os;
|
||||
use strings;
|
||||
|
||||
// invalid — input wasn't a valid number in the requested format.
|
||||
// Payload is the byte index of the first offending position.
|
||||
@@ -352,8 +353,13 @@ export fn f64tos(v: f64) str = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// strerror — Hare has strconv::strerror; ww doesn't ship it yet
|
||||
// because a `match (e) { case invalid => ... }` arm over the wider
|
||||
// `error = !(invalid | overflow)` union exposes a cstage-vs-wwstage
|
||||
// cgen divergence (one cgen spills the unused payload slot, the
|
||||
// other elides it). Restore once the cgens converge.
|
||||
// strerror — convert an strconv error to a user-readable string.
|
||||
// Returns owned str; release via os.free. Mirrors Hare's
|
||||
// strconv::strerror.
|
||||
export fn strerror(e: error) str = {
|
||||
match (e) {
|
||||
case let v: invalid => return strings.dup("input is not a valid number");
|
||||
case let v: overflow => return strings.dup("input number doesn't fit target type");
|
||||
};
|
||||
return strings.dup("");
|
||||
};
|
||||
|
||||
@@ -335,6 +335,226 @@ export fn freearena(a: *arena) void = {
|
||||
};
|
||||
};
|
||||
|
||||
// MODULE: strings
|
||||
// strings — operations over the immutable str type ({ *u8, len }).
|
||||
// Mirrors Hare's strings::; `len` and `is-empty` aren't functions
|
||||
// (callers use `s.len` and `s.len == 0` directly).
|
||||
|
||||
use os;
|
||||
|
||||
// compare — bytewise three-way comparison: negative if a<b, 0 if equal,
|
||||
// positive if a>b. Matches Hare's strings::compare. ASCII-order, not
|
||||
// locale-aware. Callers that just need equality use `compare(a, b) == 0`.
|
||||
export fn compare(a: str, b: str) i32 = {
|
||||
let n: i32 = a.len;
|
||||
if (b.len < n) { n = b.len; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
if (a[i] != b[i]) { return (a[i]: i32) - (b[i]: i32); };
|
||||
i += 1;
|
||||
};
|
||||
return a.len - b.len;
|
||||
};
|
||||
|
||||
export fn hasprefix(s: str, p: str) bool = {
|
||||
if (p.len > s.len) { return false; };
|
||||
let i: i32 = 0;
|
||||
for (i < p.len) {
|
||||
if (s[i] != p[i]) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
export fn hassuffix(s: str, suf: str) bool = {
|
||||
if (suf.len > s.len) { return false; };
|
||||
let off: i32 = s.len - suf.len;
|
||||
let i: i32 = 0;
|
||||
for (i < suf.len) {
|
||||
if (s[off + i] != suf[i]) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// indexbyte — first byte position of byte `c` in `s`. Mirrors
|
||||
// Hare's strings::byteindex when the needle is a single ASCII rune,
|
||||
// renamed to match bytes.indexbyte and to disambiguate from Hare's
|
||||
// `byteindex(haystack, needle: (str | rune))` which we don't have
|
||||
// the union-arg ABI for yet.
|
||||
export fn indexbyte(s: str, c: u8) (i32 | void) = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
if (s[i] == c) { return i; };
|
||||
i += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
// rindexbyte — last byte position of byte `c` in `s`.
|
||||
export fn rindexbyte(s: str, c: u8) (i32 | void) = {
|
||||
let i: i32 = s.len - 1;
|
||||
for (i >= 0) {
|
||||
if (s[i] == c) { return i; };
|
||||
i -= 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
// index — first index of `sub` in `s`. Naive scan; fine for short
|
||||
// patterns and small strings, which dominate config and CLI parsing.
|
||||
// Empty `sub` matches at 0.
|
||||
export fn index(s: str, sub: str) (i32 | void) = {
|
||||
if (sub.len == 0) { return 0; };
|
||||
if (sub.len > s.len) { return; };
|
||||
let last: i32 = s.len - sub.len;
|
||||
let i: i32 = 0;
|
||||
for (i <= last) {
|
||||
let j: i32 = 0;
|
||||
let ok: bool = true;
|
||||
for (j < sub.len) {
|
||||
if (s[i + j] != sub[j]) { ok = false; j = sub.len; }
|
||||
else { j += 1; };
|
||||
};
|
||||
if (ok) { return i; };
|
||||
i += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
export fn contains(s: str, sub: str) bool = {
|
||||
let r: (i32 | void) = index(s, sub);
|
||||
match (r) {
|
||||
case let i: i32 => return true;
|
||||
case void => return false;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// concat — joins two strings into a fresh str. Caller owns the
|
||||
// returned str's storage; release via `os.free(r.ptr, r.len)`. Mirrors
|
||||
// Hare's strings::concat shape.
|
||||
export fn concat(a: str, b: str) str = {
|
||||
let total: i32 = a.len + b.len;
|
||||
let buf: *u8 = os.alloc(total: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < a.len) { buf[i] = a[i]; i += 1; };
|
||||
let j: i32 = 0;
|
||||
for (j < b.len) { buf[a.len + j] = b[j]; j += 1; };
|
||||
let r: str;
|
||||
r.ptr = buf;
|
||||
r.len = total;
|
||||
return r;
|
||||
};
|
||||
|
||||
// dup — duplicate a string into a fresh allocation. Caller owns the
|
||||
// returned str's storage; release via `os.free(r.ptr, r.len)`. Mirrors
|
||||
// Hare's strings::dup shape — Hare returns `(str | nomem)`, ww doesn't
|
||||
// have nomem (os.alloc aborts on OOM), so we return plain `str`.
|
||||
//
|
||||
// Empty input yields a `{nil, 0}` str — Hare returns the static empty
|
||||
// string; same observable result.
|
||||
export fn dup(s: str) str = {
|
||||
let r: str;
|
||||
r.ptr = nil;
|
||||
r.len = 0;
|
||||
if (s.len == 0) { return r; };
|
||||
let buf: *u8 = os.alloc(s.len: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) { buf[i] = s[i]; i += 1; };
|
||||
r.ptr = buf;
|
||||
r.len = s.len;
|
||||
return r;
|
||||
};
|
||||
|
||||
// rindex — last index of `sub` in `s`. Mirrors Hare's strings::rindex
|
||||
// (slice case). Empty `sub` matches at s.len.
|
||||
export fn rindex(s: str, sub: str) (i32 | void) = {
|
||||
if (sub.len == 0) { return s.len; };
|
||||
if (sub.len > s.len) { return; };
|
||||
let i: i32 = s.len - sub.len;
|
||||
for (i >= 0) {
|
||||
let j: i32 = 0;
|
||||
let ok: bool = true;
|
||||
for (j < sub.len) {
|
||||
if (s[i + j] != sub[j]) { ok = false; j = sub.len; }
|
||||
else { j += 1; };
|
||||
};
|
||||
if (ok) { return i; };
|
||||
i -= 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
// sub — borrowed substring `s[start..end]`. Mirrors Hare's
|
||||
// strings::sub. Caller must ensure 0 <= start <= end <= s.len; out-of-
|
||||
// range indices are clamped silently here, where Hare aborts.
|
||||
export fn sub(s: str, start: i32, end: i32) str = {
|
||||
let lo: i32 = start;
|
||||
let hi: i32 = end;
|
||||
if (lo < 0) { lo = 0; };
|
||||
if (hi > s.len) { hi = s.len; };
|
||||
if (hi < lo) { hi = lo; };
|
||||
let r: str;
|
||||
r.ptr = s.ptr + (lo: u64);
|
||||
r.len = hi - lo;
|
||||
return r;
|
||||
};
|
||||
|
||||
// trimprefix — `s` with `pre` stripped from the front, or `s`
|
||||
// unchanged if it doesn't start with `pre`. Returns a borrowed view.
|
||||
// Mirrors Hare's strings::trimprefix.
|
||||
export fn trimprefix(s: str, pre: str) str = {
|
||||
if (!hasprefix(s, pre)) { return s; };
|
||||
let r: str;
|
||||
r.ptr = s.ptr + (pre.len: u64);
|
||||
r.len = s.len - pre.len;
|
||||
return r;
|
||||
};
|
||||
|
||||
// trimsuffix — `s` with `suf` stripped from the end, or `s` unchanged
|
||||
// if it doesn't end with `suf`. Returns a borrowed view. Mirrors
|
||||
// Hare's strings::trimsuffix.
|
||||
export fn trimsuffix(s: str, suf: str) str = {
|
||||
if (!hassuffix(s, suf)) { return s; };
|
||||
let r: str;
|
||||
r.ptr = s.ptr;
|
||||
r.len = s.len - suf.len;
|
||||
return r;
|
||||
};
|
||||
|
||||
// ltrimbyte / rtrimbyte / trimbyte — strip occurrences of a single
|
||||
// byte from the left, right, or both ends. Returns a borrowed view.
|
||||
// Hare's strings::ltrim / rtrim / trim take a rune varargs set; ww's
|
||||
// subset takes a single byte (the common ASCII case).
|
||||
export fn ltrimbyte(s: str, c: u8) str = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
if (s[i] != c) { break; };
|
||||
i += 1;
|
||||
};
|
||||
let r: str;
|
||||
r.ptr = s.ptr + (i: u64);
|
||||
r.len = s.len - i;
|
||||
return r;
|
||||
};
|
||||
|
||||
export fn rtrimbyte(s: str, c: u8) str = {
|
||||
let n: i32 = s.len;
|
||||
for (n > 0) {
|
||||
if (s[n - 1] != c) { break; };
|
||||
n -= 1;
|
||||
};
|
||||
let r: str;
|
||||
r.ptr = s.ptr;
|
||||
r.len = n;
|
||||
return r;
|
||||
};
|
||||
|
||||
export fn trimbyte(s: str, c: u8) str = {
|
||||
return rtrimbyte(ltrimbyte(s, c), c);
|
||||
};
|
||||
|
||||
// MODULE: strconv
|
||||
// strconv — number↔string conversions.
|
||||
//
|
||||
@@ -346,6 +566,7 @@ export fn freearena(a: *arena) void = {
|
||||
// today). Graduate to the static-buffer shape once that lands.
|
||||
|
||||
use os;
|
||||
use strings;
|
||||
|
||||
// invalid — input wasn't a valid number in the requested format.
|
||||
// Payload is the byte index of the first offending position.
|
||||
@@ -690,11 +911,16 @@ export fn f64tos(v: f64) str = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// strerror — Hare has strconv::strerror; ww doesn't ship it yet
|
||||
// because a `match (e) { case invalid => ... }` arm over the wider
|
||||
// `error = !(invalid | overflow)` union exposes a cstage-vs-wwstage
|
||||
// cgen divergence (one cgen spills the unused payload slot, the
|
||||
// other elides it). Restore once the cgens converge.
|
||||
// strerror — convert an strconv error to a user-readable string.
|
||||
// Returns owned str; release via os.free. Mirrors Hare's
|
||||
// strconv::strerror.
|
||||
export fn strerror(e: error) str = {
|
||||
match (e) {
|
||||
case let v: invalid => return strings.dup("input is not a valid number");
|
||||
case let v: overflow => return strings.dup("input number doesn't fit target type");
|
||||
};
|
||||
return strings.dup("");
|
||||
};
|
||||
|
||||
// MODULE: lex
|
||||
// lib/ww/lex/tok.ww — port of cmd/wcc/tok.c plus the Tkind /
|
||||
@@ -5426,7 +5652,7 @@ fn pushargsrev(c: *cgen, arg: *node) i32 = {
|
||||
let lc: *local = localfindnode(c, nm);
|
||||
if (lc != nil) {
|
||||
let off: i32 = lc.off;
|
||||
if (isslicetype(c, lc.tnode) || istaggedtype(lc.tnode)) {
|
||||
if (isslicetype(c, lc.tnode) || istaggedtype(c, lc.tnode)) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP), AX\n");
|
||||
@@ -6361,12 +6587,56 @@ fn isslicetype(c: *cgen, t: *node) bool = {
|
||||
return isslicetyperaw(r);
|
||||
};
|
||||
|
||||
fn istaggedtype(t: *node) bool = {
|
||||
fn istaggedtyperaw(t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
if (t.kind == nkind.N_TTAGGED) { return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// resolvetagged — return the underlying N_TTAGGED node for `t`, or nil
|
||||
// if `t` doesn't ultimately denote a tagged union. Follows N_TNAME
|
||||
// aliases (via resolvetype) and unwraps one leading N_TBANG so
|
||||
// `type error = !(invalid | overflow);` resolves to its inner
|
||||
// `(invalid | overflow)` node. Use at sites that read variant lists
|
||||
// or detect nullable folding off a scrutinee — cgmatch, cgtypetest,
|
||||
// cgtypeassert — so aliased `!(A|B)` shapes still dispatch.
|
||||
export fn resolvetagged(c: *cgen, t: *node) *node = {
|
||||
let r: *node = resolvetype(c, t);
|
||||
if (r == nil) { return nil; };
|
||||
if (r.kind == nkind.N_TBANG) {
|
||||
let inner: *node = r.lhs;
|
||||
if (inner == nil) { return nil; };
|
||||
r = resolvetype(c, inner);
|
||||
if (r == nil) { return nil; };
|
||||
};
|
||||
if (r.kind == nkind.N_TTAGGED) { return r; };
|
||||
return nil;
|
||||
};
|
||||
|
||||
// istaggedtype — alias-aware. Mirrors isstrtype: follow N_TNAME to its
|
||||
// underlying decl, then unwrap a leading N_TBANG so `type error =
|
||||
// !(invalid | overflow);` is still recognised as tagged. Without the
|
||||
// bang unwrap the prologue treats the param as scalar (8B), spilling
|
||||
// only DI and losing the value-word SI; the match read of slot+8 then
|
||||
// trails into saved BP.
|
||||
fn istaggedtype(c: *cgen, t: *node) bool = {
|
||||
if (istaggedtyperaw(t)) { return true; };
|
||||
if (c == nil) { return false; };
|
||||
let r: *node = resolvetype(c, t);
|
||||
if (istaggedtyperaw(r)) { return true; };
|
||||
if (r != nil) {
|
||||
if (r.kind == nkind.N_TBANG) {
|
||||
let inner: *node = r.lhs;
|
||||
if (istaggedtyperaw(inner)) { return true; };
|
||||
if (inner != nil) {
|
||||
let r2: *node = resolvetype(c, inner);
|
||||
if (istaggedtyperaw(r2)) { return true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// isf32typeraw / isf64typeraw — bare TNAME check, no alias resolution.
|
||||
fn isf32typeraw(t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
@@ -6891,7 +7161,7 @@ fn cgtypetest(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, lhs.str);
|
||||
if (lc != nil) {
|
||||
scrutoff = lc.off;
|
||||
scrutt = resolvetype(c, lc.tnode);
|
||||
scrutt = resolvetagged(c, lc.tnode);
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -6979,7 +7249,7 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, lhs.str);
|
||||
if (lc != nil) {
|
||||
scrutoff = lc.off;
|
||||
scrutt = resolvetype(c, lc.tnode);
|
||||
scrutt = resolvetagged(c, lc.tnode);
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -7393,7 +7663,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, scrut.str);
|
||||
if (lc != nil) {
|
||||
scrutoff = lc.off;
|
||||
scrutt = resolvetype(c, lc.tnode);
|
||||
scrutt = resolvetagged(c, lc.tnode);
|
||||
};
|
||||
} else {
|
||||
// Non-ident scrutinee (call result, ?, etc.). Spill into a
|
||||
@@ -7415,7 +7685,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
if (callee.kind == nkind.N_DOT) { cnm = callee.str; };
|
||||
if (cnm.len > 0) {
|
||||
let rt: *node = fnretlookup(c, cnm);
|
||||
if (rt != nil) { scrutt = resolvetype(c, rt); };
|
||||
if (rt != nil) { scrutt = resolvetagged(c, rt); };
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -9833,7 +10103,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
// For other variants, cgexpr leaves AX, shuffle DX←AX.
|
||||
// Nullable folded `(*T | void)`: just one word; AX is
|
||||
// already the pointer (or 0). No shuffle, no tag.
|
||||
if (istaggedtype(c.fnret)) {
|
||||
if (istaggedtype(c, c.fnret)) {
|
||||
// Forwarding a fallible call: `return f();` where f
|
||||
// also returns a tagged union. The result is already
|
||||
// in (AX=tag, DX=v0, CX=v1) — no shuffle, no tag.
|
||||
@@ -9849,7 +10119,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
if (callee.kind == nkind.N_DOT) { calleename = callee.str; };
|
||||
if (calleename.len > 0) {
|
||||
let rt: *node = fnretlookup(c, calleename);
|
||||
if (istaggedtype(rt)) { forwardtagged = true; };
|
||||
if (istaggedtype(c, rt)) { forwardtagged = true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -9890,7 +10160,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
// Bare `return;` from a tagged-union-returning fn is
|
||||
// the void variant: emit its tag. Payload is undefined
|
||||
// (void has size 0). Otherwise zero AX for determinism.
|
||||
if (istaggedtype(c.fnret)) {
|
||||
if (istaggedtype(c, c.fnret)) {
|
||||
if (isnullabletype(c.fnret)) {
|
||||
// null = void variant; AX = 0.
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
@@ -9944,7 +10214,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
// just spill all three.
|
||||
// - Otherwise rhs is a bare variant value: pack tag +
|
||||
// value(s).
|
||||
if (istaggedtype(tn)) {
|
||||
if (istaggedtype(c, tn)) {
|
||||
let nullable: bool = isnullabletype(tn);
|
||||
let rhsreturnstagged: bool = false;
|
||||
if (rhs.kind == nkind.N_CALL) {
|
||||
@@ -9956,7 +10226,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
if (callee.kind == nkind.N_DOT) { calleename = callee.str; };
|
||||
if (calleename.len > 0) {
|
||||
let rt: *node = fnretlookup(c, calleename);
|
||||
if (istaggedtype(rt)) { rhsreturnstagged = true; };
|
||||
if (istaggedtype(c, rt)) { rhsreturnstagged = true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -10998,7 +11268,7 @@ fn cgfnparams(c: *cgen, params: *node) void = {
|
||||
p = p.next;
|
||||
continue;
|
||||
};
|
||||
if (istaggedtype(p.lhs)) {
|
||||
if (istaggedtype(c, p.lhs)) {
|
||||
let slot: i32 = slotsize(c, p.lhs);
|
||||
let nw: i32 = slot / 8;
|
||||
if (idx + nw <= 6) {
|
||||
@@ -11116,7 +11386,7 @@ fn cgfn(c: *cgen, fn_: *node) void = {
|
||||
let frame: i32 = 0;
|
||||
for (scanp != nil) {
|
||||
if (scanp.kind == nkind.N_PARAM) {
|
||||
if (istaggedtype(scanp.lhs)) { frame += 24; }
|
||||
if (istaggedtype(c, scanp.lhs)) { frame += slotsize(c, scanp.lhs); }
|
||||
else { if (isslicetype(c, scanp.lhs)) { frame += 24; }
|
||||
else { if (isstrtype(c, scanp.lhs)) { frame += 16; }
|
||||
else { frame += 8; }; }; };
|
||||
|
||||
@@ -199,7 +199,7 @@ fn cgfnparams(c: *cgen, params: *node) void = {
|
||||
p = p.next;
|
||||
continue;
|
||||
};
|
||||
if (istaggedtype(p.lhs)) {
|
||||
if (istaggedtype(c, p.lhs)) {
|
||||
let slot: i32 = slotsize(c, p.lhs);
|
||||
let nw: i32 = slot / 8;
|
||||
if (idx + nw <= 6) {
|
||||
@@ -317,7 +317,7 @@ fn cgfn(c: *cgen, fn_: *node) void = {
|
||||
let frame: i32 = 0;
|
||||
for (scanp != nil) {
|
||||
if (scanp.kind == nkind.N_PARAM) {
|
||||
if (istaggedtype(scanp.lhs)) { frame += 24; }
|
||||
if (istaggedtype(c, scanp.lhs)) { frame += slotsize(c, scanp.lhs); }
|
||||
else { if (isslicetype(c, scanp.lhs)) { frame += 24; }
|
||||
else { if (isstrtype(c, scanp.lhs)) { frame += 16; }
|
||||
else { frame += 8; }; }; };
|
||||
|
||||
@@ -242,7 +242,7 @@ fn cgtypetest(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, lhs.str);
|
||||
if (lc != nil) {
|
||||
scrutoff = lc.off;
|
||||
scrutt = resolvetype(c, lc.tnode);
|
||||
scrutt = resolvetagged(c, lc.tnode);
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -330,7 +330,7 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, lhs.str);
|
||||
if (lc != nil) {
|
||||
scrutoff = lc.off;
|
||||
scrutt = resolvetype(c, lc.tnode);
|
||||
scrutt = resolvetagged(c, lc.tnode);
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -744,7 +744,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, scrut.str);
|
||||
if (lc != nil) {
|
||||
scrutoff = lc.off;
|
||||
scrutt = resolvetype(c, lc.tnode);
|
||||
scrutt = resolvetagged(c, lc.tnode);
|
||||
};
|
||||
} else {
|
||||
// Non-ident scrutinee (call result, ?, etc.). Spill into a
|
||||
@@ -766,7 +766,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
if (callee.kind == nkind.N_DOT) { cnm = callee.str; };
|
||||
if (cnm.len > 0) {
|
||||
let rt: *node = fnretlookup(c, cnm);
|
||||
if (rt != nil) { scrutt = resolvetype(c, rt); };
|
||||
if (rt != nil) { scrutt = resolvetagged(c, rt); };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -142,7 +142,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
// For other variants, cgexpr leaves AX, shuffle DX←AX.
|
||||
// Nullable folded `(*T | void)`: just one word; AX is
|
||||
// already the pointer (or 0). No shuffle, no tag.
|
||||
if (istaggedtype(c.fnret)) {
|
||||
if (istaggedtype(c, c.fnret)) {
|
||||
// Forwarding a fallible call: `return f();` where f
|
||||
// also returns a tagged union. The result is already
|
||||
// in (AX=tag, DX=v0, CX=v1) — no shuffle, no tag.
|
||||
@@ -158,7 +158,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
if (callee.kind == nkind.N_DOT) { calleename = callee.str; };
|
||||
if (calleename.len > 0) {
|
||||
let rt: *node = fnretlookup(c, calleename);
|
||||
if (istaggedtype(rt)) { forwardtagged = true; };
|
||||
if (istaggedtype(c, rt)) { forwardtagged = true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -199,7 +199,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
// Bare `return;` from a tagged-union-returning fn is
|
||||
// the void variant: emit its tag. Payload is undefined
|
||||
// (void has size 0). Otherwise zero AX for determinism.
|
||||
if (istaggedtype(c.fnret)) {
|
||||
if (istaggedtype(c, c.fnret)) {
|
||||
if (isnullabletype(c.fnret)) {
|
||||
// null = void variant; AX = 0.
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
@@ -253,7 +253,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
// just spill all three.
|
||||
// - Otherwise rhs is a bare variant value: pack tag +
|
||||
// value(s).
|
||||
if (istaggedtype(tn)) {
|
||||
if (istaggedtype(c, tn)) {
|
||||
let nullable: bool = isnullabletype(tn);
|
||||
let rhsreturnstagged: bool = false;
|
||||
if (rhs.kind == nkind.N_CALL) {
|
||||
@@ -265,7 +265,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
if (callee.kind == nkind.N_DOT) { calleename = callee.str; };
|
||||
if (calleename.len > 0) {
|
||||
let rt: *node = fnretlookup(c, calleename);
|
||||
if (istaggedtype(rt)) { rhsreturnstagged = true; };
|
||||
if (istaggedtype(c, rt)) { rhsreturnstagged = true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -118,7 +118,7 @@ fn pushargsrev(c: *cgen, arg: *node) i32 = {
|
||||
let lc: *local = localfindnode(c, nm);
|
||||
if (lc != nil) {
|
||||
let off: i32 = lc.off;
|
||||
if (isslicetype(c, lc.tnode) || istaggedtype(lc.tnode)) {
|
||||
if (isslicetype(c, lc.tnode) || istaggedtype(c, lc.tnode)) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP), AX\n");
|
||||
@@ -1053,12 +1053,56 @@ fn isslicetype(c: *cgen, t: *node) bool = {
|
||||
return isslicetyperaw(r);
|
||||
};
|
||||
|
||||
fn istaggedtype(t: *node) bool = {
|
||||
fn istaggedtyperaw(t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
if (t.kind == nkind.N_TTAGGED) { return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// resolvetagged — return the underlying N_TTAGGED node for `t`, or nil
|
||||
// if `t` doesn't ultimately denote a tagged union. Follows N_TNAME
|
||||
// aliases (via resolvetype) and unwraps one leading N_TBANG so
|
||||
// `type error = !(invalid | overflow);` resolves to its inner
|
||||
// `(invalid | overflow)` node. Use at sites that read variant lists
|
||||
// or detect nullable folding off a scrutinee — cgmatch, cgtypetest,
|
||||
// cgtypeassert — so aliased `!(A|B)` shapes still dispatch.
|
||||
export fn resolvetagged(c: *cgen, t: *node) *node = {
|
||||
let r: *node = resolvetype(c, t);
|
||||
if (r == nil) { return nil; };
|
||||
if (r.kind == nkind.N_TBANG) {
|
||||
let inner: *node = r.lhs;
|
||||
if (inner == nil) { return nil; };
|
||||
r = resolvetype(c, inner);
|
||||
if (r == nil) { return nil; };
|
||||
};
|
||||
if (r.kind == nkind.N_TTAGGED) { return r; };
|
||||
return nil;
|
||||
};
|
||||
|
||||
// istaggedtype — alias-aware. Mirrors isstrtype: follow N_TNAME to its
|
||||
// underlying decl, then unwrap a leading N_TBANG so `type error =
|
||||
// !(invalid | overflow);` is still recognised as tagged. Without the
|
||||
// bang unwrap the prologue treats the param as scalar (8B), spilling
|
||||
// only DI and losing the value-word SI; the match read of slot+8 then
|
||||
// trails into saved BP.
|
||||
fn istaggedtype(c: *cgen, t: *node) bool = {
|
||||
if (istaggedtyperaw(t)) { return true; };
|
||||
if (c == nil) { return false; };
|
||||
let r: *node = resolvetype(c, t);
|
||||
if (istaggedtyperaw(r)) { return true; };
|
||||
if (r != nil) {
|
||||
if (r.kind == nkind.N_TBANG) {
|
||||
let inner: *node = r.lhs;
|
||||
if (istaggedtyperaw(inner)) { return true; };
|
||||
if (inner != nil) {
|
||||
let r2: *node = resolvetype(c, inner);
|
||||
if (istaggedtyperaw(r2)) { return true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// isf32typeraw / isf64typeraw — bare TNAME check, no alias resolution.
|
||||
fn isf32typeraw(t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
|
||||
@@ -335,6 +335,226 @@ export fn freearena(a: *arena) void = {
|
||||
};
|
||||
};
|
||||
|
||||
// MODULE: strings
|
||||
// strings — operations over the immutable str type ({ *u8, len }).
|
||||
// Mirrors Hare's strings::; `len` and `is-empty` aren't functions
|
||||
// (callers use `s.len` and `s.len == 0` directly).
|
||||
|
||||
use os;
|
||||
|
||||
// compare — bytewise three-way comparison: negative if a<b, 0 if equal,
|
||||
// positive if a>b. Matches Hare's strings::compare. ASCII-order, not
|
||||
// locale-aware. Callers that just need equality use `compare(a, b) == 0`.
|
||||
export fn compare(a: str, b: str) i32 = {
|
||||
let n: i32 = a.len;
|
||||
if (b.len < n) { n = b.len; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
if (a[i] != b[i]) { return (a[i]: i32) - (b[i]: i32); };
|
||||
i += 1;
|
||||
};
|
||||
return a.len - b.len;
|
||||
};
|
||||
|
||||
export fn hasprefix(s: str, p: str) bool = {
|
||||
if (p.len > s.len) { return false; };
|
||||
let i: i32 = 0;
|
||||
for (i < p.len) {
|
||||
if (s[i] != p[i]) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
export fn hassuffix(s: str, suf: str) bool = {
|
||||
if (suf.len > s.len) { return false; };
|
||||
let off: i32 = s.len - suf.len;
|
||||
let i: i32 = 0;
|
||||
for (i < suf.len) {
|
||||
if (s[off + i] != suf[i]) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// indexbyte — first byte position of byte `c` in `s`. Mirrors
|
||||
// Hare's strings::byteindex when the needle is a single ASCII rune,
|
||||
// renamed to match bytes.indexbyte and to disambiguate from Hare's
|
||||
// `byteindex(haystack, needle: (str | rune))` which we don't have
|
||||
// the union-arg ABI for yet.
|
||||
export fn indexbyte(s: str, c: u8) (i32 | void) = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
if (s[i] == c) { return i; };
|
||||
i += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
// rindexbyte — last byte position of byte `c` in `s`.
|
||||
export fn rindexbyte(s: str, c: u8) (i32 | void) = {
|
||||
let i: i32 = s.len - 1;
|
||||
for (i >= 0) {
|
||||
if (s[i] == c) { return i; };
|
||||
i -= 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
// index — first index of `sub` in `s`. Naive scan; fine for short
|
||||
// patterns and small strings, which dominate config and CLI parsing.
|
||||
// Empty `sub` matches at 0.
|
||||
export fn index(s: str, sub: str) (i32 | void) = {
|
||||
if (sub.len == 0) { return 0; };
|
||||
if (sub.len > s.len) { return; };
|
||||
let last: i32 = s.len - sub.len;
|
||||
let i: i32 = 0;
|
||||
for (i <= last) {
|
||||
let j: i32 = 0;
|
||||
let ok: bool = true;
|
||||
for (j < sub.len) {
|
||||
if (s[i + j] != sub[j]) { ok = false; j = sub.len; }
|
||||
else { j += 1; };
|
||||
};
|
||||
if (ok) { return i; };
|
||||
i += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
export fn contains(s: str, sub: str) bool = {
|
||||
let r: (i32 | void) = index(s, sub);
|
||||
match (r) {
|
||||
case let i: i32 => return true;
|
||||
case void => return false;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// concat — joins two strings into a fresh str. Caller owns the
|
||||
// returned str's storage; release via `os.free(r.ptr, r.len)`. Mirrors
|
||||
// Hare's strings::concat shape.
|
||||
export fn concat(a: str, b: str) str = {
|
||||
let total: i32 = a.len + b.len;
|
||||
let buf: *u8 = os.alloc(total: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < a.len) { buf[i] = a[i]; i += 1; };
|
||||
let j: i32 = 0;
|
||||
for (j < b.len) { buf[a.len + j] = b[j]; j += 1; };
|
||||
let r: str;
|
||||
r.ptr = buf;
|
||||
r.len = total;
|
||||
return r;
|
||||
};
|
||||
|
||||
// dup — duplicate a string into a fresh allocation. Caller owns the
|
||||
// returned str's storage; release via `os.free(r.ptr, r.len)`. Mirrors
|
||||
// Hare's strings::dup shape — Hare returns `(str | nomem)`, ww doesn't
|
||||
// have nomem (os.alloc aborts on OOM), so we return plain `str`.
|
||||
//
|
||||
// Empty input yields a `{nil, 0}` str — Hare returns the static empty
|
||||
// string; same observable result.
|
||||
export fn dup(s: str) str = {
|
||||
let r: str;
|
||||
r.ptr = nil;
|
||||
r.len = 0;
|
||||
if (s.len == 0) { return r; };
|
||||
let buf: *u8 = os.alloc(s.len: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) { buf[i] = s[i]; i += 1; };
|
||||
r.ptr = buf;
|
||||
r.len = s.len;
|
||||
return r;
|
||||
};
|
||||
|
||||
// rindex — last index of `sub` in `s`. Mirrors Hare's strings::rindex
|
||||
// (slice case). Empty `sub` matches at s.len.
|
||||
export fn rindex(s: str, sub: str) (i32 | void) = {
|
||||
if (sub.len == 0) { return s.len; };
|
||||
if (sub.len > s.len) { return; };
|
||||
let i: i32 = s.len - sub.len;
|
||||
for (i >= 0) {
|
||||
let j: i32 = 0;
|
||||
let ok: bool = true;
|
||||
for (j < sub.len) {
|
||||
if (s[i + j] != sub[j]) { ok = false; j = sub.len; }
|
||||
else { j += 1; };
|
||||
};
|
||||
if (ok) { return i; };
|
||||
i -= 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
// sub — borrowed substring `s[start..end]`. Mirrors Hare's
|
||||
// strings::sub. Caller must ensure 0 <= start <= end <= s.len; out-of-
|
||||
// range indices are clamped silently here, where Hare aborts.
|
||||
export fn sub(s: str, start: i32, end: i32) str = {
|
||||
let lo: i32 = start;
|
||||
let hi: i32 = end;
|
||||
if (lo < 0) { lo = 0; };
|
||||
if (hi > s.len) { hi = s.len; };
|
||||
if (hi < lo) { hi = lo; };
|
||||
let r: str;
|
||||
r.ptr = s.ptr + (lo: u64);
|
||||
r.len = hi - lo;
|
||||
return r;
|
||||
};
|
||||
|
||||
// trimprefix — `s` with `pre` stripped from the front, or `s`
|
||||
// unchanged if it doesn't start with `pre`. Returns a borrowed view.
|
||||
// Mirrors Hare's strings::trimprefix.
|
||||
export fn trimprefix(s: str, pre: str) str = {
|
||||
if (!hasprefix(s, pre)) { return s; };
|
||||
let r: str;
|
||||
r.ptr = s.ptr + (pre.len: u64);
|
||||
r.len = s.len - pre.len;
|
||||
return r;
|
||||
};
|
||||
|
||||
// trimsuffix — `s` with `suf` stripped from the end, or `s` unchanged
|
||||
// if it doesn't end with `suf`. Returns a borrowed view. Mirrors
|
||||
// Hare's strings::trimsuffix.
|
||||
export fn trimsuffix(s: str, suf: str) str = {
|
||||
if (!hassuffix(s, suf)) { return s; };
|
||||
let r: str;
|
||||
r.ptr = s.ptr;
|
||||
r.len = s.len - suf.len;
|
||||
return r;
|
||||
};
|
||||
|
||||
// ltrimbyte / rtrimbyte / trimbyte — strip occurrences of a single
|
||||
// byte from the left, right, or both ends. Returns a borrowed view.
|
||||
// Hare's strings::ltrim / rtrim / trim take a rune varargs set; ww's
|
||||
// subset takes a single byte (the common ASCII case).
|
||||
export fn ltrimbyte(s: str, c: u8) str = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
if (s[i] != c) { break; };
|
||||
i += 1;
|
||||
};
|
||||
let r: str;
|
||||
r.ptr = s.ptr + (i: u64);
|
||||
r.len = s.len - i;
|
||||
return r;
|
||||
};
|
||||
|
||||
export fn rtrimbyte(s: str, c: u8) str = {
|
||||
let n: i32 = s.len;
|
||||
for (n > 0) {
|
||||
if (s[n - 1] != c) { break; };
|
||||
n -= 1;
|
||||
};
|
||||
let r: str;
|
||||
r.ptr = s.ptr;
|
||||
r.len = n;
|
||||
return r;
|
||||
};
|
||||
|
||||
export fn trimbyte(s: str, c: u8) str = {
|
||||
return rtrimbyte(ltrimbyte(s, c), c);
|
||||
};
|
||||
|
||||
// MODULE: strconv
|
||||
// strconv — number↔string conversions.
|
||||
//
|
||||
@@ -346,6 +566,7 @@ export fn freearena(a: *arena) void = {
|
||||
// today). Graduate to the static-buffer shape once that lands.
|
||||
|
||||
use os;
|
||||
use strings;
|
||||
|
||||
// invalid — input wasn't a valid number in the requested format.
|
||||
// Payload is the byte index of the first offending position.
|
||||
@@ -690,11 +911,16 @@ export fn f64tos(v: f64) str = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// strerror — Hare has strconv::strerror; ww doesn't ship it yet
|
||||
// because a `match (e) { case invalid => ... }` arm over the wider
|
||||
// `error = !(invalid | overflow)` union exposes a cstage-vs-wwstage
|
||||
// cgen divergence (one cgen spills the unused payload slot, the
|
||||
// other elides it). Restore once the cgens converge.
|
||||
// strerror — convert an strconv error to a user-readable string.
|
||||
// Returns owned str; release via os.free. Mirrors Hare's
|
||||
// strconv::strerror.
|
||||
export fn strerror(e: error) str = {
|
||||
match (e) {
|
||||
case let v: invalid => return strings.dup("input is not a valid number");
|
||||
case let v: overflow => return strings.dup("input number doesn't fit target type");
|
||||
};
|
||||
return strings.dup("");
|
||||
};
|
||||
|
||||
// MODULE: lex
|
||||
// lib/ww/lex/tok.ww — port of cmd/wcc/tok.c plus the Tkind /
|
||||
@@ -5426,7 +5652,7 @@ fn pushargsrev(c: *cgen, arg: *node) i32 = {
|
||||
let lc: *local = localfindnode(c, nm);
|
||||
if (lc != nil) {
|
||||
let off: i32 = lc.off;
|
||||
if (isslicetype(c, lc.tnode) || istaggedtype(lc.tnode)) {
|
||||
if (isslicetype(c, lc.tnode) || istaggedtype(c, lc.tnode)) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP), AX\n");
|
||||
@@ -6361,12 +6587,56 @@ fn isslicetype(c: *cgen, t: *node) bool = {
|
||||
return isslicetyperaw(r);
|
||||
};
|
||||
|
||||
fn istaggedtype(t: *node) bool = {
|
||||
fn istaggedtyperaw(t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
if (t.kind == nkind.N_TTAGGED) { return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// resolvetagged — return the underlying N_TTAGGED node for `t`, or nil
|
||||
// if `t` doesn't ultimately denote a tagged union. Follows N_TNAME
|
||||
// aliases (via resolvetype) and unwraps one leading N_TBANG so
|
||||
// `type error = !(invalid | overflow);` resolves to its inner
|
||||
// `(invalid | overflow)` node. Use at sites that read variant lists
|
||||
// or detect nullable folding off a scrutinee — cgmatch, cgtypetest,
|
||||
// cgtypeassert — so aliased `!(A|B)` shapes still dispatch.
|
||||
export fn resolvetagged(c: *cgen, t: *node) *node = {
|
||||
let r: *node = resolvetype(c, t);
|
||||
if (r == nil) { return nil; };
|
||||
if (r.kind == nkind.N_TBANG) {
|
||||
let inner: *node = r.lhs;
|
||||
if (inner == nil) { return nil; };
|
||||
r = resolvetype(c, inner);
|
||||
if (r == nil) { return nil; };
|
||||
};
|
||||
if (r.kind == nkind.N_TTAGGED) { return r; };
|
||||
return nil;
|
||||
};
|
||||
|
||||
// istaggedtype — alias-aware. Mirrors isstrtype: follow N_TNAME to its
|
||||
// underlying decl, then unwrap a leading N_TBANG so `type error =
|
||||
// !(invalid | overflow);` is still recognised as tagged. Without the
|
||||
// bang unwrap the prologue treats the param as scalar (8B), spilling
|
||||
// only DI and losing the value-word SI; the match read of slot+8 then
|
||||
// trails into saved BP.
|
||||
fn istaggedtype(c: *cgen, t: *node) bool = {
|
||||
if (istaggedtyperaw(t)) { return true; };
|
||||
if (c == nil) { return false; };
|
||||
let r: *node = resolvetype(c, t);
|
||||
if (istaggedtyperaw(r)) { return true; };
|
||||
if (r != nil) {
|
||||
if (r.kind == nkind.N_TBANG) {
|
||||
let inner: *node = r.lhs;
|
||||
if (istaggedtyperaw(inner)) { return true; };
|
||||
if (inner != nil) {
|
||||
let r2: *node = resolvetype(c, inner);
|
||||
if (istaggedtyperaw(r2)) { return true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// isf32typeraw / isf64typeraw — bare TNAME check, no alias resolution.
|
||||
fn isf32typeraw(t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
@@ -6891,7 +7161,7 @@ fn cgtypetest(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, lhs.str);
|
||||
if (lc != nil) {
|
||||
scrutoff = lc.off;
|
||||
scrutt = resolvetype(c, lc.tnode);
|
||||
scrutt = resolvetagged(c, lc.tnode);
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -6979,7 +7249,7 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, lhs.str);
|
||||
if (lc != nil) {
|
||||
scrutoff = lc.off;
|
||||
scrutt = resolvetype(c, lc.tnode);
|
||||
scrutt = resolvetagged(c, lc.tnode);
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -7393,7 +7663,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, scrut.str);
|
||||
if (lc != nil) {
|
||||
scrutoff = lc.off;
|
||||
scrutt = resolvetype(c, lc.tnode);
|
||||
scrutt = resolvetagged(c, lc.tnode);
|
||||
};
|
||||
} else {
|
||||
// Non-ident scrutinee (call result, ?, etc.). Spill into a
|
||||
@@ -7415,7 +7685,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
if (callee.kind == nkind.N_DOT) { cnm = callee.str; };
|
||||
if (cnm.len > 0) {
|
||||
let rt: *node = fnretlookup(c, cnm);
|
||||
if (rt != nil) { scrutt = resolvetype(c, rt); };
|
||||
if (rt != nil) { scrutt = resolvetagged(c, rt); };
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -9833,7 +10103,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
// For other variants, cgexpr leaves AX, shuffle DX←AX.
|
||||
// Nullable folded `(*T | void)`: just one word; AX is
|
||||
// already the pointer (or 0). No shuffle, no tag.
|
||||
if (istaggedtype(c.fnret)) {
|
||||
if (istaggedtype(c, c.fnret)) {
|
||||
// Forwarding a fallible call: `return f();` where f
|
||||
// also returns a tagged union. The result is already
|
||||
// in (AX=tag, DX=v0, CX=v1) — no shuffle, no tag.
|
||||
@@ -9849,7 +10119,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
if (callee.kind == nkind.N_DOT) { calleename = callee.str; };
|
||||
if (calleename.len > 0) {
|
||||
let rt: *node = fnretlookup(c, calleename);
|
||||
if (istaggedtype(rt)) { forwardtagged = true; };
|
||||
if (istaggedtype(c, rt)) { forwardtagged = true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -9890,7 +10160,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
// Bare `return;` from a tagged-union-returning fn is
|
||||
// the void variant: emit its tag. Payload is undefined
|
||||
// (void has size 0). Otherwise zero AX for determinism.
|
||||
if (istaggedtype(c.fnret)) {
|
||||
if (istaggedtype(c, c.fnret)) {
|
||||
if (isnullabletype(c.fnret)) {
|
||||
// null = void variant; AX = 0.
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
@@ -9944,7 +10214,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
// just spill all three.
|
||||
// - Otherwise rhs is a bare variant value: pack tag +
|
||||
// value(s).
|
||||
if (istaggedtype(tn)) {
|
||||
if (istaggedtype(c, tn)) {
|
||||
let nullable: bool = isnullabletype(tn);
|
||||
let rhsreturnstagged: bool = false;
|
||||
if (rhs.kind == nkind.N_CALL) {
|
||||
@@ -9956,7 +10226,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
if (callee.kind == nkind.N_DOT) { calleename = callee.str; };
|
||||
if (calleename.len > 0) {
|
||||
let rt: *node = fnretlookup(c, calleename);
|
||||
if (istaggedtype(rt)) { rhsreturnstagged = true; };
|
||||
if (istaggedtype(c, rt)) { rhsreturnstagged = true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -10998,7 +11268,7 @@ fn cgfnparams(c: *cgen, params: *node) void = {
|
||||
p = p.next;
|
||||
continue;
|
||||
};
|
||||
if (istaggedtype(p.lhs)) {
|
||||
if (istaggedtype(c, p.lhs)) {
|
||||
let slot: i32 = slotsize(c, p.lhs);
|
||||
let nw: i32 = slot / 8;
|
||||
if (idx + nw <= 6) {
|
||||
@@ -11116,7 +11386,7 @@ fn cgfn(c: *cgen, fn_: *node) void = {
|
||||
let frame: i32 = 0;
|
||||
for (scanp != nil) {
|
||||
if (scanp.kind == nkind.N_PARAM) {
|
||||
if (istaggedtype(scanp.lhs)) { frame += 24; }
|
||||
if (istaggedtype(c, scanp.lhs)) { frame += slotsize(c, scanp.lhs); }
|
||||
else { if (isslicetype(c, scanp.lhs)) { frame += 24; }
|
||||
else { if (isstrtype(c, scanp.lhs)) { frame += 16; }
|
||||
else { frame += 8; }; }; };
|
||||
|
||||
@@ -227,6 +227,226 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = {
|
||||
return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64);
|
||||
};
|
||||
|
||||
// MODULE: strings
|
||||
// strings — operations over the immutable str type ({ *u8, len }).
|
||||
// Mirrors Hare's strings::; `len` and `is-empty` aren't functions
|
||||
// (callers use `s.len` and `s.len == 0` directly).
|
||||
|
||||
use os;
|
||||
|
||||
// compare — bytewise three-way comparison: negative if a<b, 0 if equal,
|
||||
// positive if a>b. Matches Hare's strings::compare. ASCII-order, not
|
||||
// locale-aware. Callers that just need equality use `compare(a, b) == 0`.
|
||||
export fn compare(a: str, b: str) i32 = {
|
||||
let n: i32 = a.len;
|
||||
if (b.len < n) { n = b.len; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
if (a[i] != b[i]) { return (a[i]: i32) - (b[i]: i32); };
|
||||
i += 1;
|
||||
};
|
||||
return a.len - b.len;
|
||||
};
|
||||
|
||||
export fn hasprefix(s: str, p: str) bool = {
|
||||
if (p.len > s.len) { return false; };
|
||||
let i: i32 = 0;
|
||||
for (i < p.len) {
|
||||
if (s[i] != p[i]) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
export fn hassuffix(s: str, suf: str) bool = {
|
||||
if (suf.len > s.len) { return false; };
|
||||
let off: i32 = s.len - suf.len;
|
||||
let i: i32 = 0;
|
||||
for (i < suf.len) {
|
||||
if (s[off + i] != suf[i]) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// indexbyte — first byte position of byte `c` in `s`. Mirrors
|
||||
// Hare's strings::byteindex when the needle is a single ASCII rune,
|
||||
// renamed to match bytes.indexbyte and to disambiguate from Hare's
|
||||
// `byteindex(haystack, needle: (str | rune))` which we don't have
|
||||
// the union-arg ABI for yet.
|
||||
export fn indexbyte(s: str, c: u8) (i32 | void) = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
if (s[i] == c) { return i; };
|
||||
i += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
// rindexbyte — last byte position of byte `c` in `s`.
|
||||
export fn rindexbyte(s: str, c: u8) (i32 | void) = {
|
||||
let i: i32 = s.len - 1;
|
||||
for (i >= 0) {
|
||||
if (s[i] == c) { return i; };
|
||||
i -= 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
// index — first index of `sub` in `s`. Naive scan; fine for short
|
||||
// patterns and small strings, which dominate config and CLI parsing.
|
||||
// Empty `sub` matches at 0.
|
||||
export fn index(s: str, sub: str) (i32 | void) = {
|
||||
if (sub.len == 0) { return 0; };
|
||||
if (sub.len > s.len) { return; };
|
||||
let last: i32 = s.len - sub.len;
|
||||
let i: i32 = 0;
|
||||
for (i <= last) {
|
||||
let j: i32 = 0;
|
||||
let ok: bool = true;
|
||||
for (j < sub.len) {
|
||||
if (s[i + j] != sub[j]) { ok = false; j = sub.len; }
|
||||
else { j += 1; };
|
||||
};
|
||||
if (ok) { return i; };
|
||||
i += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
export fn contains(s: str, sub: str) bool = {
|
||||
let r: (i32 | void) = index(s, sub);
|
||||
match (r) {
|
||||
case let i: i32 => return true;
|
||||
case void => return false;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// concat — joins two strings into a fresh str. Caller owns the
|
||||
// returned str's storage; release via `os.free(r.ptr, r.len)`. Mirrors
|
||||
// Hare's strings::concat shape.
|
||||
export fn concat(a: str, b: str) str = {
|
||||
let total: i32 = a.len + b.len;
|
||||
let buf: *u8 = os.alloc(total: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < a.len) { buf[i] = a[i]; i += 1; };
|
||||
let j: i32 = 0;
|
||||
for (j < b.len) { buf[a.len + j] = b[j]; j += 1; };
|
||||
let r: str;
|
||||
r.ptr = buf;
|
||||
r.len = total;
|
||||
return r;
|
||||
};
|
||||
|
||||
// dup — duplicate a string into a fresh allocation. Caller owns the
|
||||
// returned str's storage; release via `os.free(r.ptr, r.len)`. Mirrors
|
||||
// Hare's strings::dup shape — Hare returns `(str | nomem)`, ww doesn't
|
||||
// have nomem (os.alloc aborts on OOM), so we return plain `str`.
|
||||
//
|
||||
// Empty input yields a `{nil, 0}` str — Hare returns the static empty
|
||||
// string; same observable result.
|
||||
export fn dup(s: str) str = {
|
||||
let r: str;
|
||||
r.ptr = nil;
|
||||
r.len = 0;
|
||||
if (s.len == 0) { return r; };
|
||||
let buf: *u8 = os.alloc(s.len: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) { buf[i] = s[i]; i += 1; };
|
||||
r.ptr = buf;
|
||||
r.len = s.len;
|
||||
return r;
|
||||
};
|
||||
|
||||
// rindex — last index of `sub` in `s`. Mirrors Hare's strings::rindex
|
||||
// (slice case). Empty `sub` matches at s.len.
|
||||
export fn rindex(s: str, sub: str) (i32 | void) = {
|
||||
if (sub.len == 0) { return s.len; };
|
||||
if (sub.len > s.len) { return; };
|
||||
let i: i32 = s.len - sub.len;
|
||||
for (i >= 0) {
|
||||
let j: i32 = 0;
|
||||
let ok: bool = true;
|
||||
for (j < sub.len) {
|
||||
if (s[i + j] != sub[j]) { ok = false; j = sub.len; }
|
||||
else { j += 1; };
|
||||
};
|
||||
if (ok) { return i; };
|
||||
i -= 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
// sub — borrowed substring `s[start..end]`. Mirrors Hare's
|
||||
// strings::sub. Caller must ensure 0 <= start <= end <= s.len; out-of-
|
||||
// range indices are clamped silently here, where Hare aborts.
|
||||
export fn sub(s: str, start: i32, end: i32) str = {
|
||||
let lo: i32 = start;
|
||||
let hi: i32 = end;
|
||||
if (lo < 0) { lo = 0; };
|
||||
if (hi > s.len) { hi = s.len; };
|
||||
if (hi < lo) { hi = lo; };
|
||||
let r: str;
|
||||
r.ptr = s.ptr + (lo: u64);
|
||||
r.len = hi - lo;
|
||||
return r;
|
||||
};
|
||||
|
||||
// trimprefix — `s` with `pre` stripped from the front, or `s`
|
||||
// unchanged if it doesn't start with `pre`. Returns a borrowed view.
|
||||
// Mirrors Hare's strings::trimprefix.
|
||||
export fn trimprefix(s: str, pre: str) str = {
|
||||
if (!hasprefix(s, pre)) { return s; };
|
||||
let r: str;
|
||||
r.ptr = s.ptr + (pre.len: u64);
|
||||
r.len = s.len - pre.len;
|
||||
return r;
|
||||
};
|
||||
|
||||
// trimsuffix — `s` with `suf` stripped from the end, or `s` unchanged
|
||||
// if it doesn't end with `suf`. Returns a borrowed view. Mirrors
|
||||
// Hare's strings::trimsuffix.
|
||||
export fn trimsuffix(s: str, suf: str) str = {
|
||||
if (!hassuffix(s, suf)) { return s; };
|
||||
let r: str;
|
||||
r.ptr = s.ptr;
|
||||
r.len = s.len - suf.len;
|
||||
return r;
|
||||
};
|
||||
|
||||
// ltrimbyte / rtrimbyte / trimbyte — strip occurrences of a single
|
||||
// byte from the left, right, or both ends. Returns a borrowed view.
|
||||
// Hare's strings::ltrim / rtrim / trim take a rune varargs set; ww's
|
||||
// subset takes a single byte (the common ASCII case).
|
||||
export fn ltrimbyte(s: str, c: u8) str = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
if (s[i] != c) { break; };
|
||||
i += 1;
|
||||
};
|
||||
let r: str;
|
||||
r.ptr = s.ptr + (i: u64);
|
||||
r.len = s.len - i;
|
||||
return r;
|
||||
};
|
||||
|
||||
export fn rtrimbyte(s: str, c: u8) str = {
|
||||
let n: i32 = s.len;
|
||||
for (n > 0) {
|
||||
if (s[n - 1] != c) { break; };
|
||||
n -= 1;
|
||||
};
|
||||
let r: str;
|
||||
r.ptr = s.ptr;
|
||||
r.len = n;
|
||||
return r;
|
||||
};
|
||||
|
||||
export fn trimbyte(s: str, c: u8) str = {
|
||||
return rtrimbyte(ltrimbyte(s, c), c);
|
||||
};
|
||||
|
||||
// MODULE: strconv
|
||||
// strconv — number↔string conversions.
|
||||
//
|
||||
@@ -238,6 +458,7 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = {
|
||||
// today). Graduate to the static-buffer shape once that lands.
|
||||
|
||||
use os;
|
||||
use strings;
|
||||
|
||||
// invalid — input wasn't a valid number in the requested format.
|
||||
// Payload is the byte index of the first offending position.
|
||||
@@ -582,11 +803,16 @@ export fn f64tos(v: f64) str = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// strerror — Hare has strconv::strerror; ww doesn't ship it yet
|
||||
// because a `match (e) { case invalid => ... }` arm over the wider
|
||||
// `error = !(invalid | overflow)` union exposes a cstage-vs-wwstage
|
||||
// cgen divergence (one cgen spills the unused payload slot, the
|
||||
// other elides it). Restore once the cgens converge.
|
||||
// strerror — convert an strconv error to a user-readable string.
|
||||
// Returns owned str; release via os.free. Mirrors Hare's
|
||||
// strconv::strerror.
|
||||
export fn strerror(e: error) str = {
|
||||
match (e) {
|
||||
case let v: invalid => return strings.dup("input is not a valid number");
|
||||
case let v: overflow => return strings.dup("input number doesn't fit target type");
|
||||
};
|
||||
return strings.dup("");
|
||||
};
|
||||
|
||||
// MODULE: ascii
|
||||
// ascii — rune-class predicates and case folding for the ASCII range.
|
||||
|
||||
@@ -470,6 +470,24 @@ static const struct row rows[] = {
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};", 40 },
|
||||
/* named alias over `!(A | B)`: param spill must size to the
|
||||
* flattened union (8B tag + 8B payload), not 8B-scalar. Pinned
|
||||
* the cstage/wwstage divergence where wwstage's istaggedtype
|
||||
* was alias-blind and the slot+8 read trailed into saved BP. */
|
||||
{ "type invalid = !i32;\n"
|
||||
"type overflow = !void;\n"
|
||||
"type error = !(invalid | overflow);\n"
|
||||
"fn errcode(e: error) i32 = {\n"
|
||||
" match (e) {\n"
|
||||
" case let v: invalid => return v: i32;\n"
|
||||
" case let v: overflow => return 99;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let e: error = 7: invalid;\n"
|
||||
" return errcode(e);\n"
|
||||
"};", 7 },
|
||||
/* str-typed variant payload: let-init with a string literal,
|
||||
* match-binding loads ptr+len from the slot */
|
||||
{ "fn main() i32 = {\n"
|
||||
|
||||
Reference in New Issue
Block a user