lib/strconv: parseint sign+overflow core; stoi64/stou64 fidelity (strconv-int fold-1 C1)
Port ref/hare/strconv/stou.ha:8-65 (rune_to_integer + parseint) and the stoi64/stou64 fidelity rewrite (stoi.ha:9-17, stou.ha:70-76) over the old digval loop. parseint is the shared sign + per-digit + multiply-overflow core returning ((bool, u64) | invalid | overflow); stoi64/stou64 destructure its `(sign, u)` tuple-in-union result — the shape unblocked by #242/#241. Wins over the prior ad-hoc parse: leading '+' accepted, '-' on stou64 is overflow (not silently dropped), wraparound overflow detection (n < old), and the invalid payload carries the offending byte index per Hare. Tests: lib/strconv/test/inttest.ww (run via test/wcc/922_strconv_int_run.c), inline per-case checks mirroring Hare's assert sequences stoi.ha:56-86 / stou.ha:116-138 (Hare's strconv int tests are flat sequences, not row tables; feedback_test_match_hare_source). Covers valid dec/hex/oct/bin, +/- sign, invalid+index, overflow, and U64_MAX / I64_MAX / I64_MIN boundaries. The I64_MIN expectation is spelled -I64_MAX-1 (Hare's own two's-complement identity) to isolate the test from #245 (wwstage mis-lexes the literal 9223372036854775808 -> 0); the parse INPUT is unaffected and yields the correct value on both stages. combined.ww regen: strconv is compiler-imported (via fmt), so w6c + wwdump main.combined.ww are regenerated.
This commit is contained in:
7
Makefile
7
Makefile
@@ -388,7 +388,8 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_intdiv_signed \
|
||||
$(BIN)/test_strings_run \
|
||||
$(BIN)/test_hex_run $(BIN)/test_utf8_run $(BIN)/test_bytes_run \
|
||||
$(BIN)/test_decimal_run $(BIN)/test_stof_run $(BIN)/test_ftos_run \
|
||||
$(BIN)/test_decimal_run $(BIN)/test_strconv_int_run \
|
||||
$(BIN)/test_stof_run $(BIN)/test_ftos_run \
|
||||
$(BIN)/test_memio_run $(BIN)/test_temp_run $(BIN)/test_getopt_run \
|
||||
$(BIN)/test_errno_run \
|
||||
$(BIN)/test_base32_run $(BIN)/test_base64_run \
|
||||
@@ -1492,6 +1493,10 @@ $(BIN)/test_decimal_run: test/wcc/922_decimal_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_strconv_int_run: test/wcc/922_strconv_int_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_stof_run: test/wcc/909_stof_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
package strconv;
|
||||
|
||||
import ascii;
|
||||
import os;
|
||||
import strings;
|
||||
|
||||
@@ -125,59 +126,110 @@ 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); };
|
||||
|
||||
// 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: base) i32 = {
|
||||
if (c >= 48u8) { if (c <= 57u8) { return (c - 48u8): i32; }; };
|
||||
if (b == base.HEX_LOWER) {
|
||||
if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; };
|
||||
return -1;
|
||||
// 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
|
||||
// bare `return;`, per lib/bytes/bytes.ww:65).
|
||||
fn rune_to_integer(r: rune) (u64 | void) = {
|
||||
if (ascii.isdigit(r)) {
|
||||
return (r: u32 - '0'): u64;
|
||||
} else if (ascii.isalpha(r) && ascii.islower(r)) {
|
||||
return (r: u32 - 'a'): u64 + 10;
|
||||
} else if (ascii.isalpha(r) && ascii.isupper(r)) {
|
||||
return (r: u32 - 'A'): u64 + 10;
|
||||
};
|
||||
if (c >= 65u8) { if (c <= 70u8) { return ((c - 65u8) + 10u8): i32; }; };
|
||||
if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; };
|
||||
return -1;
|
||||
return;
|
||||
};
|
||||
|
||||
// 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: base) (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];
|
||||
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 — parse unsigned base-b number. Mirrors Hare's strconv::stou64.
|
||||
export fn stou64(s: str, b: base) (u64 | invalid | overflow) = {
|
||||
if (s.len == 0) { return 0: invalid; };
|
||||
// parseint — shared sign+digit+overflow core for stoi64/stou64.
|
||||
// Verbatim port of ref/hare/strconv/stou.ha:17-65. Divergences:
|
||||
// - param `base` → `b` (ww: avoid the type/value name collision; the
|
||||
// file already names the enum arg `b`).
|
||||
// - Hare's DEFAULT→DEC / HEX_LOWER→HEX base reassignment + the
|
||||
// base-validity assert collapse into basenum(b), which already maps
|
||||
// every base to its numeric value {2,8,10,16} (default 10). HEX_LOWER
|
||||
// thus parses case-insensitively, matching Hare's normalize-then-parse.
|
||||
// - str is byte-indexable, so Hare's `buf = strings::toutf8(s)` is
|
||||
// elided (existing file convention, cf. the old stoi64/stou64).
|
||||
// - n *= base / n += digit spelled as plain assignment (sibling-fn
|
||||
// convention).
|
||||
fn parseint(s: str, b: base) ((bool, u64) | invalid | overflow) = {
|
||||
let nb: u64 = basenum(b): u64;
|
||||
let v: u64 = 0u64;
|
||||
|
||||
if (s.len == 0) {
|
||||
return 0: invalid;
|
||||
};
|
||||
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
let d: i32 = digval(c, b);
|
||||
if (d < 0) { return i: invalid; };
|
||||
if ((d: u64) >= nb) { return i: invalid; };
|
||||
v = v * nb + (d: u64);
|
||||
|
||||
let sign: bool = s[i] == '-';
|
||||
if (sign || s[i] == '+') {
|
||||
i += 1;
|
||||
};
|
||||
return v;
|
||||
|
||||
// Require at least one digit.
|
||||
if (i == s.len) {
|
||||
return i: invalid;
|
||||
};
|
||||
|
||||
let n: u64 = 0u64;
|
||||
// Hare's `for (i < len(buf); i += 1)` (stou.ha:43) → condition-only
|
||||
// for + tail increment (ww has no 2-clause for; sort.ww:25). Early
|
||||
// returns exit before the increment, so it's never skipped.
|
||||
for (i < s.len) {
|
||||
let digit: u64 = match (rune_to_integer(s[i]: rune)) {
|
||||
case void => return i: invalid;
|
||||
case let d: u64 => yield d;
|
||||
};
|
||||
|
||||
if (digit >= nb) {
|
||||
return i: invalid;
|
||||
};
|
||||
|
||||
let old: u64 = n;
|
||||
|
||||
n = n * nb;
|
||||
n = n + digit;
|
||||
|
||||
if (n < old) {
|
||||
return overflow{};
|
||||
};
|
||||
|
||||
i += 1;
|
||||
};
|
||||
return (sign, n);
|
||||
};
|
||||
|
||||
// stoi64 — parse signed base-b number. Verbatim port of
|
||||
// ref/hare/strconv/stoi.ha:9-17. types.I64_MAX is inlined (the const is
|
||||
// package-private — see the mulshift32 note in ftos.ww).
|
||||
export fn stoi64(s: str, b: base) (i64 | invalid | overflow) = {
|
||||
let (sign, u) = parseint(s, b)?;
|
||||
// Two's complement: I64_MIN = -I64_MAX - 1. Hare's two if-expressions
|
||||
// (stoi.ha:12,16) are lowered to statement-if — ww has no
|
||||
// if-expression (standing divergence, see the note in stof.ww:31).
|
||||
let max: u64 = 9223372036854775807u64;
|
||||
if (sign) {
|
||||
max = max + 1u64;
|
||||
};
|
||||
if (u > max) {
|
||||
return overflow{};
|
||||
};
|
||||
let r: i64 = u: i64;
|
||||
if (sign) {
|
||||
r = -r;
|
||||
};
|
||||
return r;
|
||||
};
|
||||
|
||||
// stou64 — parse unsigned base-b number. Verbatim port of
|
||||
// ref/hare/strconv/stou.ha:70-76.
|
||||
export fn stou64(s: str, b: base) (u64 | invalid | overflow) = {
|
||||
let (sign, u) = parseint(s, b)?;
|
||||
if (sign) {
|
||||
return overflow{};
|
||||
};
|
||||
return u;
|
||||
};
|
||||
|
||||
export fn stoi32(s: str, b: base) (i32 | invalid | overflow) = {
|
||||
|
||||
173
lib/strconv/test/inttest.ww
Normal file
173
lib/strconv/test/inttest.ww
Normal file
@@ -0,0 +1,173 @@
|
||||
// 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();
|
||||
};
|
||||
};
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
export fn main() i32 = {
|
||||
test_stoi64();
|
||||
test_stoi64_bases();
|
||||
test_stou64();
|
||||
test_stou64_bases();
|
||||
return 0;
|
||||
};
|
||||
@@ -5968,6 +5968,7 @@ let powers_of_ten: [596][2]u64 = [
|
||||
|
||||
package strconv;
|
||||
|
||||
import ascii;
|
||||
import os;
|
||||
import strings;
|
||||
|
||||
@@ -6085,59 +6086,110 @@ 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); };
|
||||
|
||||
// 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: base) i32 = {
|
||||
if (c >= 48u8) { if (c <= 57u8) { return (c - 48u8): i32; }; };
|
||||
if (b == base.HEX_LOWER) {
|
||||
if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; };
|
||||
return -1;
|
||||
// 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
|
||||
// bare `return;`, per lib/bytes/bytes.ww:65).
|
||||
fn rune_to_integer(r: rune) (u64 | void) = {
|
||||
if (ascii.isdigit(r)) {
|
||||
return (r: u32 - '0'): u64;
|
||||
} else if (ascii.isalpha(r) && ascii.islower(r)) {
|
||||
return (r: u32 - 'a'): u64 + 10;
|
||||
} else if (ascii.isalpha(r) && ascii.isupper(r)) {
|
||||
return (r: u32 - 'A'): u64 + 10;
|
||||
};
|
||||
if (c >= 65u8) { if (c <= 70u8) { return ((c - 65u8) + 10u8): i32; }; };
|
||||
if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; };
|
||||
return -1;
|
||||
return;
|
||||
};
|
||||
|
||||
// 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: base) (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];
|
||||
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 — parse unsigned base-b number. Mirrors Hare's strconv::stou64.
|
||||
export fn stou64(s: str, b: base) (u64 | invalid | overflow) = {
|
||||
if (s.len == 0) { return 0: invalid; };
|
||||
// parseint — shared sign+digit+overflow core for stoi64/stou64.
|
||||
// Verbatim port of ref/hare/strconv/stou.ha:17-65. Divergences:
|
||||
// - param `base` → `b` (ww: avoid the type/value name collision; the
|
||||
// file already names the enum arg `b`).
|
||||
// - Hare's DEFAULT→DEC / HEX_LOWER→HEX base reassignment + the
|
||||
// base-validity assert collapse into basenum(b), which already maps
|
||||
// every base to its numeric value {2,8,10,16} (default 10). HEX_LOWER
|
||||
// thus parses case-insensitively, matching Hare's normalize-then-parse.
|
||||
// - str is byte-indexable, so Hare's `buf = strings::toutf8(s)` is
|
||||
// elided (existing file convention, cf. the old stoi64/stou64).
|
||||
// - n *= base / n += digit spelled as plain assignment (sibling-fn
|
||||
// convention).
|
||||
fn parseint(s: str, b: base) ((bool, u64) | invalid | overflow) = {
|
||||
let nb: u64 = basenum(b): u64;
|
||||
let v: u64 = 0u64;
|
||||
|
||||
if (s.len == 0) {
|
||||
return 0: invalid;
|
||||
};
|
||||
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
let d: i32 = digval(c, b);
|
||||
if (d < 0) { return i: invalid; };
|
||||
if ((d: u64) >= nb) { return i: invalid; };
|
||||
v = v * nb + (d: u64);
|
||||
|
||||
let sign: bool = s[i] == '-';
|
||||
if (sign || s[i] == '+') {
|
||||
i += 1;
|
||||
};
|
||||
return v;
|
||||
|
||||
// Require at least one digit.
|
||||
if (i == s.len) {
|
||||
return i: invalid;
|
||||
};
|
||||
|
||||
let n: u64 = 0u64;
|
||||
// Hare's `for (i < len(buf); i += 1)` (stou.ha:43) → condition-only
|
||||
// for + tail increment (ww has no 2-clause for; sort.ww:25). Early
|
||||
// returns exit before the increment, so it's never skipped.
|
||||
for (i < s.len) {
|
||||
let digit: u64 = match (rune_to_integer(s[i]: rune)) {
|
||||
case void => return i: invalid;
|
||||
case let d: u64 => yield d;
|
||||
};
|
||||
|
||||
if (digit >= nb) {
|
||||
return i: invalid;
|
||||
};
|
||||
|
||||
let old: u64 = n;
|
||||
|
||||
n = n * nb;
|
||||
n = n + digit;
|
||||
|
||||
if (n < old) {
|
||||
return overflow{};
|
||||
};
|
||||
|
||||
i += 1;
|
||||
};
|
||||
return (sign, n);
|
||||
};
|
||||
|
||||
// stoi64 — parse signed base-b number. Verbatim port of
|
||||
// ref/hare/strconv/stoi.ha:9-17. types.I64_MAX is inlined (the const is
|
||||
// package-private — see the mulshift32 note in ftos.ww).
|
||||
export fn stoi64(s: str, b: base) (i64 | invalid | overflow) = {
|
||||
let (sign, u) = parseint(s, b)?;
|
||||
// Two's complement: I64_MIN = -I64_MAX - 1. Hare's two if-expressions
|
||||
// (stoi.ha:12,16) are lowered to statement-if — ww has no
|
||||
// if-expression (standing divergence, see the note in stof.ww:31).
|
||||
let max: u64 = 9223372036854775807u64;
|
||||
if (sign) {
|
||||
max = max + 1u64;
|
||||
};
|
||||
if (u > max) {
|
||||
return overflow{};
|
||||
};
|
||||
let r: i64 = u: i64;
|
||||
if (sign) {
|
||||
r = -r;
|
||||
};
|
||||
return r;
|
||||
};
|
||||
|
||||
// stou64 — parse unsigned base-b number. Verbatim port of
|
||||
// ref/hare/strconv/stou.ha:70-76.
|
||||
export fn stou64(s: str, b: base) (u64 | invalid | overflow) = {
|
||||
let (sign, u) = parseint(s, b)?;
|
||||
if (sign) {
|
||||
return overflow{};
|
||||
};
|
||||
return u;
|
||||
};
|
||||
|
||||
export fn stoi32(s: str, b: base) (i32 | invalid | overflow) = {
|
||||
|
||||
@@ -5968,6 +5968,7 @@ let powers_of_ten: [596][2]u64 = [
|
||||
|
||||
package strconv;
|
||||
|
||||
import ascii;
|
||||
import os;
|
||||
import strings;
|
||||
|
||||
@@ -6085,59 +6086,110 @@ 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); };
|
||||
|
||||
// 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: base) i32 = {
|
||||
if (c >= 48u8) { if (c <= 57u8) { return (c - 48u8): i32; }; };
|
||||
if (b == base.HEX_LOWER) {
|
||||
if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; };
|
||||
return -1;
|
||||
// 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
|
||||
// bare `return;`, per lib/bytes/bytes.ww:65).
|
||||
fn rune_to_integer(r: rune) (u64 | void) = {
|
||||
if (ascii.isdigit(r)) {
|
||||
return (r: u32 - '0'): u64;
|
||||
} else if (ascii.isalpha(r) && ascii.islower(r)) {
|
||||
return (r: u32 - 'a'): u64 + 10;
|
||||
} else if (ascii.isalpha(r) && ascii.isupper(r)) {
|
||||
return (r: u32 - 'A'): u64 + 10;
|
||||
};
|
||||
if (c >= 65u8) { if (c <= 70u8) { return ((c - 65u8) + 10u8): i32; }; };
|
||||
if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; };
|
||||
return -1;
|
||||
return;
|
||||
};
|
||||
|
||||
// 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: base) (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];
|
||||
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 — parse unsigned base-b number. Mirrors Hare's strconv::stou64.
|
||||
export fn stou64(s: str, b: base) (u64 | invalid | overflow) = {
|
||||
if (s.len == 0) { return 0: invalid; };
|
||||
// parseint — shared sign+digit+overflow core for stoi64/stou64.
|
||||
// Verbatim port of ref/hare/strconv/stou.ha:17-65. Divergences:
|
||||
// - param `base` → `b` (ww: avoid the type/value name collision; the
|
||||
// file already names the enum arg `b`).
|
||||
// - Hare's DEFAULT→DEC / HEX_LOWER→HEX base reassignment + the
|
||||
// base-validity assert collapse into basenum(b), which already maps
|
||||
// every base to its numeric value {2,8,10,16} (default 10). HEX_LOWER
|
||||
// thus parses case-insensitively, matching Hare's normalize-then-parse.
|
||||
// - str is byte-indexable, so Hare's `buf = strings::toutf8(s)` is
|
||||
// elided (existing file convention, cf. the old stoi64/stou64).
|
||||
// - n *= base / n += digit spelled as plain assignment (sibling-fn
|
||||
// convention).
|
||||
fn parseint(s: str, b: base) ((bool, u64) | invalid | overflow) = {
|
||||
let nb: u64 = basenum(b): u64;
|
||||
let v: u64 = 0u64;
|
||||
|
||||
if (s.len == 0) {
|
||||
return 0: invalid;
|
||||
};
|
||||
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
let d: i32 = digval(c, b);
|
||||
if (d < 0) { return i: invalid; };
|
||||
if ((d: u64) >= nb) { return i: invalid; };
|
||||
v = v * nb + (d: u64);
|
||||
|
||||
let sign: bool = s[i] == '-';
|
||||
if (sign || s[i] == '+') {
|
||||
i += 1;
|
||||
};
|
||||
return v;
|
||||
|
||||
// Require at least one digit.
|
||||
if (i == s.len) {
|
||||
return i: invalid;
|
||||
};
|
||||
|
||||
let n: u64 = 0u64;
|
||||
// Hare's `for (i < len(buf); i += 1)` (stou.ha:43) → condition-only
|
||||
// for + tail increment (ww has no 2-clause for; sort.ww:25). Early
|
||||
// returns exit before the increment, so it's never skipped.
|
||||
for (i < s.len) {
|
||||
let digit: u64 = match (rune_to_integer(s[i]: rune)) {
|
||||
case void => return i: invalid;
|
||||
case let d: u64 => yield d;
|
||||
};
|
||||
|
||||
if (digit >= nb) {
|
||||
return i: invalid;
|
||||
};
|
||||
|
||||
let old: u64 = n;
|
||||
|
||||
n = n * nb;
|
||||
n = n + digit;
|
||||
|
||||
if (n < old) {
|
||||
return overflow{};
|
||||
};
|
||||
|
||||
i += 1;
|
||||
};
|
||||
return (sign, n);
|
||||
};
|
||||
|
||||
// stoi64 — parse signed base-b number. Verbatim port of
|
||||
// ref/hare/strconv/stoi.ha:9-17. types.I64_MAX is inlined (the const is
|
||||
// package-private — see the mulshift32 note in ftos.ww).
|
||||
export fn stoi64(s: str, b: base) (i64 | invalid | overflow) = {
|
||||
let (sign, u) = parseint(s, b)?;
|
||||
// Two's complement: I64_MIN = -I64_MAX - 1. Hare's two if-expressions
|
||||
// (stoi.ha:12,16) are lowered to statement-if — ww has no
|
||||
// if-expression (standing divergence, see the note in stof.ww:31).
|
||||
let max: u64 = 9223372036854775807u64;
|
||||
if (sign) {
|
||||
max = max + 1u64;
|
||||
};
|
||||
if (u > max) {
|
||||
return overflow{};
|
||||
};
|
||||
let r: i64 = u: i64;
|
||||
if (sign) {
|
||||
r = -r;
|
||||
};
|
||||
return r;
|
||||
};
|
||||
|
||||
// stou64 — parse unsigned base-b number. Verbatim port of
|
||||
// ref/hare/strconv/stou.ha:70-76.
|
||||
export fn stou64(s: str, b: base) (u64 | invalid | overflow) = {
|
||||
let (sign, u) = parseint(s, b)?;
|
||||
if (sign) {
|
||||
return overflow{};
|
||||
};
|
||||
return u;
|
||||
};
|
||||
|
||||
export fn stoi32(s: str, b: base) (i32 | invalid | overflow) = {
|
||||
|
||||
@@ -5968,6 +5968,7 @@ let powers_of_ten: [596][2]u64 = [
|
||||
|
||||
package strconv;
|
||||
|
||||
import ascii;
|
||||
import os;
|
||||
import strings;
|
||||
|
||||
@@ -6085,59 +6086,110 @@ 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); };
|
||||
|
||||
// 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: base) i32 = {
|
||||
if (c >= 48u8) { if (c <= 57u8) { return (c - 48u8): i32; }; };
|
||||
if (b == base.HEX_LOWER) {
|
||||
if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; };
|
||||
return -1;
|
||||
// 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
|
||||
// bare `return;`, per lib/bytes/bytes.ww:65).
|
||||
fn rune_to_integer(r: rune) (u64 | void) = {
|
||||
if (ascii.isdigit(r)) {
|
||||
return (r: u32 - '0'): u64;
|
||||
} else if (ascii.isalpha(r) && ascii.islower(r)) {
|
||||
return (r: u32 - 'a'): u64 + 10;
|
||||
} else if (ascii.isalpha(r) && ascii.isupper(r)) {
|
||||
return (r: u32 - 'A'): u64 + 10;
|
||||
};
|
||||
if (c >= 65u8) { if (c <= 70u8) { return ((c - 65u8) + 10u8): i32; }; };
|
||||
if (c >= 97u8) { if (c <= 102u8) { return ((c - 97u8) + 10u8): i32; }; };
|
||||
return -1;
|
||||
return;
|
||||
};
|
||||
|
||||
// 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: base) (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];
|
||||
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 — parse unsigned base-b number. Mirrors Hare's strconv::stou64.
|
||||
export fn stou64(s: str, b: base) (u64 | invalid | overflow) = {
|
||||
if (s.len == 0) { return 0: invalid; };
|
||||
// parseint — shared sign+digit+overflow core for stoi64/stou64.
|
||||
// Verbatim port of ref/hare/strconv/stou.ha:17-65. Divergences:
|
||||
// - param `base` → `b` (ww: avoid the type/value name collision; the
|
||||
// file already names the enum arg `b`).
|
||||
// - Hare's DEFAULT→DEC / HEX_LOWER→HEX base reassignment + the
|
||||
// base-validity assert collapse into basenum(b), which already maps
|
||||
// every base to its numeric value {2,8,10,16} (default 10). HEX_LOWER
|
||||
// thus parses case-insensitively, matching Hare's normalize-then-parse.
|
||||
// - str is byte-indexable, so Hare's `buf = strings::toutf8(s)` is
|
||||
// elided (existing file convention, cf. the old stoi64/stou64).
|
||||
// - n *= base / n += digit spelled as plain assignment (sibling-fn
|
||||
// convention).
|
||||
fn parseint(s: str, b: base) ((bool, u64) | invalid | overflow) = {
|
||||
let nb: u64 = basenum(b): u64;
|
||||
let v: u64 = 0u64;
|
||||
|
||||
if (s.len == 0) {
|
||||
return 0: invalid;
|
||||
};
|
||||
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
let d: i32 = digval(c, b);
|
||||
if (d < 0) { return i: invalid; };
|
||||
if ((d: u64) >= nb) { return i: invalid; };
|
||||
v = v * nb + (d: u64);
|
||||
|
||||
let sign: bool = s[i] == '-';
|
||||
if (sign || s[i] == '+') {
|
||||
i += 1;
|
||||
};
|
||||
return v;
|
||||
|
||||
// Require at least one digit.
|
||||
if (i == s.len) {
|
||||
return i: invalid;
|
||||
};
|
||||
|
||||
let n: u64 = 0u64;
|
||||
// Hare's `for (i < len(buf); i += 1)` (stou.ha:43) → condition-only
|
||||
// for + tail increment (ww has no 2-clause for; sort.ww:25). Early
|
||||
// returns exit before the increment, so it's never skipped.
|
||||
for (i < s.len) {
|
||||
let digit: u64 = match (rune_to_integer(s[i]: rune)) {
|
||||
case void => return i: invalid;
|
||||
case let d: u64 => yield d;
|
||||
};
|
||||
|
||||
if (digit >= nb) {
|
||||
return i: invalid;
|
||||
};
|
||||
|
||||
let old: u64 = n;
|
||||
|
||||
n = n * nb;
|
||||
n = n + digit;
|
||||
|
||||
if (n < old) {
|
||||
return overflow{};
|
||||
};
|
||||
|
||||
i += 1;
|
||||
};
|
||||
return (sign, n);
|
||||
};
|
||||
|
||||
// stoi64 — parse signed base-b number. Verbatim port of
|
||||
// ref/hare/strconv/stoi.ha:9-17. types.I64_MAX is inlined (the const is
|
||||
// package-private — see the mulshift32 note in ftos.ww).
|
||||
export fn stoi64(s: str, b: base) (i64 | invalid | overflow) = {
|
||||
let (sign, u) = parseint(s, b)?;
|
||||
// Two's complement: I64_MIN = -I64_MAX - 1. Hare's two if-expressions
|
||||
// (stoi.ha:12,16) are lowered to statement-if — ww has no
|
||||
// if-expression (standing divergence, see the note in stof.ww:31).
|
||||
let max: u64 = 9223372036854775807u64;
|
||||
if (sign) {
|
||||
max = max + 1u64;
|
||||
};
|
||||
if (u > max) {
|
||||
return overflow{};
|
||||
};
|
||||
let r: i64 = u: i64;
|
||||
if (sign) {
|
||||
r = -r;
|
||||
};
|
||||
return r;
|
||||
};
|
||||
|
||||
// stou64 — parse unsigned base-b number. Verbatim port of
|
||||
// ref/hare/strconv/stou.ha:70-76.
|
||||
export fn stou64(s: str, b: base) (u64 | invalid | overflow) = {
|
||||
let (sign, u) = parseint(s, b)?;
|
||||
if (sign) {
|
||||
return overflow{};
|
||||
};
|
||||
return u;
|
||||
};
|
||||
|
||||
export fn stoi32(s: str, b: base) (i32 | invalid | overflow) = {
|
||||
|
||||
@@ -1258,8 +1258,11 @@ static const struct row rows[] = {
|
||||
" };\n"
|
||||
" return acc;\n"
|
||||
"};", 35 }, /* 42 + (-7) + 0 (invalid at index 0 in \"abc\") */
|
||||
/* strconv.stou64: success path; leading-sign rejected with
|
||||
* invalid carrying the offending index. */
|
||||
/* strconv.stou64: success path; a leading '-' is rejected with
|
||||
* overflow per Hare (ref/hare/strconv/stou.ha:72-74 — parseint
|
||||
* accepts the sign, stou64 then rejects sign==true as overflow).
|
||||
* Updated from the prior ad-hoc parse, which mis-reported it as
|
||||
* invalid(index 0) (strconv-int fold-1 fidelity fix). */
|
||||
{ "import strconv;\n"
|
||||
"type r_t = (u64 | strconv.invalid | strconv.overflow);\n"
|
||||
"fn main() i32 = {\n"
|
||||
@@ -1272,12 +1275,12 @@ static const struct row rows[] = {
|
||||
" case let e: strconv.overflow => acc += -200;\n"
|
||||
" };\n"
|
||||
" match (r2) {\n"
|
||||
" case let v: u64 => acc += -100;\n"
|
||||
" case let e: strconv.invalid => acc += e: i32;\n"
|
||||
" case let e: strconv.overflow => acc += -200;\n"
|
||||
" case let v: u64 => acc += 100;\n"
|
||||
" case let e: strconv.invalid => acc += 200;\n"
|
||||
" case let e: strconv.overflow => acc += 7;\n"
|
||||
" };\n"
|
||||
" return acc;\n"
|
||||
"};", 123 }, /* 123 + 0 (invalid at index 0 in \"-1\") */
|
||||
"};", 130 }, /* 123 + 7: \"-1\" is overflow (not invalid/success) */
|
||||
/* strings.byteindex with (str | rune) needle: returns (i32 | void). */
|
||||
{ "import strings;\n"
|
||||
"fn pick(r: (i32 | void), miss: i32) i32 = {\n"
|
||||
|
||||
54
test/wcc/922_strconv_int_run.c
Normal file
54
test/wcc/922_strconv_int_run.c
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 922_strconv_int_run — execute the lib/strconv/test/inttest fixture
|
||||
* under the C-side `ww run` driver and assert exit 0.
|
||||
*
|
||||
* Same thin-wrapper shape as 922_decimal_run / 909_stof_run: inttest.ww
|
||||
* carries its own `export fn main()` that drives the @test fns and
|
||||
* signals which case failed via the exit code (signalled + 10), so a
|
||||
* non-zero exit pinpoints the failing scenario.
|
||||
*
|
||||
* The fixture lives in lib/strconv/test/ (not lib/strconv/) so
|
||||
* `import strconv` resolves to the lib/strconv DIRECTORY rather than
|
||||
* shadowing on the strconv.ww FILE — same rationale as 922_decimal_run.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/strconv/test/inttest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "strconv_int_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("strconv_int_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user