-
-
Notifications
You must be signed in to change notification settings - Fork 402
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor JavaScript bigint rust type
- Move src/value/rcbigint.rs to src/bigint.rs - Rename RcBigInt to JsBigInt
- Loading branch information
Showing
18 changed files
with
435 additions
and
496 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |
Oops, something went wrong.