diff --git a/strconv/double.mbt b/strconv/double.mbt index ef87c30ed..c2ac08c34 100644 --- a/strconv/double.mbt +++ b/strconv/double.mbt @@ -68,38 +68,18 @@ 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 StrConvError { - if str.length() == 0 { - syntax_err() - } - if !check_underscore(str) { - syntax_err() - } + guard str.length() > 0 else { syntax_err() } + guard check_underscore(str) else { syntax_err() } // validate its a number - let (num, consumed) = match parse_number(str) { - Some(r) => r - None => - match parse_inf_nan(str) { - Some((num, consumed)) => - if str.length() != consumed { - syntax_err() - } else { - return num - } - None => syntax_err() + match parse_number(str) { + None => parse_inf_nan(str) + Some(num) => + // Clinger's fast path (How to read floating point numbers accurately)[https://doi.org/10.1145/989393.989430] + match num.try_fast_path() { + Some(value) => value + None => parse_decimal_priv(str).to_double_priv() // fallback to slow path } } - if str.length() != consumed { - syntax_err() - } - // Clinger's fast path (How to read floating point numbers accurately)[https://doi.org/10.1145/989393.989430] - match num.try_fast_path() { - Some(value) => value - None => { - // fallback to slow path - let ret = parse_decimal_priv(str) - ret.to_double_priv() - } - } } ///| diff --git a/strconv/number.mbt b/strconv/number.mbt index d645c0981..2c2d373c8 100644 --- a/strconv/number.mbt +++ b/strconv/number.mbt @@ -48,11 +48,7 @@ fn try_parse_19digits(s : StringView, x : UInt64) -> (StringView, UInt64, Int) { ///| fn parse_scientific(s : StringView) -> (StringView, Int64)? { - // the first character is 'e'/'E' and scientific mode is enabled - let mut s = match s.step(1) { - Some(s) => s - None => return None - } + let mut s = s let exp_num = 0L let mut neg_exp = false if s is ['+' | '-' as ch, .. rest] { @@ -77,18 +73,14 @@ fn parse_scientific(s : StringView) -> (StringView, Int64)? { } ///| -/// Parse the number from the string, returning the number and the length of the parsed string. -fn parse_number(s : StringView) -> (Number, Int)? { - let mut s = s +/// Parse the number from the string, raising StrConvError if invalid. +fn parse_number(s : StringView) -> Number? raise StrConvError { let start = s // handle optional +/- sign - let mut negative = false - if s is ['-', .. rest] { - negative = true - s = rest - } else if s is ['+', .. rest] { - s = rest + let (s, negative) = match s { + ['-', .. rest] => (rest, true) + ['+', .. rest] | rest => (rest, false) } if s.is_empty() { return None @@ -121,26 +113,28 @@ fn parse_number(s : StringView) -> (Number, Int)? { // handle scientific format let exp_number = 0L - if s is ['e' | 'E', ..] { - let (new_s, exp_number) = match parse_scientific(s) { + if s is ['e' | 'E', .. rest] { + let (new_s, exp_number) = match parse_scientific(rest) { Some(res) => res None => return None } s = new_s exponent += exp_number } - let len = start.length() - s.length() + guard s is "" else { syntax_err() } // handle uncommon case with many digits if n_digits <= 19 { - return Some(({ exponent, mantissa, negative, many_digits: false }, len)) + return Some({ exponent, mantissa, negative, many_digits: false }) } n_digits -= 19 let mut many_digits = false - let mut p = start - while p is ['0' | '.' as ch, .. rest] { - n_digits -= (ch.to_int() - 46) / 2 // '0' = b'.' + 2 - p = rest + loop start { + ['0' | '.' as ch, .. rest] => { + n_digits -= (ch.to_int() - 46) / 2 // '0' = b'.' + 2 + continue rest + } + _ => () } let mut mantissa = mantissa if n_digits > 0 { @@ -153,44 +147,38 @@ fn parse_number(s : StringView) -> (Number, Int)? { exponent = (if mantissa >= min_19digit_int { consumed_digit // big int } else { - let s = match s.step(1) { - Some(s) => s - None => return None - } // fractional component, skip the '.' + // fractional component, skip the '.' + guard s is [_, .. s] else { return None } let (_, new_mantissa, consumed_digit) = try_parse_19digits(s, mantissa) mantissa = new_mantissa consumed_digit }).to_int64() exponent += exp_number } // add back the explicit part - Some(({ exponent, mantissa, negative, many_digits }, len)) + Some({ exponent, mantissa, negative, many_digits }) } ///| -/// Parse the number from the string, returning the number and the length of the parsed string. -fn parse_inf_nan(s : StringView) -> (Double, Int)? { - let mut s = s - let mut pos = true - let mut len = 0 - if s is ['-' | '+' as ch, .. rest] { - pos = ch == '+' - s = rest - len += 1 - } - if s is ['n' | 'N', 'a' | 'A', 'n' | 'N', ..] { - Some((@double.not_a_number, len + 3)) - } else if s is ['i' | 'I', 'n' | 'N', 'f' | 'F', .. rest] { - len += 3 - if rest is ['i' | 'I', 'n' | 'N', 'i' | 'I', 't' | 'T', 'y' | 'Y', ..] { - len += 5 +/// 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) + } + match rest { + ['n' | 'N', 'a' | 'A', 'n' | 'N'] => @double.not_a_number + ['i' | 'I', 'n' | 'N', 'f' | 'F', .. rest] => { + guard rest + is ([] | ['i' | 'I', 'n' | 'N', 'i' | 'I', 't' | 'T', 'y' | 'Y']) else { + syntax_err() + } + if pos { + @double.infinity + } else { + @double.neg_infinity + } } - if pos { - Some((@double.infinity, len)) - } else { - Some((@double.neg_infinity, len)) - } - } else { - None + _ => syntax_err() } } diff --git a/strconv/number_wbtest.mbt b/strconv/number_wbtest.mbt index 4f3197e94..cf832e852 100644 --- a/strconv/number_wbtest.mbt +++ b/strconv/number_wbtest.mbt @@ -14,136 +14,42 @@ ///| test "parse_inf_nan basic cases" { - // Test basic NaN parsing - let result_nan = parse_inf_nan("nan") - match result_nan { - Some((value, length)) => { - inspect(value.is_nan(), content="true") - inspect(length, content="3") - } - None => fail("Expected to parse 'nan'") - } - - // Test basic infinity parsing - let result_inf = parse_inf_nan("inf") - match result_inf { - Some((value, length)) => { - inspect(value.is_inf() && value > 0.0, content="true") - inspect(length, content="3") - } - None => fail("Expected to parse 'inf'") - } + inspect(parse_inf_nan("nan"), content="NaN") + inspect(parse_inf_nan("inf"), content="Infinity") } ///| test "parse_inf_nan with signs" { - // Test positive signed NaN - let result_pos_nan = parse_inf_nan("+nan") - match result_pos_nan { - Some((value, length)) => { - inspect(value.is_nan(), content="true") - inspect(length, content="4") - } - None => fail("Expected to parse '+nan'") - } - - // Test negative signed infinity - let result_neg_inf = parse_inf_nan("-inf") - match result_neg_inf { - Some((value, length)) => { - inspect(value.is_inf() && value < 0.0, content="true") - inspect(length, content="4") - } - None => fail("Expected to parse '-inf'") - } + inspect(parse_inf_nan("+nan"), content="NaN") + inspect(parse_inf_nan("-inf"), content="-Infinity") } ///| test "parse_inf_nan case insensitive" { - // Test uppercase - let result_uppercase = parse_inf_nan("NAN") - match result_uppercase { - Some((value, length)) => { - inspect(value.is_nan(), content="true") - inspect(length, content="3") - } - None => fail("Expected to parse 'NAN'") - } - - // Test mixed case - let result_mixed = parse_inf_nan("InF") - match result_mixed { - Some((value, length)) => { - inspect(value.is_inf() && value > 0.0, content="true") - inspect(length, content="3") - } - None => fail("Expected to parse 'InF'") - } + inspect(parse_inf_nan("NAN"), content="NaN") + inspect(parse_inf_nan("InF"), content="Infinity") } ///| test "parse_inf_nan with infinity suffix" { - // Test full infinity word - let result_infinity = parse_inf_nan("infinity") - match result_infinity { - Some((value, length)) => { - inspect(value.is_inf() && value > 0.0, content="true") - inspect(length, content="8") - } - None => fail("Expected to parse 'infinity'") - } - - // Test signed full infinity - let result_neg_infinity = parse_inf_nan("-INFINITY") - match result_neg_infinity { - Some((value, length)) => { - inspect(value.is_inf() && value < 0.0, content="true") - inspect(length, content="9") - } - None => fail("Expected to parse '-INFINITY'") - } + inspect(parse_inf_nan("infinity"), content="Infinity") + inspect(parse_inf_nan("-INFINITY"), content="-Infinity") } ///| test "parse_inf_nan with trailing strings" { - // Test trailing characters after 'inf' - let result_trailing = parse_inf_nan("infabc") - match result_trailing { - Some((value, length)) => { - inspect(value.is_inf() && value > 0.0, content="true") - inspect(length, content="3") // should only parse 'inf' - } - None => fail("Expected to parse 'inf' but got None") - } - - // Test trailing characters after 'nan' - let result_nan_trailing = parse_inf_nan("nanxyz") - match result_nan_trailing { - Some((value, length)) => { - inspect(value.is_nan(), content="true") - inspect(length, content="3") // should only parse 'nan' - } - None => fail("Expected to parse 'nan' but got None") - } - - // Test trailing characters after signed infinity - let result_signed_trailing = parse_inf_nan("+infinity123") - match result_signed_trailing { - Some((value, length)) => { - inspect(value.is_inf() && value > 0.0, content="true") - inspect(length, content="9") // should only parse '+infinity' - } - None => fail("Expected to parse '+infinity' but got None") - } + inspect(try? parse_inf_nan("infabc"), content="Err(invalid syntax)") + inspect(try? parse_inf_nan("nanxyz"), content="Err(invalid syntax)") + inspect(try? parse_inf_nan("+infinity123"), content="Err(invalid syntax)") } ///| test "parse_inf_nan failures" { // Test invalid input - inspect(parse_inf_nan("hello"), content="None") - inspect(parse_inf_nan(""), content="None") - inspect(parse_inf_nan("in"), content="None") - inspect(parse_inf_nan("na"), content="None") + inspect(try? parse_inf_nan("hello"), content="Err(invalid syntax)") + inspect(try? parse_inf_nan(""), content="Err(invalid syntax)") + inspect(try? parse_inf_nan("in"), content="Err(invalid syntax)") + inspect(try? parse_inf_nan("na"), content="Err(invalid syntax)") } ///| diff --git a/strconv/string_view.mbt b/strconv/string_view.mbt index 3060dd1ef..1ddc6e5b9 100644 --- a/strconv/string_view.mbt +++ b/strconv/string_view.mbt @@ -12,25 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -///| -/// Step over `step` non-underscore characters. -/// Return None if there are not enough characters. -fn StringView::step(self : Self, step : Int) -> Self? { - let mut step = step - let mut str = self - while str is [ch, .. rest] && step > 0 { - if ch != '_' { - step -= 1 - } - str = rest - } - if step == 0 { - Some(str) - } else { - None - } -} - ///| /// Returns the accumulated value, the slice left, and the number of digits consumed. /// It ignores underscore and stops when a non-digit character is found.