luaur_ast/functions/parse_integer.rs
1use crate::enums::constant_number_parse_result::ConstantNumberParseResult;
2use luaur_common::macros::luau_assert::LUAU_ASSERT;
3
4#[allow(non_snake_case)]
5pub fn parse_integer(result: &mut f64, data: &str, base: i32) -> ConstantNumberParseResult {
6 LUAU_ASSERT!(base == 2 || base == 16);
7
8 // C++ `strtoull(data, &end, 16)` transparently skips a leading "0x"/"0X"
9 // prefix; Rust's `u64::from_str_radix` does NOT (it errors on the 'x'). The
10 // binary path strips "0b" before calling in, but the hex path passes the full
11 // "0x..." string (as C++ does, relying on strtoull) — so strip it here for
12 // base 16, otherwise EVERY hex literal is reported as "Malformed number".
13 let digits = if base == 16 && (data.starts_with("0x") || data.starts_with("0X")) {
14 &data[2..]
15 } else {
16 data
17 };
18
19 // C++ `strtoull` stops at the first char invalid for the base and parseInteger
20 // checks `*end != 0` -> Malformed, BEFORE the overflow check. Rust's
21 // from_str_radix hits overflow first on e.g. `0xffff..llllllg` (returning
22 // PosOverflow), masking the trailing junk. Validate the digits up front so
23 // trailing/invalid characters are Malformed, and a pure-overflow value (all
24 // valid digits) is Hex/BinOverflow — matching C++.
25 if digits.is_empty() || !digits.bytes().all(|b| (b as char).is_digit(base as u32)) {
26 return ConstantNumberParseResult::Malformed;
27 }
28
29 match u64::from_str_radix(digits, base as u32) {
30 Ok(value) => {
31 *result = value as f64;
32
33 // Precision check: doubles have 53 bits of mantissa. If the value is
34 // >= 2^53, it might not be representable exactly.
35 if value >= (1u64 << 53) && (*result as u64) != value {
36 return ConstantNumberParseResult::Imprecise;
37 }
38
39 ConstantNumberParseResult::Ok
40 }
41 Err(e) => {
42 // C++ distinguishes overflow (value == ULLONG_MAX && errno == ERANGE)
43 // from malformed input (a trailing invalid character).
44 if e.kind() == &core::num::IntErrorKind::PosOverflow {
45 if base == 2 {
46 ConstantNumberParseResult::BinOverflow
47 } else {
48 ConstantNumberParseResult::HexOverflow
49 }
50 } else {
51 ConstantNumberParseResult::Malformed
52 }
53 }
54 }
55}