hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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:
Mstrconv/+test/stoi_test.ha | 9++++++---
Mstrconv/+test/stou_test.ha | 8+++++---
Mstrconv/stoi.ha | 24+++++-------------------
Mstrconv/stou.ha | 54+++++++++++++++++++++++++++++++-----------------------
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