mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(ecmascript): add ToBigInt and StringToBigInt (#6508)
This commit is contained in:
parent
1bbd383d0f
commit
39c2e66b0b
7 changed files with 123 additions and 94 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1603,6 +1603,7 @@ dependencies = [
|
|||
name = "oxc_ecmascript"
|
||||
version = "0.31.0"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"oxc_ast",
|
||||
"oxc_span",
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ test = true
|
|||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
num-traits = { workspace = true }
|
||||
oxc_ast = { workspace = true }
|
||||
oxc_span = { workspace = true }
|
||||
oxc_syntax = { workspace = true }
|
||||
|
||||
num-bigint = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ mod prop_name;
|
|||
mod string_char_at;
|
||||
mod string_index_of;
|
||||
mod string_last_index_of;
|
||||
mod string_to_big_int;
|
||||
mod to_big_int;
|
||||
mod to_boolean;
|
||||
mod to_int_32;
|
||||
mod to_number;
|
||||
|
|
@ -23,6 +25,8 @@ pub use self::{
|
|||
string_char_at::StringCharAt,
|
||||
string_index_of::StringIndexOf,
|
||||
string_last_index_of::StringLastIndexOf,
|
||||
string_to_big_int::StringToBigInt,
|
||||
to_big_int::ToBigInt,
|
||||
to_boolean::ToBoolean,
|
||||
to_int_32::ToInt32,
|
||||
to_number::{NumberValue, ToNumber},
|
||||
|
|
|
|||
42
crates/oxc_ecmascript/src/string_to_big_int.rs
Normal file
42
crates/oxc_ecmascript/src/string_to_big_int.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use num_traits::Zero;
|
||||
|
||||
use num_bigint::BigInt;
|
||||
|
||||
/// `StringToBigInt`
|
||||
///
|
||||
/// <https://tc39.es/ecma262/#sec-stringtobigint>
|
||||
pub trait StringToBigInt<'a> {
|
||||
fn string_to_big_int(&self) -> Option<BigInt>;
|
||||
}
|
||||
|
||||
impl<'a> StringToBigInt<'a> for &str {
|
||||
fn string_to_big_int(&self) -> Option<BigInt> {
|
||||
if self.contains('\u{000b}') {
|
||||
// vertical tab is not always whitespace
|
||||
return None;
|
||||
}
|
||||
|
||||
let s = self.trim();
|
||||
|
||||
if s.is_empty() {
|
||||
return Some(BigInt::zero());
|
||||
}
|
||||
|
||||
if s.len() > 2 && s.starts_with('0') {
|
||||
let radix: u32 = match s.chars().nth(1) {
|
||||
Some('x' | 'X') => 16,
|
||||
Some('o' | 'O') => 8,
|
||||
Some('b' | 'B') => 2,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
if radix == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
return BigInt::parse_bytes(s[2..].as_bytes(), radix);
|
||||
}
|
||||
|
||||
return BigInt::parse_bytes(s.as_bytes(), 10);
|
||||
}
|
||||
}
|
||||
70
crates/oxc_ecmascript/src/to_big_int.rs
Normal file
70
crates/oxc_ecmascript/src/to_big_int.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
use num_bigint::BigInt;
|
||||
use num_traits::{One, Zero};
|
||||
|
||||
use oxc_ast::ast::Expression;
|
||||
use oxc_syntax::operator::UnaryOperator;
|
||||
|
||||
use crate::{StringToBigInt, ToBoolean, ToJsString};
|
||||
|
||||
/// `ToBigInt`
|
||||
///
|
||||
/// <https://tc39.es/ecma262/#sec-tobigint>
|
||||
pub trait ToBigInt<'a> {
|
||||
fn to_big_int(&self) -> Option<BigInt>;
|
||||
}
|
||||
|
||||
impl<'a> ToBigInt<'a> for Expression<'a> {
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
fn to_big_int(&self) -> Option<BigInt> {
|
||||
match self {
|
||||
Expression::NumericLiteral(number_literal) => {
|
||||
let value = number_literal.value;
|
||||
if value.abs() < 2_f64.powi(53) && value.fract() == 0.0 {
|
||||
Some(BigInt::from(value as i64))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Expression::BigIntLiteral(bigint_literal) => {
|
||||
let value = bigint_literal.raw.as_str().trim_end_matches('n').string_to_big_int();
|
||||
debug_assert!(value.is_some(), "Failed to parse {}", bigint_literal.raw);
|
||||
value
|
||||
}
|
||||
Expression::BooleanLiteral(bool_literal) => {
|
||||
if bool_literal.value {
|
||||
Some(BigInt::one())
|
||||
} else {
|
||||
Some(BigInt::zero())
|
||||
}
|
||||
}
|
||||
Expression::UnaryExpression(unary_expr) => match unary_expr.operator {
|
||||
UnaryOperator::LogicalNot => {
|
||||
self.to_boolean().map(
|
||||
|boolean| {
|
||||
if boolean {
|
||||
BigInt::one()
|
||||
} else {
|
||||
BigInt::zero()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
UnaryOperator::UnaryNegation => {
|
||||
unary_expr.argument.to_big_int().map(std::ops::Neg::neg)
|
||||
}
|
||||
UnaryOperator::BitwiseNot => {
|
||||
unary_expr.argument.to_big_int().map(std::ops::Not::not)
|
||||
}
|
||||
UnaryOperator::UnaryPlus => unary_expr.argument.to_big_int(),
|
||||
_ => None,
|
||||
},
|
||||
Expression::StringLiteral(string_literal) => {
|
||||
string_literal.value.as_str().string_to_big_int()
|
||||
}
|
||||
Expression::TemplateLiteral(_) => {
|
||||
self.to_js_string().and_then(|value| value.as_ref().string_to_big_int())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,11 +5,9 @@ mod may_have_side_effects;
|
|||
use std::borrow::Cow;
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use num_traits::{One, Zero};
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::{NumberValue, ToBoolean, ToJsString, ToNumber};
|
||||
use oxc_ecmascript::{NumberValue, StringToBigInt, ToBigInt, ToBoolean, ToJsString, ToNumber};
|
||||
use oxc_semantic::{IsGlobalReference, ScopeTree, SymbolTable};
|
||||
use oxc_syntax::operator::UnaryOperator;
|
||||
|
||||
pub use self::{is_literal_value::IsLiteralValue, may_have_side_effects::MayHaveSideEffects};
|
||||
|
||||
|
|
@ -114,55 +112,8 @@ pub trait NodeUtil<'a> {
|
|||
expr.to_number()
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn get_bigint_value(&self, expr: &Expression<'a>) -> Option<BigInt> {
|
||||
match expr {
|
||||
Expression::NumericLiteral(number_literal) => {
|
||||
let value = number_literal.value;
|
||||
if value.abs() < 2_f64.powi(53) && is_exact_int64(value) {
|
||||
Some(BigInt::from(value as i64))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Expression::BigIntLiteral(bigint_literal) => {
|
||||
let value =
|
||||
self.get_string_bigint_value(bigint_literal.raw.as_str().trim_end_matches('n'));
|
||||
debug_assert!(value.is_some(), "Failed to parse {}", bigint_literal.raw);
|
||||
value
|
||||
}
|
||||
Expression::BooleanLiteral(bool_literal) => {
|
||||
if bool_literal.value {
|
||||
Some(BigInt::one())
|
||||
} else {
|
||||
Some(BigInt::zero())
|
||||
}
|
||||
}
|
||||
Expression::UnaryExpression(unary_expr) => match unary_expr.operator {
|
||||
UnaryOperator::LogicalNot => self.get_boolean_value(expr).map(|boolean| {
|
||||
if boolean.is_true() {
|
||||
BigInt::one()
|
||||
} else {
|
||||
BigInt::zero()
|
||||
}
|
||||
}),
|
||||
UnaryOperator::UnaryNegation => {
|
||||
self.get_bigint_value(&unary_expr.argument).map(std::ops::Neg::neg)
|
||||
}
|
||||
UnaryOperator::BitwiseNot => {
|
||||
self.get_bigint_value(&unary_expr.argument).map(std::ops::Not::not)
|
||||
}
|
||||
UnaryOperator::UnaryPlus => self.get_bigint_value(&unary_expr.argument),
|
||||
_ => None,
|
||||
},
|
||||
Expression::StringLiteral(string_literal) => {
|
||||
self.get_string_bigint_value(&string_literal.value)
|
||||
}
|
||||
Expression::TemplateLiteral(_) => {
|
||||
self.get_string_value(expr).and_then(|value| self.get_string_bigint_value(&value))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
expr.to_big_int()
|
||||
}
|
||||
|
||||
/// Port from [closure-compiler](https://github.com/google/closure-compiler/blob/e13f5cd0a5d3d35f2db1e6c03fdf67ef02946009/src/com/google/javascript/jscomp/NodeUtil.java#L234)
|
||||
|
|
@ -175,33 +126,7 @@ pub trait NodeUtil<'a> {
|
|||
|
||||
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/NodeUtil.java#L540)
|
||||
fn get_string_bigint_value(&self, raw_string: &str) -> Option<BigInt> {
|
||||
if raw_string.contains('\u{000b}') {
|
||||
// vertical tab is not always whitespace
|
||||
return None;
|
||||
}
|
||||
|
||||
let s = raw_string.trim();
|
||||
|
||||
if s.is_empty() {
|
||||
return Some(BigInt::zero());
|
||||
}
|
||||
|
||||
if s.len() > 2 && s.starts_with('0') {
|
||||
let radix: u32 = match s.chars().nth(1) {
|
||||
Some('x' | 'X') => 16,
|
||||
Some('o' | 'O') => 8,
|
||||
Some('b' | 'B') => 2,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
if radix == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
return BigInt::parse_bytes(s[2..].as_bytes(), radix);
|
||||
}
|
||||
|
||||
return BigInt::parse_bytes(s.as_bytes(), 10);
|
||||
raw_string.string_to_big_int()
|
||||
}
|
||||
|
||||
/// Evaluate and attempt to determine which primitive value type it could resolve to.
|
||||
|
|
|
|||
|
|
@ -33,21 +33,6 @@ impl From<i8> for Tri {
|
|||
}
|
||||
|
||||
impl Tri {
|
||||
pub fn is_true(self) -> bool {
|
||||
self == Tri::True
|
||||
}
|
||||
|
||||
pub fn map<U, F>(self, f: F) -> Option<U>
|
||||
where
|
||||
F: FnOnce(Tri) -> U,
|
||||
{
|
||||
match self {
|
||||
Self::True => Some(f(Tri::True)),
|
||||
Self::False => Some(f(Tri::False)),
|
||||
Self::Unknown => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn not(self) -> Self {
|
||||
match self {
|
||||
Self::True => Self::False,
|
||||
|
|
|
|||
Loading…
Reference in a new issue