use lexical::parse_radix;
pub use num_bigint::BigInt;
#[derive(Debug, Clone, PartialEq)]
pub enum JsNum {
Float(f64),
BigInt(BigInt),
}
pub fn parse_js_num(num: String) -> Option<JsNum> {
let (radix, mut raw) = match num.get(0..2) {
Some("0x") | Some("0X") => (16, num.get(2..).unwrap().replace("_", "")),
Some("0b") | Some("0B") => (2, num.get(2..).unwrap().replace("_", "")),
Some("0o") | Some("0O") => (8, num.get(2..).unwrap().replace("_", "")),
_ => (10, num.as_str().replace("_", "")),
};
if radix == 10 && raw.starts_with('0') {
if let Ok(parsed) = parse_radix(raw.as_bytes(), 8) {
return Some(JsNum::Float(parsed));
}
}
let bigint = if raw.get(raw.len() - 1..raw.len()) == Some("n") {
raw = raw.split_at(raw.len() - 1).0.to_string();
true
} else {
false
};
if bigint {
Some(JsNum::BigInt(BigInt::parse_bytes(raw.as_bytes(), radix)?))
} else {
Some(JsNum::Float(
parse_radix::<f64, _>(raw.as_bytes(), radix as u8).ok()?,
))
}
}
#[cfg(test)]
mod tests {
use crate::{
ast::{Expr, LiteralKind},
parse_expr,
};
use num_bigint::ToBigInt;
macro_rules! assert_float {
($literal:literal, $value:expr) => {
let parsed = parse_expr($literal, 0);
if let Expr::Literal(literal) = parsed.tree() {
assert_eq!(literal.as_number(), Some($value));
} else {
panic!("Parsed expression is not a literal");
}
};
}
macro_rules! assert_bigint {
($literal:literal, $value:expr) => {
let parsed = parse_expr($literal, 0);
if let Expr::Literal(literal) = parsed.tree() {
let val = ($value as u64).to_bigint().unwrap();
assert_eq!(literal.kind(), LiteralKind::BigInt(val));
} else {
panic!("Parsed expression is not a literal");
}
};
}
#[test]
fn base_10_float() {
assert_float!("1234", 1234.0);
assert_float!("0", 0.0);
assert_float!("9e999", f64::INFINITY);
assert_float!("9e-999", 0.0);
}
#[test]
fn base_16_float() {
assert_float!("0xFF", 255.0);
assert_float!("0XFF", 255.0);
assert_float!("0x0", 0.0);
assert_float!("0xABC", 2748.0);
assert_float!("0XABC", 2748.0);
}
#[test]
fn base_2_float() {
assert_float!("0b0000", 0.0);
assert_float!("0B0000", 0.0);
assert_float!("0b11111111", 255.0);
assert_float!("0B11111111", 255.0);
}
#[test]
fn base_8_float() {
assert_float!("0o77", 63.0);
assert_float!("0O77", 63.0);
assert_float!("0o0", 0.0);
assert_float!("0O0", 0.0);
}
#[test]
fn base_8_legacy_float() {
assert_float!("051", 41.0);
assert_float!("058", 58.0);
}
#[test]
fn base_10_bigint() {
assert_bigint!("1010n", 1010);
assert_bigint!("0n", 0);
assert_bigint!("9007199254740991n", 9007199254740991);
}
#[test]
fn base_16_bigint() {
assert_bigint!("0xffn", 255);
assert_bigint!("0XFFn", 255);
assert_bigint!("0x1fffffffffffffn", 9007199254740991);
assert_bigint!("0X1fffffffffffffn", 9007199254740991);
}
#[test]
fn base_2_bigint() {
assert_bigint!("0b0n", 0);
assert_bigint!("0B0n", 0);
assert_bigint!(
"0b11111111111111111111111111111111111111111111111111111n",
9007199254740991
);
assert_bigint!(
"0B11111111111111111111111111111111111111111111111111111n",
9007199254740991
);
}
}