Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specification compliant ToBigInt (to_bigint) #450

Merged
merged 1 commit into from
Jun 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions boa/src/builtins/bigint/conversions.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 74 additions & 0 deletions boa/src/builtins/bigint/equality.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 66 additions & 37 deletions boa/src/builtins/bigint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,67 @@
use crate::{
builtins::{
function::{make_builtin_fn, make_constructor_fn},
value::{ResultValue, Value},
value::{ResultValue, Value, ValueData},
},
exec::Interpreter,
syntax::ast::bigint::BigInt as AstBigInt,
BoaProfiler,
};

use gc::{unsafe_empty_trace, Finalize, Trace};

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

pub mod conversions;
pub mod equality;
pub mod operations;

pub use conversions::*;
pub use equality::*;
pub use operations::*;

#[cfg(test)]
mod tests;

/// `BigInt` implementation.
#[derive(Debug, Clone, Copy)]
pub(crate) struct BigInt;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct BigInt(num_bigint::BigInt);

impl BigInt {
/// The abstract operation thisBigIntValue takes argument value.
///
/// The phrase “this BigInt value” within the specification of a method refers to the
/// result returned by calling the abstract operation thisBigIntValue with the `this` value
/// of the method invocation passed as the argument.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-thisbigintvalue
#[inline]
fn this_bigint_value(value: &Value, ctx: &mut Interpreter) -> Result<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()`
///
/// The `BigInt()` constructor is used to create BigInt objects.
Expand All @@ -46,34 +92,12 @@ impl BigInt {
ctx: &mut Interpreter,
) -> ResultValue {
let data = match args.get(0) {
Some(ref value) => {
if let Some(bigint) = value.to_bigint() {
Value::from(bigint)
} else {
let message = format!(
"{} can't be converted to BigInt because it isn't an integer",
ctx.to_string(value)?
);
return ctx.throw_range_error(message);
}
}
None => Value::from(AstBigInt::from(0)),
Some(ref value) => Value::from(ctx.to_bigint(value)?),
None => Value::from(Self::from(0)),
};
Ok(data)
}

#[inline]
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_native_string_radix(bigint: &AstBigInt, radix: u32) -> String {
bigint.to_str_radix(radix)
}

#[inline]
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_native_string(bigint: &AstBigInt) -> String {
bigint.to_string()
}

/// `BigInt.prototype.toString( [radix] )`
///
/// The `toString()` method returns a string representing the specified BigInt object.
Expand All @@ -99,10 +123,9 @@ impl BigInt {
return ctx
.throw_range_error("radix must be an integer at least 2 and no greater than 36");
}
Ok(Value::from(Self::to_native_string_radix(
&this.to_bigint().unwrap(),
radix as u32,
)))
Ok(Value::from(
Self::this_bigint_value(this, ctx)?.to_string_radix(radix as u32),
))
}

/// `BigInt.prototype.valueOf()`
Expand All @@ -118,17 +141,15 @@ impl BigInt {
pub(crate) fn value_of(
this: &mut Value,
_args: &[Value],
_ctx: &mut Interpreter,
ctx: &mut Interpreter,
) -> ResultValue {
Ok(Value::from(
this.to_bigint().expect("BigInt.prototype.valueOf"),
))
Ok(Value::from(Self::this_bigint_value(this, ctx)?))
}

/// Create a new `Number` object
pub(crate) fn create(global: &Value) -> Value {
let prototype = Value::new_object(Some(global));
prototype.set_internal_slot("BigIntData", Value::from(AstBigInt::from(0)));
prototype.set_internal_slot("BigIntData", Value::from(Self::from(0)));

make_builtin_fn(Self::to_string, "toString", &prototype, 1);
make_builtin_fn(Self::value_of, "valueOf", &prototype, 0);
Expand All @@ -143,3 +164,11 @@ impl BigInt {
global.set_field("BigInt", Self::create(global));
}
}

impl Finalize for BigInt {}
unsafe impl Trace for BigInt {
// BigInt type implements an empty trace becasue the inner structure
// `num_bigint::BigInt` does not implement `Trace` trait.
// If it did this would be unsound.
unsafe_empty_trace!();
}
Loading