use std::borrow::Cow;
use cow_utils::CowUtils;
use num_bigint::BigInt;
use num_traits::Num;
use oxc_allocator::Allocator;
use oxc_str::{Str, format_str};
use super::kind::Kind;
pub fn parse_int(s: &str, kind: Kind, has_sep: bool) -> Result<f64, &'static str> {
match kind {
Kind::Decimal => {
Ok(if has_sep { parse_decimal_with_underscores(s) } else { parse_decimal(s) })
}
Kind::Binary => {
let s = &s[2..];
Ok(if has_sep { parse_binary_with_underscores(s) } else { parse_binary(s) })
}
Kind::Octal => {
let second_byte = s.as_bytes()[1];
let s = if second_byte == b'o' || second_byte == b'O' {
unsafe { s.get_unchecked(2..) }
} else {
&s[1..] };
Ok(if has_sep { parse_octal_with_underscores(s) } else { parse_octal(s) })
}
Kind::Hex => {
let s = &s[2..];
Ok(if has_sep { parse_hex_with_underscores(s) } else { parse_hex(s) })
}
_ => unreachable!(),
}
}
pub fn parse_float(s: &str, has_sep: bool) -> Result<f64, &'static str> {
let s = if has_sep { s.cow_replace('_', "") } else { Cow::Borrowed(s) };
debug_assert!(!s.contains('_'));
s.parse::<f64>().map_err(|_| "invalid float")
}
#[inline]
const fn decimal_byte_to_value(b: u8) -> u8 {
debug_assert!(b >= b'0' && b <= b'9');
b & 15
}
#[expect(clippy::cast_precision_loss, clippy::cast_lossless)]
fn parse_decimal(s: &str) -> f64 {
const MAX_FAST_DECIMAL_LEN: usize = 19;
debug_assert!(!s.is_empty());
if s.len() > MAX_FAST_DECIMAL_LEN {
return parse_decimal_slow(s);
}
let mut result = 0_u64;
for &b in s.as_bytes() {
result *= 10;
let n = decimal_byte_to_value(b);
result += n as u64;
}
result as f64
}
#[expect(clippy::cast_precision_loss, clippy::cast_lossless)]
fn parse_decimal_with_underscores(s: &str) -> f64 {
const MAX_FAST_DECIMAL_LEN: usize = 19;
debug_assert!(!s.is_empty());
if s.len() > MAX_FAST_DECIMAL_LEN {
return parse_decimal_slow(&s.cow_replace('_', ""));
}
let mut result = 0_u64;
for &b in s.as_bytes() {
if b != b'_' {
result *= 10;
let n = decimal_byte_to_value(b);
result += n as u64;
}
}
result as f64
}
#[cold]
#[inline(never)]
fn parse_decimal_slow(s: &str) -> f64 {
s.parse::<f64>().unwrap()
}
#[inline]
const fn binary_byte_to_value(b: u8) -> u8 {
debug_assert!(b == b'0' || b == b'1');
b & 1
}
#[expect(clippy::cast_precision_loss, clippy::cast_lossless)]
fn parse_binary(s: &str) -> f64 {
const MAX_FAST_BINARY_LEN: usize = 64;
debug_assert!(!s.is_empty());
debug_assert!(!s.starts_with("0b") && !s.starts_with("0B"));
if s.len() > MAX_FAST_BINARY_LEN {
return parse_binary_slow(s);
}
let mut result = 0_u64;
for &b in s.as_bytes() {
result <<= 1;
result |= binary_byte_to_value(b) as u64;
}
result as f64
}
#[cold]
#[inline(never)]
fn parse_binary_slow(s: &str) -> f64 {
let mut result = 0_f64;
for &b in s.as_bytes() {
let value = f64::from(binary_byte_to_value(b));
result = result.mul_add(2.0, value);
}
result
}
#[expect(clippy::cast_precision_loss, clippy::cast_lossless)]
fn parse_binary_with_underscores(s: &str) -> f64 {
const MAX_FAST_BINARY_LEN: usize = 64;
debug_assert!(!s.is_empty());
debug_assert!(!s.starts_with("0b") && !s.starts_with("0B"));
if s.len() > MAX_FAST_BINARY_LEN {
return parse_binary_with_underscores_slow(s);
}
let mut result = 0_u64;
for &b in s.as_bytes() {
if b != b'_' {
result <<= 1;
result |= binary_byte_to_value(b) as u64;
}
}
result as f64
}
#[cold]
#[inline(never)]
fn parse_binary_with_underscores_slow(s: &str) -> f64 {
let mut result = 0_f64;
for &b in s.as_bytes() {
if b != b'_' {
let value = f64::from(binary_byte_to_value(b));
result = result.mul_add(2.0, value);
}
}
result
}
#[inline]
const fn octal_byte_to_value(b: u8) -> u8 {
debug_assert!(b >= b'0' && b <= b'7');
b & 7
}
#[expect(clippy::cast_precision_loss, clippy::cast_lossless)]
fn parse_octal(s: &str) -> f64 {
const MAX_FAST_OCTAL_LEN: usize = 21;
debug_assert!(!s.is_empty());
debug_assert!(!s.starts_with("0o") && !s.starts_with("0O"));
if s.len() > MAX_FAST_OCTAL_LEN {
return parse_octal_slow(s);
}
let mut result = 0_u64;
for &b in s.as_bytes() {
let n = octal_byte_to_value(b);
result <<= 3;
result |= n as u64;
}
result as f64
}
#[cold]
#[inline(never)]
fn parse_octal_slow(s: &str) -> f64 {
let mut result = 0_f64;
for &b in s.as_bytes() {
let value = f64::from(octal_byte_to_value(b));
result = result.mul_add(8.0, value);
}
result
}
#[expect(clippy::cast_precision_loss, clippy::cast_lossless)]
fn parse_octal_with_underscores(s: &str) -> f64 {
const MAX_FAST_OCTAL_LEN: usize = 21;
debug_assert!(!s.is_empty());
debug_assert!(!s.starts_with("0o") && !s.starts_with("0O"));
if s.len() > MAX_FAST_OCTAL_LEN {
return parse_octal_with_underscores_slow(s);
}
let mut result = 0_u64;
for &b in s.as_bytes() {
if b != b'_' {
let n = octal_byte_to_value(b);
result <<= 3;
result |= n as u64;
}
}
result as f64
}
#[cold]
#[inline(never)]
fn parse_octal_with_underscores_slow(s: &str) -> f64 {
let mut result = 0_f64;
for &b in s.as_bytes() {
if b != b'_' {
let value = f64::from(octal_byte_to_value(b));
result = result.mul_add(8.0, value);
}
}
result
}
#[inline]
const fn hex_byte_to_value(b: u8) -> u8 {
debug_assert!((b >= b'0' && b <= b'9') || (b >= b'A' && b <= b'F') || (b >= b'a' && b <= b'f'));
if b < b'A' {
b & 15 } else {
(b & 15) + 9 }
}
#[expect(clippy::cast_precision_loss, clippy::cast_lossless)]
fn parse_hex(s: &str) -> f64 {
const MAX_FAST_HEX_LEN: usize = 16;
debug_assert!(!s.is_empty());
debug_assert!(!s.starts_with("0x"));
if s.len() > MAX_FAST_HEX_LEN {
return parse_hex_slow(s);
}
let mut result = 0_u64;
for &b in s.as_bytes() {
let n = hex_byte_to_value(b);
result <<= 4;
result |= n as u64;
}
result as f64
}
#[cold]
#[inline(never)]
fn parse_hex_slow(s: &str) -> f64 {
let mut result = 0_f64;
for &b in s.as_bytes() {
let value = f64::from(hex_byte_to_value(b));
result = result.mul_add(16.0, value);
}
result
}
#[expect(clippy::cast_precision_loss, clippy::cast_lossless)]
fn parse_hex_with_underscores(s: &str) -> f64 {
const MAX_FAST_HEX_LEN: usize = 16;
debug_assert!(!s.is_empty());
debug_assert!(!s.starts_with("0x"));
if s.len() > MAX_FAST_HEX_LEN {
return parse_hex_with_underscores_slow(s);
}
let mut result = 0_u64;
for &b in s.as_bytes() {
if b != b'_' {
let n = hex_byte_to_value(b);
result <<= 4;
result |= n as u64;
}
}
result as f64
}
#[cold]
#[inline(never)]
fn parse_hex_with_underscores_slow(s: &str) -> f64 {
let mut result = 0_f64;
for &b in s.as_bytes() {
if b != b'_' {
let value = f64::from(hex_byte_to_value(b));
result = result.mul_add(16.0, value);
}
}
result
}
pub fn parse_big_int<'a>(
s: &'a str,
kind: Kind,
has_sep: bool,
allocator: &'a Allocator,
) -> Str<'a> {
let s = if has_sep { s.cow_replace('_', "") } else { Cow::Borrowed(s) };
debug_assert!(!s.contains('_'));
let radix = match kind {
Kind::Decimal => return Str::from_cow_in(&s, allocator),
Kind::Binary => 2,
Kind::Octal => 8,
Kind::Hex => 16,
_ => unreachable!(),
};
let s = &s[2..];
let bigint = BigInt::from_str_radix(s, radix).unwrap();
format_str!(allocator, "{bigint}")
}
#[cfg(test)]
#[expect(clippy::unreadable_literal, clippy::mixed_case_hex_literals)]
mod test {
use super::*;
#[expect(clippy::cast_precision_loss)]
fn assert_all_ints_eq<I>(test_cases: I, kind: Kind, has_sep: bool)
where
I: IntoIterator<Item = (&'static str, i64)>,
{
for (s, expected) in test_cases {
let parsed = parse_int(s, kind, has_sep);
assert_eq!(
parsed,
Ok(expected as f64),
"expected {s} to parse to {expected}, but got {parsed:?}"
);
}
}
fn assert_all_floats_eq<I>(test_cases: I, has_sep: bool)
where
I: IntoIterator<Item = (&'static str, f64)>,
{
for (s, expected) in test_cases {
let parsed = parse_float(s, has_sep);
assert_eq!(
parsed,
Ok(expected),
"expected {s} to parse to {expected}, but got {parsed:?}"
);
}
}
const _: () = {
assert!(decimal_byte_to_value(b'0') == 0);
assert!(decimal_byte_to_value(b'9') == 9);
assert!(binary_byte_to_value(b'0') == 0);
assert!(binary_byte_to_value(b'1') == 1);
assert!(octal_byte_to_value(b'0') == 0);
assert!(octal_byte_to_value(b'7') == 7);
assert!(hex_byte_to_value(b'0') == 0);
assert!(hex_byte_to_value(b'9') == 9);
assert!(hex_byte_to_value(b'A') == 10);
assert!(hex_byte_to_value(b'F') == 15);
assert!(hex_byte_to_value(b'a') == 10);
assert!(hex_byte_to_value(b'f') == 15);
};
#[test]
#[expect(clippy::cast_precision_loss)]
fn test_int_precision() {
assert_eq!(
parse_int("18446744073709551616", Kind::Decimal, false),
Ok(18446744073709551616_i128 as f64)
);
assert_eq!(
parse_int("12300000000000000000000000", Kind::Decimal, false),
Ok(12300000000000000000000000_i128 as f64)
);
assert_eq!(
parse_int("0x10000000000000000", Kind::Hex, false),
Ok(0x10000000000000000_i128 as f64)
);
assert_eq!(
parse_int("0o2000000000000000000000", Kind::Octal, false),
Ok(0o2000000000000000000000_i128 as f64)
);
assert_eq!(
parse_int(
"0b10000000000000000000000000000000000000000000000000000000000000000",
Kind::Binary,
false
),
Ok(0b10000000000000000000000000000000000000000000000000000000000000000_i128 as f64)
);
}
#[test]
fn test_large_number_of_leading_zeros() {
assert_all_ints_eq(
vec![("000000000000000000000000000000000000000000000000000000001", 1)],
Kind::Decimal,
false,
);
}
#[test]
fn test_float_precision() {
let cases = vec![
("1.7976931348623157e+308", 1.7976931348623157e+308),
("0.000000001", 0.000_000_001),
];
assert_all_floats_eq(cases, false);
}
#[test]
fn test_parse_int_no_sep() {
let decimal: Vec<(&str, i64)> = vec![
("0", 0),
("1", 1),
("000000000000", 0),
("9007199254740991", 9007199254740991), ];
let binary = vec![
("0b0", 0b0),
("0b1", 0b1),
("0b10", 0b10),
("0b110001001000100", 0b110001001000100),
("0b110001001000100", 0b110001001000100),
];
let octal = vec![("0o0", 0o0), ("0o1", 0o1), ("0o10", 0o10), ("0o777", 0o777)];
let hex: Vec<(&str, i64)> = vec![
("0x0", 0x0),
("0X0", 0x0),
("0xFF", 0xFF),
("0xc", 0xc), ("0xdeadbeef", 0xdeadbeef),
("0xFfEeDdCcBbAa", 0xFfEeDdCcBbAa),
];
assert_all_ints_eq(decimal, Kind::Decimal, false);
assert_all_ints_eq(binary, Kind::Binary, false);
assert_all_ints_eq(octal, Kind::Octal, false);
assert_all_ints_eq(hex, Kind::Hex, false);
}
#[test]
fn test_parse_int_with_sep() {
let decimal: Vec<(&str, i64)> = vec![
("0", 0),
("1", 1),
("1_000_000", 1_000_000),
("000000000000", 0),
("9_007_199_254_740_991", 9_007_199_254_740_991), ("1___000_000", 1_000_000),
("1_", 1),
("_1", 1),
];
let binary = vec![
("0b0", 0b0),
("0b1", 0b1),
("0b10", 0b10),
("0b110001001000100", 0b110001001000100),
("0b110001001000100", 0b110001001000100),
("0b1_1000_1001_0001_0000", 0b1_1000_1001_0001_0000),
("0b1_0000__0000", 0b1_0000_0000),
("0b1_", 0b1),
("0b_0", 0b0),
];
let octal = vec![
("0o0", 0o0),
("0o1", 0o1),
("0o10", 0o10),
("0o777", 0o777),
("0o7_7_7", 0o777),
("0o77_73_72", 0o77_73_72),
("0o1_0000__0000", 0o100_000_000),
("0o1_", 0o1),
("0o_0", 0o0),
];
let hex: Vec<(&str, i64)> = vec![
("0x0", 0x0),
("0X0", 0x0),
("0xFF", 0xFF),
("0xFF_AA_11", 0xFFAA11),
("0xdead_beef", 0xdead_beef),
("0xFf_Ee_Dd_Cc_Bb_Aa", 0xFfEe_DdCc_BbAa),
("0xFfEe_DdCc_BbAa", 0xFfEe_DdCc_BbAa),
("0x1_0000__0000", 0x100_000_000),
("0x1_", 0x1),
("0x_0", 0x0),
];
assert_all_ints_eq(decimal, Kind::Decimal, true);
assert_all_ints_eq(binary, Kind::Binary, true);
assert_all_ints_eq(octal, Kind::Octal, true);
assert_all_ints_eq(hex, Kind::Hex, true);
}
#[test]
fn test_decimal() {
let no_sep: Vec<(&'static str, f64)> =
vec![("0", 0.0), ("1.0", 1.0), ("1.1", 1.1), ("25.125", 25.125)];
let sep: Vec<(&'static str, f64)> = vec![
("1_000.0", 1000.0),
("1.5_000", 1.5),
("_0._5", 0.5),
("0._5", 0.5),
("0.5_", 0.5),
];
assert_all_floats_eq(no_sep.clone(), false);
assert_all_floats_eq(sep.clone(), true);
for (s, expected) in no_sep {
let parsed = parse_float(s, false);
assert_eq!(
parsed,
Ok(expected),
"expected {s} to parse to {expected}, but got {parsed:?}"
);
}
for (s, expected) in sep {
let parsed = parse_float(s, true);
assert_eq!(
parsed,
Ok(expected),
"expected {s} to parse to {expected}, but got {parsed:?}"
);
}
}
}