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

Add BigInt.asIntN() and BigInt.asUintN() functions #468

Merged
merged 6 commits into from
Jun 10, 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
1 change: 1 addition & 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 @@ -21,6 +21,7 @@ num-traits = "0.2.11"
regex = "1.3.7"
rustc-hash = "1.1.0"
num-bigint = { version = "0.2.6", features = ["serde"] }
num-integer = "0.1.42"
bitflags = "1.2.1"

# Optional Dependencies
Expand Down
71 changes: 70 additions & 1 deletion boa/src/builtins/bigint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,70 @@ impl BigInt {
Ok(Value::from(Self::this_bigint_value(this, ctx)?))
}

/// `BigInt.asIntN()`
///
/// The `BigInt.asIntN()` method wraps the value of a `BigInt` to a signed integer between `-2**(width - 1)` and `2**(width-1) - 1`.
///
/// [spec]: https://tc39.es/ecma262/#sec-bigint.asintn
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/asIntN
#[allow(clippy::wrong_self_convention)]
pub(crate) fn as_int_n(
_this: &mut Value,
args: &[Value],
ctx: &mut Interpreter,
) -> ResultValue {
let (modulo, bits) = Self::calculate_as_uint_n(args, ctx)?;

if bits > 0 && modulo >= BigInt::from(2).pow(&BigInt::from(bits as i64 - 1)) {
Ok(Value::from(
modulo - BigInt::from(2).pow(&BigInt::from(bits as i64)),
))
} else {
Ok(Value::from(modulo))
}
}

/// `BigInt.asUintN()`
///
/// The `BigInt.asUintN()` method wraps the value of a `BigInt` to an unsigned integer between `0` and `2**(width) - 1`.
///
/// [spec]: https://tc39.es/ecma262/#sec-bigint.asuintn
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/asUintN
#[allow(clippy::wrong_self_convention)]
pub(crate) fn as_uint_n(
_this: &mut Value,
args: &[Value],
ctx: &mut Interpreter,
) -> ResultValue {
let (modulo, _) = Self::calculate_as_uint_n(args, ctx)?;

Ok(Value::from(modulo))
}

/// Helper function to wrap the value of a `BigInt` to an unsigned integer.
///
/// 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], ctx: &mut Interpreter) -> Result<(BigInt, u32), Value> {
use std::convert::TryFrom;

let undefined_value = Value::undefined();

let bits_arg = args.get(0).unwrap_or(&undefined_value);
let bigint_arg = args.get(1).unwrap_or(&undefined_value);

let bits = ctx.to_index(bits_arg)?;
let bits = u32::try_from(bits).unwrap_or(u32::MAX);

let bigint = ctx.to_bigint(bigint_arg)?;

Ok((
bigint.mod_floor(&BigInt::from(2).pow(&BigInt::from(bits as i64))),
bits,
))
}

/// Create a new `Number` object
pub(crate) fn create(global: &Value) -> Value {
let prototype = Value::new_object(Some(global));
Expand All @@ -154,7 +218,12 @@ impl BigInt {
make_builtin_fn(Self::to_string, "toString", &prototype, 1);
make_builtin_fn(Self::value_of, "valueOf", &prototype, 0);

make_constructor_fn("BigInt", 1, Self::make_bigint, global, prototype, false)
let big_int = make_constructor_fn("BigInt", 1, Self::make_bigint, global, prototype, false);

make_builtin_fn(Self::as_int_n, "asIntN", &big_int, 2);
make_builtin_fn(Self::as_uint_n, "asUintN", &big_int, 2);

big_int
}

/// Initialise the `BigInt` object on the global object.
Expand Down
14 changes: 14 additions & 0 deletions boa/src/builtins/bigint/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ impl BigInt {
),
)
}

/// 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 {
Razican marked this conversation as resolved.
Show resolved Hide resolved
use num_integer::Integer;
Self(self.0.mod_floor(&other.0))
}
}

