Skip to main content

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}