Browse Source

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.
pull/460/head
HalidOdat 5 years ago committed by GitHub
parent
commit
d847ff826b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 96
      boa/src/builtins/bigint/conversions.rs
  2. 74
      boa/src/builtins/bigint/equality.rs
  3. 103
      boa/src/builtins/bigint/mod.rs
  4. 104
      boa/src/builtins/bigint/operations.rs
  5. 84
      boa/src/builtins/bigint/tests.rs
  6. 1
      boa/src/builtins/mod.rs
  7. 10
      boa/src/builtins/number/mod.rs
  8. 16
      boa/src/builtins/number/tests.rs
  9. 20
      boa/src/builtins/value/conversions.rs
  10. 215
      boa/src/builtins/value/equality.rs
  11. 46
      boa/src/builtins/value/mod.rs
  12. 192
      boa/src/builtins/value/operations.rs
  13. 41
      boa/src/exec/mod.rs
  14. 26
      boa/src/exec/tests.rs
  15. 246
      boa/src/syntax/ast/bigint.rs
  16. 2
      boa/src/syntax/ast/constant.rs
  17. 1
      boa/src/syntax/ast/mod.rs
  18. 3
      boa/src/syntax/ast/token.rs
  19. 4
      boa/src/syntax/lexer/mod.rs

96
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<Self, Value> {
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<Self> {
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<Self> {
match num_bigint::BigInt::from_str(string) {
Ok(bigint) => Some(BigInt(bigint)),
Err(_) => None,
}
}
}
impl From<i64> for BigInt {
fn from(n: i64) -> BigInt {
BigInt(num_bigint::BigInt::from(n))
}
}
impl From<i32> 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<f64> for BigInt {
type Error = TryFromF64Error;
fn try_from(n: f64) -> Result<Self, Self::Error> {
// 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),
}
}
}

74
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<i32> for BigInt {
fn eq(&self, other: &i32) -> bool {
self.0 == num_bigint::BigInt::from(*other)
}
}
impl PartialEq<BigInt> for i32 {
fn eq(&self, other: &BigInt) -> bool {
num_bigint::BigInt::from(*self) == other.0
}
}
impl PartialEq<f64> 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<BigInt> for f64 {
fn eq(&self, other: &BigInt) -> bool {
if self.fract() != 0.0 {
return false;
}
num_bigint::BigInt::from(*self as i64) == other.0
}
}

103
boa/src/builtins/bigint/mod.rs

@ -15,21 +15,67 @@
use crate::{ use crate::{
builtins::{ builtins::{
function::{make_builtin_fn, make_constructor_fn}, function::{make_builtin_fn, make_constructor_fn},
value::{ResultValue, Value}, value::{ResultValue, Value, ValueData},
}, },
exec::Interpreter, exec::Interpreter,
syntax::ast::bigint::BigInt as AstBigInt,
BoaProfiler, 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)] #[cfg(test)]
mod tests; mod tests;
/// `BigInt` implementation. /// `BigInt` implementation.
#[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub(crate) struct BigInt; #[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct BigInt(num_bigint::BigInt);
impl 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<Self, Value> {
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()` /// `BigInt()`
/// ///
/// The `BigInt()` constructor is used to create BigInt objects. /// The `BigInt()` constructor is used to create BigInt objects.
@ -46,34 +92,12 @@ impl BigInt {
ctx: &mut Interpreter, ctx: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
let data = match args.get(0) { let data = match args.get(0) {
Some(ref value) => { Some(ref value) => Value::from(ctx.to_bigint(value)?),
if let Some(bigint) = value.to_bigint() { None => Value::from(Self::from(0)),
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)),
}; };
Ok(data) 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] )` /// `BigInt.prototype.toString( [radix] )`
/// ///
/// The `toString()` method returns a string representing the specified BigInt object. /// The `toString()` method returns a string representing the specified BigInt object.
@ -99,10 +123,9 @@ impl BigInt {
return ctx return ctx
.throw_range_error("radix must be an integer at least 2 and no greater than 36"); .throw_range_error("radix must be an integer at least 2 and no greater than 36");
} }
Ok(Value::from(Self::to_native_string_radix( Ok(Value::from(
&this.to_bigint().unwrap(), Self::this_bigint_value(this, ctx)?.to_string_radix(radix as u32),
radix as u32, ))
)))
} }
/// `BigInt.prototype.valueOf()` /// `BigInt.prototype.valueOf()`
@ -118,17 +141,15 @@ impl BigInt {
pub(crate) fn value_of( pub(crate) fn value_of(
this: &mut Value, this: &mut Value,
_args: &[Value], _args: &[Value],
_ctx: &mut Interpreter, ctx: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
Ok(Value::from( Ok(Value::from(Self::this_bigint_value(this, ctx)?))
this.to_bigint().expect("BigInt.prototype.valueOf"),
))
} }
/// Create a new `Number` object /// Create a new `Number` object
pub(crate) fn create(global: &Value) -> Value { pub(crate) fn create(global: &Value) -> Value {
let prototype = Value::new_object(Some(global)); 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::to_string, "toString", &prototype, 1);
make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); make_builtin_fn(Self::value_of, "valueOf", &prototype, 0);
@ -143,3 +164,11 @@ impl BigInt {
global.set_field("BigInt", Self::create(global)); 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!();
}

104
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)
}
}

