Skip to content

Commit

Permalink
hongbo/strconv use error (#601)
Browse files Browse the repository at this point in the history
* adapt new error type

* reverted to ! when we change the type of `test` block to Error

* update int_test

* revert to ! when test block change its type

* update test

* promote, mbti does not support Error type yet

* fmt
  • Loading branch information
bobzhang committed Jun 25, 2024
1 parent 46a837e commit 909d01a
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 109 deletions.
7 changes: 4 additions & 3 deletions json/lex_number.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,9 @@ fn lex_number_end(
end : Int
) -> Result[Double, ParseError] {
let s = ctx.input.substring(~start, ~end)
match @strconv.parse_double(s) {
Ok(d) => Ok(d)
Err(_) => Err(InvalidNumber(offset_to_position(ctx.input, start), s))
try {
@strconv.parse_double(s)! |> Ok
} catch {
_ => Err(InvalidNumber(offset_to_position(ctx.input, start), s))
}
}
19 changes: 14 additions & 5 deletions strconv/bool.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@
// limitations under the License.

/// Parse a string and return the represented boolean value or an error.
pub fn parse_bool(str : String) -> Result[Bool, String] {
pub fn parse_bool(str : String) -> Bool!String {
match str {
"1" | "t" | "T" | "true" | "TRUE" | "True" => Ok(true)
"0" | "f" | "F" | "false" | "FALSE" | "False" => Ok(false)
_ => Err(syntax_err)
"1" | "t" | "T" | "true" | "TRUE" | "True" => true
"0" | "f" | "F" | "false" | "FALSE" | "False" => false
_ => {
raise syntax_err
}
}
}

Expand All @@ -40,6 +42,13 @@ test "parse_bool" {
]
for i = 0; i < tests.length(); i = i + 1 {
let t = tests[i]
@assertion.assert_eq(parse_bool(t.0), t.1)?
@assertion.assert_eq(
try {
Result::Ok(parse_bool(t.0)!)
} catch {
err => Err(err)
},
t.1,
)?
}
}
37 changes: 18 additions & 19 deletions strconv/decimal.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,24 @@ pub fn Decimal::from_int64(v : Int64) -> Decimal {
}

/// Create a decimal from number string.
pub fn Decimal::parse_decimal(str : String) -> Result[Decimal, String] {
pub fn Decimal::parse_decimal(str : String) -> Decimal!String {
let d = Decimal::new()
if str.length() <= 0 {
return Err(syntax_err)
raise syntax_err
}
let mut i = 0
// read digits part
let mut has_dp = false
let mut has_digits = false
while i < str.length() {
match str[i] {
'-' => if i != 0 { return Err(syntax_err) } else { d.negative = true }
'+' => if i != 0 { return Err(syntax_err) }
'-' => if i != 0 { raise syntax_err } else { d.negative = true }
'+' => if i != 0 { raise syntax_err }
'_' => () // skip underscores
'.' => {
// decimal point
if has_dp {
return Err(syntax_err)
raise syntax_err
}
has_dp = true
d.decimal_point = d.digits_num
Expand All @@ -98,7 +98,7 @@ pub fn Decimal::parse_decimal(str : String) -> Result[Decimal, String] {
i += 1
}
if not(has_digits) {
return Err(syntax_err)
raise syntax_err
}
if not(has_dp) {
d.decimal_point = d.digits_num
Expand All @@ -107,7 +107,7 @@ pub fn Decimal::parse_decimal(str : String) -> Result[Decimal, String] {
if i < str.length() && (str[i] == 'e' || str[i] == 'E') {
i += 1
if i >= str.length() {
return Err(syntax_err)
raise syntax_err
}
let mut exp_sign = 1
if str[i] == '+' {
Expand All @@ -117,7 +117,7 @@ pub fn Decimal::parse_decimal(str : String) -> Result[Decimal, String] {
exp_sign = -1
}
if i >= str.length() || str[i] < '0' || str[i] > '9' {
return Err(syntax_err)
raise syntax_err
}
let mut exp = 0
while i < str.length() && ('0' <= str[i] && str[i] <= '9' || str[i] == '_') {
Expand All @@ -133,14 +133,13 @@ pub fn Decimal::parse_decimal(str : String) -> Result[Decimal, String] {
// finish
d.trim()
if i != str.length() {
Err(syntax_err)
} else {
Ok(d)
raise syntax_err
}
d
}

/// Convert the decimal to Double.
pub fn to_double(self : Decimal) -> Result[Double, String] {
pub fn to_double(self : Decimal) -> Double!String {
let mut exponent = 0
let mut mantissa = 0L
// check the underflow and overflow
Expand All @@ -150,11 +149,11 @@ pub fn to_double(self : Decimal) -> Result[Double, String] {
mantissa = 0L
exponent = double_info.bias
let bits = assemble_bits(mantissa, exponent, self.negative)
return Ok(bits.reinterpret_as_double())
return bits.reinterpret_as_double()
}
if self.decimal_point > 310 {
// overflow
return Err(range_err)
raise range_err
}

// scale by powers of 2 until in range [0.5 .. 1]
Expand Down Expand Up @@ -195,7 +194,7 @@ pub fn to_double(self : Decimal) -> Result[Double, String] {
}
if exponent - double_info.bias >= (1).lsl(double_info.exponent_bits) - 1 {
// overflow
return Err(range_err)
raise range_err
}

// multiply by (2 ** precision) and round to get mantissa
Expand All @@ -209,7 +208,7 @@ pub fn to_double(self : Decimal) -> Result[Double, String] {
exponent += 1
if exponent - double_info.bias >= (1).lsl(double_info.exponent_bits) - 1 {
// overflow
return Err(range_err)
raise range_err
}
}

Expand All @@ -220,7 +219,7 @@ pub fn to_double(self : Decimal) -> Result[Double, String] {

// combining the 52 mantissa bits with the 11 exponent bits and 1 sign bit
let bits = assemble_bits(mantissa, exponent, self.negative)
Ok(bits.reinterpret_as_double())
bits.reinterpret_as_double()
}

/// Binary shift left (s > 0) or right (s < 0).
Expand Down Expand Up @@ -586,9 +585,9 @@ test "from_int64" {

test "parse_decimal" {
let s = "0.0000000000000000000000000000007888609052210118054117285652827862296732064351090230047702789306640625"
let hpd = Decimal::parse_decimal(s)?
let hpd = Decimal::parse_decimal(s)!!
@assertion.assert_eq(hpd.to_string(), s)?
let hpd = Decimal::parse_decimal("1.0e-10")?
let hpd = Decimal::parse_decimal("1.0e-10")!!
@assertion.assert_eq(hpd.to_string(), "0.0000000001")?
}

Expand Down
51 changes: 30 additions & 21 deletions strconv/double.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ let max_mantissa_fast_path : UInt64 = new_UInt64_Int(2).lsl(
mantissa_explicit_bits,
)

pub fn parse_double(str : String) -> Result[Double, String] {
pub fn parse_double(str : String) -> Double!String {
if str.length() == 0 {
return Err(syntax_err)
raise syntax_err
}
if not(check_underscore(str)) {
return Err(syntax_err)
raise syntax_err
}
// validate its a number
let (num, consumed) = match parse_number(str) {
Expand All @@ -54,23 +54,25 @@ pub fn parse_double(str : String) -> Result[Double, String] {
match parse_inf_nan(str) {
Some((num, consumed)) =>
if str.length() != consumed {
return Err(syntax_err)
raise syntax_err
} else {
return Ok(num)
return num
}
None => return Err(syntax_err)
None => {
raise syntax_err
}
}
}
if str.length() != consumed {
return Err(syntax_err)
raise syntax_err
}
// Clinger's fast path (How to read floating point numbers accurately)[https://doi.org/10.1145/989393.989430]
match try_fast_path(num) {
Some(value) => Ok(value)
Some(value) => value
None => {
// fallback to slow path
let ret = parse_decimal(str)?
ret.to_double()
let ret = parse_decimal(str)!
ret.to_double()!
}
}
}
Expand Down Expand Up @@ -248,22 +250,29 @@ test "parse_double" {
]
for i = 0; i < tests.length(); i = i + 1 {
let t = tests[i]
@assertion.assert_eq(parse_double(t.0), t.1)?
@assertion.assert_eq(
try {
Result::Ok(parse_double(t.0)!)
} catch {
err => Err(err)
},
t.1,
)?
}
}

test "parse_double_inf" {
@assertion.assert_eq(parse_double("inf"), Ok(Double::inf(1)))?
@assertion.assert_eq(parse_double("+Inf"), Ok(Double::inf(1)))?
@assertion.assert_eq(parse_double("-Inf"), Ok(Double::inf(-1)))?
@assertion.assert_eq(parse_double("+Infinity"), Ok(Double::inf(1)))?
@assertion.assert_eq(parse_double("-Infinity"), Ok(Double::inf(-1)))?
@assertion.assert_eq(parse_double("+INFINITY"), Ok(Double::inf(1)))?
@assertion.assert_eq(parse_double("-INFINITY"), Ok(Double::inf(-1)))?
@assertion.assert_eq(parse_double("inf")!!, Double::inf(1))?
@assertion.assert_eq(parse_double("+Inf")!!, Double::inf(1))?
@assertion.assert_eq(parse_double("-Inf")!!, Double::inf(-1))?
@assertion.assert_eq(parse_double("+Infinity")!!, Double::inf(1))?
@assertion.assert_eq(parse_double("-Infinity")!!, Double::inf(-1))?
@assertion.assert_eq(parse_double("+INFINITY")!!, Double::inf(1))?
@assertion.assert_eq(parse_double("-INFINITY")!!, Double::inf(-1))?
}

test "parse_double_nan" {
@assertion.assert_true(parse_double("nan")?.is_nan())?
@assertion.assert_true(parse_double("NaN")?.is_nan())?
@assertion.assert_true(parse_double("NAN")?.is_nan())?
@assertion.assert_true(parse_double("nan")!!.is_nan())?
@assertion.assert_true(parse_double("NaN")!!.is_nan())?
@assertion.assert_true(parse_double("NAN")!!.is_nan())?
}
61 changes: 39 additions & 22 deletions strconv/int.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ let int64_max = 0x7fffffffffffffffL

/// Parse a string in the given base (0, 2 to 36), return a Int64 number or an error.
/// If the `~base` argument is 0, the base will be inferred by the prefix.
pub fn parse_int64(str : String, ~base : Int = 0) -> Result[Int64, String] {
pub fn parse_int64(str : String, ~base : Int = 0) -> Int64!String {
if str == "" {
return Err(syntax_err)
raise syntax_err
}
// check underscore
if not(check_underscore(str)) {
return Err(syntax_err)
raise syntax_err
}
let mut s = str
// check sign
Expand All @@ -48,7 +48,7 @@ pub fn parse_int64(str : String, ~base : Int = 0) -> Result[Int64, String] {
} else if 2 <= base && base <= 36 {
num_base = base
} else {
return Err(base_err)
raise base_err
}

// calculate overflow threshold
Expand All @@ -69,46 +69,42 @@ pub fn parse_int64(str : String, ~base : Int = 0) -> Result[Int64, String] {
} else if 'A' <= c && c <= 'Z' {
d = c.to_int() - 'A'.to_int() + 10
} else {
return Err(syntax_err)
raise syntax_err
}
if d >= num_base {
return Err(syntax_err)
raise syntax_err
}
if neg && n < overflow_threshold || not(neg) && n >= overflow_threshold {
return Err(range_err)
raise range_err
}
// n*base overflows
n *= num_base.to_int64()
if neg {
let n1 = n - d.to_int64()
if n1 > n {
return Err(range_err)
raise range_err
}
n = n1
} else {
let n1 = n + d.to_int64()
if n1 < n {
// n+d overflows
return Err(range_err)
raise range_err
}
n = n1
}
}
Ok(n)
n
}

/// 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 : String, ~base : Int = 0) -> Result[Int, String] {
match parse_int64(str, ~base) {
Ok(n) => {
if n < int_min.to_int64() || n > int_max.to_int64() {
return Err(range_err)
}
Ok(n.to_int())
}
Err(err) => Err(err)
pub fn parse_int(str : String, ~base : Int = 0) -> Int!String {
let n = parse_int64(str, ~base)!
if n < int_min.to_int64() || n > int_max.to_int64() {
raise range_err
}
n.to_int()
}

// Check whether the underscores are correct.
Expand Down Expand Up @@ -245,7 +241,14 @@ test "parse_int64" {
]
for i = 0; i < tests.length(); i = i + 1 {
let t = tests[i]
@assertion.assert_eq(parse_int64(t.0), t.1)?
@assertion.assert_eq(
try {
Result::Ok(parse_int64(t.0)!)
} catch {
err => Err(err)
},
t.1,
)?
}
}

Expand Down Expand Up @@ -349,7 +352,14 @@ test "parse_int64_base" {
]
for i = 0; i < tests.length(); i = i + 1 {
let t = tests[i]
@assertion.assert_eq(parse_int64(t.0, base=t.1), t.2)?
@assertion.assert_eq(
try {
Result::Ok(parse_int64(t.0, base=t.1)!)
} catch {
err => Err(err)
},
t.2,
)?
}
}

Expand Down Expand Up @@ -383,6 +393,13 @@ test "parse_int" {
]
for i = 0; i < tests.length(); i = i + 1 {
let t = tests[i]
@assertion.assert_eq(parse_int(t.0), t.1)?
@assertion.assert_eq(
try {
Result::Ok(parse_int(t.0)!)
} catch {
err => Err(err)
},
t.1,
)?
}
}
Loading

0 comments on commit 909d01a

Please sign in to comment.