commit e7705d104acf25a709e9d873a5bdd54f4a664403
parent 7ef5e4df3b35586fd09c4bab3e54b0cfcfbda6d4
Author: Joe Finney <me@spxtr.net>
Date: Thu, 22 Feb 2024 15:14:28 +0000
strconv: Simplify and fix bugs in stoi/stou.
Previously, stou("+") == stoi("-+") == 0, when they should have been invalid.
Also, stoi("-x") was 0: invalid instead of 1: invalid.
Signed-off-by: Joe Finney <me@spxtr.net>
Diffstat:
4 files changed, 47 insertions(+), 48 deletions(-)
diff --git a/strconv/+test/stoi_test.ha b/strconv/+test/stoi_test.ha
@@ -4,9 +4,12 @@
use types;
@test fn stoi() void = {
- assert(stoi64("") as invalid == 0: invalid);
- assert(stoi64("abc") as invalid == 0: invalid);
- assert(stoi64("1a") as invalid == 1: invalid);
+ assert(stoi64("") as invalid == 0);
+ assert(stoi64("abc") as invalid == 0);
+ assert(stoi64("1a") as invalid == 1);
+ assert(stoi64("+") as invalid == 1);
+ assert(stoi64("-+") as invalid == 1);
+ assert(stoi64("-z") as invalid == 1);
assert(stoi64("9223372036854775808") is overflow);
assert(stoi64("-9223372036854775809") is overflow);
diff --git a/strconv/+test/stou_test.ha b/strconv/+test/stou_test.ha
@@ -2,9 +2,11 @@
// (c) Hare authors <https://harelang.org>
@test fn stou() void = {
- assert(stou64("") as invalid == 0: invalid);
- assert(stou64("abc") as invalid == 0: invalid);
- assert(stou64("1a") as invalid == 1: invalid);
+ assert(stou64("") as invalid == 0);
+ assert(stou64("+") as invalid == 1);
+ assert(stou64("+a") as invalid == 1);
+ assert(stou64("abc") as invalid == 0);
+ assert(stou64("1a") as invalid == 1);
assert(stou64("18446744073709551616") is overflow);
assert(stou64("184467440737095516150") is overflow);
diff --git a/strconv/stoi.ha b/strconv/stoi.ha
@@ -9,27 +9,13 @@ use types;
// [[invalid]] is returned. If the number is too large to be represented by an
// i64, [[overflow]] is returned.
export fn stoi64b(s: str, base: base) (i64 | invalid | overflow) = {
- if (len(s) == 0) return 0: invalid;
- let b = strings::toutf8(s);
- let sign = 1i64;
- let max = types::I64_MAX: u64;
- let start = 0z;
- if (b[0] == '-') {
- sign = -1;
- max += 1;
- start = 1;
- } else if (b[0] == '+') {
- start = 1;
- };
- if (base == base::DEFAULT) {
- base = base::DEC;
- };
- let u = stou64b(strings::fromutf8_unsafe(b[start..]), base);
- let n = u?;
- if (n > max) {
+ let (sign, u) = parseint(s, base)?;
+ // Two's complement: I64_MIN = -I64_MAX - 1
+ let max = if (sign) types::I64_MAX: u64 + 1 else types::I64_MAX: u64;
+ if (u > max) {
return overflow;
};
- return n: i64 * sign;
+ return u: i64 * (if (sign) -1 else 1);
};
// Converts a string to an i32 in the given base. If the string contains any
diff --git a/strconv/stou.ha b/strconv/stou.ha
@@ -15,10 +15,7 @@ fn rune_to_integer(r: rune) (u64 | void) = {
return (r: u32 - 'A'): u64 + 10;
};
-// Converts a string to a u64 in the given base, If the string contains any
-// non-numeric characters, or if it's empty, [[invalid]] is returned. If the
-// number is too large to be represented by a u64, [[overflow]] is returned.
-export fn stou64b(s: str, base: base) (u64 | invalid | overflow) = {
+fn parseint(s: str, base: base) ((bool, u64) | invalid | overflow) = {
if (base == base::DEFAULT) {
base = base::DEC;
};
@@ -27,34 +24,34 @@ export fn stou64b(s: str, base: base) (u64 | invalid | overflow) = {
if (len(s) == 0) {
return 0: invalid;
};
- if (strings::hasprefix(s, "-")) {
- return overflow;
- } else if (strings::hasprefix(s, "+")) {
- s = strings::trimprefix(s, "+");
+
+ let buf = strings::toutf8(s);
+ let i = 0z;
+
+ let sign = buf[i] == '-';
+ if (sign || buf[i] == '+') {
+ i += 1;
};
- let n = 0u64;
- let iter = strings::iter(s);
- for (true) {
- let r: rune = match (strings::next(&iter)) {
- case let r: rune =>
- yield r;
- case void =>
- break;
- };
+ // Require at least one digit.
+ if (i == len(buf)) {
+ return i: invalid;
+ };
- let digit = match (rune_to_integer(r)) {
+ let n = 0u64;
+ for (i < len(buf); i += 1) {
+ const digit = match (rune_to_integer(buf[i]: rune)) {
case void =>
- return (iter.dec.offs - 1): invalid;
+ return i: invalid;
case let d: u64 =>
yield d;
};
- if (digit >= base: u64) {
- return (iter.dec.offs - 1): invalid;
+ if (digit >= base) {
+ return i: invalid;
};
- let old = n;
+ const old = n;
n *= base;
n += digit;
@@ -63,7 +60,18 @@ export fn stou64b(s: str, base: base) (u64 | invalid | overflow) = {
return overflow;
};
};
- return n;
+ return (sign, n);
+};
+
+// Converts a string to a u64 in the given base, If the string contains any
+// non-numeric characters, or if it's empty, [[invalid]] is returned. If the
+// number is too large to be represented by a u64, [[overflow]] is returned.
+export fn stou64b(s: str, base: base) (u64 | invalid | overflow) = {
+ let (sign, u) = parseint(s, base)?;
+ if (sign) {
+ return overflow;
+ };
+ return u;
};
// Converts a string to a u32 in the given base, If the string contains any