Skip to content

Commit

Permalink
Refactor JavaScript bigint rust type
Browse files Browse the repository at this point in the history
 - Move src/value/rcbigint.rs to src/bigint.rs
 - Rename RcBigInt to JsBigInt
  • Loading branch information
HalidOdat committed Jul 18, 2021
1 parent be4a872 commit 0a84210
Show file tree
Hide file tree
Showing 18 changed files with 435 additions and 496 deletions.
2 changes: 1 addition & 1 deletion boa/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
342 changes: 342 additions & 0 deletions boa/src/bigint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
//! 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,
str::FromStr,
};

use num_bigint::BigInt as RawBigInt;
use num_integer::Integer;
use num_traits::pow::Pow;
use num_traits::{FromPrimitive, One, ToPrimitive, Zero};

#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};

#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[derive(Debug, Finalize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct JsBigInt {
inner: Rc<RawBigInt>,
}

unsafe impl Trace for JsBigInt {
empty_trace!();
}

impl JsBigInt {
pub fn new<T: Into<Self>>(value: T) -> Self {
value.into()
}

pub fn zero() -> Self {
Self {
inner: Rc::new(RawBigInt::zero()),
}
}

pub fn is_zero(&self) -> bool {
self.inner.is_zero()
}

pub fn one() -> Self {
Self {
inner: Rc::new(RawBigInt::one()),
}
}

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

#[inline]
pub(crate) fn from_str(string: &str) -> Option<Self> {
match RawBigInt::from_str(string) {
Ok(bigint) => Some(Self::new(bigint)),
Err(_) => None,
}
}

/// 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(crate) fn from_string(string: &str, context: &mut Context) -> Result<Self, Value> {
if string.trim().is_empty() {
return Ok(JsBigInt::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..];
}

Self::from_string_radix(string, radix).ok_or_else(|| {
context.construct_syntax_error(format!("cannot convert {} to a BigInt", string))
})
}

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

pub fn add(x: &Self, y: &Self) -> Self {
Self::new(x.inner.as_ref().clone().add(y.inner.as_ref()))
}

pub fn sub(x: &Self, y: &Self) -> Self {
Self::new(x.inner.as_ref().clone().sub(y.inner.as_ref()))
}

pub fn mul(x: &Self, y: &Self) -> Self {
Self::new(x.inner.as_ref().clone().mul(y.inner.as_ref()))
}

pub fn div(x: &Self, y: &Self) -> Self {
Self::new(x.inner.as_ref().clone().div(y.inner.as_ref()))
}

pub fn rem(x: &Self, y: &Self) -> Self {
Self::new(x.inner.as_ref().clone().rem(y.inner.as_ref()))
}

pub fn bitand(x: &Self, y: &Self) -> Self {
Self::new(x.inner.as_ref().clone().bitand(y.inner.as_ref()))
}

pub fn bitor(x: &Self, y: &Self) -> Self {
Self::new(x.inner.as_ref().clone().bitor(y.inner.as_ref()))
}

pub fn bitxor(x: &Self, y: &Self) -> Self {
Self::new(x.inner.as_ref().clone().bitxor(y.inner.as_ref()))
}

pub fn neg(x: &Self) -> Self {
Self::new(x.as_inner().neg())
}

pub(crate) fn as_inner(&self) -> &RawBigInt {
&self.inner
}
}

impl Display for JsBigInt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.inner, f)
}
}

impl From<RawBigInt> for JsBigInt {
fn from(value: RawBigInt) -> Self {
Self {
inner: Rc::new(value),
}
}
}

impl From<i32> for JsBigInt {
fn from(value: i32) -> Self {
Self {
inner: Rc::new(RawBigInt::from(value)),
}
}
}

impl From<i64> for JsBigInt {
fn from(value: i64) -> Self {
Self {
inner: Rc::new(RawBigInt::from(value)),
}
}
}

#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct TryFromF64Error;

impl 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 JsBigInt {
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 RawBigInt::from_f64(n) {
Some(bigint) => Ok(Self::new(bigint)),
None => Err(TryFromF64Error),
}
}
}

impl PartialEq<i32> for JsBigInt {
fn eq(&self, other: &i32) -> bool {
self.inner.as_ref() == &RawBigInt::from(*other)
}
}

impl PartialEq<JsBigInt> for i32 {
fn eq(&self, other: &JsBigInt) -> bool {
&RawBigInt::from(*self) == other.inner.as_ref()
}
}

impl PartialEq<f64> for JsBigInt {
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 {
fn eq(&self, other: &JsBigInt) -> bool {
if self.fract() != 0.0 {
return false;
}

&RawBigInt::from(*self as i64) == other.inner.as_ref()
}
}
Loading

0 comments on commit 0a84210

Please sign in to comment.