Skip to content

Commit

Permalink
Add missing Number methods. (#562)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshwd36 authored Jul 13, 2020
1 parent df80ee0 commit 690b194
Show file tree
Hide file tree
Showing 2 changed files with 365 additions and 0 deletions.
190 changes: 190 additions & 0 deletions boa/src/builtins/number/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,188 @@ impl Number {
}
}

/// Builtin javascript 'isFinite(number)' function.
///
/// Converts the argument to a number, throwing a type error if the conversion is invalid.
///
/// If the number is NaN, +∞, or -∞ false is returned.
///
/// Otherwise true is returned.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-isfinite-number
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite
pub(crate) fn global_is_finite(
_this: &Value,
args: &[Value],
ctx: &mut Interpreter,
) -> ResultValue {
if let Some(val) = args.get(0) {
let number = ctx.to_number(val)?;
Ok(number.is_finite().into())
} else {
Ok(false.into())
}
}

/// Builtin javascript 'isNaN(number)' function.
///
/// Converts the argument to a number, throwing a type error if the conversion is invalid.
///
/// If the number is NaN true is returned.
///
/// Otherwise false is returned.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-isnan-number
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isNaN
pub(crate) fn global_is_nan(
_this: &Value,
args: &[Value],
ctx: &mut Interpreter,
) -> ResultValue {
if let Some(val) = args.get(0) {
let number = ctx.to_number(val)?;
Ok(number.is_nan().into())
} else {
Ok(true.into())
}
}

/// `Number.isFinite( number )`
///
/// Checks if the argument is a number, returning false if it isn't.
///
/// If the number is NaN, +∞, or -∞ false is returned.
///
/// Otherwise true is returned.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-number.isfinite
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite
pub(crate) fn number_is_finite(
_this: &Value,
args: &[Value],
_ctx: &mut Interpreter,
) -> ResultValue {
Ok(Value::from(if let Some(val) = args.get(0) {
match val {
Value::Integer(_) => true,
Value::Rational(number) => number.is_finite(),
_ => false,
}
} else {
false
}))
}

/// `Number.isInteger( number )`
///
/// Checks if the argument is an integer.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-number.isinteger
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger
pub(crate) fn number_is_integer(
_this: &Value,
args: &[Value],
_ctx: &mut Interpreter,
) -> ResultValue {
Ok(args.get(0).map_or(false, Self::is_integer).into())
}

/// `Number.isNaN( number )`
///
/// Checks if the argument is a number, returning false if it isn't.
///
/// If the number is NaN true is returned.
///
/// Otherwise false is returned.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-isnan-number
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN
pub(crate) fn number_is_nan(
_this: &Value,
args: &[Value],
_ctx: &mut Interpreter,
) -> ResultValue {
Ok(Value::from(if let Some(val) = args.get(0) {
match val {
Value::Integer(_) => false,
Value::Rational(number) => number.is_nan(),
_ => false,
}
} else {
false
}))
}

/// `Number.isSafeInteger( number )`
///
/// Checks if the argument is an integer, returning false if it isn't.
///
/// If abs(number) ≤ MAX_SAFE_INTEGER true is returned.
///
/// Otherwise false is returned.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-isnan-number
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN
pub(crate) fn is_safe_integer(
_this: &Value,
args: &[Value],
_ctx: &mut Interpreter,
) -> ResultValue {
Ok(Value::from(match args.get(0) {
Some(Value::Integer(_)) => true,
Some(Value::Rational(number)) if Self::is_float_integer(*number) => {
number.abs() <= Number::MAX_SAFE_INTEGER
}
_ => false,
}))
}

/// Checks if the argument is a finite integer Number value.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isinteger
#[inline]
pub(crate) fn is_integer(val: &Value) -> bool {
match val {
Value::Integer(_) => true,
Value::Rational(number) => Number::is_float_integer(*number),
_ => false,
}
}

/// Checks if the float argument is an integer.
#[inline]
#[allow(clippy::float_cmp)]
pub(crate) fn is_float_integer(number: f64) -> bool {
number.is_finite() && number.abs().floor() == number.abs()
}

