diff --git a/lib/strconv/strconv.ww b/lib/strconv/strconv.ww index 225ff029..c2bc48cf 100644 --- a/lib/strconv/strconv.ww +++ b/lib/strconv/strconv.ww @@ -9,6 +9,7 @@ package strconv; import ascii; +import bytes; import os; import strings; @@ -49,83 +50,122 @@ fn basenum(b: base) i64 = { return 10; // DEC and DEFAULT }; -fn basedigit(d: i64, b: base) u8 = { - if (d < 10) { return (d + 48): u8; }; - let off: i64 = d - 10; - if (b == base.HEX_LOWER) { return (off + 97): u8; }; - return (off + 65): u8; -}; +// lut_upper / lut_lower — digit→glyph tables. Verbatim port of the +// `static const lut_upper`/`lut_lower` rune arrays in +// ref/hare/strconv/utos.ha:14-20. Module-level `let` (ww has no module +// `const`; never written) following the ftos_data.ww table convention. +// Declared [16]rune (faithful to Hare's inferred rune element type); +// u64tos casts the indexed glyph to u8 at the store, as Hare does +// (utos.ha:35). +let lut_upper: [16]rune = [ + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', +]; +let lut_lower: [16]rune = [ + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', +]; -// u64tos — convert v to a base-b numeric string. Returns a view into -// `u64tos_buf` which is overwritten on the next call. Matches Hare's -// strconv::u64tos. -let u64tos_buf: [65]u8; +// u64tos_buf — overwritten on each u64tos call (Hare's `static let buf`, +// utos.ha:12). 64 = the widest u64 rendering (binary). `[0...]` kept for +// fidelity; the initial value is irrelevant (only the freshly-written +// prefix is ever read) but the fill form is exercised (probed: emits +// byte-identically cross-stage). +let u64tos_buf: [64]u8 = [0...]; // 64 binary digits -export fn u64tos(v: u64, b: base) str = { +// u64tos — convert u to a base-b numeric string. Returns a view into +// `u64tos_buf`, overwritten on the next call; copy via strings.dup to +// outlive it. Verbatim port of ref/hare/strconv/utos.ha:10-42. +// Divergences: +// - Hare's `static assert(types::U64_MAX == ...)` dropped (ww has no +// static assert; the bound lives in lib/types/types.ww:20). +// - Hare selects the LUT via an if-EXPRESSION and reassigns `b` to +// HEX_UPPER / DEC inline (utos.ha:21-26). ww has no if-expression +// (standing divergence, stof.ww:31), so the glyph case is a `lower` +// bool branch and the divisor is basenum(b) — the file's existing +// normalize helper, which maps DEFAULT→10 and HEX_LOWER→16 exactly +// as Hare's reassignment does. +// - Hare's `types::string { data = &buf, ... }` + `*(&s: *str)` +// reinterpret (utos.ha:28,41) → strings.frombytes (CLAUDE.md rule 9 +// carve-out: ww's lib/types has no `string` struct; frombytes is the +// honest ww idiom, cf. ascii/strings). +export fn u64tos(u: u64, b: base) 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; }; + let lower: bool = (b == base.HEX_LOWER); + + let length: i32 = 0; + let n: u64 = u; + if (n == 0u64) { + u64tos_buf[length] = lut_upper[0]: u8; + length += 1; + }; for (n > 0u64) { let d: i64 = (n % nb): i64; - tmp[i] = basedigit(d, b); + if (lower) { u64tos_buf[length] = lut_lower[d]: u8; } + else { u64tos_buf[length] = lut_upper[d]: u8; }; + length += 1; n = n / nb; - i += 1; }; - let out: i32 = 0; - for (i > 0) { - i -= 1; - u64tos_buf[out] = tmp[i]; - out += 1; - }; - let r: str; - r.ptr = &u64tos_buf[0]; - r.len = out; - return r; + + bytes.reverse(u64tos_buf[0:length]); + return strings.frombytes(u64tos_buf[0:length]); }; -// i64tos — convert v to a base-b numeric string. Returns a view into -// `i64tos_buf` which is overwritten on the next call. Independent -// buffer from u64tos so i64tos's own call to u64tos doesn't clobber -// the in-flight result. Matches Hare's strconv::i64tos. -let i64tos_buf: [66]u8; +// i64tos_buf — independent from u64tos_buf so i64tos's own u64tos call +// (the magnitude) doesn't clobber the in-flight result. 65 = 64 digits +// plus the leading '-'. Hare's `static let buf: [65]u8` (itos.ha:18). +let i64tos_buf: [65]u8 = [0...]; // 64 binary digits plus '-' -export fn i64tos(v: i64, b: base) str = { - let neg: bool = false; - let n: i64 = v; - 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) { - let d: i64 = n % nb; - tmp[i] = basedigit(d, b); - n = n / nb; - i += 1; +// i64tos — convert i to a base-b numeric string. Returns a view into +// `i64tos_buf`. Verbatim port of ref/hare/strconv/itos.ha:10-32. +// Divergences: +// - `static assert` dropped (see u64tos); the DEFAULT→DEC normalize +// rides basenum(b) inside the u64tos call (itos.ha:12-14). +// - Hare's slice-assign `buf[1..len(u)+1] = u[..]` + the bounds assert +// (itos.ha:26-28) → explicit copy loop (existing-file convention; +// the [65] buffer holds the 64-digit max + sign exactly, so the +// bound is structural). +// - `*(&s: *str)` → strings.frombytes (see u64tos). +export fn i64tos(i: i64, b: base) str = { + if (i >= 0) { return u64tos(i: u64, b); }; + + i64tos_buf[0] = 45u8; // '-' + // `(-i): u64`: for I64_MIN, -i wraps (two's complement) back to the + // I64_MIN bit pattern; reinterpreting to u64 yields the true + // magnitude 9223372036854775808. ref/hare/strconv/itos.ha:25. Probed + // on both stages (NEG then i64→u64 reinterpret byte-identical); + // closes the i64tos-on-I64_MIN bug noted at cgen.ww #144. + let u: str = u64tos((-i): u64, b); + let k: i32 = 0; + for (k < u.len) { + i64tos_buf[k + 1] = u[k]; + k += 1; }; - let out: i32 = 0; - if (neg) { i64tos_buf[out] = 45u8; out += 1; }; // '-' - for (i > 0) { - i -= 1; - i64tos_buf[out] = tmp[i]; - out += 1; - }; - let r: str; - r.ptr = &i64tos_buf[0]; - r.len = out; - return r; + return strings.frombytes(i64tos_buf[0 : u.len + 1]); }; export fn i32tos(v: i32, b: base) str = { return i64tos(v: i64, b); }; export fn i16tos(v: i16, b: base) str = { return i64tos(v: i64, b); }; export fn i8tos(v: i8, b: base) str = { return i64tos(v: i64, b); }; +// itos — int (ww machine-word, 8B → i64-width) → string. +// ref/hare/strconv/itos.ha:52. +export fn itos(i: int, b: base) str = { return i64tos(i: i64, b); }; + export fn u32tos(v: u32, b: base) str = { return u64tos(v: u64, b); }; export fn u16tos(v: u16, b: base) str = { return u64tos(v: u64, b); }; export fn u8tos(v: u8, b: base) str = { return u64tos(v: u64, b); }; +// utos — uint (8B → u64-width) → string. ref/hare/strconv/utos.ha:62. +export fn utos(u: uint, b: base) str = { return u64tos(u: u64, b); }; + +// ztos — size (8B → u64-width) → string. ref/hare/strconv/utos.ha:67. +export fn ztos(u: size, b: base) str = { return u64tos(u: u64, b); }; + +// uptrtos — uintptr → string. ref/hare/strconv/utos.ha:72 (param `uptr` +// cast `uptr: u64`). +export fn uptrtos(uptr: uintptr, b: base) str = { return u64tos(uptr: u64, b); }; + // rune_to_integer — digit value of r (0-9 → 0-9; a-z/A-Z → 10-35), // or void if r is not alphanumeric. Verbatim port of // ref/hare/strconv/stou.ha:8-15 (ww yields the void variant with a diff --git a/lib/strconv/test/inttest.ww b/lib/strconv/test/inttest.ww index a85c1942..7bbc0133 100644 --- a/lib/strconv/test/inttest.ww +++ b/lib/strconv/test/inttest.ww @@ -233,11 +233,98 @@ fn ck_uint_ovf(id: i32, s: str, b: base) void = { ck_uint(62, "110101", base.BIN, 53u64: uint); // 0b110101 }; +// ---- format side: u64tos / i64tos / machine-word wrappers ------------ +// Verbatim ports of ref/hare/strconv/utos.ha:74-103 (utos/utos_bases) and +// itos.ha:54-87 (itos/itos_bases) — Hare's format tests are flat assert +// sequences too. Radix-literal inputs are written in DECIMAL (ww value +// side has no 0b/0o/0x form): 0b11010=26, 0o1234567=342391, +// 0x123456789ABCDEF=81985529216486895; the original radix is noted inline. + +fn streq(a: str, b: str) bool = { + if (a.len != b.len) { return false; }; + let k: i32 = 0; + for (k < a.len) { + if (a[k] != b[k]) { return false; }; + k += 1; + }; + return true; +}; + +fn cks(id: i32, got: str, want: str) void = { + signalled = id; + if (!streq(got, want)) { fail(); }; +}; + +// ref/hare/strconv/utos.ha:74-83. +@test fn test_u64tos_bases() void = { + cks(70, u64tos(26u64, base.BIN), "11010"); // 0b11010 + cks(71, u64tos(342391u64, base.OCT), "1234567"); // 0o1234567 + cks(72, u64tos(123456789u64, base.DEC), "123456789"); + cks(73, u64tos(81985529216486895u64, base.HEX), "123456789ABCDEF"); // 0x123456789ABCDEF + cks(74, u64tos(81985529216486895u64, base.HEX_UPPER), "123456789ABCDEF"); + cks(75, u64tos(81985529216486895u64, base.HEX_LOWER), "123456789abcdef"); + cks(76, u64tos(18446744073709551615u64, base.BIN), // U64_MAX + "1111111111111111111111111111111111111111111111111111111111111111"); +}; + +// ref/hare/strconv/utos.ha:85-103. +@test fn test_u64tos() void = { + cks(77, u64tos(1234u64, base.DEC), "1234"); + cks(78, u64tos(4321u64, base.DEC), "4321"); + cks(79, u64tos(0u64, base.DEC), "0"); // U64_MIN + cks(80, u64tos(18446744073709551615u64, base.DEC), "18446744073709551615"); // U64_MAX +}; + +// ref/hare/strconv/itos.ha:54-63. +@test fn test_i64tos_bases() void = { + cks(81, i64tos(26i64, base.BIN), "11010"); + cks(82, i64tos(342391i64, base.OCT), "1234567"); + cks(83, i64tos(123456789i64, base.DEC), "123456789"); + cks(84, i64tos(81985529216486895i64, base.HEX), "123456789ABCDEF"); + cks(85, i64tos(81985529216486895i64, base.HEX_UPPER), "123456789ABCDEF"); + cks(86, i64tos(81985529216486895i64, base.HEX_LOWER), "123456789abcdef"); + // I64_MIN binary = '1' + 63 zeros, prefixed '-'. Spelled -I64_MAX-1 + // (proj #245: wwstage mis-lexes the direct 2^63 / types.I64_MIN literal). + cks(87, i64tos(-9223372036854775807i64 - 1i64, base.BIN), + "-1000000000000000000000000000000000000000000000000000000000000000"); +}; + +// ref/hare/strconv/itos.ha:65-87. +@test fn test_i64tos() void = { + cks(88, i64tos(1234i64, base.DEC), "1234"); + cks(89, i64tos(4321i64, base.DEC), "4321"); + cks(90, i64tos(-1337i64, base.DEC), "-1337"); + cks(91, i64tos(0i64, base.DEC), "0"); + cks(92, i64tos(9223372036854775807i64, base.DEC), "9223372036854775807"); // I64_MAX + // drew probe-1: NEG-of-I64_MIN + i64→u64 reinterpret preserves the + // two's-complement bit pattern. I64_MIN spelled -I64_MAX-1 (proj #245). + cks(93, i64tos(-9223372036854775807i64 - 1i64, base.DEC), + "-9223372036854775808"); +}; + +// machine-word wrappers + width wrappers. ww int/uint/size/uintptr are +// all 8B, so the full i64/u64 range renders untruncated. ww-authored +// (Hare's wrappers are trivial aliases): itos.ha:37-52, utos.ha:44-72. +@test fn test_word_wrappers() void = { + cks(94, itos(-1337, base.DEC), "-1337"); + cks(95, itos(9223372036854775807i64: int, base.DEC), "9223372036854775807"); + cks(96, utos(18446744073709551615u64: uint, base.DEC), "18446744073709551615"); + cks(97, ztos(255u64: size, base.HEX), "FF"); + cks(98, uptrtos(4096u64: uintptr, base.DEC), "4096"); + cks(99, i32tos(-2147483648i32, base.DEC), "-2147483648"); + cks(100, u8tos(255u8, base.HEX_LOWER), "ff"); +}; + export fn main() i32 = { test_stoi64(); test_stoi64_bases(); test_stou64(); test_stou64_bases(); test_stoi_stou_stoz(); + test_u64tos_bases(); + test_u64tos(); + test_i64tos_bases(); + test_i64tos(); + test_word_wrappers(); return 0; }; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 1d93ef55..4d69f4c7 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -5969,6 +5969,7 @@ let powers_of_ten: [596][2]u64 = [ package strconv; import ascii; +import bytes; import os; import strings; @@ -6009,83 +6010,122 @@ fn basenum(b: base) i64 = { return 10; // DEC and DEFAULT }; -fn basedigit(d: i64, b: base) u8 = { - if (d < 10) { return (d + 48): u8; }; - let off: i64 = d - 10; - if (b == base.HEX_LOWER) { return (off + 97): u8; }; - return (off + 65): u8; -}; +// lut_upper / lut_lower — digit→glyph tables. Verbatim port of the +// `static const lut_upper`/`lut_lower` rune arrays in +// ref/hare/strconv/utos.ha:14-20. Module-level `let` (ww has no module +// `const`; never written) following the ftos_data.ww table convention. +// Declared [16]rune (faithful to Hare's inferred rune element type); +// u64tos casts the indexed glyph to u8 at the store, as Hare does +// (utos.ha:35). +let lut_upper: [16]rune = [ + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', +]; +let lut_lower: [16]rune = [ + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', +]; -// u64tos — convert v to a base-b numeric string. Returns a view into -// `u64tos_buf` which is overwritten on the next call. Matches Hare's -// strconv::u64tos. -let u64tos_buf: [65]u8; +// u64tos_buf — overwritten on each u64tos call (Hare's `static let buf`, +// utos.ha:12). 64 = the widest u64 rendering (binary). `[0...]` kept for +// fidelity; the initial value is irrelevant (only the freshly-written +// prefix is ever read) but the fill form is exercised (probed: emits +// byte-identically cross-stage). +let u64tos_buf: [64]u8 = [0...]; // 64 binary digits -export fn u64tos(v: u64, b: base) str = { +// u64tos — convert u to a base-b numeric string. Returns a view into +// `u64tos_buf`, overwritten on the next call; copy via strings.dup to +// outlive it. Verbatim port of ref/hare/strconv/utos.ha:10-42. +// Divergences: +// - Hare's `static assert(types::U64_MAX == ...)` dropped (ww has no +// static assert; the bound lives in lib/types/types.ww:20). +// - Hare selects the LUT via an if-EXPRESSION and reassigns `b` to +// HEX_UPPER / DEC inline (utos.ha:21-26). ww has no if-expression +// (standing divergence, stof.ww:31), so the glyph case is a `lower` +// bool branch and the divisor is basenum(b) — the file's existing +// normalize helper, which maps DEFAULT→10 and HEX_LOWER→16 exactly +// as Hare's reassignment does. +// - Hare's `types::string { data = &buf, ... }` + `*(&s: *str)` +// reinterpret (utos.ha:28,41) → strings.frombytes (CLAUDE.md rule 9 +// carve-out: ww's lib/types has no `string` struct; frombytes is the +// honest ww idiom, cf. ascii/strings). +export fn u64tos(u: u64, b: base) 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; }; + let lower: bool = (b == base.HEX_LOWER); + + let length: i32 = 0; + let n: u64 = u; + if (n == 0u64) { + u64tos_buf[length] = lut_upper[0]: u8; + length += 1; + }; for (n > 0u64) { let d: i64 = (n % nb): i64; - tmp[i] = basedigit(d, b); + if (lower) { u64tos_buf[length] = lut_lower[d]: u8; } + else { u64tos_buf[length] = lut_upper[d]: u8; }; + length += 1; n = n / nb; - i += 1; }; - let out: i32 = 0; - for (i > 0) { - i -= 1; - u64tos_buf[out] = tmp[i]; - out += 1; - }; - let r: str; - r.ptr = &u64tos_buf[0]; - r.len = out; - return r; + + bytes.reverse(u64tos_buf[0:length]); + return strings.frombytes(u64tos_buf[0:length]); }; -// i64tos — convert v to a base-b numeric string. Returns a view into -// `i64tos_buf` which is overwritten on the next call. Independent -// buffer from u64tos so i64tos's own call to u64tos doesn't clobber -// the in-flight result. Matches Hare's strconv::i64tos. -let i64tos_buf: [66]u8; +// i64tos_buf — independent from u64tos_buf so i64tos's own u64tos call +// (the magnitude) doesn't clobber the in-flight result. 65 = 64 digits +// plus the leading '-'. Hare's `static let buf: [65]u8` (itos.ha:18). +let i64tos_buf: [65]u8 = [0...]; // 64 binary digits plus '-' -export fn i64tos(v: i64, b: base) str = { - let neg: bool = false; - let n: i64 = v; - 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) { - let d: i64 = n % nb; - tmp[i] = basedigit(d, b); - n = n / nb; - i += 1; +// i64tos — convert i to a base-b numeric string. Returns a view into +// `i64tos_buf`. Verbatim port of ref/hare/strconv/itos.ha:10-32. +// Divergences: +// - `static assert` dropped (see u64tos); the DEFAULT→DEC normalize +// rides basenum(b) inside the u64tos call (itos.ha:12-14). +// - Hare's slice-assign `buf[1..len(u)+1] = u[..]` + the bounds assert +// (itos.ha:26-28) → explicit copy loop (existing-file convention; +// the [65] buffer holds the 64-digit max + sign exactly, so the +// bound is structural). +// - `*(&s: *str)` → strings.frombytes (see u64tos). +export fn i64tos(i: i64, b: base) str = { + if (i >= 0) { return u64tos(i: u64, b); }; + + i64tos_buf[0] = 45u8; // '-' + // `(-i): u64`: for I64_MIN, -i wraps (two's complement) back to the + // I64_MIN bit pattern; reinterpreting to u64 yields the true + // magnitude 9223372036854775808. ref/hare/strconv/itos.ha:25. Probed + // on both stages (NEG then i64→u64 reinterpret byte-identical); + // closes the i64tos-on-I64_MIN bug noted at cgen.ww #144. + let u: str = u64tos((-i): u64, b); + let k: i32 = 0; + for (k < u.len) { + i64tos_buf[k + 1] = u[k]; + k += 1; }; - let out: i32 = 0; - if (neg) { i64tos_buf[out] = 45u8; out += 1; }; // '-' - for (i > 0) { - i -= 1; - i64tos_buf[out] = tmp[i]; - out += 1; - }; - let r: str; - r.ptr = &i64tos_buf[0]; - r.len = out; - return r; + return strings.frombytes(i64tos_buf[0 : u.len + 1]); }; export fn i32tos(v: i32, b: base) str = { return i64tos(v: i64, b); }; export fn i16tos(v: i16, b: base) str = { return i64tos(v: i64, b); }; export fn i8tos(v: i8, b: base) str = { return i64tos(v: i64, b); }; +// itos — int (ww machine-word, 8B → i64-width) → string. +// ref/hare/strconv/itos.ha:52. +export fn itos(i: int, b: base) str = { return i64tos(i: i64, b); }; + export fn u32tos(v: u32, b: base) str = { return u64tos(v: u64, b); }; export fn u16tos(v: u16, b: base) str = { return u64tos(v: u64, b); }; export fn u8tos(v: u8, b: base) str = { return u64tos(v: u64, b); }; +// utos — uint (8B → u64-width) → string. ref/hare/strconv/utos.ha:62. +export fn utos(u: uint, b: base) str = { return u64tos(u: u64, b); }; + +// ztos — size (8B → u64-width) → string. ref/hare/strconv/utos.ha:67. +export fn ztos(u: size, b: base) str = { return u64tos(u: u64, b); }; + +// uptrtos — uintptr → string. ref/hare/strconv/utos.ha:72 (param `uptr` +// cast `uptr: u64`). +export fn uptrtos(uptr: uintptr, b: base) str = { return u64tos(uptr: u64, b); }; + // rune_to_integer — digit value of r (0-9 → 0-9; a-z/A-Z → 10-35), // or void if r is not alphanumeric. Verbatim port of // ref/hare/strconv/stou.ha:8-15 (ww yields the void variant with a diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 05a7965e..b871160b 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -5969,6 +5969,7 @@ let powers_of_ten: [596][2]u64 = [ package strconv; import ascii; +import bytes; import os; import strings; @@ -6009,83 +6010,122 @@ fn basenum(b: base) i64 = { return 10; // DEC and DEFAULT }; -fn basedigit(d: i64, b: base) u8 = { - if (d < 10) { return (d + 48): u8; }; - let off: i64 = d - 10; - if (b == base.HEX_LOWER) { return (off + 97): u8; }; - return (off + 65): u8; -}; +// lut_upper / lut_lower — digit→glyph tables. Verbatim port of the +// `static const lut_upper`/`lut_lower` rune arrays in +// ref/hare/strconv/utos.ha:14-20. Module-level `let` (ww has no module +// `const`; never written) following the ftos_data.ww table convention. +// Declared [16]rune (faithful to Hare's inferred rune element type); +// u64tos casts the indexed glyph to u8 at the store, as Hare does +// (utos.ha:35). +let lut_upper: [16]rune = [ + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', +]; +let lut_lower: [16]rune = [ + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', +]; -// u64tos — convert v to a base-b numeric string. Returns a view into -// `u64tos_buf` which is overwritten on the next call. Matches Hare's -// strconv::u64tos. -let u64tos_buf: [65]u8; +// u64tos_buf — overwritten on each u64tos call (Hare's `static let buf`, +// utos.ha:12). 64 = the widest u64 rendering (binary). `[0...]` kept for +// fidelity; the initial value is irrelevant (only the freshly-written +// prefix is ever read) but the fill form is exercised (probed: emits +// byte-identically cross-stage). +let u64tos_buf: [64]u8 = [0...]; // 64 binary digits -export fn u64tos(v: u64, b: base) str = { +// u64tos — convert u to a base-b numeric string. Returns a view into +// `u64tos_buf`, overwritten on the next call; copy via strings.dup to +// outlive it. Verbatim port of ref/hare/strconv/utos.ha:10-42. +// Divergences: +// - Hare's `static assert(types::U64_MAX == ...)` dropped (ww has no +// static assert; the bound lives in lib/types/types.ww:20). +// - Hare selects the LUT via an if-EXPRESSION and reassigns `b` to +// HEX_UPPER / DEC inline (utos.ha:21-26). ww has no if-expression +// (standing divergence, stof.ww:31), so the glyph case is a `lower` +// bool branch and the divisor is basenum(b) — the file's existing +// normalize helper, which maps DEFAULT→10 and HEX_LOWER→16 exactly +// as Hare's reassignment does. +// - Hare's `types::string { data = &buf, ... }` + `*(&s: *str)` +// reinterpret (utos.ha:28,41) → strings.frombytes (CLAUDE.md rule 9 +// carve-out: ww's lib/types has no `string` struct; frombytes is the +// honest ww idiom, cf. ascii/strings). +export fn u64tos(u: u64, b: base) 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; }; + let lower: bool = (b == base.HEX_LOWER); + + let length: i32 = 0; + let n: u64 = u; + if (n == 0u64) { + u64tos_buf[length] = lut_upper[0]: u8; + length += 1; + }; for (n > 0u64) { let d: i64 = (n % nb): i64; - tmp[i] = basedigit(d, b); + if (lower) { u64tos_buf[length] = lut_lower[d]: u8; } + else { u64tos_buf[length] = lut_upper[d]: u8; }; + length += 1; n = n / nb; - i += 1; }; - let out: i32 = 0; - for (i > 0) { - i -= 1; - u64tos_buf[out] = tmp[i]; - out += 1; - }; - let r: str; - r.ptr = &u64tos_buf[0]; - r.len = out; - return r; + + bytes.reverse(u64tos_buf[0:length]); + return strings.frombytes(u64tos_buf[0:length]); }; -// i64tos — convert v to a base-b numeric string. Returns a view into -// `i64tos_buf` which is overwritten on the next call. Independent -// buffer from u64tos so i64tos's own call to u64tos doesn't clobber -// the in-flight result. Matches Hare's strconv::i64tos. -let i64tos_buf: [66]u8; +// i64tos_buf — independent from u64tos_buf so i64tos's own u64tos call +// (the magnitude) doesn't clobber the in-flight result. 65 = 64 digits +// plus the leading '-'. Hare's `static let buf: [65]u8` (itos.ha:18). +let i64tos_buf: [65]u8 = [0...]; // 64 binary digits plus '-' -export fn i64tos(v: i64, b: base) str = { - let neg: bool = false; - let n: i64 = v; - 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) { - let d: i64 = n % nb; - tmp[i] = basedigit(d, b); - n = n / nb; - i += 1; +// i64tos — convert i to a base-b numeric string. Returns a view into +// `i64tos_buf`. Verbatim port of ref/hare/strconv/itos.ha:10-32. +// Divergences: +// - `static assert` dropped (see u64tos); the DEFAULT→DEC normalize +// rides basenum(b) inside the u64tos call (itos.ha:12-14). +// - Hare's slice-assign `buf[1..len(u)+1] = u[..]` + the bounds assert +// (itos.ha:26-28) → explicit copy loop (existing-file convention; +// the [65] buffer holds the 64-digit max + sign exactly, so the +// bound is structural). +// - `*(&s: *str)` → strings.frombytes (see u64tos). +export fn i64tos(i: i64, b: base) str = { + if (i >= 0) { return u64tos(i: u64, b); }; + + i64tos_buf[0] = 45u8; // '-' + // `(-i): u64`: for I64_MIN, -i wraps (two's complement) back to the + // I64_MIN bit pattern; reinterpreting to u64 yields the true + // magnitude 9223372036854775808. ref/hare/strconv/itos.ha:25. Probed + // on both stages (NEG then i64→u64 reinterpret byte-identical); + // closes the i64tos-on-I64_MIN bug noted at cgen.ww #144. + let u: str = u64tos((-i): u64, b); + let k: i32 = 0; + for (k < u.len) { + i64tos_buf[k + 1] = u[k]; + k += 1; }; - let out: i32 = 0; - if (neg) { i64tos_buf[out] = 45u8; out += 1; }; // '-' - for (i > 0) { - i -= 1; - i64tos_buf[out] = tmp[i]; - out += 1; - }; - let r: str; - r.ptr = &i64tos_buf[0]; - r.len = out; - return r; + return strings.frombytes(i64tos_buf[0 : u.len + 1]); }; export fn i32tos(v: i32, b: base) str = { return i64tos(v: i64, b); }; export fn i16tos(v: i16, b: base) str = { return i64tos(v: i64, b); }; export fn i8tos(v: i8, b: base) str = { return i64tos(v: i64, b); }; +// itos — int (ww machine-word, 8B → i64-width) → string. +// ref/hare/strconv/itos.ha:52. +export fn itos(i: int, b: base) str = { return i64tos(i: i64, b); }; + export fn u32tos(v: u32, b: base) str = { return u64tos(v: u64, b); }; export fn u16tos(v: u16, b: base) str = { return u64tos(v: u64, b); }; export fn u8tos(v: u8, b: base) str = { return u64tos(v: u64, b); }; +// utos — uint (8B → u64-width) → string. ref/hare/strconv/utos.ha:62. +export fn utos(u: uint, b: base) str = { return u64tos(u: u64, b); }; + +// ztos — size (8B → u64-width) → string. ref/hare/strconv/utos.ha:67. +export fn ztos(u: size, b: base) str = { return u64tos(u: u64, b); }; + +// uptrtos — uintptr → string. ref/hare/strconv/utos.ha:72 (param `uptr` +// cast `uptr: u64`). +export fn uptrtos(uptr: uintptr, b: base) str = { return u64tos(uptr: u64, b); }; + // rune_to_integer — digit value of r (0-9 → 0-9; a-z/A-Z → 10-35), // or void if r is not alphanumeric. Verbatim port of // ref/hare/strconv/stou.ha:8-15 (ww yields the void variant with a diff --git a/selfhost/test/smoke.combined.ww b/selfhost/test/smoke.combined.ww index 09c01862..fc203adf 100644 --- a/selfhost/test/smoke.combined.ww +++ b/selfhost/test/smoke.combined.ww @@ -5969,6 +5969,7 @@ let powers_of_ten: [596][2]u64 = [ package strconv; import ascii; +import bytes; import os; import strings; @@ -6009,83 +6010,122 @@ fn basenum(b: base) i64 = { return 10; // DEC and DEFAULT }; -fn basedigit(d: i64, b: base) u8 = { - if (d < 10) { return (d + 48): u8; }; - let off: i64 = d - 10; - if (b == base.HEX_LOWER) { return (off + 97): u8; }; - return (off + 65): u8; -}; +// lut_upper / lut_lower — digit→glyph tables. Verbatim port of the +// `static const lut_upper`/`lut_lower` rune arrays in +// ref/hare/strconv/utos.ha:14-20. Module-level `let` (ww has no module +// `const`; never written) following the ftos_data.ww table convention. +// Declared [16]rune (faithful to Hare's inferred rune element type); +// u64tos casts the indexed glyph to u8 at the store, as Hare does +// (utos.ha:35). +let lut_upper: [16]rune = [ + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', +]; +let lut_lower: [16]rune = [ + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', +]; -// u64tos — convert v to a base-b numeric string. Returns a view into -// `u64tos_buf` which is overwritten on the next call. Matches Hare's -// strconv::u64tos. -let u64tos_buf: [65]u8; +// u64tos_buf — overwritten on each u64tos call (Hare's `static let buf`, +// utos.ha:12). 64 = the widest u64 rendering (binary). `[0...]` kept for +// fidelity; the initial value is irrelevant (only the freshly-written +// prefix is ever read) but the fill form is exercised (probed: emits +// byte-identically cross-stage). +let u64tos_buf: [64]u8 = [0...]; // 64 binary digits -export fn u64tos(v: u64, b: base) str = { +// u64tos — convert u to a base-b numeric string. Returns a view into +// `u64tos_buf`, overwritten on the next call; copy via strings.dup to +// outlive it. Verbatim port of ref/hare/strconv/utos.ha:10-42. +// Divergences: +// - Hare's `static assert(types::U64_MAX == ...)` dropped (ww has no +// static assert; the bound lives in lib/types/types.ww:20). +// - Hare selects the LUT via an if-EXPRESSION and reassigns `b` to +// HEX_UPPER / DEC inline (utos.ha:21-26). ww has no if-expression +// (standing divergence, stof.ww:31), so the glyph case is a `lower` +// bool branch and the divisor is basenum(b) — the file's existing +// normalize helper, which maps DEFAULT→10 and HEX_LOWER→16 exactly +// as Hare's reassignment does. +// - Hare's `types::string { data = &buf, ... }` + `*(&s: *str)` +// reinterpret (utos.ha:28,41) → strings.frombytes (CLAUDE.md rule 9 +// carve-out: ww's lib/types has no `string` struct; frombytes is the +// honest ww idiom, cf. ascii/strings). +export fn u64tos(u: u64, b: base) 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; }; + let lower: bool = (b == base.HEX_LOWER); + + let length: i32 = 0; + let n: u64 = u; + if (n == 0u64) { + u64tos_buf[length] = lut_upper[0]: u8; + length += 1; + }; for (n > 0u64) { let d: i64 = (n % nb): i64; - tmp[i] = basedigit(d, b); + if (lower) { u64tos_buf[length] = lut_lower[d]: u8; } + else { u64tos_buf[length] = lut_upper[d]: u8; }; + length += 1; n = n / nb; - i += 1; }; - let out: i32 = 0; - for (i > 0) { - i -= 1; - u64tos_buf[out] = tmp[i]; - out += 1; - }; - let r: str; - r.ptr = &u64tos_buf[0]; - r.len = out; - return r; + + bytes.reverse(u64tos_buf[0:length]); + return strings.frombytes(u64tos_buf[0:length]); }; -// i64tos — convert v to a base-b numeric string. Returns a view into -// `i64tos_buf` which is overwritten on the next call. Independent -// buffer from u64tos so i64tos's own call to u64tos doesn't clobber -// the in-flight result. Matches Hare's strconv::i64tos. -let i64tos_buf: [66]u8; +// i64tos_buf — independent from u64tos_buf so i64tos's own u64tos call +// (the magnitude) doesn't clobber the in-flight result. 65 = 64 digits +// plus the leading '-'. Hare's `static let buf: [65]u8` (itos.ha:18). +let i64tos_buf: [65]u8 = [0...]; // 64 binary digits plus '-' -export fn i64tos(v: i64, b: base) str = { - let neg: bool = false; - let n: i64 = v; - 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) { - let d: i64 = n % nb; - tmp[i] = basedigit(d, b); - n = n / nb; - i += 1; +// i64tos — convert i to a base-b numeric string. Returns a view into +// `i64tos_buf`. Verbatim port of ref/hare/strconv/itos.ha:10-32. +// Divergences: +// - `static assert` dropped (see u64tos); the DEFAULT→DEC normalize +// rides basenum(b) inside the u64tos call (itos.ha:12-14). +// - Hare's slice-assign `buf[1..len(u)+1] = u[..]` + the bounds assert +// (itos.ha:26-28) → explicit copy loop (existing-file convention; +// the [65] buffer holds the 64-digit max + sign exactly, so the +// bound is structural). +// - `*(&s: *str)` → strings.frombytes (see u64tos). +export fn i64tos(i: i64, b: base) str = { + if (i >= 0) { return u64tos(i: u64, b); }; + + i64tos_buf[0] = 45u8; // '-' + // `(-i): u64`: for I64_MIN, -i wraps (two's complement) back to the + // I64_MIN bit pattern; reinterpreting to u64 yields the true + // magnitude 9223372036854775808. ref/hare/strconv/itos.ha:25. Probed + // on both stages (NEG then i64→u64 reinterpret byte-identical); + // closes the i64tos-on-I64_MIN bug noted at cgen.ww #144. + let u: str = u64tos((-i): u64, b); + let k: i32 = 0; + for (k < u.len) { + i64tos_buf[k + 1] = u[k]; + k += 1; }; - let out: i32 = 0; - if (neg) { i64tos_buf[out] = 45u8; out += 1; }; // '-' - for (i > 0) { - i -= 1; - i64tos_buf[out] = tmp[i]; - out += 1; - }; - let r: str; - r.ptr = &i64tos_buf[0]; - r.len = out; - return r; + return strings.frombytes(i64tos_buf[0 : u.len + 1]); }; export fn i32tos(v: i32, b: base) str = { return i64tos(v: i64, b); }; export fn i16tos(v: i16, b: base) str = { return i64tos(v: i64, b); }; export fn i8tos(v: i8, b: base) str = { return i64tos(v: i64, b); }; +// itos — int (ww machine-word, 8B → i64-width) → string. +// ref/hare/strconv/itos.ha:52. +export fn itos(i: int, b: base) str = { return i64tos(i: i64, b); }; + export fn u32tos(v: u32, b: base) str = { return u64tos(v: u64, b); }; export fn u16tos(v: u16, b: base) str = { return u64tos(v: u64, b); }; export fn u8tos(v: u8, b: base) str = { return u64tos(v: u64, b); }; +// utos — uint (8B → u64-width) → string. ref/hare/strconv/utos.ha:62. +export fn utos(u: uint, b: base) str = { return u64tos(u: u64, b); }; + +// ztos — size (8B → u64-width) → string. ref/hare/strconv/utos.ha:67. +export fn ztos(u: size, b: base) str = { return u64tos(u: u64, b); }; + +// uptrtos — uintptr → string. ref/hare/strconv/utos.ha:72 (param `uptr` +// cast `uptr: u64`). +export fn uptrtos(uptr: uintptr, b: base) str = { return u64tos(uptr: u64, b); }; + // rune_to_integer — digit value of r (0-9 → 0-9; a-z/A-Z → 10-35), // or void if r is not alphanumeric. Verbatim port of // ref/hare/strconv/stou.ha:8-15 (ww yields the void variant with a