diff --git a/internal/strconv/moon.pkg b/internal/strconv/moon.pkg index b7b17e497..1ff34d3b6 100644 --- a/internal/strconv/moon.pkg +++ b/internal/strconv/moon.pkg @@ -1,7 +1,7 @@ import { "moonbitlang/core/builtin", + "moonbitlang/core/debug", "moonbitlang/core/double", - "moonbitlang/core/error", "moonbitlang/core/uint64", "moonbitlang/core/char", "moonbitlang/core/array", diff --git a/internal/strconv/pkg.generated.mbti b/internal/strconv/pkg.generated.mbti index 9106748e3..7c12cfdb8 100644 --- a/internal/strconv/pkg.generated.mbti +++ b/internal/strconv/pkg.generated.mbti @@ -1,20 +1,33 @@ // Generated using `moon info`, DON'T EDIT IT package "moonbitlang/core/internal/strconv" +import { + "moonbitlang/core/debug", +} + // Values -pub fn parse_bool(StringView) -> Bool raise +pub fn parse_bool(StringView) -> Bool raise StrConvError -pub fn parse_double(StringView) -> Double raise +pub fn parse_double(StringView) -> Double raise StrConvError -pub fn parse_int(StringView, base? : Int) -> Int raise +pub fn parse_int(StringView, base? : Int) -> Int raise StrConvError -pub fn parse_int64(StringView, base? : Int) -> Int64 raise +pub fn parse_int64(StringView, base? : Int) -> Int64 raise StrConvError -pub fn parse_uint(StringView, base? : Int) -> UInt raise +pub fn parse_uint(StringView, base? : Int) -> UInt raise StrConvError -pub fn parse_uint64(StringView, base? : Int) -> UInt64 raise +pub fn parse_uint64(StringView, base? : Int) -> UInt64 raise StrConvError // Errors +pub(all) suberror StrConvError { + RangeError + SyntaxError + InvalidBase +} derive(Eq, @debug.Debug) +pub fn StrConvError::equal(Self, Self) -> Bool +pub fn StrConvError::message(Self) -> String +pub fn StrConvError::not_equal(Self, Self) -> Bool +pub fn StrConvError::to_repr(Self) -> @debug.Repr // Types and methods diff --git a/internal/strconv/strconv_bool.mbt b/internal/strconv/strconv_bool.mbt index c1880e3a1..4ee33777d 100644 --- a/internal/strconv/strconv_bool.mbt +++ b/internal/strconv/strconv_bool.mbt @@ -14,7 +14,7 @@ ///| /// Parse a string and return the represented boolean value or an error. -pub fn parse_bool(str : StringView) -> Bool raise { +pub fn parse_bool(str : StringView) -> Bool raise StrConvError { lexscan str with longest { re"^(true|TRUE|True|t|T|1)$" => true re"^(false|FALSE|False|f|F|0)$" => false @@ -24,9 +24,9 @@ pub fn parse_bool(str : StringView) -> Bool raise { ///| test "parse_bool" { - let tests : Array[(String, Result[Bool, String])] = [ - ("", Err(syntax_err_str)), - ("zutomayo", Err(syntax_err_str)), + let tests : Array[(String, Result[Bool, StrConvError])] = [ + ("", Err(SyntaxError)), + ("zutomayo", Err(SyntaxError)), ("0", Ok(false)), ("f", Ok(false)), ("F", Ok(false)), @@ -41,12 +41,6 @@ test "parse_bool" { ("True", Ok(true)), ] for t in tests { - @test.assert_eq( - Result::Ok(parse_bool(t.0)) catch { - Failure::Failure(err) => Err(err) - err => Err(err.to_string()) - }, - t.1, - ) + @test.assert_eq(Result::Ok(parse_bool(t.0)) catch { err => Err(err) }, t.1) } } diff --git a/internal/strconv/strconv_coverage_test.mbt b/internal/strconv/strconv_coverage_test.mbt index 74ac0c5ce..8fad61e2b 100644 --- a/internal/strconv/strconv_coverage_test.mbt +++ b/internal/strconv/strconv_coverage_test.mbt @@ -18,38 +18,35 @@ ///| /// Whether parsing `s` as an Int64 (in the given base) raises the expected -/// `Failure` (any other error propagates so it is not masked as a parse error). -fn int64_raises(s : String, base : Int) -> Bool raise { +/// `StrConvError`. +fn int64_raises(s : String, base : Int) -> Bool { try { @strconv.parse_int64(s, base~) |> ignore false } catch { - Failure::Failure(_) => true - err => raise err + RangeError | SyntaxError | InvalidBase => true } } ///| -/// Whether parsing `s` as a Double raises the expected `Failure`. -fn double_raises(s : String) -> Bool raise { +/// Whether parsing `s` as a Double raises the expected `StrConvError`. +fn double_raises(s : String) -> Bool { try { @strconv.parse_double(s) |> ignore false } catch { - Failure::Failure(_) => true - err => raise err + RangeError | SyntaxError | InvalidBase => true } } ///| -/// Whether parsing `s` as an Int (32-bit) raises the expected `Failure`. -fn int_raises(s : String) -> Bool raise { +/// Whether parsing `s` as an Int (32-bit) raises the expected `StrConvError`. +fn int_raises(s : String) -> Bool { try { @strconv.parse_int(s) |> ignore false } catch { - Failure::Failure(_) => true - err => raise err + RangeError | SyntaxError | InvalidBase => true } } diff --git a/internal/strconv/strconv_decimal.mbt b/internal/strconv/strconv_decimal.mbt index de64adce7..edd85b5c0 100644 --- a/internal/strconv/strconv_decimal.mbt +++ b/internal/strconv/strconv_decimal.mbt @@ -54,12 +54,12 @@ fn Decimal::from_int64_priv(v : Int64) -> Decimal { } ///| -fn parse_decimal_priv(str : StringView) -> Decimal raise { +fn parse_decimal_priv(str : StringView) -> Decimal raise StrConvError { parse_decimal_from_view(str) } ///| -fn parse_decimal_from_view(str : StringView) -> Decimal raise { +fn parse_decimal_from_view(str : StringView) -> Decimal raise StrConvError { let d = Decimal::new_priv() let mut has_dp = false let mut has_digits = false @@ -172,7 +172,7 @@ fn parse_decimal_from_view(str : StringView) -> Decimal raise { } ///| -fn Decimal::to_double_priv(self : Decimal) -> Double raise { +fn Decimal::to_double_priv(self : Decimal) -> Double raise StrConvError { let mut exponent = 0 let mut mantissa = 0L // check the underflow and overflow @@ -679,25 +679,18 @@ test "parse decimal with underscore" { ///| test "parse decimal error" { + assert_true(@test.expect_error(() => parse_decimal_priv("1e")) is SyntaxError) assert_true( - @test.expect_error(() => parse_decimal_priv("1e")) - is Failure("invalid syntax"), + @test.expect_error(() => parse_decimal_priv("1e+")) is SyntaxError, ) assert_true( - @test.expect_error(() => parse_decimal_priv("1e+")) - is Failure("invalid syntax"), + @test.expect_error(() => parse_decimal_priv("1e_")) is SyntaxError, ) assert_true( - @test.expect_error(() => parse_decimal_priv("1e_")) - is Failure("invalid syntax"), + @test.expect_error(() => parse_decimal_priv("1-23")) is SyntaxError, ) assert_true( - @test.expect_error(() => parse_decimal_priv("1-23")) - is Failure("invalid syntax"), - ) - assert_true( - @test.expect_error(() => parse_decimal_priv("1.2.3")) - is Failure("invalid syntax"), + @test.expect_error(() => parse_decimal_priv("1.2.3")) is SyntaxError, ) } @@ -744,14 +737,8 @@ test "rounded_integer overflow when decimal_point > 20" { ///| test "corner cases" { let _ = parse_decimal_priv(".123") - assert_true( - @test.expect_error(() => parse_decimal_priv(".")) - is Failure("invalid syntax"), - ) - assert_true( - @test.expect_error(() => parse_decimal_priv("-")) - is Failure("invalid syntax"), - ) + assert_true(@test.expect_error(() => parse_decimal_priv(".")) is SyntaxError) + assert_true(@test.expect_error(() => parse_decimal_priv("-")) is SyntaxError) } ///| @@ -776,14 +763,14 @@ test "parse_double large magnitude cancellation" { let input = "1" + String::make(399, '0') + ".5" try parse_double(input) catch { - e => inspect(e, content="Failure(value out of range)") + e => @debug.debug_inspect(e, content="RangeError") } noraise { _ => fail("expected parse_double to raise") } let input = "1" + String::make(800, '0') + ".5" try parse_double(input) catch { - e => inspect(e, content="Failure(value out of range)") + e => @debug.debug_inspect(e, content="RangeError") } noraise { _ => fail("expected parse_double to raise") } diff --git a/internal/strconv/strconv_double.mbt b/internal/strconv/strconv_double.mbt index 156a47328..53384afe0 100644 --- a/internal/strconv/strconv_double.mbt +++ b/internal/strconv/strconv_double.mbt @@ -69,7 +69,7 @@ let max_mantissa_fast_path : UInt64 = 2UL << mantissa_explicit_bits /// /// An exponent value exp scales the mantissa (significand) by 10^exp. /// For example, "1.23e2" represents 1.23 × 10² = 123. -pub fn parse_double(str : StringView) -> Double raise { +pub fn parse_double(str : StringView) -> Double raise StrConvError { guard !str.is_empty() else { syntax_err() } guard check_underscore(str) else { syntax_err() } // validate its a number @@ -149,13 +149,13 @@ fn Number::try_fast_path(self : Number) -> Double? { ///| test "parse_double" { - let tests : Array[(String, Result[Double, String])] = [ - ("", Err(syntax_err_str)), - ("1x", Err(syntax_err_str)), - ("1.1.", Err(syntax_err_str)), - ("1e", Err(syntax_err_str)), - ("1e-", Err(syntax_err_str)), - (".e-1", Err(syntax_err_str)), + let tests : Array[(String, Result[Double, StrConvError])] = [ + ("", Err(SyntaxError)), + ("1x", Err(SyntaxError)), + ("1.1.", Err(SyntaxError)), + ("1e", Err(SyntaxError)), + ("1e-", Err(SyntaxError)), + (".e-1", Err(SyntaxError)), ("1", Ok(1.0)), ("+1", Ok(1.0)), ("1e23", Ok(1.0e23)), @@ -202,26 +202,14 @@ test "parse_double" { ("1.7976931348623158e308", Ok(1.7976931348623157e308)), ("-1.7976931348623158e308", Ok(-1.7976931348623157e308)), ("1e308", Ok(1.0e308)), - ( - "1.7976931348623159e308", - Err( - // zeros - // large double - range_err_str, - ), - ), - ( - "-1.7976931348623159e308", - Err( - // overflow - range_err_str, - ), - ), - ("2e308", Err(range_err_str)), - ("1e309", Err(range_err_str)), - ("1e310", Err(range_err_str)), - ("1e400", Err(range_err_str)), - ("1e40000", Err(range_err_str)), + // zeros / large double overflow + ("1.7976931348623159e308", Err(RangeError)), + ("-1.7976931348623159e308", Err(RangeError)), + ("2e308", Err(RangeError)), + ("1e309", Err(RangeError)), + ("1e310", Err(RangeError)), + ("1e400", Err(RangeError)), + ("1e40000", Err(RangeError)), // denormalized ("1e-305", Ok(1.0e-305)), ("1e-306", Ok(1.0e-306)), @@ -240,26 +228,25 @@ test "parse_double" { ("1e-400000", Ok(0.0)), // underscores ("1_23.50_0_0e+1_2", Ok(1.235e+14)), - ("-_123.5e+12", Err(syntax_err_str)), - ("+_123.5e+12", Err(syntax_err_str)), - ("_123.5e+12", Err(syntax_err_str)), - ("1__23.5e+12", Err(syntax_err_str)), - ("123_.5e+12", Err(syntax_err_str)), - ("123._5e+12", Err(syntax_err_str)), - ("123.5_e+12", Err(syntax_err_str)), - ("123.5__0e+12", Err(syntax_err_str)), - ("123.5e_+12", Err(syntax_err_str)), - ("123.5e+_12", Err(syntax_err_str)), - ("123.5e_-12", Err(syntax_err_str)), - ("123.5e-_12", Err(syntax_err_str)), - ("123.5e+1__2", Err(syntax_err_str)), - ("123.5e+12_", Err(syntax_err_str)), + ("-_123.5e+12", Err(SyntaxError)), + ("+_123.5e+12", Err(SyntaxError)), + ("_123.5e+12", Err(SyntaxError)), + ("1__23.5e+12", Err(SyntaxError)), + ("123_.5e+12", Err(SyntaxError)), + ("123._5e+12", Err(SyntaxError)), + ("123.5_e+12", Err(SyntaxError)), + ("123.5__0e+12", Err(SyntaxError)), + ("123.5e_+12", Err(SyntaxError)), + ("123.5e+_12", Err(SyntaxError)), + ("123.5e_-12", Err(SyntaxError)), + ("123.5e-_12", Err(SyntaxError)), + ("123.5e+1__2", Err(SyntaxError)), + ("123.5e+12_", Err(SyntaxError)), ] for t in tests { @test.assert_eq( Result::Ok(parse_double(t.0)) catch { - Failure::Failure(err) => Err(err) - err => Err(err.to_string()) + err => Err(err) }, t.1, ) diff --git a/internal/strconv/strconv_errors.mbt b/internal/strconv/strconv_errors.mbt index 95d2b4f45..469b27603 100644 --- a/internal/strconv/strconv_errors.mbt +++ b/internal/strconv/strconv_errors.mbt @@ -13,25 +13,46 @@ // limitations under the License. ///| -let range_err_str = "value out of range" +/// Structured parse errors for the internal strconv helpers. +/// +/// In-module callers (e.g. the JSON number lexer) match on the variants to +/// distinguish out-of-range values from syntax problems instead of comparing +/// failure message strings. The public wrappers in `moonbitlang/core/string` +/// translate these back into `Failure` with the historical message strings, +/// so externally observable behavior is unchanged. +pub(all) suberror StrConvError { + RangeError + SyntaxError + InvalidBase +} derive(Eq, @debug.Debug) ///| -let syntax_err_str = "invalid syntax" +pub extend StrConvError with Eq::{equal, not_equal} ///| -let base_err_str = "invalid base" +pub extend StrConvError with @debug.Debug::{to_repr} ///| -fn[T] range_err() -> T raise { - raise Failure::Failure(range_err_str) +/// The historical human-readable message for this error. +pub fn StrConvError::message(self : Self) -> String { + match self { + RangeError => "value out of range" + SyntaxError => "invalid syntax" + InvalidBase => "invalid base" + } } ///| -fn[T] syntax_err() -> T raise { - raise Failure::Failure(syntax_err_str) +fn[T] range_err() -> T raise StrConvError { + raise RangeError } ///| -fn[T] base_err() -> T raise { - raise Failure::Failure(base_err_str) +fn[T] syntax_err() -> T raise StrConvError { + raise SyntaxError +} + +///| +fn[T] base_err() -> T raise StrConvError { + raise InvalidBase } diff --git a/internal/strconv/strconv_int.mbt b/internal/strconv/strconv_int.mbt index a9299361e..9649a7785 100644 --- a/internal/strconv/strconv_int.mbt +++ b/internal/strconv/strconv_int.mbt @@ -31,7 +31,7 @@ const INT64_MAX = 0x7fffffffffffffffL fn check_and_consume_base( view : StringView, base : Int, -) -> (Int, StringView, Bool) raise { +) -> (Int, StringView, Bool) raise StrConvError { // if the base is not given, we need to determine it from the prefix if base == 0 { match view { @@ -54,16 +54,13 @@ fn check_and_consume_base( ///| test { assert_true( - @test.expect_error(() => parse_int64("0b01", base=3)) - is Failure("invalid syntax"), + @test.expect_error(() => parse_int64("0b01", base=3)) is SyntaxError, ) assert_true( - @test.expect_error(() => parse_int64("0x01", base=3)) - is Failure("invalid syntax"), + @test.expect_error(() => parse_int64("0x01", base=3)) is SyntaxError, ) assert_true( - @test.expect_error(() => parse_int64("0o01", base=3)) - is Failure("invalid syntax"), + @test.expect_error(() => parse_int64("0o01", base=3)) is SyntaxError, ) } @@ -91,7 +88,10 @@ test { /// } /// ``` /// -pub fn parse_int64(str : StringView, base? : Int = 0) -> Int64 raise { +pub fn parse_int64( + str : StringView, + base? : Int = 0, +) -> Int64 raise StrConvError { guard str != "" else { syntax_err() } let (neg, rest) = match str.view() { ['+', .. rest] => (false, rest) @@ -145,7 +145,7 @@ pub fn parse_int64(str : StringView, base? : Int = 0) -> Int64 raise { ///| /// Parse a string in the given base (0, 2 to 36), return a Int number or an error. /// If the `base` argument is 0, the base will be inferred by the prefix. -pub fn parse_int(str : StringView, base? : Int = 0) -> Int raise { +pub fn parse_int(str : StringView, base? : Int = 0) -> Int raise StrConvError { let n = parse_int64(str, base~) if n < INT_MIN.to_int64() || n > INT_MAX.to_int64() { range_err() diff --git a/internal/strconv/strconv_number.mbt b/internal/strconv/strconv_number.mbt index d79dbfac5..826e88de9 100644 --- a/internal/strconv/strconv_number.mbt +++ b/internal/strconv/strconv_number.mbt @@ -77,8 +77,8 @@ fn parse_scientific(s : StringView) -> (StringView, Int64)? { } ///| -/// Parse the number from the string, raising `Failure` if invalid. -fn parse_number(s : StringView) -> Number? raise { +/// Parse the number from the string, raising `StrConvError` if invalid. +fn parse_number(s : StringView) -> Number? raise StrConvError { let start = s // handle optional +/- sign @@ -166,8 +166,8 @@ fn parse_number(s : StringView) -> Number? raise { } ///| -/// Parse the number from the string, raising `Failure` if invalid. -fn parse_inf_nan(rest : StringView) -> Double raise { +/// Parse the number from the string, raising `StrConvError` if invalid. +fn parse_inf_nan(rest : StringView) -> Double raise StrConvError { let (pos, rest) = match rest { ['-', .. rest] => (false, rest) ['+', .. rest] | rest => (true, rest) diff --git a/internal/strconv/strconv_uint.mbt b/internal/strconv/strconv_uint.mbt index 6b10cea94..5ddb78b78 100644 --- a/internal/strconv/strconv_uint.mbt +++ b/internal/strconv/strconv_uint.mbt @@ -41,7 +41,10 @@ const UINT64_MAX : UInt64 = 0xffffffffffffffffUL /// } /// ``` /// -pub fn parse_uint64(str : StringView, base? : Int = 0) -> UInt64 raise { +pub fn parse_uint64( + str : StringView, + base? : Int = 0, +) -> UInt64 raise StrConvError { guard str != "" else { syntax_err() } if str is ['+' | '-', ..] { syntax_err() @@ -89,7 +92,7 @@ pub fn parse_uint64(str : StringView, base? : Int = 0) -> UInt64 raise { ///| /// Parse a string in the given base (0, 2 to 36), return an UInt number or an error. /// If the `base` argument is 0, the base will be inferred by the prefix. -pub fn parse_uint(str : StringView, base? : Int = 0) -> UInt raise { +pub fn parse_uint(str : StringView, base? : Int = 0) -> UInt raise StrConvError { let n = parse_uint64(str, base~) if n > UINT_MAX.to_uint64() { range_err() @@ -99,30 +102,29 @@ pub fn parse_uint(str : StringView, base? : Int = 0) -> UInt raise { ///| test "parse_uint64" { - let tests : Array[(String, Result[UInt64, String])] = [ - ("", Err(syntax_err_str)), + let tests : Array[(String, Result[UInt64, StrConvError])] = [ + ("", Err(SyntaxError)), ("0", Ok(0UL)), - ("-0", Err(syntax_err_str)), - ("+0", Err(syntax_err_str)), + ("-0", Err(SyntaxError)), + ("+0", Err(SyntaxError)), ("1", Ok(1UL)), - ("-1", Err(syntax_err_str)), + ("-1", Err(SyntaxError)), ("12345", Ok(12345UL)), - ("-12345", Err(syntax_err_str)), + ("-12345", Err(SyntaxError)), ("012345", Ok(12345UL)), ("9876543210", Ok(9876543210UL)), ("18446744073709551615", Ok(18446744073709551615UL)), - ("18446744073709551616", Err(range_err_str)), + ("18446744073709551616", Err(RangeError)), ("1_2_3_4_5", Ok(12345UL)), - ("_12345", Err(syntax_err_str)), - ("1__2345", Err(syntax_err_str)), - ("12345_", Err(syntax_err_str)), - ("12345%", Err(syntax_err_str)), + ("_12345", Err(SyntaxError)), + ("1__2345", Err(SyntaxError)), + ("12345_", Err(SyntaxError)), + ("12345%", Err(SyntaxError)), ] for t in tests { @test.assert_eq( Result::Ok(parse_uint64(t.0)) catch { - Failure::Failure(err) => Err(err) - err => Err(err.to_string()) + err => Err(err) }, t.1, ) @@ -131,8 +133,8 @@ test "parse_uint64" { ///| test "parse_uint64_base" { - let tests : Array[(String, Int, Result[UInt64, String])] = [ - ("", 0, Err(syntax_err_str)), + let tests : Array[(String, Int, Result[UInt64, StrConvError])] = [ + ("", 0, Err(SyntaxError)), ("0", 0, Ok(0UL)), ("1", 0, Ok(1UL)), ("12345", 0, Ok(12345UL)), @@ -141,9 +143,9 @@ test "parse_uint64_base" { ("9876543210", 0, Ok(9876543210UL)), ("18446744073709551615", 0, Ok(UINT64_MAX)), ("0xffffffffffffffff", 0, Ok(UINT64_MAX)), - ("18446744073709551616", 0, Err(range_err_str)), - ("12345x", 0, Err(syntax_err_str)), - ("-12345x", 0, Err(syntax_err_str)), + ("18446744073709551616", 0, Err(RangeError)), + ("12345x", 0, Err(SyntaxError)), + ("-12345x", 0, Err(SyntaxError)), // other bases ("h", 18, Ok(17UL)), ("10", 25, Ok(25UL)), @@ -175,7 +177,7 @@ test "parse_uint64_base" { ), // base 2 ("0", 2, Ok(0UL)), - ("-1", 2, Err(syntax_err_str)), + ("-1", 2, Err(SyntaxError)), ("1010", 2, Ok(10UL)), ("1000000000000000", 2, Ok(1UL << 15)), ( @@ -197,30 +199,29 @@ test "parse_uint64_base" { ("ffffffffffffffff", 16, Ok(UINT64_MAX)), // underscores ("0x_1_2_3_4_5", 0, Ok(0x12345UL)), - ("-_0x12345", 0, Err(syntax_err_str)), - ("_-0x12345", 0, Err(syntax_err_str)), - ("_0x12345", 0, Err(syntax_err_str)), - ("0x__12345", 0, Err(syntax_err_str)), - ("0x1__2345", 0, Err(syntax_err_str)), - ("0x1234__5", 0, Err(syntax_err_str)), - ("0x12345_", 0, Err(syntax_err_str)), + ("-_0x12345", 0, Err(SyntaxError)), + ("_-0x12345", 0, Err(SyntaxError)), + ("_0x12345", 0, Err(SyntaxError)), + ("0x__12345", 0, Err(SyntaxError)), + ("0x1__2345", 0, Err(SyntaxError)), + ("0x1234__5", 0, Err(SyntaxError)), + ("0x12345_", 0, Err(SyntaxError)), ("0_1_2_3_4_5", 0, Ok(12345UL)), - ("-_012345", 0, Err(syntax_err_str)), - ("_-012345", 0, Err(syntax_err_str)), - ("_012345", 0, Err(syntax_err_str)), - ("0__12345", 0, Err(syntax_err_str)), - ("01234__5", 0, Err(syntax_err_str)), - ("012345_", 0, Err(syntax_err_str)), + ("-_012345", 0, Err(SyntaxError)), + ("_-012345", 0, Err(SyntaxError)), + ("_012345", 0, Err(SyntaxError)), + ("0__12345", 0, Err(SyntaxError)), + ("01234__5", 0, Err(SyntaxError)), + ("012345_", 0, Err(SyntaxError)), ("0xf", 0, Ok(0xfUL)), - ("-0xf", 0, Err(syntax_err_str)), - ("0x+f", 0, Err(syntax_err_str)), - ("0x-f", 0, Err(syntax_err_str)), + ("-0xf", 0, Err(SyntaxError)), + ("0x+f", 0, Err(SyntaxError)), + ("0x-f", 0, Err(SyntaxError)), ] for t in tests { @test.assert_eq( Result::Ok(parse_uint64(t.0, base=t.1)) catch { - Failure::Failure(err) => Err(err) - err => Err(err.to_string()) + err => Err(err) }, t.2, ) @@ -229,36 +230,30 @@ test "parse_uint64_base" { ///| test "parse_uint" { - let tests : Array[(String, Result[UInt, String])] = [ - ("", Err(syntax_err_str)), + let tests : Array[(String, Result[UInt, StrConvError])] = [ + ("", Err(SyntaxError)), ("0", Ok(0)), - ("-0", Err(syntax_err_str)), - ("+0", Err(syntax_err_str)), + ("-0", Err(SyntaxError)), + ("+0", Err(SyntaxError)), ("1", Ok(1)), - ("-1", Err(syntax_err_str)), + ("-1", Err(SyntaxError)), ("12345", Ok(12345)), ("012345", Ok(12345)), - ("12345x", Err(syntax_err_str)), - ("-12345x", Err(syntax_err_str)), + ("12345x", Err(SyntaxError)), + ("-12345x", Err(SyntaxError)), ("987654321", Ok(987654321)), ("4294967295", Ok(UINT_MAX)), ("0xffffffff", Ok(UINT_MAX)), - ("4294967296", Err(range_err_str)), + ("4294967296", Err(RangeError)), ("1_2_3_4_5", Ok(12345)), - ("-_12345", Err(syntax_err_str)), - ("_12345", Err(syntax_err_str)), - ("1__2345", Err(syntax_err_str)), - ("12345_", Err(syntax_err_str)), - ("123%45", Err(syntax_err_str)), + ("-_12345", Err(SyntaxError)), + ("_12345", Err(SyntaxError)), + ("1__2345", Err(SyntaxError)), + ("12345_", Err(SyntaxError)), + ("123%45", Err(SyntaxError)), ] for t in tests { - @test.assert_eq( - Result::Ok(parse_uint(t.0)) catch { - Failure::Failure(err) => Err(err) - err => Err(err.to_string()) - }, - t.1, - ) + @test.assert_eq(Result::Ok(parse_uint(t.0)) catch { err => Err(err) }, t.1) } } @@ -266,7 +261,6 @@ test "parse_uint" { test "parse_uint64 uppercase hex and invalid base" { @test.assert_eq(parse_uint64("ABCD", base=16), 43981) assert_true( - @test.expect_error(() => parse_uint64("1234", base=37)) - is Failure("invalid base"), + @test.expect_error(() => parse_uint64("1234", base=37)) is InvalidBase, ) } diff --git a/json/from_json.mbt b/json/from_json.mbt index 72d2e05ec..38d4a3952 100644 --- a/json/from_json.mbt +++ b/json/from_json.mbt @@ -74,9 +74,8 @@ pub impl FromJson for Int64 with fn from_json(json, path) { ) } @internal/strconv.parse_int64(str) catch { - Failure::Failure(error) => - decode_error(path, "Int64::from_json: parsing failure \{error}") - error => decode_error(path, "Int64::from_json: parsing failure \{error}") + error => + decode_error(path, "Int64::from_json: parsing failure \{error.message()}") } } @@ -105,9 +104,11 @@ pub impl FromJson for UInt64 with fn from_json(json, path) { ) } @internal/strconv.parse_uint64(str) catch { - Failure::Failure(error) => - decode_error(path, "UInt64::from_json: parsing failure \{error}") - error => decode_error(path, "UInt64::from_json: parsing failure \{error}") + error => + decode_error( + path, + "UInt64::from_json: parsing failure \{error.message()}", + ) } } diff --git a/json/lex_number.mbt b/json/lex_number.mbt index 1155b74c8..189f2ddc9 100644 --- a/json/lex_number.mbt +++ b/json/lex_number.mbt @@ -246,12 +246,14 @@ fn ParseContext::lex_integer_end( let value = @internal/strconv.parse_double(s) return { value, repr: Some(s) } } catch { - _ => + RangeError => return if negative { { value: @double.neg_infinity, repr: Some(s) } } else { { value: @double.infinity, repr: Some(s) } } + // The lexer has already validated the number grammar. + SyntaxError | InvalidBase => abort("unreachable") } } continue i + 1, acc * 10L + digit @@ -418,12 +420,14 @@ fn ParseContext::lex_number_end( // For normal values, return without string representation { value: d, repr: None } } catch { - // If parsing fails as a double, treat it as infinity and preserve the string - _ => + // Values beyond double range become infinity and preserve the string + RangeError => if scan.negative { { value: @double.neg_infinity, repr: Some(s) } } else { { value: @double.infinity, repr: Some(s) } } + // The lexer has already validated the number grammar. + SyntaxError | InvalidBase => abort("unreachable") } } diff --git a/string/strconv.mbt b/string/strconv.mbt index 1abaae9ae..e5eae6729 100644 --- a/string/strconv.mbt +++ b/string/strconv.mbt @@ -12,12 +12,63 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Thin wrappers over the internal strconv helpers. The internal package +// raises a structured `StrConvError` so in-module callers can match on the +// failure kind; these wrappers translate it back into `Failure` with the +// historical message strings ("value out of range", "invalid syntax", +// "invalid base"), keeping the public error contract unchanged. + +///| +fn[T] with_legacy_failure( + run : () -> T raise @internal/strconv.StrConvError, +) -> T raise { + run() catch { + err => raise Failure::Failure(err.message()) + } +} + +///| +/// Parses a string as an `Int64` in the given base (0 infers the base from +/// a `0x`/`0o`/`0b` prefix). Raises `Failure` on invalid syntax, invalid +/// base, or out-of-range values. +pub fn parse_int64(str : StringView, base? : Int = 0) -> Int64 raise { + with_legacy_failure(() => @internal/strconv.parse_int64(str, base~)) +} + +///| +/// Parses a string as an `Int` in the given base (0 infers the base from +/// a `0x`/`0o`/`0b` prefix). Raises `Failure` on invalid syntax, invalid +/// base, or out-of-range values. +pub fn parse_int(str : StringView, base? : Int = 0) -> Int raise { + with_legacy_failure(() => @internal/strconv.parse_int(str, base~)) +} + +///| +/// Parses a string as a `UInt64` in the given base (0 infers the base from +/// a `0x`/`0o`/`0b` prefix). Raises `Failure` on invalid syntax, invalid +/// base, or out-of-range values. +pub fn parse_uint64(str : StringView, base? : Int = 0) -> UInt64 raise { + with_legacy_failure(() => @internal/strconv.parse_uint64(str, base~)) +} + +///| +/// Parses a string as a `UInt` in the given base (0 infers the base from +/// a `0x`/`0o`/`0b` prefix). Raises `Failure` on invalid syntax, invalid +/// base, or out-of-range values. +pub fn parse_uint(str : StringView, base? : Int = 0) -> UInt raise { + with_legacy_failure(() => @internal/strconv.parse_uint(str, base~)) +} + +///| +/// Parses `"true"` or `"false"` as a `Bool`. Raises `Failure` on any other +/// input. +pub fn parse_bool(str : StringView) -> Bool raise { + with_legacy_failure(() => @internal/strconv.parse_bool(str)) +} + ///| -pub using @internal/strconv { - parse_int64, - parse_int, - parse_uint64, - parse_uint, - parse_bool, - parse_double, +/// Parses a string as a `Double` with correct rounding. Raises `Failure` on +/// invalid syntax or values beyond double range. +pub fn parse_double(str : StringView) -> Double raise { + with_legacy_failure(() => @internal/strconv.parse_double(str)) }