macro_rules! impl_bigint_operator {
Expand Down
148 changes: 148 additions & 0 deletions boa/src/builtins/bigint/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,151 @@ fn to_string() {
assert_eq!(forward(&mut engine, "255n.toString(16)"), "ff");
assert_eq!(forward(&mut engine, "1000n.toString(36)"), "rs");
}

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

assert_eq!(forward(&mut engine, "BigInt.asIntN(0, 1n)"), "0n");
assert_eq!(forward(&mut engine, "BigInt.asIntN(1, 1n)"), "-1n");
assert_eq!(forward(&mut engine, "BigInt.asIntN(3, 10n)"), "2n");
assert_eq!(forward(&mut engine, "BigInt.asIntN({}, 1n)"), "0n");
assert_eq!(forward(&mut engine, "BigInt.asIntN(2, 0n)"), "0n");
assert_eq!(forward(&mut engine, "BigInt.asIntN(2, -0n)"), "0n");

assert_eq!(
forward(&mut engine, "BigInt.asIntN(2, -123456789012345678901n)"),
"-1n"
);
assert_eq!(
forward(&mut engine, "BigInt.asIntN(2, -123456789012345678900n)"),
"0n"
);

assert_eq!(
forward(&mut engine, "BigInt.asIntN(2, 123456789012345678900n)"),
"0n"
);
assert_eq!(
forward(&mut engine, "BigInt.asIntN(2, 123456789012345678901n)"),
"1n"
);

assert_eq!(
forward(
&mut engine,
"BigInt.asIntN(200, 0xcffffffffffffffffffffffffffffffffffffffffffffffffffn)"
),
"-1n"
);
assert_eq!(
forward(
&mut engine,
"BigInt.asIntN(201, 0xcffffffffffffffffffffffffffffffffffffffffffffffffffn)"
),
"1606938044258990275541962092341162602522202993782792835301375n"
);

assert_eq!(
forward(
&mut engine,
"BigInt.asIntN(200, 0xc89e081df68b65fedb32cffea660e55df9605650a603ad5fc54n)"
),
"-741470203160010616172516490008037905920749803227695190508460n"
);
assert_eq!(
forward(
&mut engine,
"BigInt.asIntN(201, 0xc89e081df68b65fedb32cffea660e55df9605650a603ad5fc54n)"
),
"865467841098979659369445602333124696601453190555097644792916n"
);
}

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

assert_throws(&mut engine, "BigInt.asIntN(-1, 0n)", "RangeError");
assert_throws(&mut engine, "BigInt.asIntN(-2.5, 0n)", "RangeError");
assert_throws(
&mut engine,
"BigInt.asIntN(9007199254740992, 0n)",
"RangeError",
);
assert_throws(&mut engine, "BigInt.asIntN(0n, 0n)", "TypeError");
}

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

assert_eq!(forward(&mut engine, "BigInt.asUintN(0, -2n)"), "0n");
assert_eq!(forward(&mut engine, "BigInt.asUintN(0, -1n)"), "0n");
assert_eq!(forward(&mut engine, "BigInt.asUintN(0, 0n)"), "0n");
assert_eq!(forward(&mut engine, "BigInt.asUintN(0, 1n)"), "0n");
assert_eq!(forward(&mut engine, "BigInt.asUintN(0, 2n)"), "0n");

assert_eq!(forward(&mut engine, "BigInt.asUintN(1, -3n)"), "1n");
assert_eq!(forward(&mut engine, "BigInt.asUintN(1, -2n)"), "0n");
assert_eq!(forward(&mut engine, "BigInt.asUintN(1, -1n)"), "1n");
assert_eq!(forward(&mut engine, "BigInt.asUintN(1, 0n)"), "0n");
assert_eq!(forward(&mut engine, "BigInt.asUintN(1, 1n)"), "1n");
assert_eq!(forward(&mut engine, "BigInt.asUintN(1, 2n)"), "0n");
assert_eq!(forward(&mut engine, "BigInt.asUintN(1, 3n)"), "1n");

assert_eq!(
forward(&mut engine, "BigInt.asUintN(1, -123456789012345678901n)"),
"1n"
);
assert_eq!(
forward(&mut engine, "BigInt.asUintN(1, -123456789012345678900n)"),
"0n"
);
assert_eq!(
forward(&mut engine, "BigInt.asUintN(1, 123456789012345678900n)"),
"0n"
);
assert_eq!(
forward(&mut engine, "BigInt.asUintN(1, 123456789012345678901n)"),
"1n"
);

assert_eq!(
forward(
&mut engine,
"BigInt.asUintN(200, 0xbffffffffffffffffffffffffffffffffffffffffffffffffffn)"
),
"1606938044258990275541962092341162602522202993782792835301375n"
);
assert_eq!(
forward(
&mut engine,
"BigInt.asUintN(201, 0xbffffffffffffffffffffffffffffffffffffffffffffffffffn)"
),
"3213876088517980551083924184682325205044405987565585670602751n"
);
}

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