/// Initialise the `Number` object on the global object.
#[inline]
pub(crate) fn init(global: &Value) -> (&str, Value) {
Expand All @@ -577,6 +759,9 @@ impl Number {
PARSE_FLOAT_MAX_ARG_COUNT,
);

make_builtin_fn(Self::global_is_finite, "isFinite", global, 1);
make_builtin_fn(Self::global_is_nan, "isNaN", global, 1);

let number_object = make_constructor_fn(
Self::NAME,
Self::LENGTH,
Expand All @@ -586,6 +771,11 @@ impl Number {
true,
);

make_builtin_fn(Self::number_is_finite, "isFinite", &number_object, 1);
make_builtin_fn(Self::number_is_nan, "isNaN", &number_object, 1);
make_builtin_fn(Self::is_safe_integer, "isSafeInteger", &number_object, 1);
make_builtin_fn(Self::number_is_integer, "isInteger", &number_object, 1);

// Constants from:
// https://tc39.es/ecma262/#sec-properties-of-the-number-constructor
{
Expand Down
175 changes: 175 additions & 0 deletions boa/src/builtins/number/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -677,3 +677,178 @@ fn parse_float_too_many_args() {

assert_eq!(&forward(&mut engine, "parseFloat(\"100.5\", 10)"), "100.5");
}

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

assert_eq!("false", &forward(&mut engine, "isFinite(Infinity)"));
assert_eq!("false", &forward(&mut engine, "isFinite(NaN)"));
assert_eq!("false", &forward(&mut engine, "isFinite(-Infinity)"));
assert_eq!("true", &forward(&mut engine, "isFinite(0)"));
assert_eq!("true", &forward(&mut engine, "isFinite(2e64)"));
assert_eq!("true", &forward(&mut engine, "isFinite(910)"));
assert_eq!("true", &forward(&mut engine, "isFinite(null)"));
assert_eq!("true", &forward(&mut engine, "isFinite('0')"));
assert_eq!("false", &forward(&mut engine, "isFinite()"));
}

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

assert_eq!("true", &forward(&mut engine, "isNaN(NaN)"));
assert_eq!("true", &forward(&mut engine, "isNaN('NaN')"));
assert_eq!("true", &forward(&mut engine, "isNaN(undefined)"));
assert_eq!("true", &forward(&mut engine, "isNaN({})"));
assert_eq!("false", &forward(&mut engine, "isNaN(true)"));
assert_eq!("false", &forward(&mut engine, "isNaN(null)"));
assert_eq!("false", &forward(&mut engine, "isNaN(37)"));
assert_eq!("false", &forward(&mut engine, "isNaN('37')"));
assert_eq!("false", &forward(&mut engine, "isNaN('37.37')"));
assert_eq!("true", &forward(&mut engine, "isNaN('37,5')"));
assert_eq!("true", &forward(&mut engine, "isNaN('123ABC')"));
// Incorrect due to ToNumber implementation inconsistencies.
//assert_eq!("false", &forward(&mut engine, "isNaN('')"));
//assert_eq!("false", &forward(&mut engine, "isNaN(' ')"));
assert_eq!("true", &forward(&mut engine, "isNaN('blabla')"));
}

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

assert_eq!("false", &forward(&mut engine, "Number.isFinite(Infinity)"));
assert_eq!("false", &forward(&mut engine, "Number.isFinite(NaN)"));
assert_eq!("false", &forward(&mut engine, "Number.isFinite(-Infinity)"));
assert_eq!("true", &forward(&mut engine, "Number.isFinite(0)"));
assert_eq!("true", &forward(&mut engine, "Number.isFinite(2e64)"));
assert_eq!("true", &forward(&mut engine, "Number.isFinite(910)"));
assert_eq!("false", &forward(&mut engine, "Number.isFinite(null)"));
assert_eq!("false", &forward(&mut engine, "Number.isFinite('0')"));
assert_eq!("false", &forward(&mut engine, "Number.isFinite()"));
assert_eq!("false", &forward(&mut engine, "Number.isFinite({})"));
assert_eq!("true", &forward(&mut engine, "Number.isFinite(Number(5))"));
assert_eq!(
"false",
&forward(&mut engine, "Number.isFinite(new Number(5))")
);
assert_eq!(
"false",
&forward(&mut engine, "Number.isFinite(new Number(NaN))")
);
assert_eq!("false", &forward(&mut engine, "Number.isFinite(BigInt(5))"));
}

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