84
boa/src/builtins/bigint/tests.rs

@ -1,4 +1,4 @@
use crate::{forward, Interpreter, Realm}; use crate::{forward, forward_val, Interpreter, Realm};
#[test] #[test]
fn equality() { fn equality() {
@ -55,7 +55,7 @@ fn equality() {
} }
#[test] #[test]
fn bigint_function_conversion() { fn bigint_function_conversion_from_integer() {
let realm = Realm::create(); let realm = Realm::create();
let mut engine = Interpreter::new(realm); 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] #[test]
fn add() { fn add() {
let realm = Realm::create(); let realm = Realm::create();
@ -138,10 +215,7 @@ fn to_string() {
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
assert_eq!(forward(&mut engine, "1000n.toString()"), "1000"); assert_eq!(forward(&mut engine, "1000n.toString()"), "1000");
assert_eq!(forward(&mut engine, "1000n.toString(2)"), "1111101000"); assert_eq!(forward(&mut engine, "1000n.toString(2)"), "1111101000");
assert_eq!(forward(&mut engine, "255n.toString(16)"), "ff"); assert_eq!(forward(&mut engine, "255n.toString(16)"), "ff");
assert_eq!(forward(&mut engine, "1000n.toString(36)"), "rs"); assert_eq!(forward(&mut engine, "1000n.toString(36)"), "rs");
} }

1
boa/src/builtins/mod.rs

@ -21,6 +21,7 @@ pub(crate) use self::{
bigint::BigInt, bigint::BigInt,
boolean::Boolean, boolean::Boolean,
error::{Error, RangeError, TypeError}, error::{Error, RangeError, TypeError},
function::Function,
number::Number, number::Number,
regexp::RegExp, regexp::RegExp,
string::String, string::String,

10
boa/src/builtins/number/mod.rs

@ -445,8 +445,8 @@ impl Number {
/// ///
/// https://tc39.es/ecma262/#sec-numeric-types-number-equal /// https://tc39.es/ecma262/#sec-numeric-types-number-equal
#[allow(clippy::float_cmp)] #[allow(clippy::float_cmp)]
pub(crate) fn equals(a: f64, b: f64) -> bool { pub(crate) fn equal(x: f64, y: f64) -> bool {
a == b x == y
} }
/// The abstract operation Number::sameValue takes arguments /// The abstract operation Number::sameValue takes arguments
@ -476,11 +476,11 @@ impl Number {
/// ///
/// https://tc39.es/ecma262/#sec-numeric-types-number-sameValueZero /// https://tc39.es/ecma262/#sec-numeric-types-number-sameValueZero
#[allow(clippy::float_cmp)] #[allow(clippy::float_cmp)]
pub(crate) fn same_value_zero(a: f64, b: f64) -> bool { pub(crate) fn same_value_zero(x: f64, y: f64) -> bool {
if a.is_nan() && b.is_nan() { if x.is_nan() && y.is_nan() {
return true; return true;
} }
a == b x == y
} }
} }

16
boa/src/builtins/number/tests.rs

@ -403,13 +403,13 @@ fn value_of() {
#[test] #[test]
fn equal() { fn equal() {
assert_eq!(Number::equals(0.0, 0.0), true); assert_eq!(Number::equal(0.0, 0.0), true);
assert_eq!(Number::equals(-0.0, 0.0), true); assert_eq!(Number::equal(-0.0, 0.0), true);
assert_eq!(Number::equals(0.0, -0.0), true); assert_eq!(Number::equal(0.0, -0.0), true);
assert_eq!(Number::equals(f64::NAN, -0.0), false); assert_eq!(Number::equal(f64::NAN, -0.0), false);
assert_eq!(Number::equals(0.0, f64::NAN), 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] #[test]
@ -419,7 +419,7 @@ fn same_value() {
assert_eq!(Number::same_value(0.0, -0.0), false); 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(f64::NAN, -0.0), false);
assert_eq!(Number::same_value(0.0, f64::NAN), 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] #[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(0.0, -0.0), true);
assert_eq!(Number::same_value_zero(f64::NAN, -0.0), false); 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::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] #[test]

20
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<Self, Self::Error> {
match value.data() {
ValueData::BigInt(ref bigint) => Ok(bigint.clone()),
_ => Err(TryFromBigIntError),
}
}
}
impl From<BigInt> for Value { impl From<BigInt> for Value {
fn from(value: BigInt) -> Self { fn from(value: BigInt) -> Self {
Value::bigint(value) Value::bigint(value)

215
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 <https://tc39.es/ecma262/#sec-strict-equality-comparison>.
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 <https://tc39.es/ecma262/#sec-abstract-equality-comparison>
#[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<BigInt> {
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:
///
/// <https://tc39.es/ecma262/#sec-samevaluezero>
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,
}
}

46
boa/src/builtins/value/mod.rs

@ -6,20 +6,20 @@
mod tests; mod tests;
use crate::builtins::{ use crate::builtins::{
function::Function,
object::{ object::{
internal_methods_trait::ObjectInternalMethods, InternalState, InternalStateCell, Object, internal_methods_trait::ObjectInternalMethods, InternalState, InternalStateCell, Object,
ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE,
}, },
property::Property, property::Property,
BigInt, Function,
}; };
use crate::{syntax::ast::bigint::BigInt, BoaProfiler}; use crate::BoaProfiler;
use gc::{Finalize, Gc, GcCell, GcCellRef, Trace}; use gc::{Finalize, Gc, GcCell, GcCellRef, Trace};
use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue}; use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue};
use std::{ use std::{
any::Any, any::Any,
collections::HashSet, collections::HashSet,
convert::TryFrom,
f64::NAN, f64::NAN,
fmt::{self, Display}, fmt::{self, Display},
ops::{Add, BitAnd, BitOr, BitXor, Deref, DerefMut, Div, Mul, Neg, Not, Rem, Shl, Shr, Sub}, 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 conversions;
pub mod display; pub mod display;
pub mod equality;
pub mod operations; pub mod operations;
pub use conversions::*; pub use conversions::*;
pub(crate) use display::display_obj; pub(crate) use display::display_obj;
pub use equality::*;
pub use operations::*; pub use operations::*;
/// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`) /// 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 { pub fn is_undefined(&self) -> bool {
match *self { match *self {
Self::Undefined => true, 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 { pub fn is_null(&self) -> bool {
match *self { match *self {
Self::Null => true, 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 { pub fn is_null_or_undefined(&self) -> bool {
match *self { match *self {
Self::Null | Self::Undefined => true, 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 { pub fn is_double(&self) -> bool {
match *self { match *self {
Self::Rational(_) => true, 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 /// Returns true if the value is true
/// ///
/// [toBoolean](https://tc39.es/ecma262/#sec-toboolean) /// [toBoolean](https://tc39.es/ecma262/#sec-toboolean)
@ -350,27 +361,6 @@ impl ValueData {
} }
} }
/// Helper function.
pub fn to_bigint(&self) -> Option<BigInt> {
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<GcCellRef<'_, Object>> { pub fn as_object(&self) -> Option<GcCellRef<'_, Object>> {
match *self { match *self {
ValueData::Object(ref o) => Some(o.borrow()), ValueData::Object(ref o) => Some(o.borrow()),

192
boa/src/builtins/value/operations.rs

@ -1,177 +1,4 @@
use super::*; 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 <https://tc39.es/ecma262/#sec-strict-equality-comparison>.
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 <https://tc39.es/ecma262/#sec-abstract-equality-comparison>
#[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<BigInt> {
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 { impl Add for Value {
type Output = Self; 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:
///
/// <https://tc39.es/ecma262/#sec-samevaluezero>
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)
}

41
boa/src/exec/mod.rs

@ -31,6 +31,7 @@ use crate::{
}, },
BoaProfiler, BoaProfiler,
}; };
use std::convert::TryFrom;
use std::{borrow::Borrow, ops::Deref}; use std::{borrow::Borrow, ops::Deref};
pub trait Executable { pub trait Executable {
@ -147,7 +148,7 @@ impl Interpreter {
self.throw_type_error("can't convert symbol to string")?; self.throw_type_error("can't convert symbol to string")?;
unreachable!(); unreachable!();
} }
ValueData::BigInt(ref bigint) => Ok(BigInt::to_native_string(bigint)), ValueData::BigInt(ref bigint) => Ok(bigint.to_string()),
ValueData::Object(_) => { ValueData::Object(_) => {
let primitive = self.to_primitive(&mut value.clone(), Some("string")); let primitive = self.to_primitive(&mut value.clone(), Some("string"));
self.to_string(&primitive) 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<BigInt, Value> {
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. /// 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 /// This is useful for the spread operator, for any other object an `Err` is returned

26
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] #[test]
fn empty_let_decl_undefined() { fn empty_let_decl_undefined() {
@ -753,3 +753,27 @@ fn function_decl_hoisting() {
"#; "#;
assert_eq!(&exec(scenario), "5"); 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");
}

246
boa/src/syntax/ast/bigint.rs

@ -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<Self> {
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<i64> for BigInt {
fn from(n: i64) -> BigInt {
BigInt(num_bigint::BigInt::from(n))
}
}
impl From<i32> 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<f64> for BigInt {
type Error = TryFromF64Error;
fn try_from(n: f64) -> Result<Self, Self::Error> {
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<Self, Self::Err> {
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<i32> for BigInt {
fn eq(&self, other: &i32) -> bool {
self.0 == num_bigint::BigInt::from(*other)
}
}
impl PartialEq<BigInt> for i32 {
fn eq(&self, other: &BigInt) -> bool {
num_bigint::BigInt::from(*self) == other.0
}
}
impl PartialEq<f64> 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<BigInt> 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!();
}

2
boa/src/syntax/ast/constant.rs

@ -7,7 +7,7 @@
//! [spec]: https://tc39.es/ecma262/#sec-primary-expression-literals //! [spec]: https://tc39.es/ecma262/#sec-primary-expression-literals
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#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 gc::{Finalize, Trace};
use std::fmt::{Display, Formatter, Result}; use std::fmt::{Display, Formatter, Result};

1
boa/src/syntax/ast/mod.rs

@ -1,6 +1,5 @@
//! The Javascript Abstract Syntax Tree. //! The Javascript Abstract Syntax Tree.
pub mod bigint;
pub mod constant; pub mod constant;
pub mod keyword; pub mod keyword;
pub mod node; pub mod node;

3
boa/src/syntax/ast/token.rs

@ -5,8 +5,9 @@
//! //!
//! [spec]: https://tc39.es/ecma262/#sec-tokens //! [spec]: https://tc39.es/ecma262/#sec-tokens
use crate::builtins::BigInt;
use crate::syntax::{ use crate::syntax::{
ast::{bigint::BigInt, Keyword, Punctuator, Span}, ast::{Keyword, Punctuator, Span},
lexer::LexerError, lexer::LexerError,
}; };
use bitflags::bitflags; use bitflags::bitflags;

4
boa/src/syntax/lexer/mod.rs

@ -6,7 +6,7 @@
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use crate::syntax::ast::bigint::BigInt; use crate::builtins::BigInt;
use crate::{ use crate::{
syntax::ast::{ syntax::ast::{
token::{NumericLiteral, Token, TokenKind}, token::{NumericLiteral, Token, TokenKind},
@ -442,7 +442,7 @@ impl<'a> Lexer<'a> {
let num = match kind { let num = match kind {
NumericKind::BigInt(base) => { NumericKind::BigInt(base) => {
NumericLiteral::BigInt( 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 */ => { NumericKind::Rational /* base: 10 */ => {

Loading…
Cancel
Save