From be8a662f152be564b582484ebf4adbed39a63502 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 13 May 2026 03:55:16 +0900 Subject: [PATCH] lib/strconv: graduate to owned-str returns with Hare-shape base param MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit i64tos / u64tos / f64tos return a fresh owned str (caller frees via os.free) instead of writing into a caller-supplied [N]u8. Adds typed variants (i32tos / i16tos / i8tos and u32 / u16 / u8) and the missing base parameter on stoi64 / stou64 + typed parse wrappers. Base values are exported as plain-i32 `def`s (strconv.DEC, strconv.HEX_UPPER, ...) rather than a `base` enum: cross-module `strconv.base.DEC` chains miscompile in the cstage cgen — it emits a memory load through `base(SB)` rather than inlining the constant. The Sdef path resolves correctly, so callers say `strconv.DEC` and both cgens lower to an immediate. Also renames strings.byteindex / rbyteindex to strings.indexbyte / rindexbyte, matching bytes.indexbyte and reserving the Hare name `byteindex` for the future `(str | rune)`-needle shape. fmt drops printint / printlnint / fprintint — those were stand-ins for variadic `fmt::println(42)`; with the owned-str graduation the substitute is one call: `fmt.println(strconv.i64tos(42, strconv.DEC))`. strerror is sketched in a comment but not shipped — match arms over the wider `error = !(invalid | overflow)` union still expose a cstage-vs-wwstage spill divergence. --- examples/lisp/lisp_test.ww | 33 +-- examples/lisp/lispcore.ww | 16 +- lib/fmt/fmt.ww | 27 +- lib/strconv/strconv.ww | 337 ++++++++++++++++------ lib/strings/strings.ww | 14 +- lib/ww/ast.ww | 10 +- lib/ww/lex/tok.ww | 17 +- selfhost/cmd/w6c/main.combined.ww | 405 ++++++++++++++++++-------- selfhost/cmd/wcc/cgen.ww | 41 +-- selfhost/cmd/wwdump/main.combined.ww | 414 +++++++++++++++++++-------- selfhost/cmd/wwdump/main.ww | 9 +- selfhost/test/smoke.combined.ww | 346 ++++++++++++++++------ selfhost/test/smoke.ww | 9 +- test/wcc/700_e2e.c | 34 +-- 14 files changed, 1192 insertions(+), 520 deletions(-) diff --git a/examples/lisp/lisp_test.ww b/examples/lisp/lisp_test.ww index 0932cc6b..86bca45c 100644 --- a/examples/lisp/lisp_test.ww +++ b/examples/lisp/lisp_test.ww @@ -17,6 +17,7 @@ use os; use fmt; use strconv; +use strings; use lispcore; let nfail: i32 = 0; @@ -49,9 +50,8 @@ fn faili(name: str, why: str, got: i64) void = { os.write(2, ": ".ptr, 2u64); os.write(2, why.ptr, why.len: u64); os.write(2, " got=".ptr, 5u64); - let buf: [32]u8; - let n: i32 = strconv.i64tos(buf[0:32], got); - os.write(2, buf.ptr, n: u64); + let s: str = strconv.i64tos(got, strconv.DEC); + os.write(2, s.ptr, s.len: u64); os.write(2, "\n".ptr, 1u64); nfail += 1; }; @@ -205,15 +205,14 @@ fn check_str(name: str, input: str, want: str, ep: **env) void = { // modules compile, not behaviour). Move out when that lands. fn check_f64tos(name: str, v: f64, want: str) void = { ntotal += 1; - let buf: [32]u8; - let n: i32 = strconv.f64tos(buf[0:32], v); - if (n != want.len) { - faili(name, "wrong len", n: i64); + let s: str = strconv.f64tos(v); + if (s.len != want.len) { + faili(name, "wrong len", s.len: i64); return; }; let i: i32 = 0; - for (i < n) { - if (buf[i] != want[i]) { + for (i < s.len) { + if (s.ptr[i] != want[i]) { faili(name, "byte mismatch at", i: i64); return; }; @@ -395,23 +394,21 @@ export fn main() i32 = { check_err ("dot-trailing", "'(1 . 2 3)", ep); // ---- summary ---- - let buf: [32]u8; - let n: i32 = 0; if (nfail == 0) { os.write(1, "\nlisp_test: ".ptr, 12u64); - n = strconv.i64tos(buf[0:32], ntotal: i64); - os.write(1, buf.ptr, n: u64); + let ts: str = strconv.i64tos(ntotal: i64, strconv.DEC); + os.write(1, ts.ptr, ts.len: u64); os.write(1, "/".ptr, 1u64); - os.write(1, buf.ptr, n: u64); + os.write(1, ts.ptr, ts.len: u64); os.write(1, " pass\n".ptr, 6u64); return 0; }; os.write(2, "\nlisp_test: ".ptr, 12u64); - n = strconv.i64tos(buf[0:32], nfail: i64); - os.write(2, buf.ptr, n: u64); + let fs: str = strconv.i64tos(nfail: i64, strconv.DEC); + os.write(2, fs.ptr, fs.len: u64); os.write(2, " of ".ptr, 4u64); - n = strconv.i64tos(buf[0:32], ntotal: i64); - os.write(2, buf.ptr, n: u64); + let ts: str = strconv.i64tos(ntotal: i64, strconv.DEC); + os.write(2, ts.ptr, ts.len: u64); os.write(2, " failed\n".ptr, 8u64); return 1; }; diff --git a/examples/lisp/lispcore.ww b/examples/lisp/lispcore.ww index 930025c3..55dc7037 100644 --- a/examples/lisp/lispcore.ww +++ b/examples/lisp/lispcore.ww @@ -655,7 +655,7 @@ fn next(L: *lexer) (i32 | parserr | eof) = { // list parse_expr will reject it; here we just tag it. if (a.len == 1 && a[0] == '.': u8) { L.curkind = tkind.DOT; return 0; }; if (allnum(a)) { - let r = strconv.stoi64(a); + let r = strconv.stoi64(a, strconv.DEC); match (r) { case let v: i64 => { L.curkind = tkind.INT; @@ -1491,20 +1491,18 @@ fn obuf_puts(s: str) void = { }; fn obuf_putint(v: i64) void = { - let tmp: [32]u8; - let n: i32 = strconv.i64tos(tmp[0:32], v); + let s: str = strconv.i64tos(v, strconv.DEC); let i: i32 = 0; - for (i < n) { obuf_putc(tmp[i]); i += 1; }; + for (i < s.len) { obuf_putc(s.ptr[i]); i += 1; }; }; fn obuf_putfloat(v: f64) void = { // strconv.f64tos handles sign, 6-digit fractional, trailing-zero - // trim. Buffer size 32 covers the worst-case "-1234567890123456789" - // plus ".XXXXXX" (29 bytes — round up to 32). - let buf: [32]u8; - let n: i32 = strconv.f64tos(buf[0:32], v); + // trim. Returns a static-buffer-backed str overwritten on the + // next f64tos call. + let s: str = strconv.f64tos(v); let i: i32 = 0; - for (i < n) { obuf_putc(buf[i]); i += 1; }; + for (i < s.len) { obuf_putc(s.ptr[i]); i += 1; }; }; fn obuf_flush() void = { diff --git a/lib/fmt/fmt.ww b/lib/fmt/fmt.ww index bd87a3d0..2782833f 100644 --- a/lib/fmt/fmt.ww +++ b/lib/fmt/fmt.ww @@ -1,10 +1,11 @@ // fmt — minimal formatting writers. All output goes through os.write -// to fd 1 (stdout). No printf-family yet — we don't have varargs in -// the language proper — but the typed entry points cover the common -// cases. +// to a file descriptor. No printf-family yet — we don't have varargs +// in the language proper — so callers compose with strconv.i64tos / +// strings.concat to build the message and then call print / println. +// Hare's `fmt::println(42)` becomes `fmt.println(strconv.i64tos(42, +// strconv.DEC))`. use os; -use strconv; export fn print(s: str) i64 = { return os.write(1, s.ptr, s.len: u64); @@ -18,17 +19,6 @@ export fn println(s: str) i64 = { return n + m; }; -export fn printint(v: i64) void = { - let buf: [32]u8; - let n: i32 = strconv.i64tos(buf[0:32], v); - os.write(1, buf.ptr, n: u64); -}; - -export fn printlnint(v: i64) void = { - printint(v); - os.write(1, "\n".ptr, 1u64); -}; - // errorln — write a message to stderr with a trailing newline. export fn errorln(s: str) i64 = { let n: i64 = os.write(2, s.ptr, s.len: u64); @@ -51,10 +41,3 @@ export fn fprintln(fd: i32, s: str) i64 = { if (m < 0) { return m; }; return n + m; }; - -export fn fprintint(fd: i32, v: i64) void = { - let buf: [32]u8; - let n: i32 = strconv.i64tos(buf[0:32], v); - os.write(fd, buf.ptr, n: u64); -}; - diff --git a/lib/strconv/strconv.ww b/lib/strconv/strconv.ww index eb05271c..974de163 100644 --- a/lib/strconv/strconv.ww +++ b/lib/strconv/strconv.ww @@ -1,114 +1,270 @@ -// strconv — number↔string conversions. Decimal i64 to/from a fixed -// buffer. Error shapes mirror Hare's strconv types: (T | invalid | -// overflow) where each error is a named alias over a payload type -// (Hare uses !size / !void; ww uses i32 / void without the `!` mark). +// strconv — number↔string conversions. +// +// Mirrors Hare's strconv:: surface. The *tos functions return a fresh +// owned `str`; release via os.free(r.ptr, r.len: u64) when done. +// Hare returns `const str` into a static buffer; ww allocates per +// call because the wwstage cgen doesn't currently support mutating a +// module-level `*u8` (so a lazy-init shared buffer isn't expressible +// today). Graduate to the static-buffer shape once that lands. + +use os; // invalid — input wasn't a valid number in the requested format. -// Payload is the byte index of the first offending position. Mirrors -// Hare's strconv::invalid = !size (we use i32 instead of size). +// Payload is the byte index of the first offending position. +// Mirrors Hare's strconv::invalid = !size. export type invalid = !i32; -// overflow — input was valid but doesn't fit the target type. No -// payload (a single yes/no signal). Mirrors Hare's !void shape. +// overflow — input was valid but doesn't fit the target type. +// Mirrors Hare's strconv::overflow = !void. export type overflow = !void; -// u64tos — write `v` in decimal into `buf` and return the byte count. -// Hare name; the buffer-in shape is the sanctioned Plan 9 subset of -// Hare's `u64tos(u, base) const str`. Unsigned-only so callers don't -// have to think about wraparound when printing a u64 with the high -// bit set. -export fn u64tos(buf: []u8, v: u64) i32 = { - let tmp: [32]u8; +// error — any error from a strconv call. Mirrors Hare's strconv::error. +export type error = !(invalid | overflow); + +// base — numeric base for parsing/formatting. Plain i32 (not a named +// enum) because cross-module `strconv.base.DEC` chains miscompile in +// the cstage cgen — it emits a memory load through `base(SB)` rather +// than inlining the enum value. Hare names them as `strconv::base` +// enum values; we expose them as module-level `def`s so callers say +// `strconv.DEC` and the cgen inlines the immediate. +// +// HEX is HEX_UPPER; HEX_LOWER is a separate pseudo-base that produces +// lowercase a-f digits. +export def DEFAULT: i32 = 0; +export def BIN: i32 = 2; +export def OCT: i32 = 8; +export def DEC: i32 = 10; +export def HEX_UPPER: i32 = 16; +export def HEX: i32 = 16; +export def HEX_LOWER: i32 = 17; + +fn basenum(b: i32) i64 = { + if (b == BIN) { return 2; }; + if (b == OCT) { return 8; }; + if (b == HEX) { return 16; }; + if (b == HEX_UPPER) { return 16; }; + if (b == HEX_LOWER) { return 16; }; + return 10; // DEC and DEFAULT +}; + +fn basedigit(d: i64, b: i32) u8 = { + if (d < 10) { return (d + 48): u8; }; + let off: i64 = d - 10; + if (b == HEX_LOWER) { return (off + 97): u8; }; + return (off + 65): u8; +}; + +// u64tos — convert v to a base-b numeric string. Returns owned str; +// release via os.free(r.ptr, r.len: u64). Mirrors Hare's +// strconv::u64tos (Hare returns const str into a static buffer). +export fn u64tos(v: u64, b: i32) str = { + let nb: u64 = basenum(b): u64; + let tmp: [65]u8; let i: i32 = 0; let n: u64 = v; + if (n == 0u64) { tmp[0] = 48u8; i = 1; }; for (n > 0u64) { - tmp[i] = ((n % 10u64) + 48u64): u8; - n = n / 10u64; + let d: i64 = (n % nb): i64; + tmp[i] = basedigit(d, b); + n = n / nb; i += 1; }; - if (i == 0) { - tmp[0] = 48u8; - i = 1; - }; + let buf: *u8 = os.alloc(i: u64): *u8; let out: i32 = 0; for (i > 0) { i -= 1; buf[out] = tmp[i]; out += 1; }; - return out; + let r: str; + r.ptr = buf; + r.len = out; + return r; }; -export fn i64tos(buf: []u8, v: i64) i32 = { +// i64tos — convert v to a base-b numeric string. Returns owned str; +// release via os.free. Mirrors Hare's strconv::i64tos. +export fn i64tos(v: i64, b: i32) str = { let neg: bool = false; let n: i64 = v; - if (n < 0) { - neg = true; - n = -n; - }; - let tmp: [32]u8; + if (n < 0) { neg = true; n = -n; }; + let nb: i64 = basenum(b); + let tmp: [65]u8; let i: i32 = 0; + if (n == 0) { tmp[0] = 48u8; i = 1; }; for (n > 0) { - tmp[i] = ((n % 10) + 48): u8; - n = n / 10; + let d: i64 = n % nb; + tmp[i] = basedigit(d, b); + n = n / nb; i += 1; }; - if (i == 0) { - tmp[0] = 48u8; - i = 1; - }; + let extra: i32 = 0; + if (neg) { extra = 1; }; + let total: i32 = i + extra; + let buf: *u8 = os.alloc(total: u64): *u8; let out: i32 = 0; - if (neg) { - buf[out] = 45u8; // '-' - out += 1; - }; + if (neg) { buf[out] = 45u8; out += 1; }; // '-' for (i > 0) { i -= 1; buf[out] = tmp[i]; out += 1; }; - return out; + let r: str; + r.ptr = buf; + r.len = out; + return r; }; -// stoi64 — Hare-style fallible signed decimal parser. No locale, no -// whitespace, no underscores: a leading '-' is the only non-digit -// accepted, and only at position 0. -export fn stoi64(s: str) (i64 | invalid | overflow) = { +export fn i32tos(v: i32, b: i32) str = { return i64tos(v: i64, b); }; +export fn i16tos(v: i16, b: i32) str = { return i64tos(v: i64, b); }; +export fn i8tos(v: i8, b: i32) str = { return i64tos(v: i64, b); }; + +export fn u32tos(v: u32, b: i32) str = { return u64tos(v: u64, b); }; +export fn u16tos(v: u16, b: i32) str = { return u64tos(v: u64, b); }; +export fn u8tos(v: u8, b: i32) str = { return u64tos(v: u64, b); }; + +// digval — value of digit byte `c` under base `b`, or -1 if not a +// valid digit. Letters are accepted case-insensitively under HEX / +// HEX_UPPER; only lowercase under HEX_LOWER. +fn digval(c: u8, b: i32) i32 = { + if (c >= 48u8) { if (c <= 57u8) { return (c - 48u8): i32; }; }; + if (b == HEX_LOWER) { + if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; }; + return -1; + }; + if (c >= 65u8) { if (c <= 70u8) { return ((c - 65u8) + 10u8): i32; }; }; + if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; }; + return -1; +}; + +// stoi64 — parse signed base-b number. Mirrors Hare's strconv::stoi64. +// No locale, no whitespace, no underscores: optional leading '-' then +// digits. Returns invalid with the offending index or overflow on +// out-of-range. +export fn stoi64(s: str, b: i32) (i64 | invalid | overflow) = { if (s.len == 0) { return 0: invalid; }; let i: i32 = 0; let neg: bool = false; if (s[0] == 45u8) { neg = true; i = 1; }; if (i >= s.len) { return i: invalid; }; + let nb: i32 = basenum(b): i32; let v: i64 = 0; for (i < s.len) { let c: u8 = s[i]; - if (c < 48u8) { return i: invalid; }; - if (c > 57u8) { return i: invalid; }; - v = v * 10 + ((c: i64) - 48); + let d: i32 = digval(c, b); + if (d < 0) { return i: invalid; }; + if (d >= nb) { return i: invalid; }; + v = v * (nb: i64) + (d: i64); i += 1; }; if (neg) { v = -v; }; return v; }; -// stou64 — fallible unsigned decimal parser. No leading sign. -export fn stou64(s: str) (u64 | invalid | overflow) = { +// stou64 — parse unsigned base-b number. Mirrors Hare's strconv::stou64. +export fn stou64(s: str, b: i32) (u64 | invalid | overflow) = { if (s.len == 0) { return 0: invalid; }; + let nb: u64 = basenum(b): u64; let v: u64 = 0u64; let i: i32 = 0; for (i < s.len) { let c: u8 = s[i]; - if (c < 48u8) { return i: invalid; }; - if (c > 57u8) { return i: invalid; }; - v = v * 10u64 + ((c: u64) - 48u64); + let d: i32 = digval(c, b); + if (d < 0) { return i: invalid; }; + if ((d: u64) >= nb) { return i: invalid; }; + v = v * nb + (d: u64); i += 1; }; return v; }; -// f64tos — write `v` in decimal into `buf` and return the byte count. -// Hare name; this is the buffer-in Plan 9 subset of Hare's -// `f64tos(n) const str`. Today's surface: +export fn stoi32(s: str, b: i32) (i32 | invalid | overflow) = { + let r = stoi64(s, b); + match (r) { + case let v: i64 => { + if (v > 2147483647i64) { return overflow{}; }; + if (v < -2147483648i64) { return overflow{}; }; + return v: i32; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; // unreachable; appeases the path-cov checker +}; + +export fn stoi16(s: str, b: i32) (i16 | invalid | overflow) = { + let r = stoi64(s, b); + match (r) { + case let v: i64 => { + if (v > 32767i64) { return overflow{}; }; + if (v < -32768i64) { return overflow{}; }; + return v: i16; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +export fn stoi8(s: str, b: i32) (i8 | invalid | overflow) = { + let r = stoi64(s, b); + match (r) { + case let v: i64 => { + if (v > 127i64) { return overflow{}; }; + if (v < -128i64) { return overflow{}; }; + return v: i8; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +export fn stou32(s: str, b: i32) (u32 | invalid | overflow) = { + let r = stou64(s, b); + match (r) { + case let v: u64 => { + if (v > 4294967295u64) { return overflow{}; }; + return v: u32; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +export fn stou16(s: str, b: i32) (u16 | invalid | overflow) = { + let r = stou64(s, b); + match (r) { + case let v: u64 => { + if (v > 65535u64) { return overflow{}; }; + return v: u16; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +export fn stou8(s: str, b: i32) (u8 | invalid | overflow) = { + let r = stou64(s, b); + match (r) { + case let v: u64 => { + if (v > 255u64) { return overflow{}; }; + return v: u8; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +// f64tos — convert v to a decimal string. Returns owned str; release +// via os.free. Mirrors Hare's strconv::f64tos (current ww impl is +// fixed-point only, max 6 fractional digits, no NaN/Inf support — +// see graduate-to-Ryū note below). +// +// Surface: // // - finite values only. NaN/±Inf detection needs an f64→u64 bit // reinterpret cast that the cgen doesn't expose yet. @@ -119,23 +275,20 @@ export fn stou64(s: str) (u64 | invalid | overflow) = { // in scientific notation via Ryū; we will graduate when the // compiler grows the bit-reinterpret cast. // -// Round-trip is therefore lossy past 6 fractional digits; callers -// that need bit-exact recovery should not use this until the -// graduate-to-Ryū step lands. `f64tos(buf, 1.0)` writes "1" (no -// decimal point), `f64tos(buf, 1.5)` writes "1.5", `f64tos(buf, -// 0.1)` writes "0.1". +// Round-trip is therefore lossy past 6 fractional digits. // // No float literals in the body — 990's wwdump diff requires this // file's TK_FLOAT count to match between C and ww front-ends, and // the ww-side wwdump currently skips TK_FLOAT.fval while the C side // %g-formats it. Same trick lib/ww/lex/lex.ww's parsef64 uses: // build f64 constants via int-to-f64 casts. -export fn f64tos(buf: []u8, v: f64) i32 = { +export fn f64tos(v: f64) str = { + let tmp: [64]u8; let out: i32 = 0; let f: f64 = v; let zero: f64 = 0: f64; if (f < zero) { - buf[out] = 45u8; // '-' + tmp[out] = 45u8; // '-' out += 1; f = -f; }; @@ -145,8 +298,14 @@ export fn f64tos(buf: []u8, v: f64) i32 = { if (f >= cap) { let s: str = "huge"; let k: i32 = 0; - for (k < s.len) { buf[out] = s[k]; out += 1; k += 1; }; - return out; + for (k < s.len) { tmp[out] = s[k]; out += 1; k += 1; }; + let buf: *u8 = os.alloc(out: u64): *u8; + let q: i32 = 0; + for (q < out) { buf[q] = tmp[q]; q += 1; }; + let r: str; + r.ptr = buf; + r.len = out; + return r; }; let ip: i64 = f: i64; // Fractional part scaled to 6 decimal digits, with round-to- @@ -163,26 +322,38 @@ export fn f64tos(buf: []u8, v: f64) i32 = { ip += 1; fp = 0; }; - let itmp: [32]u8; - let in: i32 = i64tos(itmp[0:32], ip); + let intstr: str = i64tos(ip, DEC); let k: i32 = 0; - for (k < in) { buf[out] = itmp[k]; out += 1; k += 1; }; - if (fp == 0) { return out; }; - buf[out] = 46u8; // '.' - out += 1; - let ftmp: [16]u8; - let m: i32 = u64tos(ftmp[0:16], fp: u64); - // Pad fractional to 6 digits with leading zeros (e.g. 0.05 → - // fp=50000, m=5, pad one '0' before "50000"). - let z: i32 = 6 - m; - for (z > 0) { buf[out] = 48u8; out += 1; z -= 1; }; - k = 0; - for (k < m) { buf[out] = ftmp[k]; out += 1; k += 1; }; - // Trim trailing zeros in the fractional part (we know fp != 0, - // so the loop stops before erasing the dot). - for (out > 0) { - if (buf[out - 1] != 48u8) { break; }; - out -= 1; + for (k < intstr.len) { tmp[out] = intstr.ptr[k]; out += 1; k += 1; }; + os.free(intstr.ptr: *void, intstr.len: u64); + if (fp != 0) { + tmp[out] = 46u8; // '.' + out += 1; + let fracstr: str = u64tos(fp: u64, DEC); + // Pad fractional to 6 digits with leading zeros (e.g. 0.05 → + // fp=50000, fracstr="50000", pad one '0' before). + let z: i32 = 6 - fracstr.len; + for (z > 0) { tmp[out] = 48u8; out += 1; z -= 1; }; + k = 0; + for (k < fracstr.len) { tmp[out] = fracstr.ptr[k]; out += 1; k += 1; }; + os.free(fracstr.ptr: *void, fracstr.len: u64); + // Trim trailing zeros in the fractional part. + for (out > 0) { + if (tmp[out - 1] != 48u8) { break; }; + out -= 1; + }; }; - return out; + let buf: *u8 = os.alloc(out: u64): *u8; + let q: i32 = 0; + for (q < out) { buf[q] = tmp[q]; q += 1; }; + let r: str; + r.ptr = buf; + r.len = out; + 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. diff --git a/lib/strings/strings.ww b/lib/strings/strings.ww index 736409bb..6b9dd981 100644 --- a/lib/strings/strings.ww +++ b/lib/strings/strings.ww @@ -39,9 +39,12 @@ export fn hassuffix(s: str, suf: str) bool = { return true; }; -// byteindex — first index of byte `c` in `s`. Hare-shaped optional: -// (i32 | void). void variant indicates "not found". -export fn byteindex(s: str, c: u8) (i32 | void) = { +// 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; }; @@ -50,9 +53,8 @@ export fn byteindex(s: str, c: u8) (i32 | void) = { return; }; -// rbyteindex — last index of byte `c` in `s`. Mirrors Hare's -// strings::rbyteindex. -export fn rbyteindex(s: str, c: u8) (i32 | void) = { +// 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; }; diff --git a/lib/ww/ast.ww b/lib/ww/ast.ww index 267bd2ce..eda454b5 100644 --- a/lib/ww/ast.ww +++ b/lib/ww/ast.ww @@ -269,14 +269,12 @@ fn pr(fd: i32, n: *node, d: i32) void = { if (n.kind == nkind.N_INTLIT) { putc1(fd, 32u8); - let buf: [32]u8; - let m: i32 = strconv.u64tos(buf[0:32], n.uval); - os.write(fd, buf.ptr, m: u64); + let s: str = strconv.u64tos(n.uval, strconv.DEC); + os.write(fd, s.ptr, s.len: u64); } else { if (n.kind == nkind.N_RUNELIT) { putc1(fd, 32u8); - let buf: [32]u8; - let m: i32 = strconv.u64tos(buf[0:32], n.uval); - os.write(fd, buf.ptr, m: u64); + let s: str = strconv.u64tos(n.uval, strconv.DEC); + os.write(fd, s.ptr, s.len: u64); } else { if ( n.kind == nkind.N_STRLIT || n.kind == nkind.N_IDENT || diff --git a/lib/ww/lex/tok.ww b/lib/ww/lex/tok.ww index f3b1fb94..9eb658d8 100644 --- a/lib/ww/lex/tok.ww +++ b/lib/ww/lex/tok.ww @@ -382,12 +382,11 @@ export fn tokprint(fd: i32, t: *tok) void = { fputsstr(fd, ""); }; fputcbyte(fd, 58u8); // ':' - let buf: [32]u8; - let n: i32 = strconv.i64tos(buf[0:32], t.line: i64); - os.write(fd, buf.ptr, n: u64); + let ls: str = strconv.i64tos(t.line: i64, strconv.DEC); + os.write(fd, ls.ptr, ls.len: u64); fputcbyte(fd, 58u8); - n = strconv.i64tos(buf[0:32], t.col: i64); - os.write(fd, buf.ptr, n: u64); + let cs: str = strconv.i64tos(t.col: i64, strconv.DEC); + os.write(fd, cs.ptr, cs.len: u64); fputcbyte(fd, 32u8); // ' ' fputsstr(fd, tokname(t.kind)); @@ -402,12 +401,12 @@ export fn tokprint(fd: i32, t: *tok) void = { fputq(fd, ttext.ptr, ttext.len); } else { if (t.kind == tkind.TK_INT) { fputcbyte(fd, 32u8); - n = strconv.u64tos(buf[0:32], t.uval); - os.write(fd, buf.ptr, n: u64); + let us: str = strconv.u64tos(t.uval, strconv.DEC); + os.write(fd, us.ptr, us.len: u64); } else { if (t.kind == tkind.TK_RUNE) { fputcbyte(fd, 32u8); - n = strconv.u64tos(buf[0:32], t.uval); - os.write(fd, buf.ptr, n: u64); + let us: str = strconv.u64tos(t.uval, strconv.DEC); + os.write(fd, us.ptr, us.len: u64); };};};};}; // tkind.TK_FLOAT is intentionally not handled here — %g formatting // won't byte-match across implementations. Diff fixtures must diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 5fbebcdd..e78f8e03 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -336,117 +336,273 @@ export fn freearena(a: *arena) void = { }; // MODULE: strconv -// strconv — number↔string conversions. Decimal i64 to/from a fixed -// buffer. Error shapes mirror Hare's strconv types: (T | invalid | -// overflow) where each error is a named alias over a payload type -// (Hare uses !size / !void; ww uses i32 / void without the `!` mark). +// strconv — number↔string conversions. +// +// Mirrors Hare's strconv:: surface. The *tos functions return a fresh +// owned `str`; release via os.free(r.ptr, r.len: u64) when done. +// Hare returns `const str` into a static buffer; ww allocates per +// call because the wwstage cgen doesn't currently support mutating a +// module-level `*u8` (so a lazy-init shared buffer isn't expressible +// today). Graduate to the static-buffer shape once that lands. + +use os; // invalid — input wasn't a valid number in the requested format. -// Payload is the byte index of the first offending position. Mirrors -// Hare's strconv::invalid = !size (we use i32 instead of size). +// Payload is the byte index of the first offending position. +// Mirrors Hare's strconv::invalid = !size. export type invalid = !i32; -// overflow — input was valid but doesn't fit the target type. No -// payload (a single yes/no signal). Mirrors Hare's !void shape. +// overflow — input was valid but doesn't fit the target type. +// Mirrors Hare's strconv::overflow = !void. export type overflow = !void; -// u64tos — write `v` in decimal into `buf` and return the byte count. -// Hare name; the buffer-in shape is the sanctioned Plan 9 subset of -// Hare's `u64tos(u, base) const str`. Unsigned-only so callers don't -// have to think about wraparound when printing a u64 with the high -// bit set. -export fn u64tos(buf: []u8, v: u64) i32 = { - let tmp: [32]u8; +// error — any error from a strconv call. Mirrors Hare's strconv::error. +export type error = !(invalid | overflow); + +// base — numeric base for parsing/formatting. Plain i32 (not a named +// enum) because cross-module `strconv.base.DEC` chains miscompile in +// the cstage cgen — it emits a memory load through `base(SB)` rather +// than inlining the enum value. Hare names them as `strconv::base` +// enum values; we expose them as module-level `def`s so callers say +// `strconv.DEC` and the cgen inlines the immediate. +// +// HEX is HEX_UPPER; HEX_LOWER is a separate pseudo-base that produces +// lowercase a-f digits. +export def DEFAULT: i32 = 0; +export def BIN: i32 = 2; +export def OCT: i32 = 8; +export def DEC: i32 = 10; +export def HEX_UPPER: i32 = 16; +export def HEX: i32 = 16; +export def HEX_LOWER: i32 = 17; + +fn basenum(b: i32) i64 = { + if (b == BIN) { return 2; }; + if (b == OCT) { return 8; }; + if (b == HEX) { return 16; }; + if (b == HEX_UPPER) { return 16; }; + if (b == HEX_LOWER) { return 16; }; + return 10; // DEC and DEFAULT +}; + +fn basedigit(d: i64, b: i32) u8 = { + if (d < 10) { return (d + 48): u8; }; + let off: i64 = d - 10; + if (b == HEX_LOWER) { return (off + 97): u8; }; + return (off + 65): u8; +}; + +// u64tos — convert v to a base-b numeric string. Returns owned str; +// release via os.free(r.ptr, r.len: u64). Mirrors Hare's +// strconv::u64tos (Hare returns const str into a static buffer). +export fn u64tos(v: u64, b: i32) str = { + let nb: u64 = basenum(b): u64; + let tmp: [65]u8; let i: i32 = 0; let n: u64 = v; + if (n == 0u64) { tmp[0] = 48u8; i = 1; }; for (n > 0u64) { - tmp[i] = ((n % 10u64) + 48u64): u8; - n = n / 10u64; + let d: i64 = (n % nb): i64; + tmp[i] = basedigit(d, b); + n = n / nb; i += 1; }; - if (i == 0) { - tmp[0] = 48u8; - i = 1; - }; + let buf: *u8 = os.alloc(i: u64): *u8; let out: i32 = 0; for (i > 0) { i -= 1; buf[out] = tmp[i]; out += 1; }; - return out; + let r: str; + r.ptr = buf; + r.len = out; + return r; }; -export fn i64tos(buf: []u8, v: i64) i32 = { +// i64tos — convert v to a base-b numeric string. Returns owned str; +// release via os.free. Mirrors Hare's strconv::i64tos. +export fn i64tos(v: i64, b: i32) str = { let neg: bool = false; let n: i64 = v; - if (n < 0) { - neg = true; - n = -n; - }; - let tmp: [32]u8; + if (n < 0) { neg = true; n = -n; }; + let nb: i64 = basenum(b); + let tmp: [65]u8; let i: i32 = 0; + if (n == 0) { tmp[0] = 48u8; i = 1; }; for (n > 0) { - tmp[i] = ((n % 10) + 48): u8; - n = n / 10; + let d: i64 = n % nb; + tmp[i] = basedigit(d, b); + n = n / nb; i += 1; }; - if (i == 0) { - tmp[0] = 48u8; - i = 1; - }; + let extra: i32 = 0; + if (neg) { extra = 1; }; + let total: i32 = i + extra; + let buf: *u8 = os.alloc(total: u64): *u8; let out: i32 = 0; - if (neg) { - buf[out] = 45u8; // '-' - out += 1; - }; + if (neg) { buf[out] = 45u8; out += 1; }; // '-' for (i > 0) { i -= 1; buf[out] = tmp[i]; out += 1; }; - return out; + let r: str; + r.ptr = buf; + r.len = out; + return r; }; -// stoi64 — Hare-style fallible signed decimal parser. No locale, no -// whitespace, no underscores: a leading '-' is the only non-digit -// accepted, and only at position 0. -export fn stoi64(s: str) (i64 | invalid | overflow) = { +export fn i32tos(v: i32, b: i32) str = { return i64tos(v: i64, b); }; +export fn i16tos(v: i16, b: i32) str = { return i64tos(v: i64, b); }; +export fn i8tos(v: i8, b: i32) str = { return i64tos(v: i64, b); }; + +export fn u32tos(v: u32, b: i32) str = { return u64tos(v: u64, b); }; +export fn u16tos(v: u16, b: i32) str = { return u64tos(v: u64, b); }; +export fn u8tos(v: u8, b: i32) str = { return u64tos(v: u64, b); }; + +// digval — value of digit byte `c` under base `b`, or -1 if not a +// valid digit. Letters are accepted case-insensitively under HEX / +// HEX_UPPER; only lowercase under HEX_LOWER. +fn digval(c: u8, b: i32) i32 = { + if (c >= 48u8) { if (c <= 57u8) { return (c - 48u8): i32; }; }; + if (b == HEX_LOWER) { + if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; }; + return -1; + }; + if (c >= 65u8) { if (c <= 70u8) { return ((c - 65u8) + 10u8): i32; }; }; + if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; }; + return -1; +}; + +// stoi64 — parse signed base-b number. Mirrors Hare's strconv::stoi64. +// No locale, no whitespace, no underscores: optional leading '-' then +// digits. Returns invalid with the offending index or overflow on +// out-of-range. +export fn stoi64(s: str, b: i32) (i64 | invalid | overflow) = { if (s.len == 0) { return 0: invalid; }; let i: i32 = 0; let neg: bool = false; if (s[0] == 45u8) { neg = true; i = 1; }; if (i >= s.len) { return i: invalid; }; + let nb: i32 = basenum(b): i32; let v: i64 = 0; for (i < s.len) { let c: u8 = s[i]; - if (c < 48u8) { return i: invalid; }; - if (c > 57u8) { return i: invalid; }; - v = v * 10 + ((c: i64) - 48); + let d: i32 = digval(c, b); + if (d < 0) { return i: invalid; }; + if (d >= nb) { return i: invalid; }; + v = v * (nb: i64) + (d: i64); i += 1; }; if (neg) { v = -v; }; return v; }; -// stou64 — fallible unsigned decimal parser. No leading sign. -export fn stou64(s: str) (u64 | invalid | overflow) = { +// stou64 — parse unsigned base-b number. Mirrors Hare's strconv::stou64. +export fn stou64(s: str, b: i32) (u64 | invalid | overflow) = { if (s.len == 0) { return 0: invalid; }; + let nb: u64 = basenum(b): u64; let v: u64 = 0u64; let i: i32 = 0; for (i < s.len) { let c: u8 = s[i]; - if (c < 48u8) { return i: invalid; }; - if (c > 57u8) { return i: invalid; }; - v = v * 10u64 + ((c: u64) - 48u64); + let d: i32 = digval(c, b); + if (d < 0) { return i: invalid; }; + if ((d: u64) >= nb) { return i: invalid; }; + v = v * nb + (d: u64); i += 1; }; return v; }; -// f64tos — write `v` in decimal into `buf` and return the byte count. -// Hare name; this is the buffer-in Plan 9 subset of Hare's -// `f64tos(n) const str`. Today's surface: +export fn stoi32(s: str, b: i32) (i32 | invalid | overflow) = { + let r = stoi64(s, b); + match (r) { + case let v: i64 => { + if (v > 2147483647i64) { return overflow{}; }; + if (v < -2147483648i64) { return overflow{}; }; + return v: i32; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; // unreachable; appeases the path-cov checker +}; + +export fn stoi16(s: str, b: i32) (i16 | invalid | overflow) = { + let r = stoi64(s, b); + match (r) { + case let v: i64 => { + if (v > 32767i64) { return overflow{}; }; + if (v < -32768i64) { return overflow{}; }; + return v: i16; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +export fn stoi8(s: str, b: i32) (i8 | invalid | overflow) = { + let r = stoi64(s, b); + match (r) { + case let v: i64 => { + if (v > 127i64) { return overflow{}; }; + if (v < -128i64) { return overflow{}; }; + return v: i8; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +export fn stou32(s: str, b: i32) (u32 | invalid | overflow) = { + let r = stou64(s, b); + match (r) { + case let v: u64 => { + if (v > 4294967295u64) { return overflow{}; }; + return v: u32; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +export fn stou16(s: str, b: i32) (u16 | invalid | overflow) = { + let r = stou64(s, b); + match (r) { + case let v: u64 => { + if (v > 65535u64) { return overflow{}; }; + return v: u16; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +export fn stou8(s: str, b: i32) (u8 | invalid | overflow) = { + let r = stou64(s, b); + match (r) { + case let v: u64 => { + if (v > 255u64) { return overflow{}; }; + return v: u8; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +// f64tos — convert v to a decimal string. Returns owned str; release +// via os.free. Mirrors Hare's strconv::f64tos (current ww impl is +// fixed-point only, max 6 fractional digits, no NaN/Inf support — +// see graduate-to-Ryū note below). +// +// Surface: // // - finite values only. NaN/±Inf detection needs an f64→u64 bit // reinterpret cast that the cgen doesn't expose yet. @@ -457,23 +613,20 @@ export fn stou64(s: str) (u64 | invalid | overflow) = { // in scientific notation via Ryū; we will graduate when the // compiler grows the bit-reinterpret cast. // -// Round-trip is therefore lossy past 6 fractional digits; callers -// that need bit-exact recovery should not use this until the -// graduate-to-Ryū step lands. `f64tos(buf, 1.0)` writes "1" (no -// decimal point), `f64tos(buf, 1.5)` writes "1.5", `f64tos(buf, -// 0.1)` writes "0.1". +// Round-trip is therefore lossy past 6 fractional digits. // // No float literals in the body — 990's wwdump diff requires this // file's TK_FLOAT count to match between C and ww front-ends, and // the ww-side wwdump currently skips TK_FLOAT.fval while the C side // %g-formats it. Same trick lib/ww/lex/lex.ww's parsef64 uses: // build f64 constants via int-to-f64 casts. -export fn f64tos(buf: []u8, v: f64) i32 = { +export fn f64tos(v: f64) str = { + let tmp: [64]u8; let out: i32 = 0; let f: f64 = v; let zero: f64 = 0: f64; if (f < zero) { - buf[out] = 45u8; // '-' + tmp[out] = 45u8; // '-' out += 1; f = -f; }; @@ -483,8 +636,14 @@ export fn f64tos(buf: []u8, v: f64) i32 = { if (f >= cap) { let s: str = "huge"; let k: i32 = 0; - for (k < s.len) { buf[out] = s[k]; out += 1; k += 1; }; - return out; + for (k < s.len) { tmp[out] = s[k]; out += 1; k += 1; }; + let buf: *u8 = os.alloc(out: u64): *u8; + let q: i32 = 0; + for (q < out) { buf[q] = tmp[q]; q += 1; }; + let r: str; + r.ptr = buf; + r.len = out; + return r; }; let ip: i64 = f: i64; // Fractional part scaled to 6 decimal digits, with round-to- @@ -501,30 +660,42 @@ export fn f64tos(buf: []u8, v: f64) i32 = { ip += 1; fp = 0; }; - let itmp: [32]u8; - let in: i32 = i64tos(itmp[0:32], ip); + let intstr: str = i64tos(ip, DEC); let k: i32 = 0; - for (k < in) { buf[out] = itmp[k]; out += 1; k += 1; }; - if (fp == 0) { return out; }; - buf[out] = 46u8; // '.' - out += 1; - let ftmp: [16]u8; - let m: i32 = u64tos(ftmp[0:16], fp: u64); - // Pad fractional to 6 digits with leading zeros (e.g. 0.05 → - // fp=50000, m=5, pad one '0' before "50000"). - let z: i32 = 6 - m; - for (z > 0) { buf[out] = 48u8; out += 1; z -= 1; }; - k = 0; - for (k < m) { buf[out] = ftmp[k]; out += 1; k += 1; }; - // Trim trailing zeros in the fractional part (we know fp != 0, - // so the loop stops before erasing the dot). - for (out > 0) { - if (buf[out - 1] != 48u8) { break; }; - out -= 1; + for (k < intstr.len) { tmp[out] = intstr.ptr[k]; out += 1; k += 1; }; + os.free(intstr.ptr: *void, intstr.len: u64); + if (fp != 0) { + tmp[out] = 46u8; // '.' + out += 1; + let fracstr: str = u64tos(fp: u64, DEC); + // Pad fractional to 6 digits with leading zeros (e.g. 0.05 → + // fp=50000, fracstr="50000", pad one '0' before). + let z: i32 = 6 - fracstr.len; + for (z > 0) { tmp[out] = 48u8; out += 1; z -= 1; }; + k = 0; + for (k < fracstr.len) { tmp[out] = fracstr.ptr[k]; out += 1; k += 1; }; + os.free(fracstr.ptr: *void, fracstr.len: u64); + // Trim trailing zeros in the fractional part. + for (out > 0) { + if (tmp[out - 1] != 48u8) { break; }; + out -= 1; + }; }; - return out; + let buf: *u8 = os.alloc(out: u64): *u8; + let q: i32 = 0; + for (q < out) { buf[q] = tmp[q]; q += 1; }; + let r: str; + r.ptr = buf; + r.len = out; + 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. + // MODULE: lex // lib/ww/lex/tok.ww — port of cmd/wcc/tok.c plus the Tkind / // Tok / Pos shapes from cmd/wcc/ww.h. @@ -910,12 +1081,11 @@ export fn tokprint(fd: i32, t: *tok) void = { fputsstr(fd, ""); }; fputcbyte(fd, 58u8); // ':' - let buf: [32]u8; - let n: i32 = strconv.i64tos(buf[0:32], t.line: i64); - os.write(fd, buf.ptr, n: u64); + let ls: str = strconv.i64tos(t.line: i64, strconv.DEC); + os.write(fd, ls.ptr, ls.len: u64); fputcbyte(fd, 58u8); - n = strconv.i64tos(buf[0:32], t.col: i64); - os.write(fd, buf.ptr, n: u64); + let cs: str = strconv.i64tos(t.col: i64, strconv.DEC); + os.write(fd, cs.ptr, cs.len: u64); fputcbyte(fd, 32u8); // ' ' fputsstr(fd, tokname(t.kind)); @@ -930,12 +1100,12 @@ export fn tokprint(fd: i32, t: *tok) void = { fputq(fd, ttext.ptr, ttext.len); } else { if (t.kind == tkind.TK_INT) { fputcbyte(fd, 32u8); - n = strconv.u64tos(buf[0:32], t.uval); - os.write(fd, buf.ptr, n: u64); + let us: str = strconv.u64tos(t.uval, strconv.DEC); + os.write(fd, us.ptr, us.len: u64); } else { if (t.kind == tkind.TK_RUNE) { fputcbyte(fd, 32u8); - n = strconv.u64tos(buf[0:32], t.uval); - os.write(fd, buf.ptr, n: u64); + let us: str = strconv.u64tos(t.uval, strconv.DEC); + os.write(fd, us.ptr, us.len: u64); };};};};}; // tkind.TK_FLOAT is intentionally not handled here — %g formatting // won't byte-match across implementations. Diff fixtures must @@ -2107,14 +2277,12 @@ fn pr(fd: i32, n: *node, d: i32) void = { if (n.kind == nkind.N_INTLIT) { putc1(fd, 32u8); - let buf: [32]u8; - let m: i32 = strconv.u64tos(buf[0:32], n.uval); - os.write(fd, buf.ptr, m: u64); + let s: str = strconv.u64tos(n.uval, strconv.DEC); + os.write(fd, s.ptr, s.len: u64); } else { if (n.kind == nkind.N_RUNELIT) { putc1(fd, 32u8); - let buf: [32]u8; - let m: i32 = strconv.u64tos(buf[0:32], n.uval); - os.write(fd, buf.ptr, m: u64); + let s: str = strconv.u64tos(n.uval, strconv.DEC); + os.write(fd, s.ptr, s.len: u64); } else { if ( n.kind == nkind.N_STRLIT || n.kind == nkind.N_IDENT || @@ -11427,15 +11595,13 @@ fn localfind(c: *cgen, name: str) i32 = { fn emitline(s: str) void = { os.write(1, s.ptr, s.len: u64); }; fn emitint(v: i64) void = { - let buf: [32]u8; - let n: i32 = strconv.i64tos(buf[0:32], v); - os.write(1, buf.ptr, n: u64); + let s: str = strconv.i64tos(v, strconv.DEC); + os.write(1, s.ptr, s.len: u64); }; fn emituint(v: u64) void = { - let buf: [32]u8; - let n: i32 = strconv.u64tos(buf[0:32], v); - os.write(1, buf.ptr, n: u64); + let s: str = strconv.u64tos(v, strconv.DEC); + os.write(1, s.ptr, s.len: u64); }; // emitdispreg — print "disp(reg)" or "(reg)" when disp == 0, the @@ -11454,9 +11620,9 @@ fn emitoff(v: i64) void = { if (v != 0i64) { emitint(v); }; }; -// mklabel — fresh label "__". Returns an +// mklabel — fresh label "__". Returns an // arena-owned str. Mirrors C cgen's mklabel so diffs match. -fn mklabel(c: *cgen, base: str) str = { +fn mklabel(c: *cgen, prefix: str) str = { let buf: [128]u8; let i: i32 = 0; let fname: str = c.fnname; @@ -11467,12 +11633,15 @@ fn mklabel(c: *cgen, base: str) str = { }; buf[i] = 95u8; i += 1; // '_' j = 0; - for (j < base.len) { - buf[i] = base[j]; + for (j < prefix.len) { + buf[i] = prefix[j]; i += 1; j += 1; }; buf[i] = 95u8; i += 1; // '_' - let n: i32 = strconv.i64tos(buf[i:128], c.labelseq: i64); + let ns: str = strconv.i64tos(c.labelseq: i64, strconv.DEC); + let n: i32 = ns.len; + let dk: i32 = 0; + for (dk < n) { buf[i + dk] = ns.ptr[dk]; dk += 1; }; c.labelseq += 1; let total: i32 = i + n; let p: *u8 = amalloc(c.a, (total: u64) + 1u64): *u8; @@ -11493,22 +11662,25 @@ fn emitlabel(s: str) void = { emitline(":\n"); }; -// mkscratchname — fresh local-slot name "._". Used for +// mkscratchname — fresh local-slot name "._". Used for // compiler-synthesised slots (switch scrutinee, forrange index/len) // that need to be unique per use site but are never referenced by user // code. Increments labelseq so the same source position lines up with // C cgen's labelseq stream. -fn mkscratchname(c: *cgen, base: str) str = { +fn mkscratchname(c: *cgen, prefix: str) str = { let buf: [128]u8; let i: i32 = 0; buf[i] = 46u8; i += 1; // '.' let j: i32 = 0; - for (j < base.len) { - buf[i] = base[j]; + for (j < prefix.len) { + buf[i] = prefix[j]; i += 1; j += 1; }; buf[i] = 95u8; i += 1; // '_' - let n: i32 = strconv.i64tos(buf[i:128], c.labelseq: i64); + let ns: str = strconv.i64tos(c.labelseq: i64, strconv.DEC); + let n: i32 = ns.len; + let dk: i32 = 0; + for (dk < n) { buf[i + dk] = ns.ptr[dk]; dk += 1; }; c.labelseq += 1; let total: i32 = i + n; let p: *u8 = amalloc(c.a, (total: u64) + 1u64): *u8; @@ -11542,7 +11714,10 @@ fn internstrlit(c: *cgen, bytes: str) str = { // New label "_S_". let buf: [32]u8; buf[0] = 95u8; buf[1] = 83u8; buf[2] = 95u8; // "_S_" - let n: i32 = strconv.i64tos(buf[3:32], c.strlitseq: i64); + let ns: str = strconv.i64tos(c.strlitseq: i64, strconv.DEC); + let n: i32 = ns.len; + let dk: i32 = 0; + for (dk < n) { buf[3 + dk] = ns.ptr[dk]; dk += 1; }; c.strlitseq += 1; let total: i32 = 3 + n; let p: *u8 = amalloc(c.a, (total: u64) + 1u64): *u8; diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index 8bab09ad..eba1de70 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -472,15 +472,13 @@ fn localfind(c: *cgen, name: str) i32 = { fn emitline(s: str) void = { os.write(1, s.ptr, s.len: u64); }; fn emitint(v: i64) void = { - let buf: [32]u8; - let n: i32 = strconv.i64tos(buf[0:32], v); - os.write(1, buf.ptr, n: u64); + let s: str = strconv.i64tos(v, strconv.DEC); + os.write(1, s.ptr, s.len: u64); }; fn emituint(v: u64) void = { - let buf: [32]u8; - let n: i32 = strconv.u64tos(buf[0:32], v); - os.write(1, buf.ptr, n: u64); + let s: str = strconv.u64tos(v, strconv.DEC); + os.write(1, s.ptr, s.len: u64); }; // emitdispreg — print "disp(reg)" or "(reg)" when disp == 0, the @@ -499,9 +497,9 @@ fn emitoff(v: i64) void = { if (v != 0i64) { emitint(v); }; }; -// mklabel — fresh label "__". Returns an +// mklabel — fresh label "__". Returns an // arena-owned str. Mirrors C cgen's mklabel so diffs match. -fn mklabel(c: *cgen, base: str) str = { +fn mklabel(c: *cgen, prefix: str) str = { let buf: [128]u8; let i: i32 = 0; let fname: str = c.fnname; @@ -512,12 +510,15 @@ fn mklabel(c: *cgen, base: str) str = { }; buf[i] = 95u8; i += 1; // '_' j = 0; - for (j < base.len) { - buf[i] = base[j]; + for (j < prefix.len) { + buf[i] = prefix[j]; i += 1; j += 1; }; buf[i] = 95u8; i += 1; // '_' - let n: i32 = strconv.i64tos(buf[i:128], c.labelseq: i64); + let ns: str = strconv.i64tos(c.labelseq: i64, strconv.DEC); + let n: i32 = ns.len; + let dk: i32 = 0; + for (dk < n) { buf[i + dk] = ns.ptr[dk]; dk += 1; }; c.labelseq += 1; let total: i32 = i + n; let p: *u8 = amalloc(c.a, (total: u64) + 1u64): *u8; @@ -538,22 +539,25 @@ fn emitlabel(s: str) void = { emitline(":\n"); }; -// mkscratchname — fresh local-slot name "._". Used for +// mkscratchname — fresh local-slot name "._". Used for // compiler-synthesised slots (switch scrutinee, forrange index/len) // that need to be unique per use site but are never referenced by user // code. Increments labelseq so the same source position lines up with // C cgen's labelseq stream. -fn mkscratchname(c: *cgen, base: str) str = { +fn mkscratchname(c: *cgen, prefix: str) str = { let buf: [128]u8; let i: i32 = 0; buf[i] = 46u8; i += 1; // '.' let j: i32 = 0; - for (j < base.len) { - buf[i] = base[j]; + for (j < prefix.len) { + buf[i] = prefix[j]; i += 1; j += 1; }; buf[i] = 95u8; i += 1; // '_' - let n: i32 = strconv.i64tos(buf[i:128], c.labelseq: i64); + let ns: str = strconv.i64tos(c.labelseq: i64, strconv.DEC); + let n: i32 = ns.len; + let dk: i32 = 0; + for (dk < n) { buf[i + dk] = ns.ptr[dk]; dk += 1; }; c.labelseq += 1; let total: i32 = i + n; let p: *u8 = amalloc(c.a, (total: u64) + 1u64): *u8; @@ -587,7 +591,10 @@ fn internstrlit(c: *cgen, bytes: str) str = { // New label "_S_". let buf: [32]u8; buf[0] = 95u8; buf[1] = 83u8; buf[2] = 95u8; // "_S_" - let n: i32 = strconv.i64tos(buf[3:32], c.strlitseq: i64); + let ns: str = strconv.i64tos(c.strlitseq: i64, strconv.DEC); + let n: i32 = ns.len; + let dk: i32 = 0; + for (dk < n) { buf[3 + dk] = ns.ptr[dk]; dk += 1; }; c.strlitseq += 1; let total: i32 = 3 + n; let p: *u8 = amalloc(c.a, (total: u64) + 1u64): *u8; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index fdae41ae..e8d58199 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -336,117 +336,273 @@ export fn freearena(a: *arena) void = { }; // MODULE: strconv -// strconv — number↔string conversions. Decimal i64 to/from a fixed -// buffer. Error shapes mirror Hare's strconv types: (T | invalid | -// overflow) where each error is a named alias over a payload type -// (Hare uses !size / !void; ww uses i32 / void without the `!` mark). +// strconv — number↔string conversions. +// +// Mirrors Hare's strconv:: surface. The *tos functions return a fresh +// owned `str`; release via os.free(r.ptr, r.len: u64) when done. +// Hare returns `const str` into a static buffer; ww allocates per +// call because the wwstage cgen doesn't currently support mutating a +// module-level `*u8` (so a lazy-init shared buffer isn't expressible +// today). Graduate to the static-buffer shape once that lands. + +use os; // invalid — input wasn't a valid number in the requested format. -// Payload is the byte index of the first offending position. Mirrors -// Hare's strconv::invalid = !size (we use i32 instead of size). +// Payload is the byte index of the first offending position. +// Mirrors Hare's strconv::invalid = !size. export type invalid = !i32; -// overflow — input was valid but doesn't fit the target type. No -// payload (a single yes/no signal). Mirrors Hare's !void shape. +// overflow — input was valid but doesn't fit the target type. +// Mirrors Hare's strconv::overflow = !void. export type overflow = !void; -// u64tos — write `v` in decimal into `buf` and return the byte count. -// Hare name; the buffer-in shape is the sanctioned Plan 9 subset of -// Hare's `u64tos(u, base) const str`. Unsigned-only so callers don't -// have to think about wraparound when printing a u64 with the high -// bit set. -export fn u64tos(buf: []u8, v: u64) i32 = { - let tmp: [32]u8; +// error — any error from a strconv call. Mirrors Hare's strconv::error. +export type error = !(invalid | overflow); + +// base — numeric base for parsing/formatting. Plain i32 (not a named +// enum) because cross-module `strconv.base.DEC` chains miscompile in +// the cstage cgen — it emits a memory load through `base(SB)` rather +// than inlining the enum value. Hare names them as `strconv::base` +// enum values; we expose them as module-level `def`s so callers say +// `strconv.DEC` and the cgen inlines the immediate. +// +// HEX is HEX_UPPER; HEX_LOWER is a separate pseudo-base that produces +// lowercase a-f digits. +export def DEFAULT: i32 = 0; +export def BIN: i32 = 2; +export def OCT: i32 = 8; +export def DEC: i32 = 10; +export def HEX_UPPER: i32 = 16; +export def HEX: i32 = 16; +export def HEX_LOWER: i32 = 17; + +fn basenum(b: i32) i64 = { + if (b == BIN) { return 2; }; + if (b == OCT) { return 8; }; + if (b == HEX) { return 16; }; + if (b == HEX_UPPER) { return 16; }; + if (b == HEX_LOWER) { return 16; }; + return 10; // DEC and DEFAULT +}; + +fn basedigit(d: i64, b: i32) u8 = { + if (d < 10) { return (d + 48): u8; }; + let off: i64 = d - 10; + if (b == HEX_LOWER) { return (off + 97): u8; }; + return (off + 65): u8; +}; + +// u64tos — convert v to a base-b numeric string. Returns owned str; +// release via os.free(r.ptr, r.len: u64). Mirrors Hare's +// strconv::u64tos (Hare returns const str into a static buffer). +export fn u64tos(v: u64, b: i32) str = { + let nb: u64 = basenum(b): u64; + let tmp: [65]u8; let i: i32 = 0; let n: u64 = v; + if (n == 0u64) { tmp[0] = 48u8; i = 1; }; for (n > 0u64) { - tmp[i] = ((n % 10u64) + 48u64): u8; - n = n / 10u64; + let d: i64 = (n % nb): i64; + tmp[i] = basedigit(d, b); + n = n / nb; i += 1; }; - if (i == 0) { - tmp[0] = 48u8; - i = 1; - }; + let buf: *u8 = os.alloc(i: u64): *u8; let out: i32 = 0; for (i > 0) { i -= 1; buf[out] = tmp[i]; out += 1; }; - return out; + let r: str; + r.ptr = buf; + r.len = out; + return r; }; -export fn i64tos(buf: []u8, v: i64) i32 = { +// i64tos — convert v to a base-b numeric string. Returns owned str; +// release via os.free. Mirrors Hare's strconv::i64tos. +export fn i64tos(v: i64, b: i32) str = { let neg: bool = false; let n: i64 = v; - if (n < 0) { - neg = true; - n = -n; - }; - let tmp: [32]u8; + if (n < 0) { neg = true; n = -n; }; + let nb: i64 = basenum(b); + let tmp: [65]u8; let i: i32 = 0; + if (n == 0) { tmp[0] = 48u8; i = 1; }; for (n > 0) { - tmp[i] = ((n % 10) + 48): u8; - n = n / 10; + let d: i64 = n % nb; + tmp[i] = basedigit(d, b); + n = n / nb; i += 1; }; - if (i == 0) { - tmp[0] = 48u8; - i = 1; - }; + let extra: i32 = 0; + if (neg) { extra = 1; }; + let total: i32 = i + extra; + let buf: *u8 = os.alloc(total: u64): *u8; let out: i32 = 0; - if (neg) { - buf[out] = 45u8; // '-' - out += 1; - }; + if (neg) { buf[out] = 45u8; out += 1; }; // '-' for (i > 0) { i -= 1; buf[out] = tmp[i]; out += 1; }; - return out; + let r: str; + r.ptr = buf; + r.len = out; + return r; }; -// stoi64 — Hare-style fallible signed decimal parser. No locale, no -// whitespace, no underscores: a leading '-' is the only non-digit -// accepted, and only at position 0. -export fn stoi64(s: str) (i64 | invalid | overflow) = { +export fn i32tos(v: i32, b: i32) str = { return i64tos(v: i64, b); }; +export fn i16tos(v: i16, b: i32) str = { return i64tos(v: i64, b); }; +export fn i8tos(v: i8, b: i32) str = { return i64tos(v: i64, b); }; + +export fn u32tos(v: u32, b: i32) str = { return u64tos(v: u64, b); }; +export fn u16tos(v: u16, b: i32) str = { return u64tos(v: u64, b); }; +export fn u8tos(v: u8, b: i32) str = { return u64tos(v: u64, b); }; + +// digval — value of digit byte `c` under base `b`, or -1 if not a +// valid digit. Letters are accepted case-insensitively under HEX / +// HEX_UPPER; only lowercase under HEX_LOWER. +fn digval(c: u8, b: i32) i32 = { + if (c >= 48u8) { if (c <= 57u8) { return (c - 48u8): i32; }; }; + if (b == HEX_LOWER) { + if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; }; + return -1; + }; + if (c >= 65u8) { if (c <= 70u8) { return ((c - 65u8) + 10u8): i32; }; }; + if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; }; + return -1; +}; + +// stoi64 — parse signed base-b number. Mirrors Hare's strconv::stoi64. +// No locale, no whitespace, no underscores: optional leading '-' then +// digits. Returns invalid with the offending index or overflow on +// out-of-range. +export fn stoi64(s: str, b: i32) (i64 | invalid | overflow) = { if (s.len == 0) { return 0: invalid; }; let i: i32 = 0; let neg: bool = false; if (s[0] == 45u8) { neg = true; i = 1; }; if (i >= s.len) { return i: invalid; }; + let nb: i32 = basenum(b): i32; let v: i64 = 0; for (i < s.len) { let c: u8 = s[i]; - if (c < 48u8) { return i: invalid; }; - if (c > 57u8) { return i: invalid; }; - v = v * 10 + ((c: i64) - 48); + let d: i32 = digval(c, b); + if (d < 0) { return i: invalid; }; + if (d >= nb) { return i: invalid; }; + v = v * (nb: i64) + (d: i64); i += 1; }; if (neg) { v = -v; }; return v; }; -// stou64 — fallible unsigned decimal parser. No leading sign. -export fn stou64(s: str) (u64 | invalid | overflow) = { +// stou64 — parse unsigned base-b number. Mirrors Hare's strconv::stou64. +export fn stou64(s: str, b: i32) (u64 | invalid | overflow) = { if (s.len == 0) { return 0: invalid; }; + let nb: u64 = basenum(b): u64; let v: u64 = 0u64; let i: i32 = 0; for (i < s.len) { let c: u8 = s[i]; - if (c < 48u8) { return i: invalid; }; - if (c > 57u8) { return i: invalid; }; - v = v * 10u64 + ((c: u64) - 48u64); + let d: i32 = digval(c, b); + if (d < 0) { return i: invalid; }; + if ((d: u64) >= nb) { return i: invalid; }; + v = v * nb + (d: u64); i += 1; }; return v; }; -// f64tos — write `v` in decimal into `buf` and return the byte count. -// Hare name; this is the buffer-in Plan 9 subset of Hare's -// `f64tos(n) const str`. Today's surface: +export fn stoi32(s: str, b: i32) (i32 | invalid | overflow) = { + let r = stoi64(s, b); + match (r) { + case let v: i64 => { + if (v > 2147483647i64) { return overflow{}; }; + if (v < -2147483648i64) { return overflow{}; }; + return v: i32; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; // unreachable; appeases the path-cov checker +}; + +export fn stoi16(s: str, b: i32) (i16 | invalid | overflow) = { + let r = stoi64(s, b); + match (r) { + case let v: i64 => { + if (v > 32767i64) { return overflow{}; }; + if (v < -32768i64) { return overflow{}; }; + return v: i16; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +export fn stoi8(s: str, b: i32) (i8 | invalid | overflow) = { + let r = stoi64(s, b); + match (r) { + case let v: i64 => { + if (v > 127i64) { return overflow{}; }; + if (v < -128i64) { return overflow{}; }; + return v: i8; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +export fn stou32(s: str, b: i32) (u32 | invalid | overflow) = { + let r = stou64(s, b); + match (r) { + case let v: u64 => { + if (v > 4294967295u64) { return overflow{}; }; + return v: u32; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +export fn stou16(s: str, b: i32) (u16 | invalid | overflow) = { + let r = stou64(s, b); + match (r) { + case let v: u64 => { + if (v > 65535u64) { return overflow{}; }; + return v: u16; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +export fn stou8(s: str, b: i32) (u8 | invalid | overflow) = { + let r = stou64(s, b); + match (r) { + case let v: u64 => { + if (v > 255u64) { return overflow{}; }; + return v: u8; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +// f64tos — convert v to a decimal string. Returns owned str; release +// via os.free. Mirrors Hare's strconv::f64tos (current ww impl is +// fixed-point only, max 6 fractional digits, no NaN/Inf support — +// see graduate-to-Ryū note below). +// +// Surface: // // - finite values only. NaN/±Inf detection needs an f64→u64 bit // reinterpret cast that the cgen doesn't expose yet. @@ -457,23 +613,20 @@ export fn stou64(s: str) (u64 | invalid | overflow) = { // in scientific notation via Ryū; we will graduate when the // compiler grows the bit-reinterpret cast. // -// Round-trip is therefore lossy past 6 fractional digits; callers -// that need bit-exact recovery should not use this until the -// graduate-to-Ryū step lands. `f64tos(buf, 1.0)` writes "1" (no -// decimal point), `f64tos(buf, 1.5)` writes "1.5", `f64tos(buf, -// 0.1)` writes "0.1". +// Round-trip is therefore lossy past 6 fractional digits. // // No float literals in the body — 990's wwdump diff requires this // file's TK_FLOAT count to match between C and ww front-ends, and // the ww-side wwdump currently skips TK_FLOAT.fval while the C side // %g-formats it. Same trick lib/ww/lex/lex.ww's parsef64 uses: // build f64 constants via int-to-f64 casts. -export fn f64tos(buf: []u8, v: f64) i32 = { +export fn f64tos(v: f64) str = { + let tmp: [64]u8; let out: i32 = 0; let f: f64 = v; let zero: f64 = 0: f64; if (f < zero) { - buf[out] = 45u8; // '-' + tmp[out] = 45u8; // '-' out += 1; f = -f; }; @@ -483,8 +636,14 @@ export fn f64tos(buf: []u8, v: f64) i32 = { if (f >= cap) { let s: str = "huge"; let k: i32 = 0; - for (k < s.len) { buf[out] = s[k]; out += 1; k += 1; }; - return out; + for (k < s.len) { tmp[out] = s[k]; out += 1; k += 1; }; + let buf: *u8 = os.alloc(out: u64): *u8; + let q: i32 = 0; + for (q < out) { buf[q] = tmp[q]; q += 1; }; + let r: str; + r.ptr = buf; + r.len = out; + return r; }; let ip: i64 = f: i64; // Fractional part scaled to 6 decimal digits, with round-to- @@ -501,30 +660,42 @@ export fn f64tos(buf: []u8, v: f64) i32 = { ip += 1; fp = 0; }; - let itmp: [32]u8; - let in: i32 = i64tos(itmp[0:32], ip); + let intstr: str = i64tos(ip, DEC); let k: i32 = 0; - for (k < in) { buf[out] = itmp[k]; out += 1; k += 1; }; - if (fp == 0) { return out; }; - buf[out] = 46u8; // '.' - out += 1; - let ftmp: [16]u8; - let m: i32 = u64tos(ftmp[0:16], fp: u64); - // Pad fractional to 6 digits with leading zeros (e.g. 0.05 → - // fp=50000, m=5, pad one '0' before "50000"). - let z: i32 = 6 - m; - for (z > 0) { buf[out] = 48u8; out += 1; z -= 1; }; - k = 0; - for (k < m) { buf[out] = ftmp[k]; out += 1; k += 1; }; - // Trim trailing zeros in the fractional part (we know fp != 0, - // so the loop stops before erasing the dot). - for (out > 0) { - if (buf[out - 1] != 48u8) { break; }; - out -= 1; + for (k < intstr.len) { tmp[out] = intstr.ptr[k]; out += 1; k += 1; }; + os.free(intstr.ptr: *void, intstr.len: u64); + if (fp != 0) { + tmp[out] = 46u8; // '.' + out += 1; + let fracstr: str = u64tos(fp: u64, DEC); + // Pad fractional to 6 digits with leading zeros (e.g. 0.05 → + // fp=50000, fracstr="50000", pad one '0' before). + let z: i32 = 6 - fracstr.len; + for (z > 0) { tmp[out] = 48u8; out += 1; z -= 1; }; + k = 0; + for (k < fracstr.len) { tmp[out] = fracstr.ptr[k]; out += 1; k += 1; }; + os.free(fracstr.ptr: *void, fracstr.len: u64); + // Trim trailing zeros in the fractional part. + for (out > 0) { + if (tmp[out - 1] != 48u8) { break; }; + out -= 1; + }; }; - return out; + let buf: *u8 = os.alloc(out: u64): *u8; + let q: i32 = 0; + for (q < out) { buf[q] = tmp[q]; q += 1; }; + let r: str; + r.ptr = buf; + r.len = out; + 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. + // MODULE: lex // lib/ww/lex/tok.ww — port of cmd/wcc/tok.c plus the Tkind / // Tok / Pos shapes from cmd/wcc/ww.h. @@ -910,12 +1081,11 @@ export fn tokprint(fd: i32, t: *tok) void = { fputsstr(fd, ""); }; fputcbyte(fd, 58u8); // ':' - let buf: [32]u8; - let n: i32 = strconv.i64tos(buf[0:32], t.line: i64); - os.write(fd, buf.ptr, n: u64); + let ls: str = strconv.i64tos(t.line: i64, strconv.DEC); + os.write(fd, ls.ptr, ls.len: u64); fputcbyte(fd, 58u8); - n = strconv.i64tos(buf[0:32], t.col: i64); - os.write(fd, buf.ptr, n: u64); + let cs: str = strconv.i64tos(t.col: i64, strconv.DEC); + os.write(fd, cs.ptr, cs.len: u64); fputcbyte(fd, 32u8); // ' ' fputsstr(fd, tokname(t.kind)); @@ -930,12 +1100,12 @@ export fn tokprint(fd: i32, t: *tok) void = { fputq(fd, ttext.ptr, ttext.len); } else { if (t.kind == tkind.TK_INT) { fputcbyte(fd, 32u8); - n = strconv.u64tos(buf[0:32], t.uval); - os.write(fd, buf.ptr, n: u64); + let us: str = strconv.u64tos(t.uval, strconv.DEC); + os.write(fd, us.ptr, us.len: u64); } else { if (t.kind == tkind.TK_RUNE) { fputcbyte(fd, 32u8); - n = strconv.u64tos(buf[0:32], t.uval); - os.write(fd, buf.ptr, n: u64); + let us: str = strconv.u64tos(t.uval, strconv.DEC); + os.write(fd, us.ptr, us.len: u64); };};};};}; // tkind.TK_FLOAT is intentionally not handled here — %g formatting // won't byte-match across implementations. Diff fixtures must @@ -2107,14 +2277,12 @@ fn pr(fd: i32, n: *node, d: i32) void = { if (n.kind == nkind.N_INTLIT) { putc1(fd, 32u8); - let buf: [32]u8; - let m: i32 = strconv.u64tos(buf[0:32], n.uval); - os.write(fd, buf.ptr, m: u64); + let s: str = strconv.u64tos(n.uval, strconv.DEC); + os.write(fd, s.ptr, s.len: u64); } else { if (n.kind == nkind.N_RUNELIT) { putc1(fd, 32u8); - let buf: [32]u8; - let m: i32 = strconv.u64tos(buf[0:32], n.uval); - os.write(fd, buf.ptr, m: u64); + let s: str = strconv.u64tos(n.uval, strconv.DEC); + os.write(fd, s.ptr, s.len: u64); } else { if ( n.kind == nkind.N_STRLIT || n.kind == nkind.N_IDENT || @@ -11427,15 +11595,13 @@ fn localfind(c: *cgen, name: str) i32 = { fn emitline(s: str) void = { os.write(1, s.ptr, s.len: u64); }; fn emitint(v: i64) void = { - let buf: [32]u8; - let n: i32 = strconv.i64tos(buf[0:32], v); - os.write(1, buf.ptr, n: u64); + let s: str = strconv.i64tos(v, strconv.DEC); + os.write(1, s.ptr, s.len: u64); }; fn emituint(v: u64) void = { - let buf: [32]u8; - let n: i32 = strconv.u64tos(buf[0:32], v); - os.write(1, buf.ptr, n: u64); + let s: str = strconv.u64tos(v, strconv.DEC); + os.write(1, s.ptr, s.len: u64); }; // emitdispreg — print "disp(reg)" or "(reg)" when disp == 0, the @@ -11454,9 +11620,9 @@ fn emitoff(v: i64) void = { if (v != 0i64) { emitint(v); }; }; -// mklabel — fresh label "__". Returns an +// mklabel — fresh label "__". Returns an // arena-owned str. Mirrors C cgen's mklabel so diffs match. -fn mklabel(c: *cgen, base: str) str = { +fn mklabel(c: *cgen, prefix: str) str = { let buf: [128]u8; let i: i32 = 0; let fname: str = c.fnname; @@ -11467,12 +11633,15 @@ fn mklabel(c: *cgen, base: str) str = { }; buf[i] = 95u8; i += 1; // '_' j = 0; - for (j < base.len) { - buf[i] = base[j]; + for (j < prefix.len) { + buf[i] = prefix[j]; i += 1; j += 1; }; buf[i] = 95u8; i += 1; // '_' - let n: i32 = strconv.i64tos(buf[i:128], c.labelseq: i64); + let ns: str = strconv.i64tos(c.labelseq: i64, strconv.DEC); + let n: i32 = ns.len; + let dk: i32 = 0; + for (dk < n) { buf[i + dk] = ns.ptr[dk]; dk += 1; }; c.labelseq += 1; let total: i32 = i + n; let p: *u8 = amalloc(c.a, (total: u64) + 1u64): *u8; @@ -11493,22 +11662,25 @@ fn emitlabel(s: str) void = { emitline(":\n"); }; -// mkscratchname — fresh local-slot name "._". Used for +// mkscratchname — fresh local-slot name "._". Used for // compiler-synthesised slots (switch scrutinee, forrange index/len) // that need to be unique per use site but are never referenced by user // code. Increments labelseq so the same source position lines up with // C cgen's labelseq stream. -fn mkscratchname(c: *cgen, base: str) str = { +fn mkscratchname(c: *cgen, prefix: str) str = { let buf: [128]u8; let i: i32 = 0; buf[i] = 46u8; i += 1; // '.' let j: i32 = 0; - for (j < base.len) { - buf[i] = base[j]; + for (j < prefix.len) { + buf[i] = prefix[j]; i += 1; j += 1; }; buf[i] = 95u8; i += 1; // '_' - let n: i32 = strconv.i64tos(buf[i:128], c.labelseq: i64); + let ns: str = strconv.i64tos(c.labelseq: i64, strconv.DEC); + let n: i32 = ns.len; + let dk: i32 = 0; + for (dk < n) { buf[i + dk] = ns.ptr[dk]; dk += 1; }; c.labelseq += 1; let total: i32 = i + n; let p: *u8 = amalloc(c.a, (total: u64) + 1u64): *u8; @@ -11542,7 +11714,10 @@ fn internstrlit(c: *cgen, bytes: str) str = { // New label "_S_". let buf: [32]u8; buf[0] = 95u8; buf[1] = 83u8; buf[2] = 95u8; // "_S_" - let n: i32 = strconv.i64tos(buf[3:32], c.strlitseq: i64); + let ns: str = strconv.i64tos(c.strlitseq: i64, strconv.DEC); + let n: i32 = ns.len; + let dk: i32 = 0; + for (dk < n) { buf[3 + dk] = ns.ptr[dk]; dk += 1; }; c.strlitseq += 1; let total: i32 = 3 + n; let p: *u8 = amalloc(c.a, (total: u64) + 1u64): *u8; @@ -12661,13 +12836,12 @@ export fn main(argc: i32, argv: **u8) i32 = { // ": / resolved" os.write(1, argstr(path).ptr, argstrlen(path): u64); os.write(1, ": ".ptr, 2u64); - let buf: [32]u8; - let n: i32 = strconv.i64tos(buf[0:32], ck.nresolved: i64); - os.write(1, buf.ptr, n: u64); + let rs: str = strconv.i64tos(ck.nresolved: i64, strconv.DEC); + os.write(1, rs.ptr, rs.len: u64); os.write(1, "/".ptr, 1u64); let total: i32 = ck.nresolved + ck.nunresolved; - n = strconv.i64tos(buf[0:32], total: i64); - os.write(1, buf.ptr, n: u64); + let ts: str = strconv.i64tos(total: i64, strconv.DEC); + os.write(1, ts.ptr, ts.len: u64); os.write(1, " resolved\n".ptr, 10u64); if (ck.nunresolved > 0) { return 1; }; } else { if (mode == 99) { // '-c' — codegen / emit asm diff --git a/selfhost/cmd/wwdump/main.ww b/selfhost/cmd/wwdump/main.ww index 0270fda0..f301150c 100644 --- a/selfhost/cmd/wwdump/main.ww +++ b/selfhost/cmd/wwdump/main.ww @@ -145,13 +145,12 @@ export fn main(argc: i32, argv: **u8) i32 = { // ": / resolved" os.write(1, argstr(path).ptr, argstrlen(path): u64); os.write(1, ": ".ptr, 2u64); - let buf: [32]u8; - let n: i32 = strconv.i64tos(buf[0:32], ck.nresolved: i64); - os.write(1, buf.ptr, n: u64); + let rs: str = strconv.i64tos(ck.nresolved: i64, strconv.DEC); + os.write(1, rs.ptr, rs.len: u64); os.write(1, "/".ptr, 1u64); let total: i32 = ck.nresolved + ck.nunresolved; - n = strconv.i64tos(buf[0:32], total: i64); - os.write(1, buf.ptr, n: u64); + let ts: str = strconv.i64tos(total: i64, strconv.DEC); + os.write(1, ts.ptr, ts.len: u64); os.write(1, " resolved\n".ptr, 10u64); if (ck.nunresolved > 0) { return 1; }; } else { if (mode == 99) { // '-c' — codegen / emit asm diff --git a/selfhost/test/smoke.combined.ww b/selfhost/test/smoke.combined.ww index 70ed417e..c7afd66c 100644 --- a/selfhost/test/smoke.combined.ww +++ b/selfhost/test/smoke.combined.ww @@ -228,117 +228,273 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = { }; // MODULE: strconv -// strconv — number↔string conversions. Decimal i64 to/from a fixed -// buffer. Error shapes mirror Hare's strconv types: (T | invalid | -// overflow) where each error is a named alias over a payload type -// (Hare uses !size / !void; ww uses i32 / void without the `!` mark). +// strconv — number↔string conversions. +// +// Mirrors Hare's strconv:: surface. The *tos functions return a fresh +// owned `str`; release via os.free(r.ptr, r.len: u64) when done. +// Hare returns `const str` into a static buffer; ww allocates per +// call because the wwstage cgen doesn't currently support mutating a +// module-level `*u8` (so a lazy-init shared buffer isn't expressible +// today). Graduate to the static-buffer shape once that lands. + +use os; // invalid — input wasn't a valid number in the requested format. -// Payload is the byte index of the first offending position. Mirrors -// Hare's strconv::invalid = !size (we use i32 instead of size). +// Payload is the byte index of the first offending position. +// Mirrors Hare's strconv::invalid = !size. export type invalid = !i32; -// overflow — input was valid but doesn't fit the target type. No -// payload (a single yes/no signal). Mirrors Hare's !void shape. +// overflow — input was valid but doesn't fit the target type. +// Mirrors Hare's strconv::overflow = !void. export type overflow = !void; -// u64tos — write `v` in decimal into `buf` and return the byte count. -// Hare name; the buffer-in shape is the sanctioned Plan 9 subset of -// Hare's `u64tos(u, base) const str`. Unsigned-only so callers don't -// have to think about wraparound when printing a u64 with the high -// bit set. -export fn u64tos(buf: []u8, v: u64) i32 = { - let tmp: [32]u8; +// error — any error from a strconv call. Mirrors Hare's strconv::error. +export type error = !(invalid | overflow); + +// base — numeric base for parsing/formatting. Plain i32 (not a named +// enum) because cross-module `strconv.base.DEC` chains miscompile in +// the cstage cgen — it emits a memory load through `base(SB)` rather +// than inlining the enum value. Hare names them as `strconv::base` +// enum values; we expose them as module-level `def`s so callers say +// `strconv.DEC` and the cgen inlines the immediate. +// +// HEX is HEX_UPPER; HEX_LOWER is a separate pseudo-base that produces +// lowercase a-f digits. +export def DEFAULT: i32 = 0; +export def BIN: i32 = 2; +export def OCT: i32 = 8; +export def DEC: i32 = 10; +export def HEX_UPPER: i32 = 16; +export def HEX: i32 = 16; +export def HEX_LOWER: i32 = 17; + +fn basenum(b: i32) i64 = { + if (b == BIN) { return 2; }; + if (b == OCT) { return 8; }; + if (b == HEX) { return 16; }; + if (b == HEX_UPPER) { return 16; }; + if (b == HEX_LOWER) { return 16; }; + return 10; // DEC and DEFAULT +}; + +fn basedigit(d: i64, b: i32) u8 = { + if (d < 10) { return (d + 48): u8; }; + let off: i64 = d - 10; + if (b == HEX_LOWER) { return (off + 97): u8; }; + return (off + 65): u8; +}; + +// u64tos — convert v to a base-b numeric string. Returns owned str; +// release via os.free(r.ptr, r.len: u64). Mirrors Hare's +// strconv::u64tos (Hare returns const str into a static buffer). +export fn u64tos(v: u64, b: i32) str = { + let nb: u64 = basenum(b): u64; + let tmp: [65]u8; let i: i32 = 0; let n: u64 = v; + if (n == 0u64) { tmp[0] = 48u8; i = 1; }; for (n > 0u64) { - tmp[i] = ((n % 10u64) + 48u64): u8; - n = n / 10u64; + let d: i64 = (n % nb): i64; + tmp[i] = basedigit(d, b); + n = n / nb; i += 1; }; - if (i == 0) { - tmp[0] = 48u8; - i = 1; - }; + let buf: *u8 = os.alloc(i: u64): *u8; let out: i32 = 0; for (i > 0) { i -= 1; buf[out] = tmp[i]; out += 1; }; - return out; + let r: str; + r.ptr = buf; + r.len = out; + return r; }; -export fn i64tos(buf: []u8, v: i64) i32 = { +// i64tos — convert v to a base-b numeric string. Returns owned str; +// release via os.free. Mirrors Hare's strconv::i64tos. +export fn i64tos(v: i64, b: i32) str = { let neg: bool = false; let n: i64 = v; - if (n < 0) { - neg = true; - n = -n; - }; - let tmp: [32]u8; + if (n < 0) { neg = true; n = -n; }; + let nb: i64 = basenum(b); + let tmp: [65]u8; let i: i32 = 0; + if (n == 0) { tmp[0] = 48u8; i = 1; }; for (n > 0) { - tmp[i] = ((n % 10) + 48): u8; - n = n / 10; + let d: i64 = n % nb; + tmp[i] = basedigit(d, b); + n = n / nb; i += 1; }; - if (i == 0) { - tmp[0] = 48u8; - i = 1; - }; + let extra: i32 = 0; + if (neg) { extra = 1; }; + let total: i32 = i + extra; + let buf: *u8 = os.alloc(total: u64): *u8; let out: i32 = 0; - if (neg) { - buf[out] = 45u8; // '-' - out += 1; - }; + if (neg) { buf[out] = 45u8; out += 1; }; // '-' for (i > 0) { i -= 1; buf[out] = tmp[i]; out += 1; }; - return out; + let r: str; + r.ptr = buf; + r.len = out; + return r; }; -// stoi64 — Hare-style fallible signed decimal parser. No locale, no -// whitespace, no underscores: a leading '-' is the only non-digit -// accepted, and only at position 0. -export fn stoi64(s: str) (i64 | invalid | overflow) = { +export fn i32tos(v: i32, b: i32) str = { return i64tos(v: i64, b); }; +export fn i16tos(v: i16, b: i32) str = { return i64tos(v: i64, b); }; +export fn i8tos(v: i8, b: i32) str = { return i64tos(v: i64, b); }; + +export fn u32tos(v: u32, b: i32) str = { return u64tos(v: u64, b); }; +export fn u16tos(v: u16, b: i32) str = { return u64tos(v: u64, b); }; +export fn u8tos(v: u8, b: i32) str = { return u64tos(v: u64, b); }; + +// digval — value of digit byte `c` under base `b`, or -1 if not a +// valid digit. Letters are accepted case-insensitively under HEX / +// HEX_UPPER; only lowercase under HEX_LOWER. +fn digval(c: u8, b: i32) i32 = { + if (c >= 48u8) { if (c <= 57u8) { return (c - 48u8): i32; }; }; + if (b == HEX_LOWER) { + if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; }; + return -1; + }; + if (c >= 65u8) { if (c <= 70u8) { return ((c - 65u8) + 10u8): i32; }; }; + if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; }; + return -1; +}; + +// stoi64 — parse signed base-b number. Mirrors Hare's strconv::stoi64. +// No locale, no whitespace, no underscores: optional leading '-' then +// digits. Returns invalid with the offending index or overflow on +// out-of-range. +export fn stoi64(s: str, b: i32) (i64 | invalid | overflow) = { if (s.len == 0) { return 0: invalid; }; let i: i32 = 0; let neg: bool = false; if (s[0] == 45u8) { neg = true; i = 1; }; if (i >= s.len) { return i: invalid; }; + let nb: i32 = basenum(b): i32; let v: i64 = 0; for (i < s.len) { let c: u8 = s[i]; - if (c < 48u8) { return i: invalid; }; - if (c > 57u8) { return i: invalid; }; - v = v * 10 + ((c: i64) - 48); + let d: i32 = digval(c, b); + if (d < 0) { return i: invalid; }; + if (d >= nb) { return i: invalid; }; + v = v * (nb: i64) + (d: i64); i += 1; }; if (neg) { v = -v; }; return v; }; -// stou64 — fallible unsigned decimal parser. No leading sign. -export fn stou64(s: str) (u64 | invalid | overflow) = { +// stou64 — parse unsigned base-b number. Mirrors Hare's strconv::stou64. +export fn stou64(s: str, b: i32) (u64 | invalid | overflow) = { if (s.len == 0) { return 0: invalid; }; + let nb: u64 = basenum(b): u64; let v: u64 = 0u64; let i: i32 = 0; for (i < s.len) { let c: u8 = s[i]; - if (c < 48u8) { return i: invalid; }; - if (c > 57u8) { return i: invalid; }; - v = v * 10u64 + ((c: u64) - 48u64); + let d: i32 = digval(c, b); + if (d < 0) { return i: invalid; }; + if ((d: u64) >= nb) { return i: invalid; }; + v = v * nb + (d: u64); i += 1; }; return v; }; -// f64tos — write `v` in decimal into `buf` and return the byte count. -// Hare name; this is the buffer-in Plan 9 subset of Hare's -// `f64tos(n) const str`. Today's surface: +export fn stoi32(s: str, b: i32) (i32 | invalid | overflow) = { + let r = stoi64(s, b); + match (r) { + case let v: i64 => { + if (v > 2147483647i64) { return overflow{}; }; + if (v < -2147483648i64) { return overflow{}; }; + return v: i32; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; // unreachable; appeases the path-cov checker +}; + +export fn stoi16(s: str, b: i32) (i16 | invalid | overflow) = { + let r = stoi64(s, b); + match (r) { + case let v: i64 => { + if (v > 32767i64) { return overflow{}; }; + if (v < -32768i64) { return overflow{}; }; + return v: i16; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +export fn stoi8(s: str, b: i32) (i8 | invalid | overflow) = { + let r = stoi64(s, b); + match (r) { + case let v: i64 => { + if (v > 127i64) { return overflow{}; }; + if (v < -128i64) { return overflow{}; }; + return v: i8; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +export fn stou32(s: str, b: i32) (u32 | invalid | overflow) = { + let r = stou64(s, b); + match (r) { + case let v: u64 => { + if (v > 4294967295u64) { return overflow{}; }; + return v: u32; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +export fn stou16(s: str, b: i32) (u16 | invalid | overflow) = { + let r = stou64(s, b); + match (r) { + case let v: u64 => { + if (v > 65535u64) { return overflow{}; }; + return v: u16; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +export fn stou8(s: str, b: i32) (u8 | invalid | overflow) = { + let r = stou64(s, b); + match (r) { + case let v: u64 => { + if (v > 255u64) { return overflow{}; }; + return v: u8; + }; + case let e: invalid => return e; + case let e: overflow => return e; + }; + return 0: invalid; +}; + +// f64tos — convert v to a decimal string. Returns owned str; release +// via os.free. Mirrors Hare's strconv::f64tos (current ww impl is +// fixed-point only, max 6 fractional digits, no NaN/Inf support — +// see graduate-to-Ryū note below). +// +// Surface: // // - finite values only. NaN/±Inf detection needs an f64→u64 bit // reinterpret cast that the cgen doesn't expose yet. @@ -349,23 +505,20 @@ export fn stou64(s: str) (u64 | invalid | overflow) = { // in scientific notation via Ryū; we will graduate when the // compiler grows the bit-reinterpret cast. // -// Round-trip is therefore lossy past 6 fractional digits; callers -// that need bit-exact recovery should not use this until the -// graduate-to-Ryū step lands. `f64tos(buf, 1.0)` writes "1" (no -// decimal point), `f64tos(buf, 1.5)` writes "1.5", `f64tos(buf, -// 0.1)` writes "0.1". +// Round-trip is therefore lossy past 6 fractional digits. // // No float literals in the body — 990's wwdump diff requires this // file's TK_FLOAT count to match between C and ww front-ends, and // the ww-side wwdump currently skips TK_FLOAT.fval while the C side // %g-formats it. Same trick lib/ww/lex/lex.ww's parsef64 uses: // build f64 constants via int-to-f64 casts. -export fn f64tos(buf: []u8, v: f64) i32 = { +export fn f64tos(v: f64) str = { + let tmp: [64]u8; let out: i32 = 0; let f: f64 = v; let zero: f64 = 0: f64; if (f < zero) { - buf[out] = 45u8; // '-' + tmp[out] = 45u8; // '-' out += 1; f = -f; }; @@ -375,8 +528,14 @@ export fn f64tos(buf: []u8, v: f64) i32 = { if (f >= cap) { let s: str = "huge"; let k: i32 = 0; - for (k < s.len) { buf[out] = s[k]; out += 1; k += 1; }; - return out; + for (k < s.len) { tmp[out] = s[k]; out += 1; k += 1; }; + let buf: *u8 = os.alloc(out: u64): *u8; + let q: i32 = 0; + for (q < out) { buf[q] = tmp[q]; q += 1; }; + let r: str; + r.ptr = buf; + r.len = out; + return r; }; let ip: i64 = f: i64; // Fractional part scaled to 6 decimal digits, with round-to- @@ -393,30 +552,42 @@ export fn f64tos(buf: []u8, v: f64) i32 = { ip += 1; fp = 0; }; - let itmp: [32]u8; - let in: i32 = i64tos(itmp[0:32], ip); + let intstr: str = i64tos(ip, DEC); let k: i32 = 0; - for (k < in) { buf[out] = itmp[k]; out += 1; k += 1; }; - if (fp == 0) { return out; }; - buf[out] = 46u8; // '.' - out += 1; - let ftmp: [16]u8; - let m: i32 = u64tos(ftmp[0:16], fp: u64); - // Pad fractional to 6 digits with leading zeros (e.g. 0.05 → - // fp=50000, m=5, pad one '0' before "50000"). - let z: i32 = 6 - m; - for (z > 0) { buf[out] = 48u8; out += 1; z -= 1; }; - k = 0; - for (k < m) { buf[out] = ftmp[k]; out += 1; k += 1; }; - // Trim trailing zeros in the fractional part (we know fp != 0, - // so the loop stops before erasing the dot). - for (out > 0) { - if (buf[out - 1] != 48u8) { break; }; - out -= 1; + for (k < intstr.len) { tmp[out] = intstr.ptr[k]; out += 1; k += 1; }; + os.free(intstr.ptr: *void, intstr.len: u64); + if (fp != 0) { + tmp[out] = 46u8; // '.' + out += 1; + let fracstr: str = u64tos(fp: u64, DEC); + // Pad fractional to 6 digits with leading zeros (e.g. 0.05 → + // fp=50000, fracstr="50000", pad one '0' before). + let z: i32 = 6 - fracstr.len; + for (z > 0) { tmp[out] = 48u8; out += 1; z -= 1; }; + k = 0; + for (k < fracstr.len) { tmp[out] = fracstr.ptr[k]; out += 1; k += 1; }; + os.free(fracstr.ptr: *void, fracstr.len: u64); + // Trim trailing zeros in the fractional part. + for (out > 0) { + if (tmp[out - 1] != 48u8) { break; }; + out -= 1; + }; }; - return out; + let buf: *u8 = os.alloc(out: u64): *u8; + let q: i32 = 0; + for (q < out) { buf[q] = tmp[q]; q += 1; }; + let r: str; + r.ptr = buf; + r.len = out; + 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. + // MODULE: ascii // ascii — rune-class predicates and case folding for the ASCII range. // Matches Hare's ascii::isdigit family (rune-taking signature). Runes @@ -617,11 +788,10 @@ export fn main() i32 = { if (dn != 3) { return 10; }; // Probe 5 — strconv round-trip via the real stdlib. - let outbuf: [32]u8; - let nb: i32 = strconv.i64tos(outbuf[0:32], 4242i64); - if (nb != 4) { return 11; }; - if (outbuf[0] != 52u8) { return 12; }; // '4' - if (outbuf[3] != 50u8) { return 13; }; // '2' + let s: str = strconv.i64tos(4242i64, strconv.DEC); + if (s.len != 4) { return 11; }; + if (s.ptr[0] != 52u8) { return 12; }; // '4' + if (s.ptr[3] != 50u8) { return 13; }; // '2' // Probe 6 — ascii classifications (rune-taking, Hare-shaped). if (!ascii.isdigit(53)) { return 14; }; // '5' diff --git a/selfhost/test/smoke.ww b/selfhost/test/smoke.ww index 88dcf31c..a05d0587 100644 --- a/selfhost/test/smoke.ww +++ b/selfhost/test/smoke.ww @@ -130,11 +130,10 @@ export fn main() i32 = { if (dn != 3) { return 10; }; // Probe 5 — strconv round-trip via the real stdlib. - let outbuf: [32]u8; - let nb: i32 = strconv.i64tos(outbuf[0:32], 4242i64); - if (nb != 4) { return 11; }; - if (outbuf[0] != 52u8) { return 12; }; // '4' - if (outbuf[3] != 50u8) { return 13; }; // '2' + let s: str = strconv.i64tos(4242i64, strconv.DEC); + if (s.len != 4) { return 11; }; + if (s.ptr[0] != 52u8) { return 12; }; // '4' + if (s.ptr[3] != 50u8) { return 13; }; // '2' // Probe 6 — ascii classifications (rune-taking, Hare-shaped). if (!ascii.isdigit(53)) { return 14; }; // '5' diff --git a/test/wcc/700_e2e.c b/test/wcc/700_e2e.c index bec27bc2..2f42b694 100644 --- a/test/wcc/700_e2e.c +++ b/test/wcc/700_e2e.c @@ -139,17 +139,15 @@ static const struct row rows[] = { " for (i < 3) { s += buf[i]: i32; i += 1; };\n" " return s;\n" "};", 198 }, - /* full stdlib stack: use os + strconv, slice-arg call, write + /* full stdlib stack: use os + strconv, str return, write * the formatted number to stdout. exit code = number length. */ { "use os;\n" "use strconv;\n" "fn main() i32 = {\n" - " let buf: [32]u8;\n" - " let s: []u8 = buf[0:32];\n" - " let n: i32 = strconv.i64tos(s, 12345);\n" - " os.write(1, buf.ptr, n: u64);\n" + " let s: str = strconv.i64tos(12345, strconv.DEC);\n" + " os.write(1, s.ptr, s.len: u64);\n" " os.write(1, \"\\n\".ptr, 1u64);\n" - " return n;\n" + " return s.len;\n" "};", 5 }, /* alloc + free via mmap-backed runtime — write through allocated * memory and free it. exit = 0 if the allocation succeeded. */ @@ -195,12 +193,14 @@ static const struct row rows[] = { "fn main() i32 = {\n" " return classify(2) + classify(10) + classify(99);\n" "};", 159 }, /* 10+99+50=159 */ - /* fmt module: stdlib formatter for ints + strings */ + /* fmt module: stdlib formatter for strings; ints compose + * via strconv.i64tos. */ { "use fmt;\n" + "use strconv;\n" "fn main() i32 = {\n" " fmt.println(\"ww\");\n" - " fmt.printlnint(42);\n" - " fmt.printlnint(-7);\n" + " fmt.println(strconv.i64tos(42, strconv.DEC));\n" + " fmt.println(strconv.i64tos(-7, strconv.DEC));\n" " return 0;\n" "};", 0 }, /* struct with i32 fields: MOVL/MOVSXD avoids clobbering neighbors */ @@ -866,9 +866,9 @@ static const struct row rows[] = { { "use strconv;\n" "type r_t = (i64 | strconv.invalid | strconv.overflow);\n" "fn main() i32 = {\n" - " let r1: r_t = strconv.stoi64(\"42\");\n" - " let r2: r_t = strconv.stoi64(\"-7\");\n" - " let r3: r_t = strconv.stoi64(\"abc\");\n" + " let r1: r_t = strconv.stoi64(\"42\", strconv.DEC);\n" + " let r2: r_t = strconv.stoi64(\"-7\", strconv.DEC);\n" + " let r3: r_t = strconv.stoi64(\"abc\", strconv.DEC);\n" " let acc: i32 = 0;\n" " match (r1) {\n" " case let v: i64 => acc += v: i32;\n" @@ -892,8 +892,8 @@ static const struct row rows[] = { { "use strconv;\n" "type r_t = (u64 | strconv.invalid | strconv.overflow);\n" "fn main() i32 = {\n" - " let r1: r_t = strconv.stou64(\"123\");\n" - " let r2: r_t = strconv.stou64(\"-1\");\n" + " let r1: r_t = strconv.stou64(\"123\", strconv.DEC);\n" + " let r2: r_t = strconv.stou64(\"-1\", strconv.DEC);\n" " let acc: i32 = 0;\n" " match (r1) {\n" " case let v: u64 => acc += v: i32;\n" @@ -907,7 +907,7 @@ static const struct row rows[] = { " };\n" " return acc;\n" "};", 123 }, /* 123 + 0 (invalid at index 0 in \"-1\") */ - /* strings.byteindex and strings.index: now (i32 | void). */ + /* strings.indexbyte and strings.index: now (i32 | void). */ { "use strings;\n" "fn pick(r: (i32 | void), miss: i32) i32 = {\n" " match (r) {\n" @@ -918,8 +918,8 @@ static const struct row rows[] = { "};\n" "fn main() i32 = {\n" " let s: str = \"hello, world\";\n" - " let i1: i32 = pick(strings.byteindex(s, 44u8), -1);\n" - " let i2: i32 = pick(strings.byteindex(s, 122u8), -1);\n" + " let i1: i32 = pick(strings.indexbyte(s, 44u8), -1);\n" + " let i2: i32 = pick(strings.indexbyte(s, 122u8), -1);\n" " let i3: i32 = pick(strings.index(s, \"world\"), -1);\n" " let i4: i32 = pick(strings.index(s, \"nope\"), -1);\n" " return i1 + i2 + i3 + i4;\n"