Skip to content

Commit

Permalink
feat(ecmascript): add ToBigInt and StringToBigInt
Browse files Browse the repository at this point in the history
  • Loading branch information
Boshen committed Oct 13, 2024
1 parent c8fa2eb commit 0921cea
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 94 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion crates/oxc_ecmascript/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
4 changes: 4 additions & 0 deletions crates/oxc_ecmascript/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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},
Expand Down
42 changes: 42 additions & 0 deletions crates/oxc_ecmascript/src/string_to_big_int.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 70 additions & 0 deletions crates/oxc_ecmascript/src/to_big_int.rs
Original file line number Diff line number Diff line change
@@ -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,
}
}
}
81 changes: 3 additions & 78 deletions crates/oxc_minifier/src/node_util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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)
Expand All @@ -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.
Expand Down
15 changes: 0 additions & 15 deletions crates/oxc_minifier/src/tri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 0921cea

Please sign in to comment.