Browse Source

Refactor JavaScript bigint rust type (#1408)

pull/1424/head
Halid Odat 3 years ago committed by GitHub
parent
commit
2cd32ed816
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      boa/Cargo.toml
  2. 437
      boa/src/bigint.rs
  3. 107
      boa/src/builtins/bigint/conversions.rs
  4. 74
      boa/src/builtins/bigint/equality.rs
  5. 66
      boa/src/builtins/bigint/mod.rs
  6. 107
      boa/src/builtins/bigint/operations.rs
  7. 7
      boa/src/bytecompiler.rs
  8. 5
      boa/src/lib.rs
  9. 11
      boa/src/object/mod.rs
  10. 2
      boa/src/string.rs
  11. 8
      boa/src/syntax/ast/constant.rs
  12. 4
      boa/src/syntax/lexer/number.rs
  13. 8
      boa/src/syntax/lexer/token.rs
  14. 10
      boa/src/value/conversions.rs
  15. 38
      boa/src/value/equality.rs
  16. 50
      boa/src/value/mod.rs
  17. 72
      boa/src/value/operations.rs
  18. 45
      boa/src/value/rcbigint.rs

2
boa/Cargo.toml

@ -23,7 +23,7 @@ console = []
[dependencies]
boa_unicode = { path = "../boa_unicode", version = "0.11.0" }
gc = { version = "0.4.1", features = ["derive"] }
serde = { version = "1.0.126", features = ["derive"] }
serde = { version = "1.0.126", features = ["derive", "rc"] }
serde_json = "1.0.64"
rand = "0.8.4"
num-traits = "0.2.14"

437
boa/src/bigint.rs

@ -0,0 +1,437 @@
//! This module implements the JavaScript bigint primitive rust type.
use crate::{
builtins::Number,
gc::{empty_trace, Finalize, Trace},
Context, Value,
};
use std::{
convert::TryFrom,
fmt::{self, Display},
ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub},
rc::Rc,
};
use num_integer::Integer;
use num_traits::pow::Pow;
use num_traits::{FromPrimitive, One, ToPrimitive, Zero};
/// The raw bigint type.
pub type RawBigInt = num_bigint::BigInt;
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
/// JavaScript bigint primitive rust type.
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[derive(Debug, Finalize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct JsBigInt {
inner: Rc<RawBigInt>,
}
// Safety: BigInt does not contain any objects which needs to be traced,
// so this is safe.
unsafe impl Trace for JsBigInt {
empty_trace!();
}
impl JsBigInt {
/// Create a new [`JsBigInt`].
#[inline]
pub fn new<T: Into<Self>>(value: T) -> Self {
value.into()
}
/// Create a [`JsBigInt`] with value `0`.
#[inline]
pub fn zero() -> Self {
Self {
inner: Rc::new(RawBigInt::zero()),
}
}
/// Check if is zero.
#[inline]
pub fn is_zero(&self) -> bool {
self.inner.is_zero()
}
/// Create a [`JsBigInt`] with value `1`.
#[inline]
pub fn one() -> Self {
Self {
inner: Rc::new(RawBigInt::one()),
}
}
/// Check if is one.
#[inline]
pub fn is_one(&self) -> bool {
self.inner.is_one()
}
/// Convert bigint to string with radix.
#[inline]
pub fn to_string_radix(&self, radix: u32) -> String {
self.inner.to_str_radix(radix)
}
/// Converts the BigInt to a f64 type.
///
/// Returns `f64::INFINITY` if the BigInt is too big.
#[inline]
pub fn to_f64(&self) -> f64 {
self.inner.to_f64().unwrap_or(f64::INFINITY)
}
/// Converts a string to a BigInt with the specified radix.
#[inline]
pub fn from_string_radix(buf: &str, radix: u32) -> Option<Self> {
Some(Self {
inner: Rc::new(RawBigInt::parse_bytes(buf.as_bytes(), radix)?),
})
}
/// 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 fn from_string(mut string: &str) -> Option<Self> {
string = string.trim();
if string.is_empty() {
return Some(JsBigInt::zero());
}
let mut radix = 10;
if string.starts_with("0b") || string.starts_with("0B") {
radix = 2;
string = &string[2..];
} else if string.starts_with("0x") || string.starts_with("0X") {
radix = 16;
string = &string[2..];
} else if string.starts_with("0o") || string.starts_with("0O") {
radix = 8;
string = &string[2..];
}
Self::from_string_radix(string, radix)
}
/// Checks for `SameValueZero` equality.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-numeric-types-bigint-equal
#[inline]
pub 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 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 fn equal(x: &Self, y: &Self) -> bool {
x == y
}
#[inline]
pub fn pow(x: &Self, y: &Self, context: &mut Context) -> Result<Self, Value> {
let y = if let Some(y) = y.inner.to_biguint() {
y
} else {
return Err(context.construct_range_error("BigInt negative exponent"));
};
Ok(Self::new(x.inner.as_ref().clone().pow(y)))
}
#[inline]
pub fn shift_right(x: &Self, y: &Self, context: &mut Context) -> Result<Self, Value> {
if let Some(n) = y.inner.to_i32() {
let inner = if n > 0 {
x.inner.as_ref().clone().shr(n as usize)
} else {
x.inner.as_ref().clone().shl(n.abs() as usize)
};
Ok(Self::new(inner))
} else {
Err(context.construct_range_error("Maximum BigInt size exceeded"))
}
}
#[inline]
pub fn shift_left(x: &Self, y: &Self, context: &mut Context) -> Result<Self, Value> {
if let Some(n) = y.inner.to_i32() {
let inner = if n > 0 {
x.inner.as_ref().clone().shl(n as usize)
} else {
x.inner.as_ref().clone().shr(n.abs() as usize)
};
Ok(Self::new(inner))
} else {
Err(context.construct_range_error("Maximum BigInt size exceeded"))
}
}
/// Floored integer modulo.
///
/// # Examples
/// ```
/// # use num_integer::Integer;
/// assert_eq!((8).mod_floor(&3), 2);
/// assert_eq!((8).mod_floor(&-3), -1);
/// ```
#[inline]
pub fn mod_floor(x: &Self, y: &Self) -> Self {
Self::new(x.inner.mod_floor(&y.inner))
}
#[inline]
pub fn add(x: &Self, y: &Self) -> Self {
Self::new(x.inner.as_ref().clone().add(y.inner.as_ref()))
}
#[inline]
pub fn sub(x: &Self, y: &Self) -> Self {
Self::new(x.inner.as_ref().clone().sub(y.inner.as_ref()))
}
#[inline]
pub fn mul(x: &Self, y: &Self) -> Self {
Self::new(x.inner.as_ref().clone().mul(y.inner.as_ref()))
}
#[inline]
pub fn div(x: &Self, y: &Self) -> Self {
Self::new(x.inner.as_ref().clone().div(y.inner.as_ref()))
}
#[inline]
pub fn rem(x: &Self, y: &Self) -> Self {
Self::new(x.inner.as_ref().clone().rem(y.inner.as_ref()))
}
#[inline]
pub fn bitand(x: &Self, y: &Self) -> Self {
Self::new(x.inner.as_ref().clone().bitand(y.inner.as_ref()))
}
#[inline]
pub fn bitor(x: &Self, y: &Self) -> Self {
Self::new(x.inner.as_ref().clone().bitor(y.inner.as_ref()))
}
#[inline]
pub fn bitxor(x: &Self, y: &Self) -> Self {
Self::new(x.inner.as_ref().clone().bitxor(y.inner.as_ref()))
}
#[inline]
pub fn neg(x: &Self) -> Self {
Self::new(x.as_inner().neg())
}
#[inline]
pub(crate) fn as_inner(&self) -> &RawBigInt {
&self.inner
}
}
impl Display for JsBigInt {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.inner, f)
}
}
impl From<RawBigInt> for JsBigInt {
#[inline]
fn from(value: RawBigInt) -> Self {
Self {
inner: Rc::new(value),
}
}
}
impl From<i8> for JsBigInt {
#[inline]
fn from(value: i8) -> Self {
Self {
inner: Rc::new(RawBigInt::from(value)),
}
}
}
impl From<u8> for JsBigInt {
#[inline]
fn from(value: u8) -> Self {
Self {
inner: Rc::new(RawBigInt::from(value)),
}
}
}
impl From<i16> for JsBigInt {
#[inline]
fn from(value: i16) -> Self {
Self {
inner: Rc::new(RawBigInt::from(value)),
}
}
}
impl From<u16> for JsBigInt {
#[inline]
fn from(value: u16) -> Self {
Self {
inner: Rc::new(RawBigInt::from(value)),
}
}
}
impl From<i32> for JsBigInt {
#[inline]
fn from(value: i32) -> Self {
Self {
inner: Rc::new(RawBigInt::from(value)),
}
}
}
impl From<u32> for JsBigInt {
#[inline]
fn from(value: u32) -> Self {
Self {
inner: Rc::new(RawBigInt::from(value)),
}
}
}
impl From<i64> for JsBigInt {
#[inline]
fn from(value: i64) -> Self {
Self {
inner: Rc::new(RawBigInt::from(value)),
}
}
}
impl From<u64> for JsBigInt {
#[inline]
fn from(value: u64) -> Self {
Self {
inner: Rc::new(RawBigInt::from(value)),
}
}
}
impl From<isize> for JsBigInt {
#[inline]
fn from(value: isize) -> Self {
Self {
inner: Rc::new(RawBigInt::from(value)),
}
}
}
impl From<usize> for JsBigInt {
#[inline]
fn from(value: usize) -> Self {
Self {
inner: Rc::new(RawBigInt::from(value)),
}
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct TryFromF64Error;
impl Display for TryFromF64Error {
#[inline]
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 JsBigInt {
type Error = TryFromF64Error;
#[inline]
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 RawBigInt::from_f64(n) {
Some(bigint) => Ok(Self::new(bigint)),
None => Err(TryFromF64Error),
}
}
}
impl PartialEq<i32> for JsBigInt {
#[inline]
fn eq(&self, other: &i32) -> bool {
self.inner.as_ref() == &RawBigInt::from(*other)
}
}
impl PartialEq<JsBigInt> for i32 {
#[inline]
fn eq(&self, other: &JsBigInt) -> bool {
&RawBigInt::from(*self) == other.inner.as_ref()
}
}
impl PartialEq<f64> for JsBigInt {
#[inline]
fn eq(&self, other: &f64) -> bool {
if other.fract() != 0.0 {
return false;
}
self.inner.as_ref() == &RawBigInt::from(*other as i64)
}
}
impl PartialEq<JsBigInt> for f64 {
#[inline]
fn eq(&self, other: &JsBigInt) -> bool {
if self.fract() != 0.0 {
return false;
}
&RawBigInt::from(*self as i64) == other.inner.as_ref()
}
}

107
boa/src/builtins/bigint/conversions.rs

@ -1,107 +0,0 @@
use super::BigInt;
use crate::{builtins::Number, Context, Value};
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, context: &mut Context) -> Result<Self, Value> {
if string.trim().is_empty() {
return Ok(BigInt::from(0));
}
let mut radix = 10;
let mut string = string;
if string.starts_with("0b") || string.starts_with("0B") {
radix = 2;
string = &string[2..];
}
if string.starts_with("0x") || string.starts_with("0X") {
radix = 16;
string = &string[2..];
}
if string.starts_with("0o") || string.starts_with("0O") {
radix = 8;
string = &string[2..];
}
BigInt::from_string_radix(string, radix).ok_or_else(|| {
context.construct_syntax_error(format!("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 `f64::INFINITY` if the BigInt is too big.
#[inline]
pub fn to_f64(&self) -> f64 {
self.0.to_f64().unwrap_or(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

@ -1,74 +0,0 @@
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
}
}

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

@ -14,32 +14,17 @@
use crate::{
builtins::BuiltIn,
gc::{empty_trace, Finalize, Trace},
object::{ConstructorBuilder, ObjectData},
property::Attribute,
symbol::WellKnownSymbols,
value::{RcBigInt, Value},
BoaProfiler, Context, Result,
BoaProfiler, Context, JsBigInt, Result, Value,
};
#[cfg(feature = "deser")]
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.
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct BigInt(num_bigint::BigInt);
#[derive(Debug, Clone, Copy)]
pub struct BigInt;
impl BuiltIn for BigInt {
const NAME: &'static str = "BigInt";
@ -94,7 +79,7 @@ impl BigInt {
fn constructor(_: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
let data = match args.get(0) {
Some(ref value) => value.to_bigint(context)?,
None => RcBigInt::from(Self::from(0)),
None => JsBigInt::zero(),
};
Ok(Value::from(data))
}
@ -110,7 +95,7 @@ impl BigInt {
///
/// [spec]: https://tc39.es/ecma262/#sec-thisbigintvalue
#[inline]
fn this_bigint_value(value: &Value, context: &mut Context) -> Result<RcBigInt> {
fn this_bigint_value(value: &Value, context: &mut Context) -> Result<JsBigInt> {
match value {
// 1. If Type(value) is BigInt, return value.
Value::BigInt(ref bigint) => return Ok(bigint.clone()),
@ -181,17 +166,12 @@ impl BigInt {
let (modulo, bits) = Self::calculate_as_uint_n(args, context)?;
if bits > 0
&& modulo
>= BigInt::from(2)
.pow(&BigInt::from(bits as i64 - 1))
.expect("the exponent must be positive")
&& modulo >= JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(bits as i64 - 1), context)?
{
Ok(Value::from(
modulo
- BigInt::from(2)
.pow(&BigInt::from(bits as i64))
.expect("the exponent must be positive"),
))
Ok(Value::from(JsBigInt::sub(
&modulo,
&JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(bits as i64), context)?,
)))
} else {
Ok(Value::from(modulo))
}
@ -215,7 +195,7 @@ impl BigInt {
/// This function expects the same arguments as `as_uint_n` and wraps the value of a `BigInt`.
/// Additionally to the wrapped unsigned value it returns the converted `bits` argument, so it
/// can be reused from the `as_int_n` method.
fn calculate_as_uint_n(args: &[Value], context: &mut Context) -> Result<(BigInt, u32)> {
fn calculate_as_uint_n(args: &[Value], context: &mut Context) -> Result<(JsBigInt, u32)> {
use std::convert::TryFrom;
let undefined_value = Value::undefined();
@ -229,29 +209,11 @@ impl BigInt {
let bigint = bigint_arg.to_bigint(context)?;
Ok((
bigint.as_inner().clone().mod_floor(
&BigInt::from(2)
.pow(&BigInt::from(bits as i64))
.expect("the exponent must be positive"),
JsBigInt::mod_floor(
&bigint,
&JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(bits as i64), context)?,
),
bits,
))
}
/// helper function for checking if the BigInt represents 0
///
/// creating BigInts requires an allocation and for a few operations we need to know if the
/// inner value is 0, this solves that situation
pub(crate) fn is_zero(&self) -> bool {
use num_traits::Zero;
self.0.is_zero()
}
}
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.
empty_trace!();
}

107
boa/src/builtins/bigint/operations.rs

@ -1,107 +0,0 @@
//! 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) -> Result<Self, String> {
Ok(Self(self.0.pow(
other.0.to_biguint().ok_or("BigInt negative exponent")?,
)))
}
#[inline]
pub fn shift_right(mut self, other: Self) -> Result<Self, String> {
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)
}
Ok(self)
} else {
Err("Maximum BigInt size exceeded".into())
}
}
#[inline]
pub fn shift_left(mut self, other: Self) -> Result<Self, String> {
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)
}
Ok(self)
} else {
Err("Maximum BigInt size exceeded".into())
}
}
/// Floored integer modulo.
///
/// # Examples
/// ```
/// # use num_integer::Integer;
/// assert_eq!((8).mod_floor(&3), 2);
/// assert_eq!((8).mod_floor(&-3), -1);
/// ```
#[inline]
pub fn mod_floor(self, other: &Self) -> Self {
use num_integer::Integer;
Self(self.0.mod_floor(&other.0))
}
}
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::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)
}
}

7
boa/src/bytecompiler.rs

@ -4,16 +4,15 @@ use crate::{
op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp},
Const, Node,
},
value::RcBigInt,
vm::{CodeBlock, Opcode},
JsString, Value,
JsBigInt, JsString, Value,
};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum Literal {
String(JsString),
BigInt(RcBigInt),
BigInt(JsBigInt),
}
#[must_use]
@ -352,7 +351,7 @@ impl ByteCompiler {
Const::String(v) => self.emit_push_literal(Literal::String(v.as_ref().into())),
Const::Int(v) => self.emit_push_integer(*v),
Const::Num(v) => self.emit_push_rational(*v),
Const::BigInt(v) => self.emit_push_literal(Literal::BigInt(v.clone().into())),
Const::BigInt(v) => self.emit_push_literal(Literal::BigInt(v.clone())),
Const::Bool(true) => self.emit(Opcode::PushTrue, &[]),
Const::Bool(false) => self.emit(Opcode::PushFalse, &[]),
Const::Null => self.emit(Opcode::PushNull, &[]),

5
boa/src/lib.rs

@ -42,6 +42,7 @@ This is an experimental Javascript lexer, parser and compiler written in Rust. C
missing_doc_code_examples
)]
pub mod bigint;
// builtins module has a lot of built-in functions that need unnecessary_wraps
#[allow(clippy::unnecessary_wraps)]
pub mod builtins;
@ -72,7 +73,9 @@ pub(crate) use crate::{exec::Executable, profiler::BoaProfiler};
// Export things to root level
#[doc(inline)]
pub use crate::{context::Context, string::JsString, symbol::JsSymbol, value::Value};
pub use crate::{
bigint::JsBigInt, context::Context, string::JsString, symbol::JsSymbol, value::Value,
};
use crate::syntax::{
ast::node::StatementList,

11
boa/src/object/mod.rs

@ -10,13 +10,12 @@ use crate::{
set::ordered_set::OrderedSet,
set::set_iterator::SetIterator,
string::string_iterator::StringIterator,
BigInt, Date, RegExp,
Date, RegExp,
},
context::StandardConstructor,
gc::{Finalize, Trace},
property::{AccessorDescriptor, Attribute, DataDescriptor, PropertyDescriptor, PropertyKey},
value::{RcBigInt, Value},
BoaProfiler, Context, JsString, JsSymbol,
BoaProfiler, Context, JsBigInt, JsString, JsSymbol, Value,
};
use rustc_hash::FxHashMap;
use std::{
@ -87,7 +86,7 @@ pub enum ObjectData {
MapIterator(MapIterator),
RegExp(Box<RegExp>),
RegExpStringIterator(RegExpStringIterator),
BigInt(RcBigInt),
BigInt(JsBigInt),
Boolean(bool),
ForInIterator(ForInIterator),
Function(Function),
@ -230,7 +229,7 @@ impl Object {
/// Return a new `BigInt` object whose `[[BigIntData]]` internal slot is set to argument.
#[inline]
pub fn bigint(value: RcBigInt) -> Self {
pub fn bigint(value: JsBigInt) -> Self {
Self {
data: ObjectData::BigInt(value),
indexed_properties: FxHashMap::default(),
@ -497,7 +496,7 @@ impl Object {
}
#[inline]
pub fn as_bigint(&self) -> Option<&BigInt> {
pub fn as_bigint(&self) -> Option<&JsBigInt> {
match self.data {
ObjectData::BigInt(ref bigint) => Some(bigint),
_ => None,

2
boa/src/string.rs

@ -307,7 +307,7 @@ impl Hash for JsString {
impl PartialOrd for JsString {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.as_str().partial_cmp(other)
self.as_str().partial_cmp(other.as_str())
}
}

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

@ -8,8 +8,8 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Literals
use crate::{
builtins::bigint::BigInt,
gc::{Finalize, Trace},
JsBigInt,
};
use std::fmt::{Display, Formatter, Result};
@ -76,7 +76,7 @@ pub enum Const {
///
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-bigint-value
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Numeric_literals
BigInt(BigInt),
BigInt(JsBigInt),
/// The Boolean type has two literal values: `true` and `false`.
///
@ -151,8 +151,8 @@ impl From<i32> for Const {
}
}
impl From<BigInt> for Const {
fn from(i: BigInt) -> Self {
impl From<JsBigInt> for Const {
fn from(i: JsBigInt) -> Self {
Self::BigInt(i)
}
}

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

@ -2,12 +2,12 @@
use super::{Cursor, Error, TokenKind, Tokenizer};
use crate::{
builtins::BigInt,
profiler::BoaProfiler,
syntax::{
ast::{Position, Span},
lexer::{token::Numeric, Token},
},
JsBigInt,
};
use std::io::Read;
use std::str;
@ -377,7 +377,7 @@ impl<R> Tokenizer<R> for NumberLiteral {
let num = match kind {
NumericKind::BigInt(base) => {
Numeric::BigInt(
BigInt::from_string_radix(num_str, base).expect("Could not convert to BigInt")
JsBigInt::from_string_radix(num_str, base).expect("Could not convert to BigInt")
)
}
NumericKind::Rational /* base: 10 */ => {

8
boa/src/syntax/lexer/token.rs

@ -8,9 +8,9 @@
use super::regex::RegExpFlags;
use crate::{
builtins::BigInt,
syntax::ast::{Keyword, Punctuator, Span},
syntax::lexer::template::TemplateString,
JsBigInt,
};
use std::fmt::{self, Debug, Display, Formatter};
@ -69,7 +69,7 @@ pub enum Numeric {
Integer(i32),
// A BigInt
BigInt(BigInt),
BigInt(JsBigInt),
}
impl From<f64> for Numeric {
@ -86,9 +86,9 @@ impl From<i32> for Numeric {
}
}
impl From<BigInt> for Numeric {
impl From<JsBigInt> for Numeric {
#[inline]
fn from(n: BigInt) -> Self {
fn from(n: JsBigInt) -> Self {
Self::BigInt(n)
}
}

10
boa/src/value/conversions.rs

@ -90,14 +90,8 @@ impl From<i32> for Value {
}
}
impl From<BigInt> for Value {
fn from(value: BigInt) -> Self {
Value::bigint(value)
}
}
impl From<RcBigInt> for Value {
fn from(value: RcBigInt) -> Self {
impl From<JsBigInt> for Value {
fn from(value: JsBigInt) -> Self {
Value::BigInt(value)
}
}

38
boa/src/value/equality.rs

@ -15,7 +15,7 @@ impl Value {
match (self, other) {
// 2. If Type(x) is Number or BigInt, then
// a. Return ! Type(x)::equal(x, y).
(Self::BigInt(x), Self::BigInt(y)) => BigInt::equal(x, y),
(Self::BigInt(x), Self::BigInt(y)) => JsBigInt::equal(x, y),
(Self::Rational(x), Self::Rational(y)) => Number::equal(*x, *y),
(Self::Rational(x), Self::Integer(y)) => Number::equal(*x, f64::from(*y)),
(Self::Integer(x), Self::Rational(y)) => Number::equal(f64::from(*x), *y),
@ -68,14 +68,14 @@ impl Value {
// a. Let n be ! StringToBigInt(y).
// b. If n is NaN, return false.
// c. Return the result of the comparison x == n.
(Self::BigInt(ref a), Self::String(ref b)) => match string_to_bigint(b) {
Some(ref b) => a.as_inner() == b,
(Self::BigInt(ref a), Self::String(ref b)) => match JsBigInt::from_string(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.
(Self::String(ref a), Self::BigInt(ref b)) => match string_to_bigint(a) {
Some(ref a) => a == b.as_inner(),
(Self::String(ref a), Self::BigInt(ref b)) => match JsBigInt::from_string(a) {
Some(ref a) => a == b,
None => false,
},
@ -102,10 +102,10 @@ impl Value {
// 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.
(Self::BigInt(ref a), Self::Rational(ref b)) => a.as_inner() == b,
(Self::Rational(ref a), Self::BigInt(ref b)) => a == b.as_inner(),
(Self::BigInt(ref a), Self::Integer(ref b)) => a.as_inner() == b,
(Self::Integer(ref a), Self::BigInt(ref b)) => a == b.as_inner(),
(Self::BigInt(ref a), Self::Rational(ref b)) => a == b,
(Self::Rational(ref a), Self::BigInt(ref b)) => a == b,
(Self::BigInt(ref a), Self::Integer(ref b)) => a == b,
(Self::Integer(ref a), Self::BigInt(ref b)) => a == b,
// 13. Return false.
_ => false,
@ -128,7 +128,7 @@ impl Value {
match (x, y) {
// 2. If Type(x) is Number or BigInt, then
// a. Return ! Type(x)::SameValue(x, y).
(Value::BigInt(x), Value::BigInt(y)) => BigInt::same_value(x, y),
(Value::BigInt(x), Value::BigInt(y)) => JsBigInt::same_value(x, y),
(Value::Rational(x), Value::Rational(y)) => Number::same_value(*x, *y),
(Value::Rational(x), Value::Integer(y)) => Number::same_value(*x, f64::from(*y)),
(Value::Integer(x), Value::Rational(y)) => Number::same_value(f64::from(*x), *y),
@ -156,7 +156,7 @@ impl Value {
match (x, y) {
// 2. If Type(x) is Number or BigInt, then
// a. Return ! Type(x)::SameValueZero(x, y).
(Value::BigInt(x), Value::BigInt(y)) => BigInt::same_value_zero(x, y),
(Value::BigInt(x), Value::BigInt(y)) => JsBigInt::same_value_zero(x, y),
(Value::Rational(x), Value::Rational(y)) => Number::same_value_zero(*x, *y),
(Value::Rational(x), Value::Integer(y)) => Number::same_value_zero(*x, f64::from(*y)),
@ -180,19 +180,3 @@ impl Value {
}
}
}
/// 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)
}

50
boa/src/value/mod.rs

@ -9,12 +9,12 @@ use crate::{
builtins::{
number::{f64_to_int32, f64_to_uint32},
string::is_trimmable_whitespace,
BigInt, Number,
Number,
},
object::{GcObject, Object, ObjectData},
property::{Attribute, DataDescriptor, PropertyDescriptor, PropertyKey},
symbol::{JsSymbol, WellKnownSymbols},
BoaProfiler, Context, JsString, Result,
BoaProfiler, Context, JsBigInt, JsString, Result,
};
use gc::{Finalize, Trace};
use serde_json::{Number as JSONNumber, Value as JSONValue};
@ -30,7 +30,6 @@ pub(crate) mod display;
mod equality;
mod hash;
mod operations;
mod rcbigint;
mod r#type;
pub use conversions::*;
@ -39,7 +38,6 @@ pub use equality::*;
pub use hash::*;
pub use operations::*;
pub use r#type::Type;
pub use rcbigint::RcBigInt;
/// A Javascript value
#[derive(Trace, Finalize, Debug, Clone)]
@ -57,7 +55,7 @@ pub enum Value {
/// `Number` - A 32-bit integer, such as `42`.
Integer(i32),
/// `BigInt` - holds any arbitrary large signed integer.
BigInt(RcBigInt),
BigInt(JsBigInt),
/// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values.
Object(GcObject),
/// `Symbol` - A Symbol Primitive type.
@ -143,7 +141,7 @@ impl Value {
#[inline]
pub fn bigint<B>(value: B) -> Self
where
B: Into<RcBigInt>,
B: Into<JsBigInt>,
{
Self::BigInt(value.into())
}
@ -395,7 +393,7 @@ impl Value {
/// Returns an optional reference to a `BigInt` if the value is a BigInt primitive.
#[inline]
pub fn as_bigint(&self) -> Option<&BigInt> {
pub fn as_bigint(&self) -> Option<&JsBigInt> {
match self {
Self::BigInt(bigint) => Some(bigint),
_ => None,
@ -415,7 +413,7 @@ impl Value {
Self::String(ref s) if !s.is_empty() => true,
Self::Rational(n) if n != 0.0 && !n.is_nan() => true,
Self::Integer(n) if n != 0 => true,
Self::BigInt(ref n) if *n.as_inner() != 0 => true,
Self::BigInt(ref n) if !n.is_zero() => true,
Self::Boolean(v) => v,
_ => false,
}
@ -592,19 +590,28 @@ impl Value {
/// Converts the value to a `BigInt`.
///
/// This function is equivelent to `BigInt(value)` in JavaScript.
pub fn to_bigint(&self, context: &mut Context) -> Result<RcBigInt> {
pub fn to_bigint(&self, context: &mut Context) -> Result<JsBigInt> {
match self {
Value::Null => Err(context.construct_type_error("cannot convert null to a BigInt")),
Value::Undefined => {
Err(context.construct_type_error("cannot convert undefined to a BigInt"))
}
Value::String(ref string) => Ok(RcBigInt::from(BigInt::from_string(string, context)?)),
Value::Boolean(true) => Ok(RcBigInt::from(BigInt::from(1))),
Value::Boolean(false) => Ok(RcBigInt::from(BigInt::from(0))),
Value::Integer(num) => Ok(RcBigInt::from(BigInt::from(*num))),
Value::String(ref string) => {
if let Some(value) = JsBigInt::from_string(string) {
Ok(value)
} else {
Err(context.construct_syntax_error(format!(
"cannot convert string '{}' to bigint primitive",
string
)))
}
}
Value::Boolean(true) => Ok(JsBigInt::one()),
Value::Boolean(false) => Ok(JsBigInt::zero()),
Value::Integer(num) => Ok(JsBigInt::new(*num)),
Value::Rational(num) => {
if let Ok(bigint) = BigInt::try_from(*num) {
return Ok(RcBigInt::from(bigint));
if let Ok(bigint) = JsBigInt::try_from(*num) {
return Ok(bigint);
}
Err(context.construct_type_error(format!(
"The number {} cannot be converted to a BigInt because it is not an integer",
@ -978,7 +985,7 @@ pub enum Numeric {
/// Double precision floating point number.
Number(f64),
/// BigInt an integer of arbitrary size.
BigInt(RcBigInt),
BigInt(JsBigInt),
}
impl From<f64> for Numeric {
@ -1030,16 +1037,9 @@ impl From<u8> for Numeric {
}
}
impl From<BigInt> for Numeric {
#[inline]
fn from(value: BigInt) -> Self {
Self::BigInt(value.into())
}
}
impl From<RcBigInt> for Numeric {
impl From<JsBigInt> for Numeric {
#[inline]
fn from(value: RcBigInt) -> Self {
fn from(value: JsBigInt) -> Self {
Self::BigInt(value)
}
}

72
boa/src/value/operations.rs

@ -110,7 +110,7 @@ impl Value {
(Self::Rational(x), Self::Integer(y)) => Self::rational(x / f64::from(*y)),
(Self::BigInt(ref a), Self::BigInt(ref b)) => {
if b.as_inner().is_zero() {
if b.is_zero() {
return context.throw_range_error("BigInt division by zero");
}
Self::bigint(a.as_inner().clone() / b.as_inner().clone())
@ -120,7 +120,7 @@ impl Value {
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => Self::rational(a / b),
(Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => {
if b.as_inner().is_zero() {
if b.is_zero() {
return context.throw_range_error("BigInt division by zero");
}
Self::bigint(a.as_inner().clone() / b.as_inner().clone())
@ -150,7 +150,7 @@ impl Value {
(Self::Rational(x), Self::Integer(y)) => Self::rational(x % f64::from(*y)),
(Self::BigInt(ref a), Self::BigInt(ref b)) => {
if b.as_inner().is_zero() {
if b.is_zero() {
return context.throw_range_error("BigInt division by zero");
}
Self::bigint(a.as_inner().clone() % b.as_inner().clone())
@ -180,22 +180,16 @@ impl Value {
(Self::Integer(x), Self::Rational(y)) => Self::rational(f64::from(*x).powf(*y)),
(Self::Rational(x), Self::Integer(y)) => Self::rational(x.powi(*y)),
(Self::BigInt(ref a), Self::BigInt(ref b)) => Self::bigint(
a.as_inner()
.clone()
.pow(b)
.map_err(|msg| context.construct_range_error(msg))?,
),
(Self::BigInt(ref a), Self::BigInt(ref b)) => {
Self::bigint(JsBigInt::pow(a, b, context)?)
}
// Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => Self::rational(a.powf(b)),
(Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => Self::bigint(
a.as_inner()
.clone()
.pow(b)
.map_err(|msg| context.construct_range_error(msg))?,
),
(Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => {
Self::bigint(JsBigInt::pow(a, b, context)?)
}
(_, _) => {
return context.throw_type_error(
"cannot mix BigInt and other types, use explicit conversions",
@ -316,24 +310,18 @@ impl Value {
Self::integer(f64_to_int32(*x).wrapping_shl(*y as u32))
}
(Self::BigInt(ref a), Self::BigInt(ref b)) => Self::bigint(
a.as_inner()
.clone()
.shift_left(b.as_inner().clone())
.map_err(|msg| context.construct_range_error(msg))?,
),
(Self::BigInt(ref a), Self::BigInt(ref b)) => {
Self::bigint(JsBigInt::shift_left(a, b, context)?)
}
// Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(x), Numeric::Number(y)) => {
Self::integer(f64_to_int32(x).wrapping_shl(f64_to_uint32(y)))
}
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => Self::bigint(
x.as_inner()
.clone()
.shift_left(y.as_inner().clone())
.map_err(|msg| context.construct_range_error(msg))?,
),
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
Self::bigint(JsBigInt::shift_left(x, y, context)?)
}
(_, _) => {
return context.throw_type_error(
"cannot mix BigInt and other types, use explicit conversions",
@ -358,24 +346,18 @@ impl Value {
Self::integer(f64_to_int32(*x).wrapping_shr(*y as u32))
}
(Self::BigInt(ref a), Self::BigInt(ref b)) => Self::bigint(
a.as_inner()
.clone()
.shift_right(b.as_inner().clone())
.map_err(|msg| context.construct_range_error(msg))?,
),
(Self::BigInt(ref a), Self::BigInt(ref b)) => {
Self::bigint(JsBigInt::shift_right(a, b, context)?)
}
// Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(x), Numeric::Number(y)) => {
Self::integer(f64_to_int32(x).wrapping_shr(f64_to_uint32(y)))
}
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => Self::bigint(
x.as_inner()
.clone()
.shift_right(y.as_inner().clone())
.map_err(|msg| context.construct_range_error(msg))?,
),
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
Self::bigint(JsBigInt::shift_right(x, y, context)?)
}
(_, _) => {
return context.throw_type_error(
"cannot mix BigInt and other types, use explicit conversions",
@ -505,15 +487,15 @@ impl Value {
unreachable!()
}
(Self::BigInt(ref x), Self::String(ref y)) => {
if let Some(y) = string_to_bigint(&y) {
(*x.as_inner() < y).into()
if let Some(y) = JsBigInt::from_string(&y) {
(*x < y).into()
} else {
AbstractRelation::Undefined
}
}
(Self::String(ref x), Self::BigInt(ref y)) => {
if let Some(x) = string_to_bigint(&x) {
(x < *y.as_inner()).into()
if let Some(x) = JsBigInt::from_string(&x) {
(x < *y).into()
} else {
AbstractRelation::Undefined
}
@ -533,7 +515,7 @@ impl Value {
} else {
y.ceil()
};
(*x.as_inner() < BigInt::try_from(n).unwrap()).into()
(*x < JsBigInt::try_from(n).unwrap()).into()
}
(Numeric::Number(x), Numeric::BigInt(ref y)) => {
if x.is_nan() {
@ -547,7 +529,7 @@ impl Value {
} else {
x.ceil()
};
(BigInt::try_from(n).unwrap() < *y.as_inner()).into()
(JsBigInt::try_from(n).unwrap() < *y).into()
}
},
}

45
boa/src/value/rcbigint.rs

@ -1,45 +0,0 @@
use crate::{
builtins::BigInt,
gc::{empty_trace, Finalize, Trace},
};
use std::{
fmt::{self, Display},
ops::Deref,
rc::Rc,
};
#[derive(Debug, Finalize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RcBigInt(Rc<BigInt>);
unsafe impl Trace for RcBigInt {
empty_trace!();
}
impl RcBigInt {
pub(crate) fn as_inner(&self) -> &BigInt {
&self.0
}
}
impl Display for RcBigInt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl Deref for RcBigInt {
type Target = BigInt;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<BigInt> for RcBigInt {
#[inline]
fn from(bigint: BigInt) -> Self {
Self(Rc::from(bigint))
}
}
Loading…
Cancel
Save