From d847ff826b5164791191163f3e273cb1d2c247a0 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Sat, 6 Jun 2020 04:07:42 +0200 Subject: [PATCH] Specification compliant `ToBigInt` (`to_bigint`) (#450) - Merged Ast `BigInt` to Builtin `BigInt`. - Split `BigInt` logic to separate files. - Added `builtins/bigint/operations.rs` for `BigInt` operations. - Added `builtins/bigint/conversions.rs` for `BigInt` conversions. - Added` builtins/bigint/equality.rs` for `BigInt` equality checking. - Added tests. --- boa/src/builtins/bigint/conversions.rs | 96 ++++++++++ boa/src/builtins/bigint/equality.rs | 74 ++++++++ boa/src/builtins/bigint/mod.rs | 103 +++++++---- boa/src/builtins/bigint/operations.rs | 104 +++++++++++ boa/src/builtins/bigint/tests.rs | 84 ++++++++- boa/src/builtins/mod.rs | 1 + boa/src/builtins/number/mod.rs | 10 +- boa/src/builtins/number/tests.rs | 16 +- boa/src/builtins/value/conversions.rs | 20 -- boa/src/builtins/value/equality.rs | 215 +++++++++++++++++++++ boa/src/builtins/value/mod.rs | 46 ++--- boa/src/builtins/value/operations.rs | 192 ------------------- boa/src/exec/mod.rs | 41 ++++- boa/src/exec/tests.rs | 26 ++- boa/src/syntax/ast/bigint.rs | 246 ------------------------- boa/src/syntax/ast/constant.rs | 2 +- boa/src/syntax/ast/mod.rs | 1 - boa/src/syntax/ast/token.rs | 3 +- boa/src/syntax/lexer/mod.rs | 4 +- 19 files changed, 736 insertions(+), 548 deletions(-) create mode 100644 boa/src/builtins/bigint/conversions.rs create mode 100644 boa/src/builtins/bigint/equality.rs create mode 100644 boa/src/builtins/bigint/operations.rs create mode 100644 boa/src/builtins/value/equality.rs delete mode 100644 boa/src/syntax/ast/bigint.rs diff --git a/boa/src/builtins/bigint/conversions.rs b/boa/src/builtins/bigint/conversions.rs new file mode 100644 index 0000000000..572e289976 --- /dev/null +++ b/boa/src/builtins/bigint/conversions.rs @@ -0,0 +1,96 @@ +use super::BigInt; + +use crate::{ + builtins::{Number, Value}, + exec::Interpreter, +}; +use num_traits::cast::{FromPrimitive, ToPrimitive}; + +use std::convert::TryFrom; +use std::str::FromStr; + +impl BigInt { + /// This function takes a string and conversts it to BigInt type. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-stringtobigint + #[inline] + pub(crate) fn from_string(string: &str, _ctx: &mut Interpreter) -> Result { + if string.is_empty() { + return Ok(BigInt::from(0)); + } + + match num_bigint::BigInt::from_str(string) { + Ok(bigint) => Ok(Self(bigint)), + _ => panic!("SyntaxError: cannot convert {} to a BigInt", string), + } + } + + /// Converts a string to a BigInt with the specified radix. + #[inline] + pub fn from_string_radix(buf: &str, radix: u32) -> Option { + num_bigint::BigInt::parse_bytes(buf.as_bytes(), radix).map(Self) + } + + /// Convert bigint to string with radix. + #[inline] + pub fn to_string_radix(&self, radix: u32) -> String { + self.0.to_str_radix(radix) + } + + /// Converts the BigInt to a f64 type. + /// + /// Returns `std::f64::INFINITY` if the BigInt is too big. + #[inline] + pub fn to_f64(&self) -> f64 { + self.0.to_f64().unwrap_or(std::f64::INFINITY) + } + + #[inline] + pub(crate) fn from_str(string: &str) -> Option { + match num_bigint::BigInt::from_str(string) { + Ok(bigint) => Some(BigInt(bigint)), + Err(_) => None, + } + } +} + +impl From for BigInt { + fn from(n: i64) -> BigInt { + BigInt(num_bigint::BigInt::from(n)) + } +} + +impl From for BigInt { + fn from(n: i32) -> BigInt { + BigInt(num_bigint::BigInt::from(n)) + } +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct TryFromF64Error; + +impl std::fmt::Display for TryFromF64Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Could not convert f64 value to a BigInt type") + } +} + +impl TryFrom for BigInt { + type Error = TryFromF64Error; + + fn try_from(n: f64) -> Result { + // If the truncated version of the number is not the + // same as the non-truncated version then the floating-point + // number conains a fractional part. + if !Number::equal(n.trunc(), n) { + return Err(TryFromF64Error); + } + match num_bigint::BigInt::from_f64(n) { + Some(bigint) => Ok(BigInt(bigint)), + None => Err(TryFromF64Error), + } + } +} diff --git a/boa/src/builtins/bigint/equality.rs b/boa/src/builtins/bigint/equality.rs new file mode 100644 index 0000000000..9eee4935a1 --- /dev/null +++ b/boa/src/builtins/bigint/equality.rs @@ -0,0 +1,74 @@ +use super::BigInt; + +impl BigInt { + /// Checks for `SameValueZero` equality. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-numeric-types-bigint-equal + #[inline] + pub(crate) fn same_value_zero(x: &Self, y: &Self) -> bool { + // Return BigInt::equal(x, y) + Self::equal(x, y) + } + + /// Checks for `SameValue` equality. + /// + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-numeric-types-bigint-sameValue + #[inline] + pub(crate) fn same_value(x: &Self, y: &Self) -> bool { + // Return BigInt::equal(x, y) + Self::equal(x, y) + } + + /// Checks for mathematical equality. + /// + /// The abstract operation BigInt::equal takes arguments x (a `BigInt`) and y (a `BigInt`). + /// It returns `true` if x and y have the same mathematical integer value and false otherwise. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-numeric-types-bigint-sameValueZero + #[inline] + pub(crate) fn equal(x: &Self, y: &Self) -> bool { + x == y + } +} + +impl PartialEq for BigInt { + fn eq(&self, other: &i32) -> bool { + self.0 == num_bigint::BigInt::from(*other) + } +} + +impl PartialEq for i32 { + fn eq(&self, other: &BigInt) -> bool { + num_bigint::BigInt::from(*self) == other.0 + } +} + +impl PartialEq for BigInt { + fn eq(&self, other: &f64) -> bool { + if other.fract() != 0.0 { + return false; + } + + self.0 == num_bigint::BigInt::from(*other as i64) + } +} + +impl PartialEq for f64 { + fn eq(&self, other: &BigInt) -> bool { + if self.fract() != 0.0 { + return false; + } + + num_bigint::BigInt::from(*self as i64) == other.0 + } +} diff --git a/boa/src/builtins/bigint/mod.rs b/boa/src/builtins/bigint/mod.rs index f48a507118..1417fbbb60 100644 --- a/boa/src/builtins/bigint/mod.rs +++ b/boa/src/builtins/bigint/mod.rs @@ -15,21 +15,67 @@ use crate::{ builtins::{ function::{make_builtin_fn, make_constructor_fn}, - value::{ResultValue, Value}, + value::{ResultValue, Value, ValueData}, }, exec::Interpreter, - syntax::ast::bigint::BigInt as AstBigInt, BoaProfiler, }; +use gc::{unsafe_empty_trace, Finalize, Trace}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +pub mod conversions; +pub mod equality; +pub mod operations; + +pub use conversions::*; +pub use equality::*; +pub use operations::*; + #[cfg(test)] mod tests; /// `BigInt` implementation. -#[derive(Debug, Clone, Copy)] -pub(crate) struct BigInt; +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] +pub struct BigInt(num_bigint::BigInt); impl BigInt { + /// The abstract operation thisBigIntValue takes argument value. + /// + /// The phrase “this BigInt value” within the specification of a method refers to the + /// result returned by calling the abstract operation thisBigIntValue with the `this` value + /// of the method invocation passed as the argument. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-thisbigintvalue + #[inline] + fn this_bigint_value(value: &Value, ctx: &mut Interpreter) -> Result { + match value.data() { + // 1. If Type(value) is BigInt, return value. + ValueData::BigInt(ref bigint) => return Ok(bigint.clone()), + + // 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then + // a. Assert: Type(value.[[BigIntData]]) is BigInt. + // b. Return value.[[BigIntData]]. + ValueData::Object(_) => { + let bigint = value.get_internal_slot("BigIntData"); + if let ValueData::BigInt(bigint) = bigint.data() { + return Ok(bigint.clone()); + } + } + _ => {} + } + + // 3. Throw a TypeError exception. + ctx.throw_type_error("'this' is not a BigInt")?; + unreachable!(); + } + /// `BigInt()` /// /// The `BigInt()` constructor is used to create BigInt objects. @@ -46,34 +92,12 @@ impl BigInt { ctx: &mut Interpreter, ) -> ResultValue { let data = match args.get(0) { - Some(ref value) => { - if let Some(bigint) = value.to_bigint() { - Value::from(bigint) - } else { - let message = format!( - "{} can't be converted to BigInt because it isn't an integer", - ctx.to_string(value)? - ); - return ctx.throw_range_error(message); - } - } - None => Value::from(AstBigInt::from(0)), + Some(ref value) => Value::from(ctx.to_bigint(value)?), + None => Value::from(Self::from(0)), }; Ok(data) } - #[inline] - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_native_string_radix(bigint: &AstBigInt, radix: u32) -> String { - bigint.to_str_radix(radix) - } - - #[inline] - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_native_string(bigint: &AstBigInt) -> String { - bigint.to_string() - } - /// `BigInt.prototype.toString( [radix] )` /// /// The `toString()` method returns a string representing the specified BigInt object. @@ -99,10 +123,9 @@ impl BigInt { return ctx .throw_range_error("radix must be an integer at least 2 and no greater than 36"); } - Ok(Value::from(Self::to_native_string_radix( - &this.to_bigint().unwrap(), - radix as u32, - ))) + Ok(Value::from( + Self::this_bigint_value(this, ctx)?.to_string_radix(radix as u32), + )) } /// `BigInt.prototype.valueOf()` @@ -118,17 +141,15 @@ impl BigInt { pub(crate) fn value_of( this: &mut Value, _args: &[Value], - _ctx: &mut Interpreter, + ctx: &mut Interpreter, ) -> ResultValue { - Ok(Value::from( - this.to_bigint().expect("BigInt.prototype.valueOf"), - )) + Ok(Value::from(Self::this_bigint_value(this, ctx)?)) } /// Create a new `Number` object pub(crate) fn create(global: &Value) -> Value { let prototype = Value::new_object(Some(global)); - prototype.set_internal_slot("BigIntData", Value::from(AstBigInt::from(0))); + prototype.set_internal_slot("BigIntData", Value::from(Self::from(0))); make_builtin_fn(Self::to_string, "toString", &prototype, 1); make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); @@ -143,3 +164,11 @@ impl BigInt { global.set_field("BigInt", Self::create(global)); } } + +impl Finalize for BigInt {} +unsafe impl Trace for BigInt { + // BigInt type implements an empty trace becasue the inner structure + // `num_bigint::BigInt` does not implement `Trace` trait. + // If it did this would be unsound. + unsafe_empty_trace!(); +} diff --git a/boa/src/builtins/bigint/operations.rs b/boa/src/builtins/bigint/operations.rs new file mode 100644 index 0000000000..27a1cd4e1a --- /dev/null +++ b/boa/src/builtins/bigint/operations.rs @@ -0,0 +1,104 @@ +//! This module implements the `BigInt` operations. + +use num_traits::cast::ToPrimitive; +use num_traits::pow::Pow; + +use super::BigInt; + +impl BigInt { + #[inline] + pub fn pow(self, other: &Self) -> Self { + Self( + self.0.pow( + other + .0 + .to_biguint() + .expect("RangeError: \"BigInt negative exponent\""), + ), + ) + } +} + +macro_rules! impl_bigint_operator { + ($op:ident, $op_method:ident, $assign_op:ident, $assign_op_method:ident) => { + impl std::ops::$op for BigInt { + type Output = Self; + + fn $op_method(mut self, other: Self) -> Self { + std::ops::$assign_op::$assign_op_method(&mut self.0, other.0); + self + } + } + }; +} + +impl_bigint_operator!(Add, add, AddAssign, add_assign); +impl_bigint_operator!(Sub, sub, SubAssign, sub_assign); +impl_bigint_operator!(Mul, mul, MulAssign, mul_assign); +impl_bigint_operator!(Div, div, DivAssign, div_assign); +impl_bigint_operator!(Rem, rem, RemAssign, rem_assign); +impl_bigint_operator!(BitAnd, bitand, BitAndAssign, bitand_assign); +impl_bigint_operator!(BitOr, bitor, BitOrAssign, bitor_assign); +impl_bigint_operator!(BitXor, bitxor, BitXorAssign, bitxor_assign); + +impl std::ops::Shr for BigInt { + type Output = Self; + + fn shr(mut self, other: Self) -> Self::Output { + use std::ops::ShlAssign; + use std::ops::ShrAssign; + + if let Some(n) = other.0.to_i32() { + if n > 0 { + self.0.shr_assign(n as usize) + } else { + self.0.shl_assign(n.abs() as usize) + } + + return self; + } + + panic!("RangeError: Maximum BigInt size exceeded"); + } +} + +impl std::ops::Shl for BigInt { + type Output = Self; + + fn shl(mut self, other: Self) -> Self::Output { + use std::ops::ShlAssign; + use std::ops::ShrAssign; + + if let Some(n) = other.0.to_i32() { + if n > 0 { + self.0.shl_assign(n as usize) + } else { + self.0.shr_assign(n.abs() as usize) + } + + return self; + } + + panic!("RangeError: Maximum BigInt size exceeded"); + } +} + +impl std::ops::Neg for BigInt { + type Output = Self; + + fn neg(self) -> Self::Output { + Self(-self.0) + } +} + +impl std::fmt::Debug for BigInt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl std::fmt::Display for BigInt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/boa/src/builtins/bigint/tests.rs b/boa/src/builtins/bigint/tests.rs index e8f1b84d1d..7dafb618f4 100644 --- a/boa/src/builtins/bigint/tests.rs +++ b/boa/src/builtins/bigint/tests.rs @@ -1,4 +1,4 @@ -use crate::{forward, Interpreter, Realm}; +use crate::{forward, forward_val, Interpreter, Realm}; #[test] fn equality() { @@ -55,7 +55,7 @@ fn equality() { } #[test] -fn bigint_function_conversion() { +fn bigint_function_conversion_from_integer() { let realm = Realm::create(); let mut engine = Interpreter::new(realm); @@ -70,6 +70,83 @@ fn bigint_function_conversion() { ); } +#[test] +fn bigint_function_conversion_from_rational() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + assert_eq!(forward(&mut engine, "BigInt(0.0)"), "0n"); + assert_eq!(forward(&mut engine, "BigInt(1.0)"), "1n"); + assert_eq!(forward(&mut engine, "BigInt(10000.0)"), "10000n"); +} + +#[test] +fn bigint_function_conversion_from_rational_with_fractional_part() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let scenario = r#" + var x = false; + try { + BigInt(0.1); + } catch (e) { + x = true; + } + "#; + forward_val(&mut engine, scenario).unwrap(); + assert_eq!(forward(&mut engine, "x"), "true"); +} + +#[test] +fn bigint_function_conversion_from_null() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let scenario = r#" + var x = false; + try { + BigInt(null); + } catch (e) { + x = true; + } + "#; + forward_val(&mut engine, scenario).unwrap(); + assert_eq!(forward(&mut engine, "x"), "true"); +} + +#[test] +fn bigint_function_conversion_from_undefined() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let scenario = r#" + var x = false; + try { + BigInt(undefined); + } catch (e) { + x = true; + } + "#; + forward_val(&mut engine, scenario).unwrap(); + assert_eq!(forward(&mut engine, "x"), "true"); +} + +#[test] +fn bigint_function_conversion_from_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + assert_eq!(forward(&mut engine, "BigInt('')"), "0n"); + assert_eq!( + forward(&mut engine, "BigInt('200000000000000000')"), + "200000000000000000n" + ); + assert_eq!( + forward(&mut engine, "BigInt('1000000000000000000000000000000000')"), + "1000000000000000000000000000000000n" + ); +} + #[test] fn add() { let realm = Realm::create(); @@ -138,10 +215,7 @@ fn to_string() { let mut engine = Interpreter::new(realm); assert_eq!(forward(&mut engine, "1000n.toString()"), "1000"); - assert_eq!(forward(&mut engine, "1000n.toString(2)"), "1111101000"); - assert_eq!(forward(&mut engine, "255n.toString(16)"), "ff"); - assert_eq!(forward(&mut engine, "1000n.toString(36)"), "rs"); } diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index 2d7ecdcc1a..ad89b6b33c 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -21,6 +21,7 @@ pub(crate) use self::{ bigint::BigInt, boolean::Boolean, error::{Error, RangeError, TypeError}, + function::Function, number::Number, regexp::RegExp, string::String, diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 6b0bf116c1..86ee9d4344 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -445,8 +445,8 @@ impl Number { /// /// https://tc39.es/ecma262/#sec-numeric-types-number-equal #[allow(clippy::float_cmp)] - pub(crate) fn equals(a: f64, b: f64) -> bool { - a == b + pub(crate) fn equal(x: f64, y: f64) -> bool { + x == y } /// The abstract operation Number::sameValue takes arguments @@ -476,11 +476,11 @@ impl Number { /// /// https://tc39.es/ecma262/#sec-numeric-types-number-sameValueZero #[allow(clippy::float_cmp)] - pub(crate) fn same_value_zero(a: f64, b: f64) -> bool { - if a.is_nan() && b.is_nan() { + pub(crate) fn same_value_zero(x: f64, y: f64) -> bool { + if x.is_nan() && y.is_nan() { return true; } - a == b + x == y } } diff --git a/boa/src/builtins/number/tests.rs b/boa/src/builtins/number/tests.rs index 39725ba555..090f9f180e 100644 --- a/boa/src/builtins/number/tests.rs +++ b/boa/src/builtins/number/tests.rs @@ -403,13 +403,13 @@ fn value_of() { #[test] fn equal() { - assert_eq!(Number::equals(0.0, 0.0), true); - assert_eq!(Number::equals(-0.0, 0.0), true); - assert_eq!(Number::equals(0.0, -0.0), true); - assert_eq!(Number::equals(f64::NAN, -0.0), false); - assert_eq!(Number::equals(0.0, f64::NAN), false); + assert_eq!(Number::equal(0.0, 0.0), true); + assert_eq!(Number::equal(-0.0, 0.0), true); + assert_eq!(Number::equal(0.0, -0.0), true); + assert_eq!(Number::equal(f64::NAN, -0.0), false); + assert_eq!(Number::equal(0.0, f64::NAN), false); - assert_eq!(Number::equals(1.0, 1.0), true); + assert_eq!(Number::equal(1.0, 1.0), true); } #[test] @@ -419,7 +419,7 @@ fn same_value() { assert_eq!(Number::same_value(0.0, -0.0), false); assert_eq!(Number::same_value(f64::NAN, -0.0), false); assert_eq!(Number::same_value(0.0, f64::NAN), false); - assert_eq!(Number::equals(1.0, 1.0), true); + assert_eq!(Number::equal(1.0, 1.0), true); } #[test] @@ -429,7 +429,7 @@ fn same_value_zero() { assert_eq!(Number::same_value_zero(0.0, -0.0), true); assert_eq!(Number::same_value_zero(f64::NAN, -0.0), false); assert_eq!(Number::same_value_zero(0.0, f64::NAN), false); - assert_eq!(Number::equals(1.0, 1.0), true); + assert_eq!(Number::equal(1.0, 1.0), true); } #[test] diff --git a/boa/src/builtins/value/conversions.rs b/boa/src/builtins/value/conversions.rs index 8deadd3b63..201f7656cb 100644 --- a/boa/src/builtins/value/conversions.rs +++ b/boa/src/builtins/value/conversions.rs @@ -83,26 +83,6 @@ impl From<&Value> for i32 { } } -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub struct TryFromBigIntError; - -impl Display for TryFromBigIntError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Could not convert value to a BigInt type") - } -} - -impl TryFrom<&Value> for BigInt { - type Error = TryFromBigIntError; - - fn try_from(value: &Value) -> Result { - match value.data() { - ValueData::BigInt(ref bigint) => Ok(bigint.clone()), - _ => Err(TryFromBigIntError), - } - } -} - impl From for Value { fn from(value: BigInt) -> Self { Value::bigint(value) diff --git a/boa/src/builtins/value/equality.rs b/boa/src/builtins/value/equality.rs new file mode 100644 index 0000000000..f76fd35769 --- /dev/null +++ b/boa/src/builtins/value/equality.rs @@ -0,0 +1,215 @@ +use super::*; +use crate::{builtins::Number, Interpreter}; + +use std::borrow::Borrow; + +impl Value { + /// Strict equality comparison. + /// + /// This method is executed when doing strict equality comparisons with the `===` operator. + /// For more information, check . + pub fn strict_equals(&self, other: &Self) -> bool { + // 1. If Type(x) is different from Type(y), return false. + if self.get_type() != other.get_type() { + return false; + } + + match (self.data(), other.data()) { + // 2. If Type(x) is Number or BigInt, then + // a. Return ! Type(x)::equal(x, y). + (ValueData::BigInt(x), ValueData::BigInt(y)) => BigInt::equal(x, y), + (ValueData::Rational(x), ValueData::Rational(y)) => Number::equal(*x, *y), + (ValueData::Rational(x), ValueData::Integer(y)) => Number::equal(*x, f64::from(*y)), + (ValueData::Integer(x), ValueData::Rational(y)) => Number::equal(f64::from(*x), *y), + (ValueData::Integer(x), ValueData::Integer(y)) => x == y, + + //Null has to be handled specially because "typeof null" returns object and if we managed + //this without a special case we would compare self and other as if they were actually + //objects which unfortunately fails + //Specification Link: https://tc39.es/ecma262/#sec-typeof-operator + (ValueData::Null, ValueData::Null) => true, + + // 3. Return ! SameValueNonNumeric(x, y). + (_, _) => same_value_non_numeric(self, other), + } + } + + /// Abstract equality comparison. + /// + /// This method is executed when doing abstract equality comparisons with the `==` operator. + /// For more information, check + #[allow(clippy::float_cmp)] + pub fn equals(&mut self, other: &mut Self, interpreter: &mut Interpreter) -> bool { + // 1. If Type(x) is the same as Type(y), then + // a. Return the result of performing Strict Equality Comparison x === y. + if self.get_type() == other.get_type() { + return self.strict_equals(other); + } + + match (self.data(), other.data()) { + // 2. If x is null and y is undefined, return true. + // 3. If x is undefined and y is null, return true. + _ if self.is_null_or_undefined() && other.is_null_or_undefined() => true, + + // 3. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ! ToNumber(y). + // 4. If Type(x) is String and Type(y) is Number, return the result of the comparison ! ToNumber(x) == y. + // + // https://github.com/rust-lang/rust/issues/54883 + (ValueData::Integer(_), ValueData::String(_)) + | (ValueData::Rational(_), ValueData::String(_)) + | (ValueData::String(_), ValueData::Integer(_)) + | (ValueData::String(_), ValueData::Rational(_)) + | (ValueData::Rational(_), ValueData::Boolean(_)) + | (ValueData::Integer(_), ValueData::Boolean(_)) => { + let a: &Value = self.borrow(); + let b: &Value = other.borrow(); + Number::equal(f64::from(a), f64::from(b)) + } + + // 6. If Type(x) is BigInt and Type(y) is String, then + // a. Let n be ! StringToBigInt(y). + // b. If n is NaN, return false. + // c. Return the result of the comparison x == n. + (ValueData::BigInt(ref a), ValueData::String(ref b)) => match string_to_bigint(b) { + Some(ref b) => a == b, + None => false, + }, + + // 7. If Type(x) is String and Type(y) is BigInt, return the result of the comparison y == x. + (ValueData::String(ref a), ValueData::BigInt(ref b)) => match string_to_bigint(a) { + Some(ref a) => a == b, + None => false, + }, + + // 8. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y. + (ValueData::Boolean(_), _) => { + other.equals(&mut Value::from(self.to_integer()), interpreter) + } + + // 9. If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y). + (_, ValueData::Boolean(_)) => { + self.equals(&mut Value::from(other.to_integer()), interpreter) + } + + // 10. If Type(x) is either String, Number, BigInt, or Symbol and Type(y) is Object, return the result + // of the comparison x == ? ToPrimitive(y). + (ValueData::Object(_), _) => { + let mut primitive = interpreter.to_primitive(self, None); + primitive.equals(other, interpreter) + } + + // 11. If Type(x) is Object and Type(y) is either String, Number, BigInt, or Symbol, return the result + // of the comparison ? ToPrimitive(x) == y. + (_, ValueData::Object(_)) => { + let mut primitive = interpreter.to_primitive(other, None); + primitive.equals(self, interpreter) + } + + // 12. If Type(x) is BigInt and Type(y) is Number, or if Type(x) is Number and Type(y) is BigInt, then + // a. If x or y are any of NaN, +∞, or -∞, return false. + // b. If the mathematical value of x is equal to the mathematical value of y, return true; otherwise return false. + (ValueData::BigInt(ref a), ValueData::Rational(ref b)) => a == b, + (ValueData::Rational(ref a), ValueData::BigInt(ref b)) => a == b, + (ValueData::BigInt(ref a), ValueData::Integer(ref b)) => a == b, + (ValueData::Integer(ref a), ValueData::BigInt(ref b)) => a == b, + + // 13. Return false. + _ => false, + } + } +} + +/// This function takes a string and conversts it to BigInt type. +/// +/// If the result is `NaN` than `None` is returned. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-stringtobigint +pub fn string_to_bigint(string: &str) -> Option { + if string.is_empty() { + return Some(BigInt::from(0)); + } + + BigInt::from_str(string) +} + +/// The internal comparison abstract operation SameValue(x, y), +/// where x and y are ECMAScript language values, produces true or false. +/// Such a comparison is performed as follows: +/// +/// https://tc39.es/ecma262/#sec-samevalue +/// strict mode currently compares the pointers +pub fn same_value(x: &Value, y: &Value, strict: bool) -> bool { + if strict { + // Do both Values point to the same underlying valueData? + return std::ptr::eq(x.data(), y.data()); + } + + // 1. If Type(x) is different from Type(y), return false. + if x.get_type() != y.get_type() { + return false; + } + + match (x.data(), y.data()) { + // 2. If Type(x) is Number or BigInt, then + // a. Return ! Type(x)::SameValue(x, y). + (ValueData::BigInt(x), ValueData::BigInt(y)) => BigInt::same_value(x, y), + (ValueData::Rational(x), ValueData::Rational(y)) => Number::same_value(*x, *y), + (ValueData::Rational(x), ValueData::Integer(y)) => Number::same_value(*x, f64::from(*y)), + (ValueData::Integer(x), ValueData::Rational(y)) => Number::same_value(f64::from(*x), *y), + (ValueData::Integer(x), ValueData::Integer(y)) => x == y, + + // 3. Return ! SameValueNonNumeric(x, y). + (_, _) => same_value_non_numeric(x, y), + } +} + +/// The internal comparison abstract operation SameValueZero(x, y), +/// where x and y are ECMAScript language values, produces true or false. +/// SameValueZero differs from SameValue only in its treatment of +0 and -0. +/// +/// Such a comparison is performed as follows: +/// +/// +pub fn same_value_zero(x: &Value, y: &Value) -> bool { + if x.get_type() != y.get_type() { + return false; + } + + match (x.data(), y.data()) { + // 2. If Type(x) is Number or BigInt, then + // a. Return ! Type(x)::SameValueZero(x, y). + (ValueData::BigInt(x), ValueData::BigInt(y)) => BigInt::same_value_zero(x, y), + + (ValueData::Rational(x), ValueData::Rational(y)) => Number::same_value_zero(*x, *y), + (ValueData::Rational(x), ValueData::Integer(y)) => { + Number::same_value_zero(*x, f64::from(*y)) + } + (ValueData::Integer(x), ValueData::Rational(y)) => { + Number::same_value_zero(f64::from(*x), *y) + } + (ValueData::Integer(x), ValueData::Integer(y)) => x == y, + + // 3. Return ! SameValueNonNumeric(x, y). + (_, _) => same_value_non_numeric(x, y), + } +} + +pub fn same_value_non_numeric(x: &Value, y: &Value) -> bool { + debug_assert!(x.get_type() == y.get_type()); + match x.get_type() { + "undefined" => true, + "null" => true, + "string" => { + if x.to_string() == y.to_string() { + return true; + } + false + } + "boolean" => bool::from(x) == bool::from(y), + "object" => std::ptr::eq(x, y), + _ => false, + } +} diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index 4def097028..1bf320ed88 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -6,20 +6,20 @@ mod tests; use crate::builtins::{ - function::Function, object::{ internal_methods_trait::ObjectInternalMethods, InternalState, InternalStateCell, Object, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE, }, property::Property, + BigInt, Function, }; -use crate::{syntax::ast::bigint::BigInt, BoaProfiler}; +use crate::BoaProfiler; + use gc::{Finalize, Gc, GcCell, GcCellRef, Trace}; use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue}; use std::{ any::Any, collections::HashSet, - convert::TryFrom, f64::NAN, fmt::{self, Display}, ops::{Add, BitAnd, BitOr, BitXor, Deref, DerefMut, Div, Mul, Neg, Not, Rem, Shl, Shr, Sub}, @@ -28,9 +28,12 @@ use std::{ pub mod conversions; pub mod display; +pub mod equality; pub mod operations; + pub use conversions::*; pub(crate) use display::display_obj; +pub use equality::*; pub use operations::*; /// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`) @@ -220,7 +223,7 @@ impl ValueData { } } - /// Returns true if the value is undefined + /// Returns true if the value is undefined. pub fn is_undefined(&self) -> bool { match *self { Self::Undefined => true, @@ -228,7 +231,7 @@ impl ValueData { } } - /// Returns true if the value is null + /// Returns true if the value is null. pub fn is_null(&self) -> bool { match *self { Self::Null => true, @@ -236,7 +239,7 @@ impl ValueData { } } - /// Returns true if the value is null or undefined + /// Returns true if the value is null or undefined. pub fn is_null_or_undefined(&self) -> bool { match *self { Self::Null | Self::Undefined => true, @@ -244,7 +247,7 @@ impl ValueData { } } - /// Returns true if the value is a 64-bit floating-point number + /// Returns true if the value is a 64-bit floating-point number. pub fn is_double(&self) -> bool { match *self { Self::Rational(_) => true, @@ -290,6 +293,14 @@ impl ValueData { } } + /// Returns true if the value is a bigint + pub fn is_bigint(&self) -> bool { + match *self { + Self::BigInt(_) => true, + _ => false, + } + } + /// Returns true if the value is true /// /// [toBoolean](https://tc39.es/ecma262/#sec-toboolean) @@ -350,27 +361,6 @@ impl ValueData { } } - /// Helper function. - pub fn to_bigint(&self) -> Option { - match self { - Self::String(ref string) => string_to_bigint(string), - Self::Boolean(true) => Some(BigInt::from(1)), - Self::Boolean(false) | Self::Null => Some(BigInt::from(0)), - Self::Rational(num) => BigInt::try_from(*num).ok(), - Self::Integer(num) => Some(BigInt::from(*num)), - ValueData::BigInt(b) => Some(b.clone()), - ValueData::Object(ref o) => { - let object = (o).deref().borrow(); - if object.kind == ObjectKind::BigInt { - object.get_internal_slot("BigIntData").to_bigint() - } else { - None - } - } - _ => None, - } - } - pub fn as_object(&self) -> Option> { match *self { ValueData::Object(ref o) => Some(o.borrow()), diff --git a/boa/src/builtins/value/operations.rs b/boa/src/builtins/value/operations.rs index e54459ea84..a0a529ef82 100644 --- a/boa/src/builtins/value/operations.rs +++ b/boa/src/builtins/value/operations.rs @@ -1,177 +1,4 @@ use super::*; -use crate::{builtins::Number, Interpreter}; - -use std::borrow::Borrow; -use std::convert::TryFrom; - -impl Value { - /// Strict equality comparison. - /// - /// This method is executed when doing strict equality comparisons with the `===` operator. - /// For more information, check . - pub fn strict_equals(&self, other: &Self) -> bool { - if self.get_type() != other.get_type() { - return false; - } - - if self.is_number() { - return Number::equals(f64::from(self), f64::from(other)); - } - - //Null has to be handled specially because "typeof null" returns object and if we managed - //this without a special case we would compare self and other as if they were actually - //objects which unfortunately fails - //Specification Link: https://tc39.es/ecma262/#sec-typeof-operator - if self.is_null() { - return true; - } - - same_value_non_number(self, other) - } - - /// Abstract equality comparison. - /// - /// This method is executed when doing abstract equality comparisons with the `==` operator. - /// For more information, check - #[allow(clippy::float_cmp)] - pub fn equals(&mut self, other: &mut Self, interpreter: &mut Interpreter) -> bool { - // 1. If Type(x) is the same as Type(y), then - // a. Return the result of performing Strict Equality Comparison x === y. - if self.get_type() == other.get_type() { - return self.strict_equals(other); - } - - match (self.data(), other.data()) { - // 2. If x is null and y is undefined, return true. - // 3. If x is undefined and y is null, return true. - _ if self.is_null_or_undefined() && other.is_null_or_undefined() => true, - - // 3. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ! ToNumber(y). - // 4. If Type(x) is String and Type(y) is Number, return the result of the comparison ! ToNumber(x) == y. - // - // https://github.com/rust-lang/rust/issues/54883 - (ValueData::Integer(_), ValueData::String(_)) - | (ValueData::Rational(_), ValueData::String(_)) - | (ValueData::String(_), ValueData::Integer(_)) - | (ValueData::String(_), ValueData::Rational(_)) - | (ValueData::Rational(_), ValueData::Boolean(_)) - | (ValueData::Integer(_), ValueData::Boolean(_)) => { - let a: &Value = self.borrow(); - let b: &Value = other.borrow(); - Number::equals(f64::from(a), f64::from(b)) - } - - // 6. If Type(x) is BigInt and Type(y) is String, then - // a. Let n be ! StringToBigInt(y). - // b. If n is NaN, return false. - // c. Return the result of the comparison x == n. - (ValueData::BigInt(ref a), ValueData::String(ref b)) => match string_to_bigint(b) { - Some(ref b) => a == b, - None => false, - }, - - // 7. If Type(x) is String and Type(y) is BigInt, return the result of the comparison y == x. - (ValueData::String(ref a), ValueData::BigInt(ref b)) => match string_to_bigint(a) { - Some(ref a) => a == b, - None => false, - }, - - // 8. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y. - (ValueData::Boolean(_), _) => { - other.equals(&mut Value::from(self.to_integer()), interpreter) - } - - // 9. If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y). - (_, ValueData::Boolean(_)) => { - self.equals(&mut Value::from(other.to_integer()), interpreter) - } - - // 10. If Type(x) is either String, Number, BigInt, or Symbol and Type(y) is Object, return the result - // of the comparison x == ? ToPrimitive(y). - (ValueData::Object(_), _) => { - let mut primitive = interpreter.to_primitive(self, None); - primitive.equals(other, interpreter) - } - - // 11. If Type(x) is Object and Type(y) is either String, Number, BigInt, or Symbol, return the result - // of the comparison ? ToPrimitive(x) == y. - (_, ValueData::Object(_)) => { - let mut primitive = interpreter.to_primitive(other, None); - primitive.equals(self, interpreter) - } - - // 12. If Type(x) is BigInt and Type(y) is Number, or if Type(x) is Number and Type(y) is BigInt, then - // a. If x or y are any of NaN, +∞, or -∞, return false. - // b. If the mathematical value of x is equal to the mathematical value of y, return true; otherwise return false. - (ValueData::BigInt(ref a), ValueData::Rational(ref b)) => a == b, - (ValueData::Rational(ref a), ValueData::BigInt(ref b)) => a == b, - (ValueData::BigInt(ref a), ValueData::Integer(ref b)) => a == b, - (ValueData::Integer(ref a), ValueData::BigInt(ref b)) => a == b, - - // 13. Return false. - _ => false, - } - } -} - -/// This function takes a string and conversts it to BigInt type. -/// -/// If the result is `NaN` than `None` is returned. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-stringtobigint -pub fn string_to_bigint(string: &str) -> Option { - if string.is_empty() { - return Some(BigInt::from(0)); - } - - BigInt::from_str(string).ok() -} - -/// The internal comparison abstract operation SameValue(x, y), -/// where x and y are ECMAScript language values, produces true or false. -/// Such a comparison is performed as follows: -/// -/// https://tc39.es/ecma262/#sec-samevalue -/// strict mode currently compares the pointers -pub fn same_value(x: &Value, y: &Value, strict: bool) -> bool { - if strict { - // Do both Values point to the same underlying valueData? - return std::ptr::eq(x.data(), y.data()); - } - - if x.get_type() != y.get_type() { - return false; - } - - // TODO: check BigInt - // https://github.com/jasonwilliams/boa/pull/358 - if x.is_number() { - return Number::same_value(f64::from(x), f64::from(y)); - } - - same_value_non_number(x, y) -} - -pub fn same_value_non_number(x: &Value, y: &Value) -> bool { - debug_assert!(x.get_type() == y.get_type()); - match x.get_type() { - "undefined" => true, - "null" => true, - "string" => { - if x.to_string() == y.to_string() { - return true; - } - false - } - "bigint" => BigInt::try_from(x).unwrap() == BigInt::try_from(y).unwrap(), - "boolean" => bool::from(x) == bool::from(y), - "object" => std::ptr::eq(x, y), - _ => false, - } -} impl Add for Value { type Output = Self; @@ -315,22 +142,3 @@ impl Neg for Value { } } } - -/// The internal comparison abstract operation SameValueZero(x, y), -/// where x and y are ECMAScript language values, produces true or false. -/// SameValueZero differs from SameValue only in its treatment of +0 and -0. -/// -/// Such a comparison is performed as follows: -/// -/// -pub fn same_value_zero(x: &Value, y: &Value) -> bool { - if x.get_type() != y.get_type() { - return false; - } - - if x.is_number() { - return Number::same_value_zero(f64::from(x), f64::from(y)); - } - - same_value_non_number(x, y) -} diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index ea24f09e6d..fb468d9a14 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -31,6 +31,7 @@ use crate::{ }, BoaProfiler, }; +use std::convert::TryFrom; use std::{borrow::Borrow, ops::Deref}; pub trait Executable { @@ -147,7 +148,7 @@ impl Interpreter { self.throw_type_error("can't convert symbol to string")?; unreachable!(); } - ValueData::BigInt(ref bigint) => Ok(BigInt::to_native_string(bigint)), + ValueData::BigInt(ref bigint) => Ok(bigint.to_string()), ValueData::Object(_) => { let primitive = self.to_primitive(&mut value.clone(), Some("string")); self.to_string(&primitive) @@ -155,6 +156,44 @@ impl Interpreter { } } + /// Helper function. + #[allow(clippy::wrong_self_convention)] + pub fn to_bigint(&mut self, value: &Value) -> Result { + match value.data() { + ValueData::Null => { + self.throw_type_error("cannot convert null to a BigInt")?; + unreachable!(); + } + ValueData::Undefined => { + self.throw_type_error("cannot convert undefined to a BigInt")?; + unreachable!(); + } + ValueData::String(ref string) => Ok(BigInt::from_string(string, self)?), + ValueData::Boolean(true) => Ok(BigInt::from(1)), + ValueData::Boolean(false) => Ok(BigInt::from(0)), + ValueData::Integer(num) => Ok(BigInt::from(*num)), + ValueData::Rational(num) => { + if let Ok(bigint) = BigInt::try_from(*num) { + return Ok(bigint); + } + self.throw_type_error(format!( + "The number {} cannot be converted to a BigInt because it is not an integer", + num + ))?; + unreachable!(); + } + ValueData::BigInt(b) => Ok(b.clone()), + ValueData::Object(_) => { + let primitive = self.to_primitive(&mut value.clone(), Some("number")); + self.to_bigint(&primitive) + } + ValueData::Symbol(_) => { + self.throw_type_error("cannot convert Symbol to a BigInt")?; + unreachable!(); + } + } + } + /// Converts an array object into a rust vector of values. /// /// This is useful for the spread operator, for any other object an `Err` is returned diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index a09c98904c..64ee6e4d5f 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -1,4 +1,4 @@ -use crate::{exec, exec::Interpreter, forward, realm::Realm}; +use crate::{builtins::Value, exec, exec::Interpreter, forward, realm::Realm}; #[test] fn empty_let_decl_undefined() { @@ -753,3 +753,27 @@ fn function_decl_hoisting() { "#; assert_eq!(&exec(scenario), "5"); } + +#[test] +fn to_bigint() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + assert!(engine.to_bigint(&Value::null()).is_err()); + assert!(engine.to_bigint(&Value::undefined()).is_err()); + assert!(engine.to_bigint(&Value::integer(55)).is_ok()); + assert!(engine.to_bigint(&Value::rational(10.0)).is_ok()); + assert!(engine.to_bigint(&Value::string("100")).is_ok()); +} + +#[test] +fn to_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + assert_eq!(engine.to_string(&Value::null()).unwrap(), "null"); + assert_eq!(engine.to_string(&Value::undefined()).unwrap(), "undefined"); + assert_eq!(engine.to_string(&Value::integer(55)).unwrap(), "55"); + assert_eq!(engine.to_string(&Value::rational(55.0)).unwrap(), "55"); + assert_eq!(engine.to_string(&Value::string("hello")).unwrap(), "hello"); +} diff --git a/boa/src/syntax/ast/bigint.rs b/boa/src/syntax/ast/bigint.rs deleted file mode 100644 index c432ca01f5..0000000000 --- a/boa/src/syntax/ast/bigint.rs +++ /dev/null @@ -1,246 +0,0 @@ -//! This module implements the `BigInt` structure, which represents the BigInt values in JavaScript. -//! -//! More information: -//! - [ECMAScript reference][spec] -//! - [MDN documentation][mdn] -//! -//! [spec]: https://tc39.es/ecma262/#sec-bigint-objects -//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt - -use gc::{unsafe_empty_trace, Finalize, Trace}; -use num_traits::cast::{FromPrimitive, ToPrimitive}; -use num_traits::pow::Pow; - -use std::convert::TryFrom; -use std::str::FromStr; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] -pub struct BigInt(num_bigint::BigInt); - -impl BigInt { - /// Returns the inner bigint structure. - #[inline] - pub fn into_inner(self) -> num_bigint::BigInt { - self.0 - } - - /// Converts a string to a BigInt with the specified radix. - #[inline] - pub fn from_str_radix(buf: &str, radix: u32) -> Option { - num_bigint::BigInt::parse_bytes(buf.as_bytes(), radix).map(Self) - } - - #[inline] - pub fn pow(self, other: &Self) -> Self { - Self( - self.0.pow( - other - .0 - .to_biguint() - .expect("RangeError: \"BigInt negative exponent\""), - ), - ) - } - - /// Converts the BigInt to a f64 type. - /// - /// Returns `std::f64::INFINITY` if the BigInt is too big. - #[inline] - pub fn to_f64(&self) -> f64 { - self.0.to_f64().unwrap_or(std::f64::INFINITY) - } -} - -impl From for BigInt { - fn from(n: i64) -> BigInt { - BigInt(num_bigint::BigInt::from(n)) - } -} - -impl From for BigInt { - fn from(n: i32) -> BigInt { - BigInt(num_bigint::BigInt::from(n)) - } -} - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct TryFromF64Error; - -impl std::fmt::Display for TryFromF64Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Could not convert f64 value to a BigInt type") - } -} - -impl TryFrom for BigInt { - type Error = TryFromF64Error; - - fn try_from(n: f64) -> Result { - match num_bigint::BigInt::from_f64(n) { - Some(bigint) => Ok(BigInt(bigint)), - None => Err(TryFromF64Error), - } - } -} - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct ParseBigIntError; - -impl std::fmt::Display for ParseBigIntError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Could not parse to BigInt type") - } -} - -impl FromStr for BigInt { - type Err = ParseBigIntError; - - fn from_str(string: &str) -> Result { - match num_bigint::BigInt::from_str(string) { - Ok(bigint) => Ok(BigInt(bigint)), - Err(_) => Err(ParseBigIntError), - } - } -} - -macro_rules! impl_bigint_operator { - ($op:ident, $op_method:ident, $assign_op:ident, $assign_op_method:ident) => { - impl std::ops::$op for BigInt { - type Output = Self; - - fn $op_method(mut self, other: Self) -> Self { - std::ops::$assign_op::$assign_op_method(&mut self.0, other.0); - self - } - } - }; -} - -impl_bigint_operator!(Add, add, AddAssign, add_assign); -impl_bigint_operator!(Sub, sub, SubAssign, sub_assign); -impl_bigint_operator!(Mul, mul, MulAssign, mul_assign); -impl_bigint_operator!(Div, div, DivAssign, div_assign); -impl_bigint_operator!(Rem, rem, RemAssign, rem_assign); -impl_bigint_operator!(BitAnd, bitand, BitAndAssign, bitand_assign); -impl_bigint_operator!(BitOr, bitor, BitOrAssign, bitor_assign); -impl_bigint_operator!(BitXor, bitxor, BitXorAssign, bitxor_assign); - -impl std::ops::Shr for BigInt { - type Output = Self; - - fn shr(mut self, other: Self) -> Self::Output { - use std::ops::ShlAssign; - use std::ops::ShrAssign; - - if let Some(n) = other.0.to_i32() { - if n > 0 { - self.0.shr_assign(n as usize) - } else { - self.0.shl_assign(n.abs() as usize) - } - - return self; - } - - panic!("RangeError: Maximum BigInt size exceeded"); - } -} - -impl std::ops::Shl for BigInt { - type Output = Self; - - fn shl(mut self, other: Self) -> Self::Output { - use std::ops::ShlAssign; - use std::ops::ShrAssign; - - if let Some(n) = other.0.to_i32() { - if n > 0 { - self.0.shl_assign(n as usize) - } else { - self.0.shr_assign(n.abs() as usize) - } - - return self; - } - - panic!("RangeError: Maximum BigInt size exceeded"); - } -} - -impl std::ops::Neg for BigInt { - type Output = Self; - - fn neg(self) -> Self::Output { - Self(-self.0) - } -} - -impl PartialEq for BigInt { - fn eq(&self, other: &i32) -> bool { - self.0 == num_bigint::BigInt::from(*other) - } -} - -impl PartialEq for i32 { - fn eq(&self, other: &BigInt) -> bool { - num_bigint::BigInt::from(*self) == other.0 - } -} - -impl PartialEq for BigInt { - fn eq(&self, other: &f64) -> bool { - if other.fract() != 0.0 { - return false; - } - - self.0 == num_bigint::BigInt::from(*other as i64) - } -} - -impl PartialEq for f64 { - fn eq(&self, other: &BigInt) -> bool { - if self.fract() != 0.0 { - return false; - } - - num_bigint::BigInt::from(*self as i64) == other.0 - } -} - -impl std::fmt::Debug for BigInt { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl std::fmt::Display for BigInt { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl std::ops::Deref for BigInt { - type Target = num_bigint::BigInt; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl std::ops::DerefMut for BigInt { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Finalize for BigInt {} -unsafe impl Trace for BigInt { - // BigInt type implements an empty trace becasue the inner structure - // `num_bigint::BigInt` does not implement `Trace` trait. - // If it did this would be unsound. - unsafe_empty_trace!(); -} diff --git a/boa/src/syntax/ast/constant.rs b/boa/src/syntax/ast/constant.rs index eb05b1bee5..07b1fc2777 100644 --- a/boa/src/syntax/ast/constant.rs +++ b/boa/src/syntax/ast/constant.rs @@ -7,7 +7,7 @@ //! [spec]: https://tc39.es/ecma262/#sec-primary-expression-literals //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Literals -use crate::syntax::ast::bigint::BigInt; +use crate::builtins::bigint::BigInt; use gc::{Finalize, Trace}; use std::fmt::{Display, Formatter, Result}; diff --git a/boa/src/syntax/ast/mod.rs b/boa/src/syntax/ast/mod.rs index 98c116b656..6d96c9512d 100644 --- a/boa/src/syntax/ast/mod.rs +++ b/boa/src/syntax/ast/mod.rs @@ -1,6 +1,5 @@ //! The Javascript Abstract Syntax Tree. -pub mod bigint; pub mod constant; pub mod keyword; pub mod node; diff --git a/boa/src/syntax/ast/token.rs b/boa/src/syntax/ast/token.rs index dafcc0ae92..f63dfd13b3 100644 --- a/boa/src/syntax/ast/token.rs +++ b/boa/src/syntax/ast/token.rs @@ -5,8 +5,9 @@ //! //! [spec]: https://tc39.es/ecma262/#sec-tokens +use crate::builtins::BigInt; use crate::syntax::{ - ast::{bigint::BigInt, Keyword, Punctuator, Span}, + ast::{Keyword, Punctuator, Span}, lexer::LexerError, }; use bitflags::bitflags; diff --git a/boa/src/syntax/lexer/mod.rs b/boa/src/syntax/lexer/mod.rs index 3d370d4029..04c05e53e2 100644 --- a/boa/src/syntax/lexer/mod.rs +++ b/boa/src/syntax/lexer/mod.rs @@ -6,7 +6,7 @@ #[cfg(test)] mod tests; -use crate::syntax::ast::bigint::BigInt; +use crate::builtins::BigInt; use crate::{ syntax::ast::{ token::{NumericLiteral, Token, TokenKind}, @@ -442,7 +442,7 @@ impl<'a> Lexer<'a> { let num = match kind { NumericKind::BigInt(base) => { NumericLiteral::BigInt( - BigInt::from_str_radix(&buf, base as u32).expect("Could not conver to BigInt") + BigInt::from_string_radix(&buf, base as u32).expect("Could not conver to BigInt") ) } NumericKind::Rational /* base: 10 */ => {