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

Feature BigInt #358

Merged
merged 6 commits into from
May 19, 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
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions boa/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ rand = "0.7.3"
num-traits = "0.2.11"
regex = "1.3.7"
rustc-hash = "1.1.0"
num-bigint = {version = "0.2.6", features = ["serde"]}

# Optional Dependencies
serde = { version = "1.0.106", features = ["derive"], optional = true }
Expand Down
106 changes: 106 additions & 0 deletions boa/src/builtins/bigint/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//! This module implements the global `BigInt` object.
//!
//! `BigInt` is a built-in object that provides a way to represent whole numbers larger
//! than the largest number JavaScript can reliably represent with the Number primitive
//! and represented by the `Number.MAX_SAFE_INTEGER` constant.
//! `BigInt` can be used for arbitrarily large integers.
//!
//! 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 crate::{
builtins::{
function::make_constructor_fn,
value::{ResultValue, Value},
},
exec::Interpreter,
syntax::ast::bigint::BigInt,
};

#[cfg(test)]
mod tests;

/// `BigInt()`
///
/// The `BigInt()` constructor is used to create BigInt objects.
///
/// 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/BigInt
pub fn make_bigint(_this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue {
let data = match args.get(0) {
Some(ref value) => {
if let Some(bigint) = value.to_bigint() {
Value::from(bigint)
} else {
panic!("RangeError: The value cannot be converted to a BigInt because it is not an integer");
}
}
None => Value::from(BigInt::from(0)),
};
Ok(data)
}

/// `BigInt.prototype.toString( [radix] )`
///
/// The `toString()` method returns a string representing the specified BigInt object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.tostring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/toString
pub fn to_string(this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue {
let radix = if !args.is_empty() {
args[0].to_integer()
} else {
10
};
if radix < 2 && radix > 36 {
panic!("RangeError: toString() radix argument must be between 2 and 36");
}
Ok(Value::from(
this.to_bigint().unwrap().to_str_radix(radix as u32),
))
}

// /// `BigInt.prototype.valueOf()`
// ///
// /// The `valueOf()` method returns the wrapped primitive value of a Number object.
// ///
// /// More information:
// /// - [ECMAScript reference][spec]
// /// - [MDN documentation][mdn]
// ///
/// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.valueof
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/valueOf
pub fn value_of(this: &mut Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue {
Ok(Value::from(
this.to_bigint().expect("BigInt.prototype.valueOf"),
))
}

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

make_builtin_fn!(to_string, named "toString", with length 1, of prototype);
make_builtin_fn!(value_of, named "valueOf", of prototype);

make_constructor_fn(make_bigint, global, prototype)
}

/// Initialise the `BigInt` object on the global object.
#[inline]
pub fn init(global: &Value) {
global.set_field_slice("BigInt", create(global));
}
147 changes: 147 additions & 0 deletions boa/src/builtins/bigint/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use crate::{forward, Executor, Realm};

#[test]
fn equality() {
let realm = Realm::create();
let mut engine = Executor::new(realm);

assert_eq!(forward(&mut engine, "0n == 0n"), "true");
assert_eq!(forward(&mut engine, "1n == 0n"), "false");
assert_eq!(
forward(
&mut engine,
"1000000000000000000000000000000000n == 1000000000000000000000000000000000n"
),
"true"
);

assert_eq!(forward(&mut engine, "0n == ''"), "true");
assert_eq!(forward(&mut engine, "100n == '100'"), "true");
assert_eq!(forward(&mut engine, "100n == '100.5'"), "false");
assert_eq!(
forward(&mut engine, "10000000000000000n == '10000000000000000'"),
"true"
);

assert_eq!(forward(&mut engine, "'' == 0n"), "true");
assert_eq!(forward(&mut engine, "'100' == 100n"), "true");
assert_eq!(forward(&mut engine, "'100.5' == 100n"), "false");
assert_eq!(
forward(&mut engine, "'10000000000000000' == 10000000000000000n"),
"true"
);

assert_eq!(forward(&mut engine, "0n == 0"), "true");
assert_eq!(forward(&mut engine, "0n == 0.0"), "true");
assert_eq!(forward(&mut engine, "100n == 100"), "true");
assert_eq!(forward(&mut engine, "100n == 100.0"), "true");
assert_eq!(forward(&mut engine, "100n == '100.5'"), "false");
assert_eq!(forward(&mut engine, "100n == '1005'"), "false");
assert_eq!(
forward(&mut engine, "10000000000000000n == 10000000000000000"),
"true"
);

assert_eq!(forward(&mut engine, "0 == 0n"), "true");
assert_eq!(forward(&mut engine, "0.0 == 0n"), "true");
assert_eq!(forward(&mut engine, "100 == 100n"), "true");
assert_eq!(forward(&mut engine, "100.0 == 100n"), "true");
assert_eq!(forward(&mut engine, "100.5 == 100n"), "false");
assert_eq!(forward(&mut engine, "1005 == 100n"), "false");
assert_eq!(
forward(&mut engine, "10000000000000000 == 10000000000000000n"),
"true"
);
}

#[test]
fn bigint_function_conversion() {
let realm = Realm::create();
let mut engine = Executor::new(realm);

assert_eq!(forward(&mut engine, "BigInt(1000)"), "1000n");
assert_eq!(
forward(&mut engine, "BigInt(20000000000000000)"),
"20000000000000000n"
);
assert_eq!(
forward(&mut engine, "BigInt(1000000000000000000000000000000000)"),
"999999999999999945575230987042816n"
);
}

#[test]
fn add() {
let realm = Realm::create();
let mut engine = Executor::new(realm);

assert_eq!(forward(&mut engine, "10000n + 1000n"), "11000n");
}

#[test]
fn sub() {
let realm = Realm::create();
let mut engine = Executor::new(realm);

assert_eq!(forward(&mut engine, "10000n - 1000n"), "9000n");
}

#[test]
fn mul() {
let realm = Realm::create();
let mut engine = Executor::new(realm);

assert_eq!(
forward(&mut engine, "123456789n * 102030n"),
"12596296181670n"
);
}

#[test]
fn div() {
let realm = Realm::create();
let mut engine = Executor::new(realm);

assert_eq!(forward(&mut engine, "15000n / 50n"), "300n");
}

#[test]
fn div_with_truncation() {
let realm = Realm::create();
let mut engine = Executor::new(realm);

assert_eq!(forward(&mut engine, "15001n / 50n"), "300n");
}

#[test]
fn r#mod() {
let realm = Realm::create();
let mut engine = Executor::new(realm);

assert_eq!(forward(&mut engine, "15007n % 10n"), "7n");
}

#[test]
fn pow() {
let realm = Realm::create();
let mut engine = Executor::new(realm);

assert_eq!(
forward(&mut engine, "100n ** 10n"),
"100000000000000000000n"
);
}

#[test]
fn to_string() {
let realm = Realm::create();
let mut engine = Executor::new(realm);

assert_eq!(forward(&mut engine, "1000n.toString()"), "1000");

assert_eq!(forward(&mut engine, "1000n.toString(2)"), "1111101000");

assert_eq!(forward(&mut engine, "255n.toString(16)"), "ff");

assert_eq!(forward(&mut engine, "1000n.toString(36)"), "rs");
}
2 changes: 2 additions & 0 deletions boa/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ macro_rules! make_builtin_fn {
}

pub mod array;
pub mod bigint;
pub mod boolean;
pub mod console;
pub mod error;
Expand All @@ -42,6 +43,7 @@ use value::Value;
#[inline]
pub fn init(global: &Value) {
array::init(global);
bigint::init(global);
boolean::init(global);
json::init(global);
math::init(global);
Expand Down
1 change: 1 addition & 0 deletions boa/src/builtins/number/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ fn to_number(value: &Value) -> Value {
ValueData::Object(ref o) => (o).deref().borrow().get_internal_slot("NumberData"),
ValueData::Null => Value::from(0),
ValueData::Rational(n) => Value::from(n),
ValueData::BigInt(ref bigint) => Value::from(bigint.to_f64()),
ValueData::String(ref s) => match s.parse::<f64>() {
Ok(n) => Value::from(n),
Err(_) => Value::from(f64::NAN),
Expand Down
11 changes: 11 additions & 0 deletions boa/src/builtins/number/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,3 +426,14 @@ fn same_value_zero() {
assert_eq!(super::same_value_zero(0.0, f64::NAN), false);
assert_eq!(super::equals(1.0, 1.0), true);
}

#[test]
fn from_bigint() {
let realm = Realm::create();
let mut engine = Executor::new(realm);

assert_eq!(&forward(&mut engine, "Number(0n)"), "0",);
assert_eq!(&forward(&mut engine, "Number(100000n)"), "100000",);
assert_eq!(&forward(&mut engine, "Number(100000n)"), "100000",);
assert_eq!(&forward(&mut engine, "Number(1n << 1240n)"), "Infinity",);
}
19 changes: 19 additions & 0 deletions boa/src/builtins/object/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,22 @@ impl Object {
obj
}

/// Return a new `BigInt` object whose `[[BigIntData]]` internal slot is set to argument.
fn from_bigint(argument: &Value) -> Self {
let mut obj = Self {
kind: ObjectKind::BigInt,
internal_slots: FxHashMap::default(),
properties: FxHashMap::default(),
sym_properties: FxHashMap::default(),
state: None,
func: None,
};

obj.internal_slots
.insert("BigIntData".to_string(), argument.clone());
obj
}

/// Converts the `Value` to an `Object` type.
///
/// More information:
Expand All @@ -442,6 +458,7 @@ impl Object {
ValueData::Boolean(_) => Ok(Self::from_boolean(value)),
ValueData::Rational(_) => Ok(Self::from_number(value)),
ValueData::String(_) => Ok(Self::from_string(value)),
ValueData::BigInt(_) => Ok(Self::from_bigint(value)),
ValueData::Object(ref obj) => Ok((*obj).deref().borrow().clone()),
_ => Err(()),
}
Expand Down Expand Up @@ -479,6 +496,7 @@ pub enum ObjectKind {
Ordinary,
Boolean,
Number,
BigInt,
}

impl Display for ObjectKind {
Expand All @@ -495,6 +513,7 @@ impl Display for ObjectKind {
Self::Ordinary => "Ordinary",
Self::Boolean => "Boolean",
Self::Number => "Number",
Self::BigInt => "BigInt",
}
)
}
Expand Down
Loading