assert_throws(&mut engine, "BigInt.asUintN(-1, 0n)", "RangeError");
assert_throws(&mut engine, "BigInt.asUintN(-2.5, 0n)", "RangeError");
assert_throws(
&mut engine,
"BigInt.asUintN(9007199254740992, 0n)",
"RangeError",
);
assert_throws(&mut engine, "BigInt.asUintN(0n, 0n)", "TypeError");
}

fn assert_throws(engine: &mut Interpreter, src: &str, error_type: &str) {
let result = forward(engine, src);
assert!(result.contains(error_type));
}
69 changes: 69 additions & 0 deletions boa/src/exec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,74 @@ impl Interpreter {
}
}

/// Converts a value to a non-negative integer if it is a valid integer index value.
///
/// See: https://tc39.es/ecma262/#sec-toindex
#[allow(clippy::wrong_self_convention)]
pub fn to_index(&mut self, value: &Value) -> Result<usize, Value> {
Razican marked this conversation as resolved.
Show resolved Hide resolved
if value.is_undefined() {
return Ok(0);
}

let integer_index = self.to_integer(value)?;

if integer_index < 0 {
self.throw_range_error("Integer index must be >= 0")?;
unreachable!();
}

if integer_index > 2i64.pow(53) - 1 {
self.throw_range_error("Integer index must be less than 2**(53) - 1")?;
unreachable!()
}

Ok(integer_index as usize)
}

/// Converts a value to an integral 64 bit signed integer.
///
/// See: https://tc39.es/ecma262/#sec-tointeger
#[allow(clippy::wrong_self_convention)]
pub fn to_integer(&mut self, value: &Value) -> Result<i64, Value> {
Razican marked this conversation as resolved.
Show resolved Hide resolved
let number = self.to_number(value)?;

if number.is_nan() {
return Ok(0);
}

Ok(number as i64)
}

/// Converts a value to a double precision floating point.
///
/// See: https://tc39.es/ecma262/#sec-tonumber
#[allow(clippy::wrong_self_convention)]
pub fn to_number(&mut self, value: &Value) -> Result<f64, Value> {
Razican marked this conversation as resolved.
Show resolved Hide resolved
match *value.deref().borrow() {
ValueData::Null => Ok(0.0),
ValueData::Undefined => Ok(f64::NAN),
ValueData::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }),
ValueData::String(ref string) => match string.parse::<f64>() {
Ok(number) => Ok(number),
Err(_) => Ok(0.0),
}, // this is probably not 100% correct, see https://tc39.es/ecma262/#sec-tonumber-applied-to-the-string-type
ValueData::Rational(number) => Ok(number),
ValueData::Integer(integer) => Ok(f64::from(integer)),
ValueData::Symbol(_) => {
self.throw_type_error("argument must not be a symbol")?;
unreachable!()
}
ValueData::BigInt(_) => {
self.throw_type_error("argument must not be a bigint")?;
unreachable!()
}
ValueData::Object(_) => {
let prim_value = self.to_primitive(&mut (value.clone()), Some("number"));
self.to_number(&prim_value)
}
}
}

/// Converts an array object into a rust vector of values.
///
/// This is useful for the spread operator, for any other object an `Err` is returned
Expand Down Expand Up @@ -382,6 +450,7 @@ impl Interpreter {
.parse::<f64>()
.expect("cannot parse value to f64")
}
ValueData::Undefined => f64::NAN,
_ => {
// TODO: Make undefined?
f64::from(0)
Expand Down
20 changes: 20 additions & 0 deletions boa/src/exec/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,26 @@ fn to_bigint() {
assert!(engine.to_bigint(&Value::string("100")).is_ok());
}

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

assert_eq!(engine.to_index(&Value::undefined()).unwrap(), 0);
assert!(engine.to_index(&Value::integer(-1)).is_err());
}

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

assert_eq!(engine.to_integer(&Value::number(f64::NAN)).unwrap(), 0);
assert_eq!(engine.to_integer(&Value::number(0.0f64)).unwrap(), 0);
assert_eq!(engine.to_integer(&Value::number(20.9)).unwrap(), 20);
assert_eq!(engine.to_integer(&Value::number(-20.9)).unwrap(), -20);
}

#[test]
fn to_string() {
let realm = Realm::create();
Expand Down