Files
ww/lib/strconv/test/inttest.ww
Hojun-Cho db5c5b6149 lib/strconv: add itos/utos integer format (strconv-int fold-2)
Graduates the integer FORMAT side to verbatim Hare ports, completing the
round-trip whose parse half landed in fold-1, and adds the machine-word
entry points.

  - u64tos: ref/hare/strconv/utos.ha:10-42. Replaces the pre-graduation
    basedigit() helper with Hare's rune LUT (lut_upper/lut_lower), single
    static buffer + bytes.reverse, and strings.frombytes for the
    `*(&s: *str)` reinterpret (rule-9 carve-out; ww's lib/types has no
    `string` struct). basedigit deleted (now dead).
  - i64tos: ref/hare/strconv/itos.ha:10-32. Now `if (i >= 0) u64tos(i)`
    else negate-and-prefix via `u64tos((-i): u64)`. This fixes the
    i64tos-on-I64_MIN bug (cgen.ww #144): the old `n = -n; for (n > 0)`
    left n at the I64_MIN bit pattern (still negative), emitting just
    "-". The `(-i): u64` two's-complement reinterpret yields the true
    magnitude 9223372036854775808.
  - itos/utos/ztos/uptrtos: int/uint/size/uintptr 8B machine-word
    wrappers (itos.ha:52, utos.ha:62/67/72), parallel to fold-1's
    stoi/stou/stoz. The existing iN/uN width wrappers are unchanged.

Divergences documented at-site: no static assert; LUT-select + base
normalize via the existing basenum() (ww has no if-expression); explicit
copy loop for Hare's slice-assign.

Probes (drew PROBE-BEFORE-COMMIT, all green on BOTH stages):
  - i64tos(I64_MIN) == "-9223372036854775808": cstage `ww run` exit 0 +
    wwstage-compiled binary exit 0; cs==ww .s byte-identical on the real
    combined (30190 lines).
  - static `[0...]` fill + rune LUT static-init emit byte-identically
    cross-stage (isolated smoke probe + the combined byte-id).
  - frombytes (not a types::string mirror) per rule 9.

Tests: extend inttest.ww with test_u64tos[_bases] / test_i64tos[_bases]
(verbatim utos.ha:74-103 / itos.ha:54-87, flat assert sequences;
feedback_test_match_hare_source) + test_word_wrappers. I64_MIN inputs
spelled -I64_MAX-1 (proj #245: wwstage mis-lexes the 2^63 literal).

combined.ww regen: strconv is compiler-imported via fmt, so w6c +
wwdump main.combined.ww + smoke.combined.ww are regenerated.
2026-06-01 23:36:36 +09:00

331 lines
11 KiB
Plaintext

// inttest — exercises lib/strconv integer parse: parseint / stoi64 /
// stou64 / the iN/uN width wrappers. Run with
// `out/bin/ww run lib/strconv/test/inttest.ww`. Same
// signalled-then-fail()-with-+10 pattern as decimaltest / bytestest:
// a non-zero exit code (signalled+10) pinpoints the failing case.
//
// Verbatim port of ref/hare/strconv/stoi.ha:56-86 (stoi/stoi_bases) and
// stou.ha:116-138 (stou/stou_bases). Hare's strconv integer tests are
// flat assert SEQUENCES, not row-array tables — mirrored here as inline
// per-case checks (feedback_test_match_hare_source: inline @test-fn for
// verbatim ports). Each case sets `signalled` first so a failure's exit
// code identifies the exact assertion.
//
// Lives in lib/strconv/test/ (not lib/strconv/) so `import strconv`
// resolves to the lib/strconv DIRECTORY (pulls the full package), not
// the strconv.ww FILE — same rationale as 922_decimal_run.
//
// Hex/oct/bin expected values are written in decimal (ww has no 0x/0o/0b
// literal form for the expectation side); the original Hare radix form
// is noted inline.
package strconv;
import strconv;
import os;
let signalled: i32 = 0;
fn fail() void = { os.exit(signalled + 10); };
fn cki(id: i32, s: str, b: base, want: i64) void = {
signalled = id;
match (stoi64(s, b)) {
case let v: i64 => if (v != want) { fail(); };
case let e: invalid => fail();
case let e: overflow => fail();
};
};
fn cki_inv(id: i32, s: str, b: base, idx: i32) void = {
signalled = id;
match (stoi64(s, b)) {
case let v: i64 => fail();
case let e: invalid => if (e: i32 != idx) { fail(); };
case let e: overflow => fail();
};
};
fn cki_ovf(id: i32, s: str, b: base) void = {
signalled = id;
match (stoi64(s, b)) {
case let v: i64 => fail();
case let e: invalid => fail();
case let e: overflow => { };
};
};
fn cku(id: i32, s: str, b: base, want: u64) void = {
signalled = id;
match (stou64(s, b)) {
case let v: u64 => if (v != want) { fail(); };
case let e: invalid => fail();
case let e: overflow => fail();
};
};
fn cku_inv(id: i32, s: str, b: base, idx: i32) void = {
signalled = id;
match (stou64(s, b)) {
case let v: u64 => fail();
case let e: invalid => if (e: i32 != idx) { fail(); };
case let e: overflow => fail();
};
};
fn cku_ovf(id: i32, s: str, b: base) void = {
signalled = id;
match (stou64(s, b)) {
case let v: u64 => fail();
case let e: invalid => fail();
case let e: overflow => { };
};
};
fn cki32_ovf(id: i32, s: str, b: base) void = {
signalled = id;
match (stoi32(s, b)) {
case let v: i32 => fail();
case let e: invalid => fail();
case let e: overflow => { };
};
};
fn cki32(id: i32, s: str, b: base, want: i32) void = {
signalled = id;
match (stoi32(s, b)) {
case let v: i32 => if (v != want) { fail(); };
case let e: invalid => fail();
case let e: overflow => fail();
};
};
fn ck_int(id: i32, s: str, b: base, want: int) void = {
signalled = id;
match (stoi(s, b)) {
case let v: int => if (v != want) { fail(); };
case let e: invalid => fail();
case let e: overflow => fail();
};
};
fn ck_uint(id: i32, s: str, b: base, want: uint) void = {
signalled = id;
match (stou(s, b)) {
case let v: uint => if (v != want) { fail(); };
case let e: invalid => fail();
case let e: overflow => fail();
};
};
fn ck_size(id: i32, s: str, b: base, want: size) void = {
signalled = id;
match (stoz(s, b)) {
case let v: size => if (v != want) { fail(); };
case let e: invalid => fail();
case let e: overflow => fail();
};
};
fn ck_int_ovf(id: i32, s: str, b: base) void = {
signalled = id;
match (stoi(s, b)) {
case let v: int => fail();
case let e: invalid => fail();
case let e: overflow => { };
};
};
fn ck_uint_ovf(id: i32, s: str, b: base) void = {
signalled = id;
match (stou(s, b)) {
case let v: uint => fail();
case let e: invalid => fail();
case let e: overflow => { };
};
};
// ref/hare/strconv/stoi.ha:56-79.
@test fn test_stoi64() void = {
cki_inv(1, "", base.DEC, 0);
cki_inv(2, "abc", base.DEC, 0);
cki_inv(3, "1a", base.DEC, 1);
cki_inv(4, "+", base.DEC, 1);
cki_inv(5, "-+", base.DEC, 1);
cki_inv(6, "-z", base.DEC, 1);
cki_ovf(7, "9223372036854775808", base.DEC);
cki_ovf(8, "-9223372036854775809", base.DEC);
cki(9, "0", base.DEC, 0);
cki(10, "1", base.DEC, 1);
cki(11, "+1", base.DEC, 1);
cki(12, "-1", base.DEC, -1);
cki(13, "9223372036854775807", base.DEC, 9223372036854775807i64);
// I64_MIN. Spelled -I64_MAX-1 (Hare's own two's-complement identity,
// stoi64 comment in stoi.ha:11) because the wwstage mis-lexes the
// direct literal -9223372036854775808 (and types.I64_MIN) to 0 —
// proj #245. The INPUT string is unaffected; stoi64 parses it to the
// correct value on both stages. This isolates the parse test from #245.
cki(14, "-9223372036854775808", base.DEC, -9223372036854775807i64 - 1i64);
// width wrapper boundaries (ref/hare/strconv/stoi.ha:74-78).
cki32_ovf(15, "2147483648", base.DEC);
cki32_ovf(16, "-2147483649", base.DEC);
cki32(17, "2147483647", base.DEC, 2147483647i32);
cki32(18, "-2147483648", base.DEC, -2147483648i32);
};
// ref/hare/strconv/stoi.ha:81-86.
@test fn test_stoi64_bases() void = {
cki(20, "-7f", base.HEX, -127i64); // -0x7f
cki(21, "7F", base.HEX, 127i64); // 0x7f
cki(22, "37", base.OCT, 31i64); // 0o37
cki(23, "-110101", base.BIN, -53i64); // -0b110101
};
// ref/hare/strconv/stou.ha:116-130.
@test fn test_stou64() void = {
cku_inv(30, "", base.DEC, 0);
cku_inv(31, "+", base.DEC, 1);
cku_inv(32, "+a", base.DEC, 1);
cku_inv(33, "abc", base.DEC, 0);
cku_inv(34, "1a", base.DEC, 1);
cku_ovf(35, "18446744073709551616", base.DEC);
cku_ovf(36, "184467440737095516150", base.DEC);
cku_ovf(37, "-1", base.DEC);
cku(38, "0", base.DEC, 0u64);
cku(39, "1", base.DEC, 1u64);
cku(40, "18446744073709551615", base.DEC, 18446744073709551615u64);
};
// ref/hare/strconv/stou.ha:132-138.
@test fn test_stou64_bases() void = {
cku(41, "f", base.HEX_LOWER, 15u64); // 0xf
cku(42, "7f", base.HEX, 127u64); // 0x7f
cku(43, "7F", base.HEX, 127u64); // 0x7f
cku(44, "37", base.OCT, 31u64); // 0o37
cku(45, "110101", base.BIN, 53u64); // 0b110101
};
// stoi / stou / stoz — int/uint/size machine-word wrappers
// (ref/hare/strconv/stoi.ha:53, stou.ha:107,113). ww's int/uint/size are
// 8B, so the iN/uN clamp is a no-op: the full i64/u64 range parses with
// no spurious overflow. That no-clamp fidelity is what these check.
@test fn test_stoi_stou_stoz() void = {
ck_int(50, "0", base.DEC, 0);
ck_int(51, "-1", base.DEC, -1);
ck_int(52, "9223372036854775807", base.DEC, 9223372036854775807i64: int); // I64_MAX fits int
ck_int_ovf(53, "9223372036854775808", base.DEC);
ck_int_ovf(54, "-9223372036854775809", base.DEC);
ck_uint(55, "0", base.DEC, 0u64: uint);
ck_uint(56, "18446744073709551615", base.DEC, 18446744073709551615u64: uint); // U64_MAX fits uint
ck_uint_ovf(57, "18446744073709551616", base.DEC);
ck_uint_ovf(58, "-1", base.DEC);
ck_size(59, "0", base.DEC, 0u64: size);
ck_size(60, "18446744073709551615", base.DEC, 18446744073709551615u64: size); // U64_MAX fits size
// bases route through the same parseint core.
ck_int(61, "-7f", base.HEX, -127i64: int); // -0x7f
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;
};