assert_eq!("true", &forward(&mut engine, "Number.isInteger(0)"));
assert_eq!("true", &forward(&mut engine, "Number.isInteger(1)"));
assert_eq!("true", &forward(&mut engine, "Number.isInteger(-100000)"));
assert_eq!(
"true",
&forward(&mut engine, "Number.isInteger(99999999999999999999999)")
);
assert_eq!("false", &forward(&mut engine, "Number.isInteger(0.1)"));
assert_eq!("false", &forward(&mut engine, "Number.isInteger(Math.PI)"));
assert_eq!("false", &forward(&mut engine, "Number.isInteger(NaN)"));
assert_eq!("false", &forward(&mut engine, "Number.isInteger(Infinity)"));
assert_eq!(
"false",
&forward(&mut engine, "Number.isInteger(-Infinity)")
);
assert_eq!("false", &forward(&mut engine, "Number.isInteger('10')"));
assert_eq!("false", &forward(&mut engine, "Number.isInteger(true)"));
assert_eq!("false", &forward(&mut engine, "Number.isInteger(false)"));
assert_eq!("false", &forward(&mut engine, "Number.isInteger([1])"));
assert_eq!("true", &forward(&mut engine, "Number.isInteger(5.0)"));
assert_eq!(
"false",
&forward(&mut engine, "Number.isInteger(5.000000000000001)")
);
assert_eq!(
"true",
&forward(&mut engine, "Number.isInteger(5.0000000000000001)")
);
assert_eq!(
"false",
&forward(&mut engine, "Number.isInteger(Number(5.000000000000001))")
);
assert_eq!(
"true",
&forward(&mut engine, "Number.isInteger(Number(5.0000000000000001))")
);
assert_eq!("false", &forward(&mut engine, "Number.isInteger()"));
assert_eq!(
"false",
&forward(&mut engine, "Number.isInteger(new Number(5))")
);
}

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

assert_eq!("true", &forward(&mut engine, "Number.isNaN(NaN)"));
assert_eq!("true", &forward(&mut engine, "Number.isNaN(Number.NaN)"));
assert_eq!("true", &forward(&mut engine, "Number.isNaN(0 / 0)"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN(undefined)"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN({})"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN(true)"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN(null)"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN(37)"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN('37')"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN('37.37')"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN('37,5')"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN('123ABC')"));
// Incorrect due to ToNumber implementation inconsistencies.
//assert_eq!("false", &forward(&mut engine, "Number.isNaN('')"));
//assert_eq!("false", &forward(&mut engine, "Number.isNaN(' ')"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN('blabla')"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN(Number(5))"));
assert_eq!("true", &forward(&mut engine, "Number.isNaN(Number(NaN))"));
assert_eq!("false", &forward(&mut engine, "Number.isNaN(BigInt(5))"));
assert_eq!(
"false",
&forward(&mut engine, "Number.isNaN(new Number(5))")
);
assert_eq!(
"false",
&forward(&mut engine, "Number.isNaN(new Number(NaN))")
);
}

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

assert_eq!("true", &forward(&mut engine, "Number.isSafeInteger(3)"));
assert_eq!(
"false",
&forward(&mut engine, "Number.isSafeInteger(Math.pow(2, 53))")
);
assert_eq!(
"true",
&forward(&mut engine, "Number.isSafeInteger(Math.pow(2, 53) - 1)")
);
assert_eq!("false", &forward(&mut engine, "Number.isSafeInteger(NaN)"));
assert_eq!(
"false",
&forward(&mut engine, "Number.isSafeInteger(Infinity)")
);
assert_eq!("false", &forward(&mut engine, "Number.isSafeInteger('3')"));
assert_eq!("false", &forward(&mut engine, "Number.isSafeInteger(3.1)"));
assert_eq!("true", &forward(&mut engine, "Number.isSafeInteger(3.0)"));
assert_eq!(
"false",
&forward(&mut engine, "Number.isSafeInteger(new Number(5))")
);
}

0 comments on commit 690b194

Please sign in to comment.