Browse Source

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

Co-authored-by: HalidOdat <halidodat@gmail.com>
pull/479/head
Richard 4 years ago committed by GitHub
parent
commit
a987e76b9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      Cargo.lock
  2. 1
      boa/Cargo.toml
  3. 71
      boa/src/builtins/bigint/mod.rs
  4. 14
      boa/src/builtins/bigint/operations.rs
  5. 148
      boa/src/builtins/bigint/tests.rs
  6. 69
      boa/src/exec/mod.rs
  7. 20
      boa/src/exec/tests.rs

1
Cargo.lock generated

@ -10,6 +10,7 @@ dependencies = [
"jemallocator", "jemallocator",
"measureme", "measureme",
"num-bigint", "num-bigint",
"num-integer",
"num-traits", "num-traits",
"once_cell", "once_cell",
"rand", "rand",

1
boa/Cargo.toml

@ -21,6 +21,7 @@ num-traits = "0.2.11"
regex = "1.3.9" regex = "1.3.9"
rustc-hash = "1.1.0" rustc-hash = "1.1.0"
num-bigint = { version = "0.2.6", features = ["serde"] } num-bigint = { version = "0.2.6", features = ["serde"] }
num-integer = "0.1.42"
bitflags = "1.2.1" bitflags = "1.2.1"
# Optional Dependencies # Optional Dependencies

71
boa/src/builtins/bigint/mod.rs

@ -146,6 +146,70 @@ impl BigInt {
Ok(Value::from(Self::this_bigint_value(this, ctx)?)) 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 /// Create a new `Number` object
pub(crate) fn create(global: &Value) -> Value { pub(crate) fn create(global: &Value) -> Value {
let prototype = Value::new_object(Some(global)); let prototype = Value::new_object(Some(global));
@ -154,7 +218,12 @@ impl BigInt {
make_builtin_fn(Self::to_string, "toString", &prototype, 1); make_builtin_fn(Self::to_string, "toString", &prototype, 1);
make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); 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. /// Initialise the `BigInt` object on the global object.

14
boa/src/builtins/bigint/operations.rs

@ -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 {
use num_integer::Integer;
Self(self.0.mod_floor(&other.0))
}
} }
macro_rules! impl_bigint_operator { macro_rules! impl_bigint_operator {

148
boa/src/builtins/bigint/tests.rs

@ -219,3 +219,151 @@ fn to_string() {
assert_eq!(forward(&mut engine, "255n.toString(16)"), "ff"); assert_eq!(forward(&mut engine, "255n.toString(16)"), "ff");
assert_eq!(forward(&mut engine, "1000n.toString(36)"), "rs"); 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
boa/src/exec/mod.rs

@ -207,6 +207,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> {
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> {
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> {
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. /// 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 /// This is useful for the spread operator, for any other object an `Err` is returned
@ -382,6 +450,7 @@ impl Interpreter {
.parse::<f64>() .parse::<f64>()
.expect("cannot parse value to f64") .expect("cannot parse value to f64")
} }
ValueData::Undefined => f64::NAN,
_ => { _ => {
// TODO: Make undefined? // TODO: Make undefined?
f64::from(0) f64::from(0)

20
boa/src/exec/tests.rs

@ -804,6 +804,26 @@ fn to_bigint() {
assert!(engine.to_bigint(&Value::string("100")).is_ok()); 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] #[test]
fn to_string() { fn to_string() {
let realm = Realm::create(); let realm = Realm::create();

Loading…
Cancel
Save