From df13272fc022887b2a39dc305c5396027568d8f7 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Tue, 16 Jun 2020 00:56:52 +0200 Subject: [PATCH] Object specialization (#419) --- boa/src/builtins/array/mod.rs | 53 +- boa/src/builtins/bigint/mod.rs | 44 +- boa/src/builtins/boolean/mod.rs | 96 +- boa/src/builtins/console/mod.rs | 5 +- boa/src/builtins/error/mod.rs | 27 +- boa/src/builtins/error/range.rs | 27 +- boa/src/builtins/error/type.rs | 27 +- boa/src/builtins/function/mod.rs | 116 +- boa/src/builtins/global_this/mod.rs | 33 +- boa/src/builtins/json/mod.rs | 269 ++--- boa/src/builtins/math/mod.rs | 999 +++++++++--------- boa/src/builtins/mod.rs | 50 +- boa/src/builtins/nan/mod.rs | 32 +- boa/src/builtins/number/mod.rs | 138 +-- boa/src/builtins/number/tests.rs | 12 +- ...l_methods_trait.rs => internal_methods.rs} | 215 +++- boa/src/builtins/object/mod.rs | 634 +++++------ boa/src/builtins/regexp/mod.rs | 49 +- boa/src/builtins/string/mod.rs | 88 +- boa/src/builtins/symbol/mod.rs | 172 +-- boa/src/builtins/symbol/tests.rs | 2 +- boa/src/builtins/value/conversions.rs | 4 +- boa/src/builtins/value/display.rs | 46 +- boa/src/builtins/value/equality.rs | 7 +- boa/src/builtins/value/hash.rs | 54 + boa/src/builtins/value/mod.rs | 361 +++---- boa/src/builtins/value/tests.rs | 64 ++ boa/src/environment/lexical_environment.rs | 8 +- boa/src/exec/exception.rs | 42 +- boa/src/exec/expression/mod.rs | 15 +- boa/src/exec/mod.rs | 197 ++-- 31 files changed, 2127 insertions(+), 1759 deletions(-) rename boa/src/builtins/object/{internal_methods_trait.rs => internal_methods.rs} (55%) create mode 100644 boa/src/builtins/value/hash.rs diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 646011e841..0517e0c3cc 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -15,7 +15,7 @@ mod tests; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ - object::{ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, + object::{ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE}, property::Property, value::{same_value_zero, ResultValue, Value, ValueData}, }, @@ -25,7 +25,6 @@ use crate::{ use std::{ borrow::Borrow, cmp::{max, min}, - ops::Deref, }; /// JavaScript `Array` built-in implementation. @@ -33,6 +32,12 @@ use std::{ pub(crate) struct Array; impl Array { + /// The name of the object. + pub(crate) const NAME: &'static str = "Array"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: usize = 1; + /// Creates a new `Array` instance. pub(crate) fn new_array(interpreter: &Interpreter) -> ResultValue { let array = Value::new_object(Some( @@ -42,7 +47,7 @@ impl Array { .get_global_object() .expect("Could not get global object"), )); - array.set_kind(ObjectKind::Array); + array.set_data(ObjectData::Array); array.borrow().set_internal_slot( INSTANCE_PROTOTYPE, interpreter @@ -117,7 +122,7 @@ impl Array { this.set_internal_slot(INSTANCE_PROTOTYPE, prototype); // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) - this.set_kind(ObjectKind::Array); + this.set_data(ObjectData::Array); // add our arguments in let mut length = args.len() as i32; @@ -167,25 +172,9 @@ impl Array { args: &[Value], _interpreter: &mut Interpreter, ) -> ResultValue { - let value_true = Value::boolean(true); - let value_false = Value::boolean(false); - - match args.get(0) { - Some(arg) => { - match arg.data() { - // 1. - ValueData::Object(ref obj) => { - // 2. - if (*obj).deref().borrow().kind == ObjectKind::Array { - return Ok(value_true); - } - Ok(value_false) - } - // 3. - _ => Ok(value_false), - } - } - None => Ok(value_false), + match args.get(0).and_then(|x| x.as_object()) { + Some(object) => Ok(Value::from(object.is_array())), + None => Ok(Value::from(false)), } } @@ -1008,7 +997,7 @@ impl Array { let prototype = Value::new_object(None); let length = Property::default().value(Value::from(0)); - prototype.set_property_slice("length", length); + prototype.set_property("length", length); make_builtin_fn(Self::concat, "concat", &prototype, 1); make_builtin_fn(Self::push, "push", &prototype, 1); @@ -1031,7 +1020,14 @@ impl Array { make_builtin_fn(Self::slice, "slice", &prototype, 2); make_builtin_fn(Self::some, "some", &prototype, 2); - let array = make_constructor_fn("Array", 1, Self::make_array, global, prototype, true); + let array = make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::make_array, + global, + prototype, + true, + ); // Static Methods make_builtin_fn(Self::is_array, "isArray", &array, 1); @@ -1041,8 +1037,9 @@ impl Array { /// Initialise the `Array` object on the global object. #[inline] - pub(crate) fn init(global: &Value) { - let _timer = BoaProfiler::global().start_event("array", "init"); - global.set_field("Array", Self::create(global)); + pub(crate) fn init(global: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/bigint/mod.rs b/boa/src/builtins/bigint/mod.rs index a685d1f6e8..577f71e118 100644 --- a/boa/src/builtins/bigint/mod.rs +++ b/boa/src/builtins/bigint/mod.rs @@ -15,6 +15,7 @@ use crate::{ builtins::{ function::{make_builtin_fn, make_constructor_fn}, + object::ObjectData, value::{ResultValue, Value, ValueData}, }, exec::Interpreter, @@ -43,6 +44,12 @@ mod tests; pub struct BigInt(num_bigint::BigInt); impl BigInt { + /// The name of the object. + pub(crate) const NAME: &'static str = "BigInt"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: usize = 1; + /// The abstract operation thisBigIntValue takes argument value. /// /// The phrase “this BigInt value” within the specification of a method refers to the @@ -62,9 +69,8 @@ impl BigInt { // 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() { + ValueData::Object(ref object) => { + if let ObjectData::BigInt(ref bigint) = object.borrow().data { return Ok(bigint.clone()); } } @@ -72,8 +78,7 @@ impl BigInt { } // 3. Throw a TypeError exception. - ctx.throw_type_error("'this' is not a BigInt")?; - unreachable!(); + Err(ctx.construct_type_error("'this' is not a BigInt")) } /// `BigInt()` @@ -86,16 +91,12 @@ impl BigInt { /// /// [spec]: https://tc39.es/ecma262/#sec-bigint-objects /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt - pub(crate) fn make_bigint( - _this: &mut Value, - args: &[Value], - ctx: &mut Interpreter, - ) -> ResultValue { + pub(crate) fn make_bigint(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { let data = match args.get(0) { - Some(ref value) => Value::from(ctx.to_bigint(value)?), - None => Value::from(Self::from(0)), + Some(ref value) => ctx.to_bigint(value)?, + None => Self::from(0), }; - Ok(data) + Ok(Value::from(data)) } /// `BigInt.prototype.toString( [radix] )` @@ -213,12 +214,18 @@ impl BigInt { /// 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(Self::from(0))); make_builtin_fn(Self::to_string, "toString", &prototype, 1); make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); - let big_int = make_constructor_fn("BigInt", 1, Self::make_bigint, global, prototype, false); + let big_int = make_constructor_fn( + Self::NAME, + Self::LENGTH, + 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); @@ -228,9 +235,10 @@ impl BigInt { /// Initialise the `BigInt` object on the global object. #[inline] - pub(crate) fn init(global: &Value) { - let _timer = BoaProfiler::global().start_event("bigint", "init"); - global.set_field("BigInt", Self::create(global)); + pub(crate) fn init(global: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/boolean/mod.rs b/boa/src/builtins/boolean/mod.rs index 76012a40e7..652147e90c 100644 --- a/boa/src/builtins/boolean/mod.rs +++ b/boa/src/builtins/boolean/mod.rs @@ -15,19 +15,45 @@ mod tests; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ - object::{internal_methods_trait::ObjectInternalMethods, ObjectKind}, + object::ObjectData, value::{ResultValue, Value, ValueData}, }, exec::Interpreter, BoaProfiler, }; -use std::{borrow::Borrow, ops::Deref}; /// Boolean implementation. #[derive(Debug, Clone, Copy)] pub(crate) struct Boolean; impl Boolean { + /// The name of the object. + pub(crate) const NAME: &'static str = "Boolean"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: usize = 1; + + /// An Utility function used to get the internal [[BooleanData]]. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-thisbooleanvalue + fn this_boolean_value(value: &Value, ctx: &mut Interpreter) -> Result { + match value.data() { + ValueData::Boolean(boolean) => return Ok(*boolean), + ValueData::Object(ref object) => { + let object = object.borrow(); + if let Some(boolean) = object.as_boolean() { + return Ok(boolean); + } + } + _ => {} + } + + Err(ctx.construct_type_error("'this' is not a boolean")) + } + /// `[[Construct]]` Create a new boolean object /// /// `[[Call]]` Creates a new boolean primitive @@ -36,19 +62,11 @@ impl Boolean { args: &[Value], _: &mut Interpreter, ) -> ResultValue { - this.set_kind(ObjectKind::Boolean); - // Get the argument, if any - if let Some(ref value) = args.get(0) { - this.set_internal_slot("BooleanData", Self::to_boolean(value)); - } else { - this.set_internal_slot("BooleanData", Self::to_boolean(&Value::from(false))); - } + let data = args.get(0).map(|x| x.to_boolean()).unwrap_or(false); + this.set_data(ObjectData::Boolean(data)); - match args.get(0) { - Some(ref value) => Ok(Self::to_boolean(value)), - None => Ok(Self::to_boolean(&Value::from(false))), - } + Ok(Value::from(data)) } /// The `toString()` method returns a string representing the specified `Boolean` object. @@ -60,9 +78,9 @@ impl Boolean { /// [spec]: https://tc39.es/ecma262/#sec-boolean-object /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/toString #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - let b = Self::this_boolean_value(this); - Ok(Value::from(b.to_string())) + pub(crate) fn to_string(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + let boolean = Self::this_boolean_value(this, ctx)?; + Ok(Value::from(boolean.to_string())) } /// The valueOf() method returns the primitive value of a `Boolean` object. @@ -73,37 +91,9 @@ impl Boolean { /// /// [spec]: https://tc39.es/ecma262/#sec-boolean.prototype.valueof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/valueOf - pub(crate) fn value_of(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Self::this_boolean_value(this)) - } - - // === Utility Functions === - /// [toBoolean](https://tc39.es/ecma262/#sec-toboolean) - /// Creates a new boolean value from the input - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_boolean(value: &Value) -> Value { - match *value.deref().borrow() { - ValueData::Object(_) => Value::from(true), - ValueData::String(ref s) if !s.is_empty() => Value::from(true), - ValueData::Rational(n) if n != 0.0 && !n.is_nan() => Value::from(true), - ValueData::Integer(n) if n != 0 => Value::from(true), - ValueData::Boolean(v) => Value::from(v), - _ => Value::from(false), - } - } - - /// An Utility function used to get the internal BooleanData. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-thisbooleanvalue - pub(crate) fn this_boolean_value(value: &Value) -> Value { - match *value.deref().borrow() { - ValueData::Boolean(v) => Value::from(v), - ValueData::Object(ref v) => (v).deref().borrow().get_internal_slot("BooleanData"), - _ => Value::from(false), - } + #[inline] + pub(crate) fn value_of(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::from(Self::this_boolean_value(this, ctx)?)) } /// Create a new `Boolean` object. @@ -111,14 +101,13 @@ impl Boolean { // Create Prototype // https://tc39.es/ecma262/#sec-properties-of-the-boolean-prototype-object let prototype = Value::new_object(Some(global)); - prototype.set_internal_slot("BooleanData", Self::to_boolean(&Value::from(false))); make_builtin_fn(Self::to_string, "toString", &prototype, 0); make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); make_constructor_fn( - "Boolean", - 1, + Self::NAME, + Self::LENGTH, Self::construct_boolean, global, prototype, @@ -128,8 +117,9 @@ impl Boolean { /// Initialise the `Boolean` object on the global object. #[inline] - pub(crate) fn init(global: &Value) { - let _timer = BoaProfiler::global().start_event("boolean", "init"); - global.set_field("Boolean", Self::create(global)); + pub(crate) fn init(global: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/console/mod.rs b/boa/src/builtins/console/mod.rs index aaf8261abe..39bcb43b41 100644 --- a/boa/src/builtins/console/mod.rs +++ b/boa/src/builtins/console/mod.rs @@ -554,7 +554,8 @@ pub fn create(global: &Value) -> Value { /// Initialise the `console` object on the global object. #[inline] -pub fn init(global: &Value) { +pub fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event("console", "init"); - global.set_field("console", create(global)); + + ("console", create(global)) } diff --git a/boa/src/builtins/error/mod.rs b/boa/src/builtins/error/mod.rs index a6df97025b..7bdee31bc7 100644 --- a/boa/src/builtins/error/mod.rs +++ b/boa/src/builtins/error/mod.rs @@ -13,7 +13,7 @@ use crate::{ builtins::{ function::{make_builtin_fn, make_constructor_fn}, - object::ObjectKind, + object::ObjectData, value::{ResultValue, Value}, }, exec::Interpreter, @@ -35,6 +35,12 @@ pub(crate) use self::range::RangeError; pub(crate) struct Error; impl Error { + /// The name of the object. + pub(crate) const NAME: &'static str = "Error"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: usize = 1; + /// Create a new error object. pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { if !args.is_empty() { @@ -49,7 +55,7 @@ impl Error { } // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) - this.set_kind(ObjectKind::Error); + this.set_data(ObjectData::Error); Err(this.clone()) } @@ -77,12 +83,21 @@ impl Error { make_builtin_fn(Self::to_string, "toString", &prototype, 0); - make_constructor_fn("Error", 1, Self::make_error, global, prototype, true) + make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::make_error, + global, + prototype, + true, + ) } /// Initialise the global object with the `Error` object. - pub(crate) fn init(global: &Value) { - let _timer = BoaProfiler::global().start_event("error", "init"); - global.set_field("Error", Self::create(global)); + #[inline] + pub(crate) fn init(global: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/error/range.rs b/boa/src/builtins/error/range.rs index 0af07ff45a..c51df0b350 100644 --- a/boa/src/builtins/error/range.rs +++ b/boa/src/builtins/error/range.rs @@ -13,7 +13,7 @@ use crate::{ builtins::{ function::make_builtin_fn, function::make_constructor_fn, - object::ObjectKind, + object::ObjectData, value::{ResultValue, Value}, }, exec::Interpreter, @@ -25,6 +25,12 @@ use crate::{ pub(crate) struct RangeError; impl RangeError { + /// The name of the object. + pub(crate) const NAME: &'static str = "RangeError"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: usize = 1; + /// Create a new error object. pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { if !args.is_empty() { @@ -39,7 +45,7 @@ impl RangeError { } // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) - this.set_kind(ObjectKind::Error); + this.set_data(ObjectData::Error); Err(this.clone()) } @@ -67,12 +73,21 @@ impl RangeError { make_builtin_fn(Self::to_string, "toString", &prototype, 0); - make_constructor_fn("RangeError", 1, Self::make_error, global, prototype, true) + make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::make_error, + global, + prototype, + true, + ) } /// Initialise the global object with the `RangeError` object. - pub(crate) fn init(global: &Value) { - let _timer = BoaProfiler::global().start_event("rangeerror", "init"); - global.set_field("RangeError", Self::create(global)); + #[inline] + pub(crate) fn init(global: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/error/type.rs b/boa/src/builtins/error/type.rs index f15c39cd7f..3a4f2de977 100644 --- a/boa/src/builtins/error/type.rs +++ b/boa/src/builtins/error/type.rs @@ -19,10 +19,11 @@ use crate::{ builtins::{ function::make_builtin_fn, function::make_constructor_fn, - object::ObjectKind, + object::ObjectData, value::{ResultValue, Value}, }, exec::Interpreter, + BoaProfiler, }; /// JavaScript `TypeError` implementation. @@ -30,6 +31,12 @@ use crate::{ pub(crate) struct TypeError; impl TypeError { + /// The name of the object. + pub(crate) const NAME: &'static str = "TypeError"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: usize = 1; + /// Create a new error object. pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { if !args.is_empty() { @@ -45,7 +52,7 @@ impl TypeError { // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) - this.set_kind(ObjectKind::Error); + this.set_data(ObjectData::Error); Err(this.clone()) } @@ -73,11 +80,21 @@ impl TypeError { make_builtin_fn(Self::to_string, "toString", &prototype, 0); - make_constructor_fn("TypeError", 1, Self::make_error, global, prototype, true) + make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::make_error, + global, + prototype, + true, + ) } /// Initialise the global object with the `RangeError` object. - pub(crate) fn init(global: &Value) { - global.set_field("TypeError", Self::create(global)); + #[inline] + pub(crate) fn init(global: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 5ad1ed724e..6566895805 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -14,7 +14,7 @@ use crate::{ builtins::{ array::Array, - object::{Object, ObjectInternalMethods, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, + object::{Object, ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE}, property::Property, value::{ResultValue, Value}, }, @@ -40,7 +40,8 @@ pub enum ConstructorKind { /// Defines how this references are interpreted within the formal parameters and code body of the function. /// /// Arrow functions don't define a `this` and thus are lexical, `function`s do define a this and thus are NonLexical -#[derive(Copy, Finalize, Debug, Clone)] + +#[derive(Debug, Copy, Finalize, Clone, PartialEq, PartialOrd, Hash)] pub enum ThisMode { Lexical, NonLexical, @@ -60,12 +61,24 @@ pub enum FunctionBody { impl Debug for FunctionBody { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::BuiltIn(_) => write!(f, "native code"), + Self::BuiltIn(_) => write!(f, "[native]"), Self::Ordinary(statements) => write!(f, "{:?}", statements), } } } +impl PartialEq for FunctionBody { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::BuiltIn(a), Self::BuiltIn(b)) => std::ptr::eq(a, b), + (Self::Ordinary(a), Self::Ordinary(b)) => a == b, + (_, _) => false, + } + } +} + +impl Eq for FunctionBody {} + /// `Trace` implementation for `FunctionBody`. /// /// This is indeed safe, but we need to mark this as an empty trace because neither @@ -164,20 +177,20 @@ impl Function { /// pub fn call( &self, - this: &mut Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) + function: Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) + this: &mut Value, args_list: &[Value], interpreter: &mut Interpreter, - this_obj: &mut Value, ) -> ResultValue { let _timer = BoaProfiler::global().start_event("function::call", "function"); if self.callable { match self.body { - FunctionBody::BuiltIn(func) => func(this_obj, args_list, interpreter), + FunctionBody::BuiltIn(func) => func(this, args_list, interpreter), FunctionBody::Ordinary(ref body) => { // Create a new Function environment who's parent is set to the scope of the function declaration (self.environment) // let local_env = new_function_environment( - this.clone(), + function, None, Some(self.environment.as_ref().unwrap().clone()), BindingStatus::Uninitialized, @@ -223,23 +236,23 @@ impl Function { /// pub fn construct( &self, - this: &mut Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) + function: Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) + this: &mut Value, args_list: &[Value], interpreter: &mut Interpreter, - this_obj: &mut Value, ) -> ResultValue { if self.constructable { match self.body { FunctionBody::BuiltIn(func) => { - func(this_obj, args_list, interpreter)?; - Ok(this_obj.clone()) + func(this, args_list, interpreter)?; + Ok(this.clone()) } FunctionBody::Ordinary(ref body) => { // Create a new Function environment who's parent is set to the scope of the function declaration (self.environment) // let local_env = new_function_environment( - this.clone(), - Some(this_obj.clone()), + function, + Some(this.clone()), Some(self.environment.as_ref().unwrap().clone()), BindingStatus::Initialized, ); @@ -364,7 +377,7 @@ pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value { .writable(true) .configurable(true); - obj.properties.insert(index.to_string(), prop); + obj.properties_mut().insert(index.to_string(), prop); index += 1; } @@ -375,7 +388,10 @@ pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value { /// // This gets called when a new Function() is created. pub fn make_function(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - this.set_kind(ObjectKind::Function); + this.set_data(ObjectData::Function(Function::builtin( + Vec::new(), + |_, _, _| Ok(Value::undefined()), + ))); Ok(this.clone()) } @@ -391,46 +407,55 @@ pub fn create(global: &Value) -> Value { /// So far this is only used by internal functions pub fn make_constructor_fn( name: &str, - length: i32, + length: usize, body: NativeFunctionData, global: &Value, - proto: Value, + prototype: Value, constructable: bool, ) -> Value { + let _timer = + BoaProfiler::global().start_event(&format!("make_constructor_fn: {}", name), "init"); + // Create the native function - let mut constructor_fn = Function::builtin(Vec::new(), body); + let mut function = Function::builtin(Vec::new(), body); + function.constructable = constructable; - constructor_fn.constructable = constructable; + let mut constructor = Object::function(function); // Get reference to Function.prototype - let func_prototype = global.get_field("Function").get_field(PROTOTYPE); - // Create the function object and point its instance prototype to Function.prototype - let mut constructor_obj = Object::function(); - constructor_obj.set_func(constructor_fn); - - constructor_obj.set_internal_slot(INSTANCE_PROTOTYPE, func_prototype); - let constructor_val = Value::from(constructor_obj); - - // Set proto.constructor -> constructor_obj - proto.set_field("constructor", constructor_val.clone()); - constructor_val.set_field(PROTOTYPE, proto); + constructor.set_internal_slot( + INSTANCE_PROTOTYPE, + global.get_field("Function").get_field(PROTOTYPE), + ); let length = Property::new() .value(Value::from(length)) .writable(false) .configurable(false) .enumerable(false); - constructor_val.set_property_slice("length", length); + constructor.insert_property("length", length); let name = Property::new() .value(Value::from(name)) .writable(false) .configurable(false) .enumerable(false); - constructor_val.set_property_slice("name", name); + constructor.insert_property("name", name); + + let constructor = Value::from(constructor); + + prototype + .as_object_mut() + .unwrap() + .insert_field("constructor", constructor.clone()); - constructor_val + constructor + .as_object_mut() + .expect("constructor object") + .insert_field(PROTOTYPE, prototype); + + constructor } /// Creates a new member function of a `Object` or `prototype`. @@ -451,27 +476,26 @@ pub fn make_constructor_fn( /// some other number of arguments. /// /// If no length is provided, the length will be set to 0. -pub fn make_builtin_fn(function: NativeFunctionData, name: N, parent: &Value, length: i32) +pub fn make_builtin_fn(function: NativeFunctionData, name: N, parent: &Value, length: usize) where N: Into, { - let name_copy: String = name.into(); - let label = format!("{}{}", String::from("make_builtin_fn: "), &name_copy); - let _timer = BoaProfiler::global().start_event(&label, "init"); - let func = Function::builtin(Vec::new(), function); - - let mut new_func = Object::function(); - new_func.set_func(func); + let name = name.into(); + let _timer = BoaProfiler::global().start_event(&format!("make_builtin_fn: {}", &name), "init"); - let new_func_obj = Value::from(new_func); - new_func_obj.set_field("length", length); + let mut function = Object::function(Function::builtin(Vec::new(), function)); + function.insert_field("length", Value::from(length)); - parent.set_field(Value::from(name_copy), new_func_obj); + parent + .as_object_mut() + .unwrap() + .insert_field(name, Value::from(function)); } /// Initialise the `Function` object on the global object. #[inline] -pub fn init(global: &Value) { +pub fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event("function", "init"); - global.set_field("Function", create(global)); + + ("Function", create(global)) } diff --git a/boa/src/builtins/global_this/mod.rs b/boa/src/builtins/global_this/mod.rs index c668313332..2ef49347b9 100644 --- a/boa/src/builtins/global_this/mod.rs +++ b/boa/src/builtins/global_this/mod.rs @@ -1,11 +1,32 @@ +//! This module implements the global `globalThis` property. +//! +//! The global globalThis property contains the global this value, +//! which is akin to the global object. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript reference][spec] +//! +//! [spec]: https://tc39.es/ecma262/#sec-globalthis +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis + +use crate::{builtins::value::Value, BoaProfiler}; + #[cfg(test)] mod tests; -use crate::{builtins::value::Value, BoaProfiler}; +/// The JavaScript `globalThis`. +pub(crate) struct GlobalThis; + +impl GlobalThis { + /// The binding name of the property. + pub(crate) const NAME: &'static str = "globalThis"; + + /// Initialize the `globalThis` property on the global object. + #[inline] + pub(crate) fn init(global: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); -/// Initialize the `globalThis` property on the global object. -#[inline] -pub fn init(global: &Value) { - let _timer = BoaProfiler::global().start_event("globalThis", "init"); - global.set_field("globalThis", global.clone()); + (Self::NAME, global.clone()) + } } diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index d0edaeca66..60f9231ef9 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -15,7 +15,6 @@ use crate::builtins::{ function::make_builtin_fn, - object::ObjectKind, property::Property, value::{ResultValue, Value}, }; @@ -25,155 +24,165 @@ use serde_json::{self, Value as JSONValue}; #[cfg(test)] mod tests; -/// `JSON.parse( text[, reviver] )` -/// -/// This `JSON` method parses a JSON string, constructing the JavaScript value or object described by the string. -/// -/// An optional `reviver` function can be provided to perform a transformation on the resulting object before it is returned. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-json.parse -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse -pub fn parse(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - match serde_json::from_str::( - &ctx.to_string(args.get(0).expect("cannot get argument for JSON.parse"))?, - ) { - Ok(json) => { - let j = Value::from_json(json, ctx); - match args.get(1) { - Some(reviver) if reviver.is_function() => { - let mut holder = Value::new_object(None); - holder.set_field(Value::from(""), j); - walk(reviver, ctx, &mut holder, Value::from("")) +/// JavaScript `JSON` global object. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct Json; + +impl Json { + /// The name of the object. + pub(crate) const NAME: &'static str = "JSON"; + + /// `JSON.parse( text[, reviver] )` + /// + /// This `JSON` method parses a JSON string, constructing the JavaScript value or object described by the string. + /// + /// An optional `reviver` function can be provided to perform a transformation on the resulting object before it is returned. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-json.parse + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse + pub(crate) fn parse(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + match serde_json::from_str::( + &ctx.to_string(args.get(0).expect("cannot get argument for JSON.parse"))?, + ) { + Ok(json) => { + let j = Value::from_json(json, ctx); + match args.get(1) { + Some(reviver) if reviver.is_function() => { + let mut holder = Value::new_object(None); + holder.set_field(Value::from(""), j); + Self::walk(reviver, ctx, &mut holder, Value::from("")) + } + _ => Ok(j), } - _ => Ok(j), } + Err(err) => Err(Value::from(err.to_string())), } - Err(err) => Err(Value::from(err.to_string())), } -} -/// This is a translation of the [Polyfill implementation][polyfill] -/// -/// This function recursively walks the structure, passing each key-value pair to the reviver function -/// for possible transformation. -/// -/// [polyfill]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse -fn walk(reviver: &Value, ctx: &mut Interpreter, holder: &mut Value, key: Value) -> ResultValue { - let mut value = holder.get_field(key.clone()); + /// This is a translation of the [Polyfill implementation][polyfill] + /// + /// This function recursively walks the structure, passing each key-value pair to the reviver function + /// for possible transformation. + /// + /// [polyfill]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse + fn walk(reviver: &Value, ctx: &mut Interpreter, holder: &mut Value, key: Value) -> ResultValue { + let mut value = holder.get_field(key.clone()); - let obj = value.as_object().as_deref().cloned(); - if let Some(obj) = obj { - for key in obj.properties.keys() { - let v = walk(reviver, ctx, &mut value, Value::from(key.as_str())); - match v { - Ok(v) if !v.is_undefined() => { - value.set_field(Value::from(key.as_str()), v); + let obj = value.as_object().as_deref().cloned(); + if let Some(obj) = obj { + for key in obj.properties().keys() { + let v = Self::walk(reviver, ctx, &mut value, Value::from(key.as_str())); + match v { + Ok(v) if !v.is_undefined() => { + value.set_field(Value::from(key.as_str()), v); + } + Ok(_) => { + value.remove_property(key.as_str()); + } + Err(_v) => {} } - Ok(_) => { - value.remove_property(key.as_str()); - } - Err(_v) => {} } } + ctx.call(reviver, holder, &[key, value]) } - ctx.call(reviver, holder, &[key, value]) -} -/// `JSON.stringify( value[, replacer[, space]] )` -/// -/// This `JSON` method converts a JavaScript object or value to a JSON string. -/// -/// This medhod optionally replaces values if a `replacer` function is specified or -/// optionally including only the specified properties if a replacer array is specified. -/// -/// An optional `space` argument can be supplied of type `String` or `Number` that's used to insert -/// white space into the output JSON string for readability purposes. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-json.stringify -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify -pub fn stringify(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - let object = match args.get(0) { - Some(obj) if obj.is_symbol() || obj.is_function() => return Ok(Value::undefined()), - None => return Ok(Value::undefined()), - Some(obj) => obj, - }; - let replacer = match args.get(1) { - Some(replacer) if replacer.is_object() => replacer, - _ => return Ok(Value::from(object.to_json(ctx)?.to_string())), - }; + /// `JSON.stringify( value[, replacer[, space]] )` + /// + /// This `JSON` method converts a JavaScript object or value to a JSON string. + /// + /// This medhod optionally replaces values if a `replacer` function is specified or + /// optionally including only the specified properties if a replacer array is specified. + /// + /// An optional `space` argument can be supplied of type `String` or `Number` that's used to insert + /// white space into the output JSON string for readability purposes. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-json.stringify + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify + pub(crate) fn stringify(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let object = match args.get(0) { + Some(obj) if obj.is_symbol() || obj.is_function() => return Ok(Value::undefined()), + None => return Ok(Value::undefined()), + Some(obj) => obj, + }; + let replacer = match args.get(1) { + Some(replacer) if replacer.is_object() => replacer, + _ => return Ok(Value::from(object.to_json(ctx)?.to_string())), + }; - let replacer_as_object = replacer - .as_object() - .expect("JSON.stringify replacer was an object"); - if replacer_as_object.is_callable() { - object + let replacer_as_object = replacer .as_object() - .map(|obj| { - let object_to_return = Value::new_object(None); - for (key, val) in obj - .properties - .iter() - .filter_map(|(k, v)| v.value.as_ref().map(|value| (k, value))) + .expect("JSON.stringify replacer was an object"); + if replacer_as_object.is_callable() { + object + .as_object() + .map(|obj| { + let object_to_return = Value::new_object(None); + for (key, val) in obj + .properties() + .iter() + .filter_map(|(k, v)| v.value.as_ref().map(|value| (k, value))) + { + let mut this_arg = object.clone(); + object_to_return.set_property( + key.to_owned(), + Property::default().value(ctx.call( + replacer, + &mut this_arg, + &[Value::string(key), val.clone()], + )?), + ); + } + Ok(Value::from(object_to_return.to_json(ctx)?.to_string())) + }) + .ok_or_else(Value::undefined)? + } else if replacer_as_object.is_array() { + let mut obj_to_return = + serde_json::Map::with_capacity(replacer_as_object.properties().len() - 1); + let fields = replacer_as_object.properties().keys().filter_map(|key| { + if key == "length" { + None + } else { + Some(replacer.get_field(key.to_owned())) + } + }); + for field in fields { + if let Some(value) = object + .get_property(&ctx.to_string(&field)?) + .and_then(|prop| prop.value.as_ref().map(|v| v.to_json(ctx))) + .transpose()? { - let mut this_arg = object.clone(); - object_to_return.set_property( - key.to_owned(), - Property::default().value(ctx.call( - replacer, - &mut this_arg, - &[Value::string(key), val.clone()], - )?), - ); + obj_to_return.insert(field.to_string(), value); } - Ok(Value::from(object_to_return.to_json(ctx)?.to_string())) - }) - .ok_or_else(Value::undefined)? - } else if replacer_as_object.kind == ObjectKind::Array { - let mut obj_to_return = - serde_json::Map::with_capacity(replacer_as_object.properties.len() - 1); - let fields = replacer_as_object.properties.keys().filter_map(|key| { - if key == "length" { - None - } else { - Some(replacer.get_field(key.to_owned())) - } - }); - for field in fields { - if let Some(value) = object - .get_property(&ctx.to_string(&field)?) - .and_then(|prop| prop.value.as_ref().map(|v| v.to_json(ctx))) - .transpose()? - { - obj_to_return.insert(field.to_string(), value); } + Ok(Value::from(JSONValue::Object(obj_to_return).to_string())) + } else { + Ok(Value::from(object.to_json(ctx)?.to_string())) } - Ok(Value::from(JSONValue::Object(obj_to_return).to_string())) - } else { - Ok(Value::from(object.to_json(ctx)?.to_string())) } -} -/// Create a new `JSON` object. -pub fn create(global: &Value) -> Value { - let json = Value::new_object(Some(global)); + /// Create a new `JSON` object. + pub(crate) fn create(global: &Value) -> Value { + let json = Value::new_object(Some(global)); - make_builtin_fn(parse, "parse", &json, 2); - make_builtin_fn(stringify, "stringify", &json, 3); + make_builtin_fn(Self::parse, "parse", &json, 2); + make_builtin_fn(Self::stringify, "stringify", &json, 3); - json -} + json + } + + /// Initialise the `JSON` object on the global object. + #[inline] + pub(crate) fn init(global: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); -/// Initialise the `JSON` object on the global object. -#[inline] -pub fn init(global: &Value) { - let _timer = BoaProfiler::global().start_event("json", "init"); - global.set_field("JSON", create(global)); + (Self::NAME, Self::create(global)) + } } diff --git a/boa/src/builtins/math/mod.rs b/boa/src/builtins/math/mod.rs index 775642ae29..08db86df1d 100644 --- a/boa/src/builtins/math/mod.rs +++ b/boa/src/builtins/math/mod.rs @@ -19,542 +19,553 @@ use crate::{ exec::Interpreter, BoaProfiler, }; -use rand::random; use std::f64; #[cfg(test)] mod tests; -/// Get the absolute value of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.abs -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs -pub fn abs(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).abs() - })) -} +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct Math; + +impl Math { + /// The name of the object. + pub(crate) const NAME: &'static str = "Math"; + + /// Get the absolute value of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.abs + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs + pub(crate) fn abs(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).abs() + })) + } -/// Get the arccos of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.acos -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acos -pub fn acos(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).acos() - })) -} + /// Get the arccos of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.acos + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acos + pub(crate) fn acos(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).acos() + })) + } -/// Get the hyperbolic arccos of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.acosh -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acosh -pub fn acosh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).acosh() - })) -} + /// Get the hyperbolic arccos of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.acosh + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acosh + pub(crate) fn acosh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).acosh() + })) + } -/// Get the arcsine of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.asin -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asin -pub fn asin(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).asin() - })) -} + /// Get the arcsine of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.asin + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asin + pub(crate) fn asin(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).asin() + })) + } -/// Get the hyperbolic arcsine of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.asinh -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asinh -pub fn asinh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).asinh() - })) -} + /// Get the hyperbolic arcsine of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.asinh + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asinh + pub(crate) fn asinh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).asinh() + })) + } -/// Get the arctangent of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.atan -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan -pub fn atan(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).atan() - })) -} + /// Get the arctangent of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.atan + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan + pub(crate) fn atan(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).atan() + })) + } -/// Get the hyperbolic arctangent of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.atanh -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atanh -pub fn atanh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).atanh() - })) -} + /// Get the hyperbolic arctangent of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.atanh + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atanh + pub(crate) fn atanh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).atanh() + })) + } -/// Get the arctangent of a numbers. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.atan2 -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2 -pub fn atan2(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")) - .atan2(args.get(1).expect("Could not get argument").to_number()) - })) -} + /// Get the arctangent of a numbers. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.atan2 + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2 + pub(crate) fn atan2(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")) + .atan2(args.get(1).expect("Could not get argument").to_number()) + })) + } -/// Get the cubic root of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.cbrt -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cbrt -pub fn cbrt(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).cbrt() - })) -} + /// Get the cubic root of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.cbrt + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cbrt + pub(crate) fn cbrt(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).cbrt() + })) + } -/// Get lowest integer above a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.ceil -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil -pub fn ceil(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).ceil() - })) -} + /// Get lowest integer above a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.ceil + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil + pub(crate) fn ceil(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).ceil() + })) + } -/// Get the cosine of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.cos -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cos -pub fn cos(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).cos() - })) -} + /// Get the cosine of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.cos + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cos + pub(crate) fn cos(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).cos() + })) + } -/// Get the hyperbolic cosine of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.cosh -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cosh -pub fn cosh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).cosh() - })) -} + /// Get the hyperbolic cosine of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.cosh + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cosh + pub(crate) fn cosh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).cosh() + })) + } -/// Get the power to raise the natural logarithm to get the number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.exp -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/exp -pub fn exp(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).exp() - })) -} + /// Get the power to raise the natural logarithm to get the number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.exp + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/exp + pub(crate) fn exp(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).exp() + })) + } -/// Get the highest integer below a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.floor -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor -pub fn floor(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).floor() - })) -} + /// Get the highest integer below a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.floor + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor + pub(crate) fn floor(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).floor() + })) + } -/// Get the natural logarithm of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.log -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log -pub fn log(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - let value = f64::from(args.get(0).expect("Could not get argument")); - - if value <= 0.0 { + /// Get the natural logarithm of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.log + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log + pub(crate) fn log(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { f64::NAN } else { - value.log(f64::consts::E) - } - })) -} + let value = f64::from(args.get(0).expect("Could not get argument")); + + if value <= 0.0 { + f64::NAN + } else { + value.log(f64::consts::E) + } + })) + } -/// Get the base 10 logarithm of the number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.log10 -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log10 -pub fn log10(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - let value = f64::from(args.get(0).expect("Could not get argument")); - - if value <= 0.0 { + /// Get the base 10 logarithm of the number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.log10 + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log10 + pub(crate) fn log10(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { f64::NAN } else { - value.log10() - } - })) -} + let value = f64::from(args.get(0).expect("Could not get argument")); + + if value <= 0.0 { + f64::NAN + } else { + value.log10() + } + })) + } -/// Get the base 2 logarithm of the number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.log2 -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2 -pub fn log2(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - let value = f64::from(args.get(0).expect("Could not get argument")); - - if value <= 0.0 { + /// Get the base 2 logarithm of the number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.log2 + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2 + pub(crate) fn log2(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { f64::NAN } else { - value.log2() + let value = f64::from(args.get(0).expect("Could not get argument")); + + if value <= 0.0 { + f64::NAN + } else { + value.log2() + } + })) + } + + /// Get the maximum of several numbers. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.max + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max + pub(crate) fn max(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + let mut max = f64::NEG_INFINITY; + for arg in args { + let num = f64::from(arg); + max = max.max(num); } - })) -} + Ok(Value::from(max)) + } -/// Get the maximum of several numbers. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.max -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max -pub fn max(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let mut max = f64::NEG_INFINITY; - for arg in args { - let num = f64::from(arg); - max = max.max(num); + /// Get the minimum of several numbers. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.min + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min + pub(crate) fn min(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + let mut max = f64::INFINITY; + for arg in args { + let num = f64::from(arg); + max = max.min(num); + } + Ok(Value::from(max)) } - Ok(Value::from(max)) -} -/// Get the minimum of several numbers. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.min -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min -pub fn min(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let mut max = f64::INFINITY; - for arg in args { - let num = f64::from(arg); - max = max.min(num); + /// Raise a number to a power. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.pow + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow + pub(crate) fn pow(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.len() >= 2 { + let num = f64::from(args.get(0).expect("Could not get argument")); + let power = f64::from(args.get(1).expect("Could not get argument")); + num.powf(power) + } else { + f64::NAN + })) } - Ok(Value::from(max)) -} -/// Raise a number to a power. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.pow -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow -pub fn pow(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.len() >= 2 { - let num = f64::from(args.get(0).expect("Could not get argument")); - let power = f64::from(args.get(1).expect("Could not get argument")); - num.powf(power) - } else { - f64::NAN - })) -} + /// Generate a random floating-point number between `0` and `1`. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.random + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random + pub(crate) fn random(_: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(rand::random::())) + } -/// Generate a random floating-point number between `0` and `1`. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.random -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random -pub fn _random(_: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(random::())) -} + /// Round a number to the nearest integer. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.round + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round + pub(crate) fn round(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).round() + })) + } -/// Round a number to the nearest integer. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.round -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round -pub fn round(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).round() - })) -} + /// Get the sign of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.sign + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign + pub(crate) fn sign(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + let value = f64::from(args.get(0).expect("Could not get argument")); + + if value == 0.0 || value == -0.0 { + value + } else { + value.signum() + } + })) + } -/// Get the sign of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.sign -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign -pub fn sign(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - let value = f64::from(args.get(0).expect("Could not get argument")); - - if value == 0.0 || value == -0.0 { - value + /// Get the sine of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.sin + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sin + pub(crate) fn sin(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN } else { - value.signum() - } - })) -} + f64::from(args.get(0).expect("Could not get argument")).sin() + })) + } -/// Get the sine of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.sin -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sin -pub fn sin(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).sin() - })) -} + /// Get the hyperbolic sine of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.sinh + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sinh + pub(crate) fn sinh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).sinh() + })) + } -/// Get the hyperbolic sine of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.sinh -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sinh -pub fn sinh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).sinh() - })) -} + /// Get the square root of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.sqrt + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sqrt + pub(crate) fn sqrt(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).sqrt() + })) + } + /// Get the tangent of a number + pub(crate) fn tan(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).tan() + })) + } -/// Get the square root of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.sqrt -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sqrt -pub fn sqrt(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).sqrt() - })) -} -/// Get the tangent of a number -pub fn tan(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).tan() - })) -} + /// Get the hyperbolic tangent of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.tanh + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tanh + pub(crate) fn tanh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).tanh() + })) + } -/// Get the hyperbolic tangent of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.tanh -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tanh -pub fn tanh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).tanh() - })) -} + /// Get the integer part of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.trunc + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc + pub(crate) fn trunc(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).trunc() + })) + } -/// Get the integer part of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.trunc -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc -pub fn trunc(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).trunc() - })) -} + /// Create a new `Math` object + pub(crate) fn create(global: &Value) -> Value { + let _timer = BoaProfiler::global().start_event("math:create", "init"); + let math = Value::new_object(Some(global)); + + { + let mut properties = math.as_object_mut().unwrap(); + properties.insert_field("E", Value::from(f64::consts::E)); + properties.insert_field("LN2", Value::from(f64::consts::LN_2)); + properties.insert_field("LN10", Value::from(f64::consts::LN_10)); + properties.insert_field("LOG2E", Value::from(f64::consts::LOG2_E)); + properties.insert_field("LOG10E", Value::from(f64::consts::LOG10_E)); + properties.insert_field("SQRT1_2", Value::from(0.5_f64.sqrt())); + properties.insert_field("SQRT2", Value::from(f64::consts::SQRT_2)); + properties.insert_field("PI", Value::from(f64::consts::PI)); + } + make_builtin_fn(Self::abs, "abs", &math, 1); + make_builtin_fn(Self::acos, "acos", &math, 1); + make_builtin_fn(Self::acosh, "acosh", &math, 1); + make_builtin_fn(Self::asin, "asin", &math, 1); + make_builtin_fn(Self::asinh, "asinh", &math, 1); + make_builtin_fn(Self::atan, "atan", &math, 1); + make_builtin_fn(Self::atanh, "atanh", &math, 1); + make_builtin_fn(Self::atan2, "atan2", &math, 2); + make_builtin_fn(Self::cbrt, "cbrt", &math, 1); + make_builtin_fn(Self::ceil, "ceil", &math, 1); + make_builtin_fn(Self::cos, "cos", &math, 1); + make_builtin_fn(Self::cosh, "cosh", &math, 1); + make_builtin_fn(Self::exp, "exp", &math, 1); + make_builtin_fn(Self::floor, "floor", &math, 1); + make_builtin_fn(Self::log, "log", &math, 1); + make_builtin_fn(Self::log10, "log10", &math, 1); + make_builtin_fn(Self::log2, "log2", &math, 1); + make_builtin_fn(Self::max, "max", &math, 2); + make_builtin_fn(Self::min, "min", &math, 2); + make_builtin_fn(Self::pow, "pow", &math, 2); + make_builtin_fn(Self::random, "random", &math, 0); + make_builtin_fn(Self::round, "round", &math, 1); + make_builtin_fn(Self::sign, "sign", &math, 1); + make_builtin_fn(Self::sin, "sin", &math, 1); + make_builtin_fn(Self::sinh, "sinh", &math, 1); + make_builtin_fn(Self::sqrt, "sqrt", &math, 1); + make_builtin_fn(Self::tan, "tan", &math, 1); + make_builtin_fn(Self::tanh, "tanh", &math, 1); + make_builtin_fn(Self::trunc, "trunc", &math, 1); + + math + } -/// Create a new `Math` object -pub fn create(global: &Value) -> Value { - let _timer = BoaProfiler::global().start_event("math:create", "init"); - let math = Value::new_object(Some(global)); - - math.set_field("E", Value::from(f64::consts::E)); - math.set_field("LN2", Value::from(f64::consts::LN_2)); - math.set_field("LN10", Value::from(f64::consts::LN_10)); - math.set_field("LOG2E", Value::from(f64::consts::LOG2_E)); - math.set_field("LOG10E", Value::from(f64::consts::LOG10_E)); - math.set_field("SQRT1_2", Value::from(0.5_f64.sqrt())); - math.set_field("SQRT2", Value::from(f64::consts::SQRT_2)); - math.set_field("PI", Value::from(f64::consts::PI)); - make_builtin_fn(abs, "abs", &math, 1); - make_builtin_fn(acos, "acos", &math, 1); - make_builtin_fn(acosh, "acosh", &math, 1); - make_builtin_fn(asin, "asin", &math, 1); - make_builtin_fn(asinh, "asinh", &math, 1); - make_builtin_fn(atan, "atan", &math, 1); - make_builtin_fn(atanh, "atanh", &math, 1); - make_builtin_fn(atan2, "atan2", &math, 2); - make_builtin_fn(cbrt, "cbrt", &math, 1); - make_builtin_fn(ceil, "ceil", &math, 1); - make_builtin_fn(cos, "cos", &math, 1); - make_builtin_fn(cosh, "cosh", &math, 1); - make_builtin_fn(exp, "exp", &math, 1); - make_builtin_fn(floor, "floor", &math, 1); - make_builtin_fn(log, "log", &math, 1); - make_builtin_fn(log10, "log10", &math, 1); - make_builtin_fn(log2, "log2", &math, 1); - make_builtin_fn(max, "max", &math, 2); - make_builtin_fn(min, "min", &math, 2); - make_builtin_fn(pow, "pow", &math, 2); - make_builtin_fn(_random, "random", &math, 0); - make_builtin_fn(round, "round", &math, 1); - make_builtin_fn(sign, "sign", &math, 1); - make_builtin_fn(sin, "sin", &math, 1); - make_builtin_fn(sinh, "sinh", &math, 1); - make_builtin_fn(sqrt, "sqrt", &math, 1); - make_builtin_fn(tan, "tan", &math, 1); - make_builtin_fn(tanh, "tanh", &math, 1); - make_builtin_fn(trunc, "trunc", &math, 1); - - math -} + /// Initialise the `Math` object on the global object. + #[inline] + pub(crate) fn init(global: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); -/// Initialise the `Math` object on the global object. -#[inline] -pub fn init(global: &Value) { - let _timer = BoaProfiler::global().start_event("math", "init"); - global.set_field("Math", create(global)); + (Self::NAME, Self::create(global)) + } } diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index d61d9c6909..e0810a4374 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -23,31 +23,45 @@ pub(crate) use self::{ bigint::BigInt, boolean::Boolean, error::{Error, RangeError, TypeError}, - function::Function, + global_this::GlobalThis, + json::Json, + math::Math, + nan::NaN, number::Number, regexp::RegExp, string::String, + symbol::Symbol, value::{ResultValue, Value}, }; /// Initializes builtin objects and functions #[inline] pub fn init(global: &Value) { - Array::init(global); - BigInt::init(global); - Boolean::init(global); - global_this::init(global); - json::init(global); - math::init(global); - nan::init(global); - Number::init(global); - object::init(global); - function::init(global); - RegExp::init(global); - String::init(global); - symbol::init(global); - console::init(global); - Error::init(global); - RangeError::init(global); - TypeError::init(global); + let globals = vec![ + // The `Function` global must be initialized before other types. + function::init(global), + Array::init(global), + BigInt::init(global), + Boolean::init(global), + Json::init(global), + Math::init(global), + Number::init(global), + object::init(global), + RegExp::init(global), + String::init(global), + Symbol::init(global), + console::init(global), + // Global error types. + Error::init(global), + RangeError::init(global), + TypeError::init(global), + // Global properties. + NaN::init(global), + GlobalThis::init(global), + ]; + + let mut global_object = global.as_object_mut().expect("global object"); + for (name, value) in globals { + global_object.insert_field(name, value); + } } diff --git a/boa/src/builtins/nan/mod.rs b/boa/src/builtins/nan/mod.rs index 41ddfcca82..a87e17c403 100644 --- a/boa/src/builtins/nan/mod.rs +++ b/boa/src/builtins/nan/mod.rs @@ -1,11 +1,33 @@ +//! This module implements the global `NaN` property. +//! +//! The global `NaN` is a property of the global object. In other words, +//! it is a variable in global scope. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript reference][spec] +//! +//! [spec]: https://tc39.es/ecma262/#sec-value-properties-of-the-global-object-nan +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN + #[cfg(test)] mod tests; use crate::{builtins::value::Value, BoaProfiler}; -/// Initialize the `NaN` property on the global object. -#[inline] -pub fn init(global: &Value) { - let _timer = BoaProfiler::global().start_event("NaN", "init"); - global.set_field("NaN", Value::from(f64::NAN)); +/// JavaScript global `NaN` property. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct NaN; + +impl NaN { + /// The binding name of the property. + pub(crate) const NAME: &'static str = "NaN"; + + /// Initialize the `NaN` property on the global object. + #[inline] + pub(crate) fn init(_: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + (Self::NAME, Value::from(f64::NAN)) + } } diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index b2cd26b5f7..c06b22b7c6 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -18,18 +18,14 @@ mod tests; use super::{ function::{make_builtin_fn, make_constructor_fn}, - object::ObjectKind, + object::ObjectData, }; use crate::{ - builtins::{ - object::internal_methods_trait::ObjectInternalMethods, - value::{ResultValue, Value, ValueData}, - }, + builtins::value::{ResultValue, Value, ValueData}, exec::Interpreter, BoaProfiler, }; use num_traits::float::FloatCore; -use std::{borrow::Borrow, ops::Deref}; const BUF_SIZE: usize = 2200; @@ -44,28 +40,35 @@ const PARSE_INT_MAX_ARG_COUNT: usize = 2; const PARSE_FLOAT_MAX_ARG_COUNT: usize = 1; impl Number { - /// Helper function that converts a Value to a Number. - #[allow(clippy::wrong_self_convention)] - fn to_number(value: &Value) -> Value { - match *value.deref().borrow() { - ValueData::Boolean(b) => { - if b { - Value::from(1) - } else { - Value::from(0) + /// The name of the object. + pub(crate) const NAME: &'static str = "Number"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: usize = 1; + + /// This function returns a `Result` of the number `Value`. + /// + /// If the `Value` is a `Number` primitive of `Number` object the number is returned. + /// Otherwise an `TypeError` is thrown. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-thisnumbervalue + fn this_number_value(value: &Value, ctx: &mut Interpreter) -> Result { + match *value.data() { + ValueData::Integer(integer) => return Ok(f64::from(integer)), + ValueData::Rational(rational) => return Ok(rational), + ValueData::Object(ref object) => { + if let Some(number) = object.borrow().as_number() { + return Ok(number); } } - ValueData::Symbol(_) | ValueData::Undefined => Value::from(f64::NAN), - ValueData::Integer(i) => Value::from(f64::from(i)), - 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::() { - Ok(n) => Value::from(n), - Err(_) => Value::from(f64::NAN), - }, + _ => {} } + + Err(ctx.construct_type_error("'this' is not a number")) } /// Helper function that formats a float as a ES6-style exponential number string. @@ -83,16 +86,15 @@ impl Number { pub(crate) fn make_number( this: &mut Value, args: &[Value], - _ctx: &mut Interpreter, + ctx: &mut Interpreter, ) -> ResultValue { let data = match args.get(0) { - Some(ref value) => Self::to_number(value), - None => Self::to_number(&Value::from(0)), + Some(ref value) => ctx.to_numeric_number(value)?, + None => 0.0, }; - this.set_kind(ObjectKind::Number); - this.set_internal_slot("NumberData", data.clone()); + this.set_data(ObjectData::Number(data)); - Ok(data) + Ok(Value::from(data)) } /// `Number.prototype.toExponential( [fractionDigits] )` @@ -109,9 +111,9 @@ impl Number { pub(crate) fn to_exponential( this: &mut Value, _args: &[Value], - _ctx: &mut Interpreter, + ctx: &mut Interpreter, ) -> ResultValue { - let this_num = Self::to_number(this).to_number(); + let this_num = Self::this_number_value(this, ctx)?; let this_str_num = Self::num_to_exponential(this_num); Ok(Value::from(this_str_num)) } @@ -127,12 +129,8 @@ impl Number { /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tofixed /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_fixed( - this: &mut Value, - args: &[Value], - _ctx: &mut Interpreter, - ) -> ResultValue { - let this_num = Self::to_number(this).to_number(); + pub(crate) fn to_fixed(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let this_num = Self::this_number_value(this, ctx)?; let precision = match args.get(0) { Some(n) => match n.to_integer() { x if x > 0 => n.to_integer() as usize, @@ -161,9 +159,9 @@ impl Number { pub(crate) fn to_locale_string( this: &mut Value, _args: &[Value], - _ctx: &mut Interpreter, + ctx: &mut Interpreter, ) -> ResultValue { - let this_num = Self::to_number(this).to_number(); + let this_num = Self::this_number_value(this, ctx)?; let this_str_num = format!("{}", this_num); Ok(Value::from(this_str_num)) } @@ -182,10 +180,10 @@ impl Number { pub(crate) fn to_precision( this: &mut Value, args: &[Value], - _ctx: &mut Interpreter, + ctx: &mut Interpreter, ) -> ResultValue { - let this_num = Self::to_number(this); - let _num_str_len = format!("{}", this_num.to_number()).len(); + let this_num = Self::this_number_value(this, ctx)?; + let _num_str_len = format!("{}", this_num).len(); let _precision = match args.get(0) { Some(n) => match n.to_integer() { x if x > 0 => n.to_integer() as usize, @@ -353,7 +351,8 @@ impl Number { ctx: &mut Interpreter, ) -> ResultValue { // 1. Let x be ? thisNumberValue(this value). - let x = Self::to_number(this).to_number(); + let x = Self::this_number_value(this, ctx)?; + // 2. If radix is undefined, let radixNumber be 10. // 3. Else, let radixNumber be ? ToInteger(radix). let radix = args.get(0).map_or(10, |arg| arg.to_integer()) as u8; @@ -406,9 +405,9 @@ impl Number { pub(crate) fn value_of( this: &mut Value, _args: &[Value], - _ctx: &mut Interpreter, + ctx: &mut Interpreter, ) -> ResultValue { - Ok(Self::to_number(this)) + Ok(Value::from(Self::this_number_value(this, ctx)?)) } /// Builtin javascript 'parseInt(str, radix)' function. @@ -530,7 +529,6 @@ impl Number { /// Create a new `Number` object pub(crate) fn create(global: &Value) -> Value { let prototype = Value::new_object(Some(global)); - prototype.set_internal_slot("NumberData", Value::from(0)); make_builtin_fn(Self::to_exponential, "toExponential", &prototype, 1); make_builtin_fn(Self::to_fixed, "toFixed", &prototype, 1); @@ -539,40 +537,46 @@ impl Number { make_builtin_fn(Self::to_string, "toString", &prototype, 1); make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); - make_builtin_fn( - Self::parse_int, - "parseInt", - global, - PARSE_INT_MAX_ARG_COUNT as i32, - ); + make_builtin_fn(Self::parse_int, "parseInt", global, PARSE_INT_MAX_ARG_COUNT); make_builtin_fn( Self::parse_float, "parseFloat", global, - PARSE_FLOAT_MAX_ARG_COUNT as i32, + PARSE_FLOAT_MAX_ARG_COUNT, ); - let number = make_constructor_fn("Number", 1, Self::make_number, global, prototype, true); + let number = make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::make_number, + global, + prototype, + true, + ); // Constants from: // https://tc39.es/ecma262/#sec-properties-of-the-number-constructor - number.set_field("EPSILON", Value::from(f64::EPSILON)); - number.set_field("MAX_SAFE_INTEGER", Value::from(9_007_199_254_740_991_f64)); - number.set_field("MIN_SAFE_INTEGER", Value::from(-9_007_199_254_740_991_f64)); - number.set_field("MAX_VALUE", Value::from(f64::MAX)); - number.set_field("MIN_VALUE", Value::from(f64::MIN)); - number.set_field("NEGATIVE_INFINITY", Value::from(f64::NEG_INFINITY)); - number.set_field("POSITIVE_INFINITY", Value::from(f64::INFINITY)); - number.set_field("NaN", Value::from(f64::NAN)); + { + let mut properties = number.as_object_mut().expect("'Number' object"); + properties.insert_field("EPSILON", Value::from(f64::EPSILON)); + properties.insert_field("MAX_SAFE_INTEGER", Value::from(9_007_199_254_740_991_f64)); + properties.insert_field("MIN_SAFE_INTEGER", Value::from(-9_007_199_254_740_991_f64)); + properties.insert_field("MAX_VALUE", Value::from(f64::MAX)); + properties.insert_field("MIN_VALUE", Value::from(f64::MIN)); + properties.insert_field("NEGATIVE_INFINITY", Value::from(f64::NEG_INFINITY)); + properties.insert_field("POSITIVE_INFINITY", Value::from(f64::INFINITY)); + properties.insert_field("NaN", Value::from(f64::NAN)); + } number } /// Initialise the `Number` object on the global object. #[inline] - pub(crate) fn init(global: &Value) { - let _timer = BoaProfiler::global().start_event("number", "init"); - global.set_field("Number", Self::create(global)); + pub(crate) fn init(global: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + (Self::NAME, Self::create(global)) } /// The abstract operation Number::equal takes arguments diff --git a/boa/src/builtins/number/tests.rs b/boa/src/builtins/number/tests.rs index 63734b986e..2696ee2f26 100644 --- a/boa/src/builtins/number/tests.rs +++ b/boa/src/builtins/number/tests.rs @@ -82,12 +82,12 @@ fn to_exponential() { let nan_exp = forward(&mut engine, "nan_exp"); let noop_exp = forward(&mut engine, "noop_exp"); - assert_eq!(default_exp, String::from("0e+0")); - assert_eq!(int_exp, String::from("5e+0")); - assert_eq!(float_exp, String::from("1.234e+0")); - assert_eq!(big_exp, String::from("1.234e+3")); - assert_eq!(nan_exp, String::from("NaN")); - assert_eq!(noop_exp, String::from("1.23e+2")); + assert_eq!(default_exp, "0e+0"); + assert_eq!(int_exp, "5e+0"); + assert_eq!(float_exp, "1.234e+0"); + assert_eq!(big_exp, "1.234e+3"); + assert_eq!(nan_exp, "NaN"); + assert_eq!(noop_exp, "1.23e+2"); } #[test] diff --git a/boa/src/builtins/object/internal_methods_trait.rs b/boa/src/builtins/object/internal_methods.rs similarity index 55% rename from boa/src/builtins/object/internal_methods_trait.rs rename to boa/src/builtins/object/internal_methods.rs index 47b21dd282..996fddc90a 100644 --- a/boa/src/builtins/object/internal_methods_trait.rs +++ b/boa/src/builtins/object/internal_methods.rs @@ -1,38 +1,27 @@ -//! This module defines the `ObjectInternalMethods` trait. +//! This module defines the object internal methods. //! //! More information: //! - [ECMAScript reference][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots -use crate::{ - builtins::{ - object::{Object, INSTANCE_PROTOTYPE}, - property::Property, - value::{same_value, Value, ValueData}, - }, - BoaProfiler, +use crate::builtins::{ + object::{Object, INSTANCE_PROTOTYPE, PROTOTYPE}, + property::Property, + value::{same_value, Value, ValueData}, }; +use crate::BoaProfiler; use std::borrow::Borrow; use std::ops::Deref; -/// Here lies the internal methods for ordinary objects. -/// -/// Most objects make use of these methods, including exotic objects like functions. -/// So thats why this is a trait -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots -pub trait ObjectInternalMethods { - /// Check if has property. +impl Object { + /// Check if object has property. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p - fn has_property(&self, val: &Value) -> bool { + pub fn has_property(&self, val: &Value) -> bool { debug_assert!(Property::is_property_key(val)); let prop = self.get_own_property(val); if prop.value.is_none() { @@ -57,7 +46,8 @@ pub trait ObjectInternalMethods { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible - fn is_extensible(&self) -> bool { + #[inline] + pub fn is_extensible(&self) -> bool { let val = self.get_internal_slot("extensible"); match *val.deref().borrow() { ValueData::Boolean(b) => b, @@ -71,13 +61,14 @@ pub trait ObjectInternalMethods { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions - fn prevent_extensions(&mut self) -> bool { + #[inline] + pub fn prevent_extensions(&mut self) -> bool { self.set_internal_slot("extensible", Value::from(false)); true } /// Delete property. - fn delete(&mut self, prop_key: &Value) -> bool { + pub fn delete(&mut self, prop_key: &Value) -> bool { debug_assert!(Property::is_property_key(prop_key)); let desc = self.get_own_property(prop_key); if desc @@ -97,7 +88,7 @@ pub trait ObjectInternalMethods { } // [[Get]] - fn get(&self, val: &Value) -> Value { + pub fn get(&self, val: &Value) -> Value { debug_assert!(Property::is_property_key(val)); let desc = self.get_own_property(val); if desc.value.clone().is_none() @@ -127,13 +118,13 @@ pub trait ObjectInternalMethods { return Value::undefined(); } - // TODO!!!!! Call getter from here + // TODO: Call getter from here! Value::undefined() } /// [[Set]] /// - fn set(&mut self, field: Value, val: Value) -> bool { + pub fn set(&mut self, field: Value, val: Value) -> bool { let _timer = BoaProfiler::global().start_event("Object::set", "object"); // [1] debug_assert!(Property::is_property_key(&field)); @@ -171,8 +162,15 @@ pub trait ObjectInternalMethods { } } - fn define_own_property(&mut self, property_key: String, desc: Property) -> bool { + /// Define an own property. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc + pub fn define_own_property(&mut self, property_key: String, desc: Property) -> bool { let _timer = BoaProfiler::global().start_event("Object::define_own_property", "object"); + let mut current = self.get_own_property(&Value::from(property_key.to_string())); let extensible = self.is_extensible(); @@ -282,25 +280,164 @@ pub trait ObjectInternalMethods { true } - /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p - /// The specification returns a Property Descriptor or Undefined. These are 2 separate types and we can't do that here. - fn get_own_property(&self, prop: &Value) -> Property; + /// The specification returns a Property Descriptor or Undefined. + /// + /// These are 2 separate types and we can't do that here. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p + pub fn get_own_property(&self, prop: &Value) -> Property { + let _timer = BoaProfiler::global().start_event("Object::get_own_property", "object"); + + debug_assert!(Property::is_property_key(prop)); + // Prop could either be a String or Symbol + match *(*prop) { + ValueData::String(ref st) => { + self.properties() + .get(st) + .map_or_else(Property::default, |v| { + let mut d = Property::default(); + if v.is_data_descriptor() { + d.value = v.value.clone(); + d.writable = v.writable; + } else { + debug_assert!(v.is_accessor_descriptor()); + d.get = v.get.clone(); + d.set = v.set.clone(); + } + d.enumerable = v.enumerable; + d.configurable = v.configurable; + d + }) + } + ValueData::Symbol(ref symbol) => self + .symbol_properties() + .get(&symbol.hash()) + .map_or_else(Property::default, |v| { + let mut d = Property::default(); + if v.is_data_descriptor() { + d.value = v.value.clone(); + d.writable = v.writable; + } else { + debug_assert!(v.is_accessor_descriptor()); + d.get = v.get.clone(); + d.set = v.set.clone(); + } + d.enumerable = v.enumerable; + d.configurable = v.configurable; + d + }), + _ => Property::default(), + } + } - /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v - fn set_prototype_of(&mut self, val: Value) -> bool; + /// `Object.setPropertyOf(obj, prototype)` + /// + /// This method sets the prototype (i.e., the internal `[[Prototype]]` property) + /// of a specified object to another object or `null`. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf + pub fn set_prototype_of(&mut self, val: Value) -> bool { + debug_assert!(val.is_object() || val.is_null()); + let current = self.get_internal_slot(PROTOTYPE); + if same_value(¤t, &val, false) { + return true; + } + let extensible = self.get_internal_slot("extensible"); + if extensible.is_null() { + return false; + } + let mut p = val.clone(); + let mut done = false; + while !done { + if p.is_null() { + done = true + } else if same_value(&Value::from(self.clone()), &p, false) { + return false; + } else { + p = p.get_internal_slot(PROTOTYPE); + } + } + self.set_internal_slot(PROTOTYPE, val); + true + } /// Returns either the prototype or null - /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof - fn get_prototype_of(&self) -> Value { + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof + #[inline] + pub fn get_prototype_of(&self) -> Value { self.get_internal_slot(INSTANCE_PROTOTYPE) } - /// Utility function to get an immutable internal slot or Null - fn get_internal_slot(&self, name: &str) -> Value; + /// Helper function to get an immutable internal slot or `Null`. + #[inline] + pub fn get_internal_slot(&self, name: &str) -> Value { + let _timer = BoaProfiler::global().start_event("Object::get_internal_slot", "object"); + + self.internal_slots() + .get(name) + .cloned() + .unwrap_or_else(Value::null) + } + + /// Helper function to set an internal slot. + #[inline] + pub fn set_internal_slot(&mut self, name: &str, val: Value) { + self.internal_slots.insert(name.to_string(), val); + } - fn set_internal_slot(&mut self, name: &str, val: Value); + /// Helper function for property insertion. + #[inline] + pub(crate) fn insert_property(&mut self, name: N, p: Property) + where + N: Into, + { + self.properties.insert(name.into(), p); + } - fn insert_property(&mut self, name: String, p: Property); + /// Helper function for property removal. + #[inline] + pub(crate) fn remove_property(&mut self, name: &str) { + self.properties.remove(name); + } - fn remove_property(&mut self, name: &str); + /// Inserts a field in the object `properties` without checking if it's writable. + /// + /// If a field was already in the object with the same name that a `Some` is returned + /// with that field, otherwise None is retuned. + #[inline] + pub(crate) fn insert_field(&mut self, name: N, value: Value) -> Option + where + N: Into, + { + self.properties.insert( + name.into(), + Property::default() + .value(value) + .writable(true) + .configurable(true) + .enumerable(true), + ) + } + + /// This function returns an Optional reference value to the objects field. + /// + /// if it exist `Some` is returned with a reference to that fields value. + /// Otherwise `None` is retuned. + #[inline] + pub fn get_field(&self, name: &str) -> Option<&Value> { + self.properties.get(name).and_then(|x| x.value.as_ref()) + } } diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index c367fdab63..0a7468f4e7 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -17,24 +17,23 @@ use crate::{ builtins::{ function::Function, property::Property, - value::{same_value, ResultValue, Value, ValueData}, + value::{ResultValue, Value, ValueData}, + BigInt, Symbol, }, exec::Interpreter, BoaProfiler, }; -use gc::{unsafe_empty_trace, Finalize, Trace}; +use gc::{Finalize, Trace}; use rustc_hash::FxHashMap; use std::{ - borrow::Borrow, - fmt::{self, Debug, Display, Error, Formatter}, + fmt::{Debug, Display, Error, Formatter}, ops::Deref, }; use super::function::{make_builtin_fn, make_constructor_fn}; -pub use internal_methods_trait::ObjectInternalMethods; pub use internal_state::{InternalState, InternalStateCell}; -pub mod internal_methods_trait; +pub mod internal_methods; mod internal_state; #[cfg(test)] @@ -47,322 +46,87 @@ pub static PROTOTYPE: &str = "prototype"; pub static INSTANCE_PROTOTYPE: &str = "__proto__"; /// The internal representation of an JavaScript object. -#[derive(Trace, Finalize, Clone)] +#[derive(Debug, Trace, Finalize, Clone)] pub struct Object { /// The type of the object. - pub kind: ObjectKind, + pub data: ObjectData, /// Internal Slots - pub internal_slots: FxHashMap, + internal_slots: FxHashMap, /// Properties - pub properties: FxHashMap, + properties: FxHashMap, /// Symbol Properties - pub sym_properties: FxHashMap, + symbol_properties: FxHashMap, /// Some rust object that stores internal state - pub state: Option, - /// Function - pub func: Option, + state: Option, } -impl Debug for Object { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - writeln!(f, "{{")?; - writeln!(f, "\tkind: {}", self.kind)?; - writeln!(f, "\tstate: {:?}", self.state)?; - writeln!(f, "\tfunc: {:?}", self.func)?; - writeln!(f, "\tproperties: {{")?; - for (key, _) in self.properties.iter() { - writeln!(f, "\t\t{}", key)?; - } - writeln!(f, "\t }}")?; - write!(f, "}}") - } +/// Defines the different types of objects. +#[derive(Debug, Trace, Finalize, Clone)] +pub enum ObjectData { + Array, + BigInt(BigInt), + Boolean(bool), + Function(Function), + String(String), + Number(f64), + Symbol(Symbol), + Error, + Ordinary, } -impl ObjectInternalMethods for Object { - /// `Object.setPropertyOf(obj, prototype)` - /// - /// This method sets the prototype (i.e., the internal `[[Prototype]]` property) - /// of a specified object to another object or `null`. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf - fn set_prototype_of(&mut self, val: Value) -> bool { - debug_assert!(val.is_object() || val.is_null()); - let current = self.get_internal_slot(PROTOTYPE); - if same_value(¤t, &val, false) { - return true; - } - let extensible = self.get_internal_slot("extensible"); - if extensible.is_null() { - return false; - } - let mut p = val.clone(); - let mut done = false; - while !done { - if p.is_null() { - done = true - } else if same_value(&Value::from(self.clone()), &p, false) { - return false; - } else { - p = p.get_internal_slot(PROTOTYPE); - } - } - self.set_internal_slot(PROTOTYPE, val); - true - } - - /// Helper function for property insertion. - fn insert_property(&mut self, name: String, p: Property) { - self.properties.insert(name, p); - } - - /// Helper function for property removal. - fn remove_property(&mut self, name: &str) { - self.properties.remove(name); - } - - /// Helper function to set an internal slot - fn set_internal_slot(&mut self, name: &str, val: Value) { - self.internal_slots.insert(name.to_string(), val); - } - - /// Helper function to get an immutable internal slot or Null - fn get_internal_slot(&self, name: &str) -> Value { - let _timer = BoaProfiler::global().start_event("Object::get_internal_slot", "object"); - match self.internal_slots.get(name) { - Some(v) => v.clone(), - None => Value::null(), - } - } - - /// The specification returns a Property Descriptor or Undefined. - /// - /// These are 2 separate types and we can't do that here. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p - fn get_own_property(&self, prop: &Value) -> Property { - let _timer = BoaProfiler::global().start_event("Object::get_own_property", "object"); - debug_assert!(Property::is_property_key(prop)); - // Prop could either be a String or Symbol - match *(*prop) { - ValueData::String(ref st) => { - match self.properties.get(st) { - // If O does not have an own property with key P, return undefined. - // In this case we return a new empty Property - None => Property::default(), - Some(ref v) => { - let mut d = Property::default(); - if v.is_data_descriptor() { - d.value = v.value.clone(); - d.writable = v.writable; - } else { - debug_assert!(v.is_accessor_descriptor()); - d.get = v.get.clone(); - d.set = v.set.clone(); - } - d.enumerable = v.enumerable; - d.configurable = v.configurable; - d - } - } - } - ValueData::Symbol(ref sym) => { - let sym_id = (**sym) - .borrow() - .get_internal_slot("SymbolData") - .to_string() - .parse::() - .expect("Could not get Symbol ID"); - match self.sym_properties.get(&sym_id) { - // If O does not have an own property with key P, return undefined. - // In this case we return a new empty Property - None => Property::default(), - Some(ref v) => { - let mut d = Property::default(); - if v.is_data_descriptor() { - d.value = v.value.clone(); - d.writable = v.writable; - } else { - debug_assert!(v.is_accessor_descriptor()); - d.get = v.get.clone(); - d.set = v.set.clone(); - } - d.enumerable = v.enumerable; - d.configurable = v.configurable; - d - } - } - } - _ => Property::default(), - } - } - - /// Define an own property. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc - #[allow(clippy::option_unwrap_used)] - fn define_own_property(&mut self, property_key: String, desc: Property) -> bool { - let mut current = self.get_own_property(&Value::from(property_key.to_string())); - let extensible = self.is_extensible(); - - // https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor - // There currently isn't a property, lets create a new one - if current.value.is_none() || current.value.as_ref().expect("failed").is_undefined() { - if !extensible { - return false; - } - if desc.value.is_some() && desc.value.clone().unwrap().is_symbol() { - let sym_id = desc - .value - .clone() - .unwrap() - .to_string() - .parse::() - .expect("parsing failed"); - self.sym_properties.insert(sym_id, desc); - } else { - self.properties.insert(property_key, desc); - } - return true; - } - // If every field is absent we don't need to set anything - if desc.is_none() { - return true; - } - - // 4 - if !current.configurable.unwrap_or(false) { - if desc.configurable.is_some() && desc.configurable.unwrap() { - return false; - } - - if desc.enumerable.is_some() - && (desc.enumerable.as_ref().unwrap() != current.enumerable.as_ref().unwrap()) - { - return false; - } - } - - // 5 - if desc.is_generic_descriptor() { - // 6 - } else if current.is_data_descriptor() != desc.is_data_descriptor() { - // a - if !current.configurable.unwrap() { - return false; - } - // b - if current.is_data_descriptor() { - // Convert to accessor - current.value = None; - current.writable = None; - } else { - // c - // convert to data - current.get = None; - current.set = None; - } - - if current.value.is_some() && current.value.clone().unwrap().is_symbol() { - let sym_id = current - .value - .clone() - .unwrap() - .to_string() - .parse::() - .expect("parsing failed"); - self.sym_properties.insert(sym_id, current); - } else { - self.properties.insert(property_key.clone(), current); - } - // 7 - } else if current.is_data_descriptor() && desc.is_data_descriptor() { - // a - if !current.configurable.unwrap() && !current.writable.unwrap() { - if desc.writable.is_some() && desc.writable.unwrap() { - return false; - } - - if desc.value.is_some() - && !same_value( - &desc.value.clone().unwrap(), - ¤t.value.clone().unwrap(), - false, - ) - { - return false; - } - - return true; - } - // 8 - } else { - if !current.configurable.unwrap() { - if desc.set.is_some() - && !same_value( - &desc.set.clone().unwrap(), - ¤t.set.clone().unwrap(), - false, - ) - { - return false; - } - - if desc.get.is_some() - && !same_value( - &desc.get.clone().unwrap(), - ¤t.get.clone().unwrap(), - false, - ) - { - return false; - } +impl Display for ObjectData { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!( + f, + "{}", + match self { + Self::Function(_) => "Function", + Self::Array => "Array", + Self::String(_) => "String", + Self::Symbol(_) => "Symbol", + Self::Error => "Error", + Self::Ordinary => "Ordinary", + Self::Boolean(_) => "Boolean", + Self::Number(_) => "Number", + Self::BigInt(_) => "BigInt", } - - return true; - } - // 9 - self.properties.insert(property_key, desc); - true + ) } } -impl Object { +impl Default for Object { /// Return a new ObjectData struct, with `kind` set to Ordinary - pub fn default() -> Self { + #[inline] + fn default() -> Self { let mut object = Self { - kind: ObjectKind::Ordinary, + data: ObjectData::Ordinary, internal_slots: FxHashMap::default(), properties: FxHashMap::default(), - sym_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), state: None, - func: None, }; object.set_internal_slot("extensible", Value::from(true)); object } +} + +impl Object { + #[inline] + pub fn new() -> Self { + Default::default() + } /// Return a new ObjectData struct, with `kind` set to Ordinary - pub fn function() -> Self { + pub fn function(function: Function) -> Self { let _timer = BoaProfiler::global().start_event("Object::Function", "object"); + let mut object = Self { - kind: ObjectKind::Function, + data: ObjectData::Function(function), internal_slots: FxHashMap::default(), properties: FxHashMap::default(), - sym_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), state: None, - func: None, }; object.set_internal_slot("extensible", Value::from(true)); @@ -385,73 +149,48 @@ impl Object { obj } - /// Set the function this object wraps - pub fn set_func(&mut self, val: Function) { - self.func = Some(val); - } - /// Return a new Boolean object whose `[[BooleanData]]` internal slot is set to argument. - fn from_boolean(argument: &Value) -> Self { - let mut obj = Self { - kind: ObjectKind::Boolean, + pub fn boolean(value: bool) -> Self { + Self { + data: ObjectData::Boolean(value), internal_slots: FxHashMap::default(), properties: FxHashMap::default(), - sym_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), state: None, - func: None, - }; - - obj.internal_slots - .insert("BooleanData".to_string(), argument.clone()); - obj + } } /// Return a new `Number` object whose `[[NumberData]]` internal slot is set to argument. - fn from_number(argument: &Value) -> Self { - let mut obj = Self { - kind: ObjectKind::Number, + pub fn number(value: f64) -> Self { + Self { + data: ObjectData::Number(value), internal_slots: FxHashMap::default(), properties: FxHashMap::default(), - sym_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), state: None, - func: None, - }; - - obj.internal_slots - .insert("NumberData".to_string(), argument.clone()); - obj + } } /// Return a new `String` object whose `[[StringData]]` internal slot is set to argument. - fn from_string(argument: &Value) -> Self { - let mut obj = Self { - kind: ObjectKind::String, + pub fn string(value: String) -> Self { + Self { + data: ObjectData::String(value), internal_slots: FxHashMap::default(), properties: FxHashMap::default(), - sym_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), state: None, - func: None, - }; - - obj.internal_slots - .insert("StringData".to_string(), argument.clone()); - 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, + pub fn bigint(value: BigInt) -> Self { + Self { + data: ObjectData::BigInt(value), internal_slots: FxHashMap::default(), properties: FxHashMap::default(), - sym_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), state: None, - func: None, - }; - - obj.internal_slots - .insert("BigIntData".to_string(), argument.clone()); - obj + } } /// Converts the `Value` to an `Object` type. @@ -461,11 +200,12 @@ impl Object { /// /// [spec]: https://tc39.es/ecma262/#sec-toobject pub fn from(value: &Value) -> Result { - match *value.deref().borrow() { - 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)), + match *value.data() { + ValueData::Boolean(a) => Ok(Self::boolean(a)), + ValueData::Rational(a) => Ok(Self::number(a)), + ValueData::Integer(a) => Ok(Self::number(f64::from(a))), + ValueData::String(ref a) => Ok(Self::string(a.clone())), + ValueData::BigInt(ref bigint) => Ok(Self::bigint(bigint.clone())), ValueData::Object(ref obj) => Ok((*obj).deref().borrow().clone()), _ => Err(()), } @@ -477,11 +217,9 @@ impl Object { /// - [EcmaScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-iscallable + #[inline] pub fn is_callable(&self) -> bool { - match self.func { - Some(ref function) => function.is_callable(), - None => false, - } + matches!(self.data, ObjectData::Function(ref f) if f.is_callable()) } /// It determines if Object is a function object with a [[Construct]] internal method. @@ -490,58 +228,168 @@ impl Object { /// - [EcmaScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-isconstructor + #[inline] pub fn is_constructable(&self) -> bool { - match self.func { - Some(ref function) => function.is_constructable(), - None => false, + matches!(self.data, ObjectData::Function(ref f) if f.is_constructable()) + } + + /// Checks if it an `Array` object. + #[inline] + pub fn is_array(&self) -> bool { + matches!(self.data, ObjectData::Array) + } + + #[inline] + pub fn as_array(&self) -> Option<()> { + match self.data { + ObjectData::Array => Some(()), + _ => None, } } -} -/// Defines the different types of objects. -#[derive(Finalize, Debug, Copy, Clone, Eq, PartialEq)] -pub enum ObjectKind { - Function, - Array, - String, - Symbol, - Error, - Ordinary, - Boolean, - Number, - BigInt, -} + /// Checks if it a `String` object. + #[inline] + pub fn is_string(&self) -> bool { + matches!(self.data, ObjectData::String(_)) + } -impl Display for ObjectKind { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { - write!( - f, - "{}", - match self { - Self::Function => "Function", - Self::Array => "Array", - Self::String => "String", - Self::Symbol => "Symbol", - Self::Error => "Error", - Self::Ordinary => "Ordinary", - Self::Boolean => "Boolean", - Self::Number => "Number", - Self::BigInt => "BigInt", - } - ) + #[inline] + pub fn as_string(&self) -> Option<&str> { + match self.data { + ObjectData::String(ref string) => Some(string.as_str()), + _ => None, + } } -} -/// `Trace` implementation for `ObjectKind`. -/// -/// This is indeed safe, but we need to mark this as an empty trace because neither -// `NativeFunctionData` nor Node hold any GC'd objects, but Gc doesn't know that. So we need to -/// signal it manually. `rust-gc` does not have a `Trace` implementation for `fn(_, _, _)`. -/// -/// -/// Waiting on until we can derive Copy -unsafe impl Trace for ObjectKind { - unsafe_empty_trace!(); + /// Checks if it a `Function` object. + #[inline] + pub fn is_function(&self) -> bool { + matches!(self.data, ObjectData::Function(_)) + } + + #[inline] + pub fn as_function(&self) -> Option<&Function> { + match self.data { + ObjectData::Function(ref function) => Some(function), + _ => None, + } + } + + /// Checks if it a Symbol object. + #[inline] + pub fn is_symbol(&self) -> bool { + matches!(self.data, ObjectData::Symbol(_)) + } + + #[inline] + pub fn as_symbol(&self) -> Option<&Symbol> { + match self.data { + ObjectData::Symbol(ref symbol) => Some(symbol), + _ => None, + } + } + + /// Checks if it an Error object. + #[inline] + pub fn is_error(&self) -> bool { + matches!(self.data, ObjectData::Error) + } + + #[inline] + pub fn as_error(&self) -> Option<()> { + match self.data { + ObjectData::Error => Some(()), + _ => None, + } + } + + /// Checks if it a Boolean object. + #[inline] + pub fn is_boolean(&self) -> bool { + matches!(self.data, ObjectData::Boolean(_)) + } + + #[inline] + pub fn as_boolean(&self) -> Option { + match self.data { + ObjectData::Boolean(boolean) => Some(boolean), + _ => None, + } + } + + /// Checks if it a `Number` object. + #[inline] + pub fn is_number(&self) -> bool { + matches!(self.data, ObjectData::Number(_)) + } + + #[inline] + pub fn as_number(&self) -> Option { + match self.data { + ObjectData::Number(number) => Some(number), + _ => None, + } + } + + /// Checks if it a `BigInt` object. + #[inline] + pub fn is_bigint(&self) -> bool { + matches!(self.data, ObjectData::BigInt(_)) + } + + #[inline] + pub fn as_bigint(&self) -> Option<&BigInt> { + match self.data { + ObjectData::BigInt(ref bigint) => Some(bigint), + _ => None, + } + } + + /// Checks if it an ordinary object. + #[inline] + pub fn is_ordinary(&self) -> bool { + matches!(self.data, ObjectData::Ordinary) + } + + #[inline] + pub fn internal_slots(&self) -> &FxHashMap { + &self.internal_slots + } + + #[inline] + pub fn internal_slots_mut(&mut self) -> &mut FxHashMap { + &mut self.internal_slots + } + + #[inline] + pub fn properties(&self) -> &FxHashMap { + &self.properties + } + + #[inline] + pub fn properties_mut(&mut self) -> &mut FxHashMap { + &mut self.properties + } + + #[inline] + pub fn symbol_properties(&self) -> &FxHashMap { + &self.symbol_properties + } + + #[inline] + pub fn symbol_properties_mut(&mut self) -> &mut FxHashMap { + &mut self.symbol_properties + } + + #[inline] + pub fn state(&self) -> &Option { + &self.state + } + + #[inline] + pub fn state_mut(&mut self) -> &mut Option { + &mut self.state + } } /// Create a new object. @@ -633,7 +481,6 @@ pub fn create(global: &Value) -> Value { let object = make_constructor_fn("Object", 1, make_object, global, prototype, true); - object.set_field("length", Value::from(1)); make_builtin_fn(set_prototype_of, "setPrototypeOf", &object, 2); make_builtin_fn(get_prototype_of, "getPrototypeOf", &object, 1); make_builtin_fn(define_property, "defineProperty", &object, 3); @@ -643,7 +490,8 @@ pub fn create(global: &Value) -> Value { /// Initialise the `Object` object on the global object. #[inline] -pub fn init(global: &Value) { +pub fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event("object", "init"); - global.set_field("Object", create(global)); + + ("Object", create(global)) } diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index 684763ff0f..c697045778 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -16,7 +16,7 @@ use regex::Regex; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ - object::{InternalState, ObjectKind}, + object::{InternalState, ObjectData}, property::Property, value::{ResultValue, Value, ValueData}, }, @@ -61,6 +61,12 @@ pub(crate) struct RegExp { impl InternalState for RegExp {} impl RegExp { + /// The name of the object. + pub(crate) const NAME: &'static str = "RegExp"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: usize = 2; + /// Create a new `RegExp` pub(crate) fn make_regexp( this: &mut Value, @@ -79,7 +85,8 @@ impl RegExp { regex_body = body.into(); } ValueData::Object(ref obj) => { - let slots = &obj.borrow().internal_slots; + let obj = obj.borrow(); + let slots = obj.internal_slots(); if slots.get("RegExpMatcher").is_some() { // first argument is another `RegExp` object, so copy its pattern and flags if let Some(body) = slots.get("OriginalSource") { @@ -160,7 +167,7 @@ impl RegExp { // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) - this.set_kind(ObjectKind::Ordinary); + this.set_data(ObjectData::Ordinary); this.set_internal_slot("RegExpMatcher", Value::undefined()); this.set_internal_slot("OriginalSource", Value::from(regex_body)); this.set_internal_slot("OriginalFlags", Value::from(regex_flags)); @@ -354,9 +361,8 @@ impl RegExp { } let result = Value::from(result); - result - .set_property_slice("index", Property::default().value(Value::from(m.start()))); - result.set_property_slice("input", Property::default().value(Value::from(arg_str))); + result.set_property("index", Property::default().value(Value::from(m.start()))); + result.set_property("input", Property::default().value(Value::from(arg_str))); result } else { if regex.use_last_index { @@ -441,11 +447,9 @@ impl RegExp { let match_val = Value::from(match_vec); - match_val.set_property_slice( - "index", - Property::default().value(Value::from(m.start())), - ); - match_val.set_property_slice( + match_val + .set_property("index", Property::default().value(Value::from(m.start()))); + match_val.set_property( "input", Property::default().value(Value::from(arg_str.clone())), ); @@ -463,7 +467,7 @@ impl RegExp { let length = matches.len(); let result = Value::from(matches); result.set_field("length", Value::from(length)); - result.set_kind(ObjectKind::Array); + result.set_data(ObjectData::Array); Ok(result) } @@ -472,7 +476,10 @@ impl RegExp { pub(crate) fn create(global: &Value) -> Value { // Create prototype let prototype = Value::new_object(Some(global)); - prototype.set_field("lastIndex", Value::from(0)); + prototype + .as_object_mut() + .unwrap() + .insert_field("lastIndex", Value::from(0)); make_builtin_fn(Self::test, "test", &prototype, 1); make_builtin_fn(Self::exec, "exec", &prototype, 1); @@ -486,13 +493,21 @@ impl RegExp { make_builtin_fn(Self::get_sticky, "sticky", &prototype, 0); make_builtin_fn(Self::get_unicode, "unicode", &prototype, 0); - make_constructor_fn("RegExp", 1, Self::make_regexp, global, prototype, true) + make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::make_regexp, + global, + prototype, + true, + ) } /// Initialise the `RegExp` object on the global object. #[inline] - pub(crate) fn init(global: &Value) { - let _timer = BoaProfiler::global().start_event("regexp", "init"); - global.set_field("RegExp", Self::create(global)); + pub(crate) fn init(global: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 1632c4855a..d9abe9ae68 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -15,7 +15,7 @@ mod tests; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ - object::{Object, ObjectKind}, + object::{Object, ObjectData}, property::Property, value::{ResultValue, Value, ValueData}, RegExp, @@ -36,6 +36,27 @@ use std::{ pub(crate) struct String; impl String { + /// The name of the object. + pub(crate) const NAME: &'static str = "String"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: usize = 1; + + fn this_string_value(this: &Value, ctx: &mut Interpreter) -> Result { + match this.data() { + ValueData::String(ref string) => return Ok(string.clone()), + ValueData::Object(ref object) => { + let object = object.borrow(); + if let Some(string) = object.as_string() { + return Ok(string.to_owned()); + } + } + _ => {} + } + + Err(ctx.construct_type_error("'this' is not a string")) + } + /// [[Construct]] - Creates a new instance `this` /// /// [[Call]] - Returns a new native `string` @@ -43,39 +64,30 @@ impl String { pub(crate) fn make_string( this: &mut Value, args: &[Value], - _: &mut Interpreter, + ctx: &mut Interpreter, ) -> ResultValue { // This value is used by console.log and other routines to match Obexpecty"failed to parse argument for String method"pe // to its Javascript Identifier (global constructor method name) - let s = args.get(0).unwrap_or(&Value::string("")).clone(); - let length_str = s.to_string().chars().count(); - - this.set_field("length", Value::from(length_str as i32)); + let string = match args.get(0) { + Some(ref value) => ctx.to_string(value)?, + None => StdString::new(), + }; - this.set_kind(ObjectKind::String); - this.set_internal_slot("StringData", s); + let length = string.chars().count(); - let arg = match args.get(0) { - Some(v) => v.clone(), - None => Value::undefined(), - }; + this.set_field("length", Value::from(length as i32)); - if arg.is_undefined() { - return Ok("".into()); - } + this.set_data(ObjectData::String(string.clone())); - Ok(Value::from(arg.to_string())) + Ok(Value::from(string)) } /// Get the string value to a primitive string #[allow(clippy::wrong_self_convention)] + #[inline] pub(crate) fn to_string(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { // Get String from String Object and send it back as a new value - match this.get_internal_slot("StringData").data() { - ValueData::String(ref string) => Ok(Value::from(string.clone())), - // Throw expection here: - _ => ctx.throw_type_error("'this' is not a string"), - } + Ok(Value::from(Self::this_string_value(this, ctx)?)) } /// `String.prototype.charAt( index )` @@ -183,14 +195,14 @@ impl String { pub(crate) fn concat(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value - let mut new_str = ctx.to_string(this)?; + let object = ctx.require_object_coercible(this)?; + let mut string = ctx.to_string(object)?; for arg in args { - let concat_str = arg.to_string(); - new_str.push_str(&concat_str); + string.push_str(&ctx.to_string(arg)?); } - Ok(Value::from(new_str)) + Ok(Value::from(string)) } /// `String.prototype.repeat( count )` @@ -402,10 +414,11 @@ impl String { match value.deref() { ValueData::String(ref body) => body.into(), ValueData::Object(ref obj) => { - let slots = &obj.borrow().internal_slots; - if slots.get("RegExpMatcher").is_some() { + let obj = obj.borrow(); + + if obj.internal_slots().get("RegExpMatcher").is_some() { // first argument is another `RegExp` object, so copy its pattern and flags - if let Some(body) = slots.get("OriginalSource") { + if let Some(body) = obj.internal_slots().get("OriginalSource") { return body.to_string(); } } @@ -1046,7 +1059,8 @@ impl String { let prototype = Value::new_object(Some(global)); let length = Property::default().value(Value::from(0)); - prototype.set_property_slice("length", length); + prototype.set_property("length", length); + make_builtin_fn(Self::char_at, "charAt", &prototype, 1); make_builtin_fn(Self::char_code_at, "charCodeAt", &prototype, 1); make_builtin_fn(Self::to_string, "toString", &prototype, 0); @@ -1072,13 +1086,21 @@ impl String { make_builtin_fn(Self::match_all, "matchAll", &prototype, 1); make_builtin_fn(Self::replace, "replace", &prototype, 2); - make_constructor_fn("String", 1, Self::make_string, global, prototype, true) + make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::make_string, + global, + prototype, + true, + ) } /// Initialise the `String` object on the global object. #[inline] - pub(crate) fn init(global: &Value) { - let _timer = BoaProfiler::global().start_event("string", "init"); - global.set_field("String", Self::create(global)); + pub(crate) fn init(global: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index bbf915bfb4..b764199cfc 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -20,87 +20,107 @@ mod tests; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ - builtins::{ - object::{ - internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, INSTANCE_PROTOTYPE, - PROTOTYPE, - }, - value::{ResultValue, Value, ValueData}, - }, + builtins::value::{ResultValue, Value, ValueData}, exec::Interpreter, BoaProfiler, }; -use gc::{Gc, GcCell}; -use rand::random; - -/// Creates Symbol instances. -/// -/// Symbol instances are ordinary objects that inherit properties from the Symbol prototype object. -/// Symbol instances have a `[[SymbolData]]` internal slot. -/// The `[[SymbolData]]` internal slot is the Symbol value represented by this Symbol object. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-symbol-description -pub fn call_symbol(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // From an implementation and specificaition perspective Symbols are similar to Objects. - // They have internal slots to hold the SymbolData and Description, they also have methods and a prototype. - // So we start by creating an Object - // TODO: Set prototype to Symbol.prototype (by changing to Object::create(), use interpreter to get Symbol.prototype) - let mut sym_instance = Object::default(); - sym_instance.kind = ObjectKind::Symbol; - - // Set description which should either be undefined or a string - let desc_string = match args.get(0) { - Some(value) => Value::from(value.to_string()), - None => Value::undefined(), - }; - - sym_instance.set_internal_slot("Description", desc_string); - sym_instance.set_internal_slot("SymbolData", Value::from(random::())); - - // Set __proto__ internal slot - let proto = ctx - .realm - .global_obj - .get_field("Symbol") - .get_field(PROTOTYPE); - sym_instance.set_internal_slot(INSTANCE_PROTOTYPE, proto); - - Ok(Value(Gc::new(ValueData::Symbol(Box::new(GcCell::new( - sym_instance, - )))))) -} +use gc::{Finalize, Trace}; -/// `Symbol.prototype.toString()` -/// -/// This method returns a string representing the specified `Symbol` object. -/// -/// /// More information: -/// - [MDN documentation][mdn] -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.tostring -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toString -pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - let s: Value = this.get_internal_slot("Description"); - let full_string = format!(r#"Symbol({})"#, s.to_string()); - Ok(Value::from(full_string)) -} +#[derive(Debug, Finalize, Trace, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Symbol(Option>, u32); -/// Create a new `Symbol` object. -pub fn create(global: &Value) -> Value { - // Create prototype object - let prototype = Value::new_object(Some(global)); +impl Symbol { + /// The name of the object. + pub(crate) const NAME: &'static str = "Symbol"; - make_builtin_fn(to_string, "toString", &prototype, 0); - make_constructor_fn("Symbol", 1, call_symbol, global, prototype, false) -} + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: usize = 0; + + /// Returns the `Symbol`s description. + pub fn description(&self) -> Option<&str> { + self.0.as_deref() + } + + /// Returns the `Symbol`s hash. + pub fn hash(&self) -> u32 { + self.1 + } + + fn this_symbol_value(value: &Value, ctx: &mut Interpreter) -> Result { + match value.data() { + ValueData::Symbol(ref symbol) => return Ok(symbol.clone()), + ValueData::Object(ref object) => { + let object = object.borrow(); + if let Some(symbol) = object.as_symbol() { + return Ok(symbol.clone()); + } + } + _ => {} + } + + Err(ctx.construct_type_error("'this' is not a Symbol")) + } + + /// The `Symbol()` constructor returns a value of type symbol. + /// + /// It is incomplete as a constructor because it does not support + /// the syntax `new Symbol()` and it is not intended to be subclassed. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-symbol-description + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/Symbol + pub(crate) fn call(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let description = match args.get(0) { + Some(ref value) if !value.is_undefined() => { + Some(ctx.to_string(value)?.into_boxed_str()) + } + _ => None, + }; + + Ok(Value::symbol(Symbol(description, ctx.generate_hash()))) + } + + /// `Symbol.prototype.toString()` + /// + /// This method returns a string representing the specified `Symbol` object. + /// + /// /// More information: + /// - [MDN documentation][mdn] + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.tostring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toString + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_string(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + let symbol = Self::this_symbol_value(this, ctx)?; + let description = symbol.description().unwrap_or(""); + Ok(Value::from(format!("Symbol({})", description))) + } + + /// Create a new `Symbol` object. + pub(crate) fn create(global: &Value) -> Value { + // Create prototype object + let prototype = Value::new_object(Some(global)); + + make_builtin_fn(Self::to_string, "toString", &prototype, 0); + make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::call, + global, + prototype, + false, + ) + } + + /// Initialise the `Symbol` object on the global object. + #[inline] + pub fn init(global: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); -/// Initialise the `Symbol` object on the global object. -#[inline] -pub fn init(global: &Value) { - let _timer = BoaProfiler::global().start_event("symbol", "init"); - global.set_field("Symbol", create(global)); + (Self::NAME, Self::create(global)) + } } diff --git a/boa/src/builtins/symbol/tests.rs b/boa/src/builtins/symbol/tests.rs index ca72edb1f5..16cb6c23a0 100644 --- a/boa/src/builtins/symbol/tests.rs +++ b/boa/src/builtins/symbol/tests.rs @@ -4,7 +4,7 @@ use crate::{exec::Interpreter, forward, forward_val, realm::Realm}; #[test] fn check_symbol_constructor_is_function() { let global = Value::new_object(None); - let symbol_constructor = create(&global); + let symbol_constructor = Symbol::create(&global); assert_eq!(symbol_constructor.is_function(), true); } diff --git a/boa/src/builtins/value/conversions.rs b/boa/src/builtins/value/conversions.rs index 997ea9651e..36392f204a 100644 --- a/boa/src/builtins/value/conversions.rs +++ b/boa/src/builtins/value/conversions.rs @@ -119,7 +119,7 @@ where fn from(value: &[T]) -> Self { let mut array = Object::default(); for (i, item) in value.iter().enumerate() { - array.properties.insert( + array.properties_mut().insert( i.to_string(), Property::default().value(item.clone().into()), ); @@ -136,7 +136,7 @@ where let mut array = Object::default(); for (i, item) in value.into_iter().enumerate() { array - .properties + .properties_mut() .insert(i.to_string(), Property::default().value(item.into())); } Value::from(array) diff --git a/boa/src/builtins/value/display.rs b/boa/src/builtins/value/display.rs index b035bb35bf..fef0925192 100644 --- a/boa/src/builtins/value/display.rs +++ b/boa/src/builtins/value/display.rs @@ -57,7 +57,7 @@ macro_rules! print_obj_value { (impl $field:ident, $v:expr, $f:expr) => { $v .borrow() - .$field + .$field() .iter() .map($f) .collect::>() @@ -70,26 +70,13 @@ pub(crate) fn log_string_from(x: &ValueData, print_internals: bool) -> String { ValueData::Object(ref v) => { // Can use the private "type" field of an Object to match on // which type of Object it represents for special printing - match v.borrow().kind { - ObjectKind::String => match v - .borrow() - .internal_slots - .get("StringData") - .expect("Cannot get primitive value from String") - .data() - { - ValueData::String(ref string) => format!("\"{}\"", string), - _ => unreachable!("[[StringData]] should always contain String"), - }, - ObjectKind::Boolean => { - let bool_data = v.borrow().get_internal_slot("BooleanData").to_string(); - - format!("Boolean {{ {} }}", bool_data) - } - ObjectKind::Array => { + match v.borrow().data { + ObjectData::String(ref string) => format!("String {{ \"{}\" }}", string), + ObjectData::Boolean(boolean) => format!("Boolean {{ {} }}", boolean), + ObjectData::Array => { let len = i32::from( &v.borrow() - .properties + .properties() .get("length") .unwrap() .value @@ -107,7 +94,7 @@ pub(crate) fn log_string_from(x: &ValueData, print_internals: bool) -> String { // which are part of the Array log_string_from( &v.borrow() - .properties + .properties() .get(&i.to_string()) .unwrap() .value @@ -124,14 +111,10 @@ pub(crate) fn log_string_from(x: &ValueData, print_internals: bool) -> String { _ => display_obj(&x, print_internals), } } - ValueData::Symbol(ref sym) => { - let desc: Value = sym.borrow().get_internal_slot("Description"); - match *desc { - ValueData::String(ref st) => format!("Symbol(\"{}\")", st.to_string()), - _ => String::from("Symbol()"), - } - } - + ValueData::Symbol(ref symbol) => match symbol.description() { + Some(ref desc) => format!("Symbol({})", desc), + None => String::from("Symbol()"), + }, _ => format!("{}", x), } } @@ -198,10 +181,9 @@ impl Display for ValueData { Self::Null => write!(f, "null"), Self::Undefined => write!(f, "undefined"), Self::Boolean(v) => write!(f, "{}", v), - Self::Symbol(ref v) => match *v.borrow().get_internal_slot("Description") { - // If a description exists use it - Self::String(ref v) => write!(f, "{}", format!("Symbol({})", v)), - _ => write!(f, "Symbol()"), + Self::Symbol(ref symbol) => match symbol.description() { + Some(description) => write!(f, "Symbol({})", description), + None => write!(f, "Symbol()"), }, Self::String(ref v) => write!(f, "{}", v), Self::Rational(v) => write!( diff --git a/boa/src/builtins/value/equality.rs b/boa/src/builtins/value/equality.rs index 155814ac44..8e0254dacd 100644 --- a/boa/src/builtins/value/equality.rs +++ b/boa/src/builtins/value/equality.rs @@ -197,14 +197,13 @@ pub fn same_value_zero(x: &Value, y: &Value) -> bool { } } -pub fn same_value_non_numeric(x: &Value, y: &Value) -> bool { +fn same_value_non_numeric(x: &Value, y: &Value) -> bool { debug_assert!(x.get_type() == y.get_type()); match x.get_type() { - Type::Undefined => true, - Type::Null => true, + Type::Null | Type::Undefined => true, Type::String => x.to_string() == y.to_string(), Type::Boolean => bool::from(x) == bool::from(y), - Type::Object => std::ptr::eq(x, y), + Type::Object => std::ptr::eq(x.data(), y.data()), _ => false, } } diff --git a/boa/src/builtins/value/hash.rs b/boa/src/builtins/value/hash.rs new file mode 100644 index 0000000000..2ddc7dc3eb --- /dev/null +++ b/boa/src/builtins/value/hash.rs @@ -0,0 +1,54 @@ +use super::*; + +use crate::builtins::Number; +use std::hash::{Hash, Hasher}; + +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + same_value_zero(self, other) + } +} + +impl Eq for Value {} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +struct UndefinedHashable; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +struct NullHashable; + +#[derive(Debug, Clone, Copy)] +struct RationalHashable(f64); + +impl PartialEq for RationalHashable { + #[inline] + fn eq(&self, other: &Self) -> bool { + Number::same_value(self.0, other.0) + } +} + +impl Eq for RationalHashable {} + +impl Hash for RationalHashable { + #[inline] + fn hash(&self, state: &mut H) { + self.0.to_bits().hash(state); + } +} + +impl Hash for Value { + fn hash(&self, state: &mut H) { + let data = self.data(); + match data { + ValueData::Undefined => UndefinedHashable.hash(state), + ValueData::Null => NullHashable.hash(state), + ValueData::String(ref string) => string.hash(state), + ValueData::Boolean(boolean) => boolean.hash(state), + ValueData::Integer(integer) => integer.hash(state), + ValueData::BigInt(ref bigint) => bigint.hash(state), + ValueData::Rational(rational) => RationalHashable(*rational).hash(state), + ValueData::Symbol(ref symbol) => Hash::hash(symbol, state), + ValueData::Object(_) => std::ptr::hash(data, state), + } + } +} diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index 12661598f0..50f518b2e0 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -10,17 +10,14 @@ pub mod val_type; pub use crate::builtins::value::val_type::Type; use crate::builtins::{ - object::{ - internal_methods_trait::ObjectInternalMethods, InternalState, InternalStateCell, Object, - ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE, - }, + function::Function, + object::{InternalState, InternalStateCell, Object, ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE}, property::Property, - BigInt, Function, + BigInt, Symbol, }; -use crate::BoaProfiler; - use crate::exec::Interpreter; -use gc::{Finalize, Gc, GcCell, GcCellRef, Trace}; +use crate::BoaProfiler; +use gc::{Finalize, Gc, GcCell, GcCellRef, GcCellRefMut, Trace}; use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue}; use std::{ any::Any, @@ -28,18 +25,20 @@ use std::{ convert::TryFrom, f64::NAN, fmt::{self, Display}, - ops::{Add, BitAnd, BitOr, BitXor, Deref, DerefMut, Div, Mul, Neg, Not, Rem, Shl, Shr, Sub}, + ops::{Add, BitAnd, BitOr, BitXor, Deref, Div, Mul, Neg, Not, Rem, Shl, Shr, Sub}, str::FromStr, }; pub mod conversions; pub mod display; pub mod equality; +pub mod hash; pub mod operations; pub use conversions::*; pub(crate) use display::display_obj; pub use equality::*; +pub use hash::*; pub use operations::*; /// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`) @@ -48,7 +47,7 @@ pub type ResultValue = Result; /// A Garbage-collected Javascript value as represented in the interpreter. #[derive(Debug, Clone, Trace, Finalize, Default)] -pub struct Value(pub(crate) Gc); +pub struct Value(Gc); impl Value { /// Creates a new `undefined` value. @@ -63,6 +62,12 @@ impl Value { Self(Gc::new(ValueData::Null)) } + /// Creates a new number with `NaN` value. + #[inline] + pub fn nan() -> Self { + Self::number(NAN) + } + /// Creates a new string value. #[inline] pub fn string(value: S) -> Self @@ -117,6 +122,12 @@ impl Value { Self(Gc::new(ValueData::Object(Box::new(GcCell::new(object))))) } + /// Creates a new symbol value. + #[inline] + pub fn symbol(symbol: Symbol) -> Self { + Self(Gc::new(ValueData::Symbol(symbol))) + } + /// Gets the underlying `ValueData` structure. #[inline] pub fn data(&self) -> &ValueData { @@ -145,12 +156,12 @@ impl Value { } /// Similar to `new_object`, but you can pass a prototype to create from, plus a kind - pub fn new_object_from_prototype(proto: Value, kind: ObjectKind) -> Self { + pub fn new_object_from_prototype(proto: Value, data: ObjectData) -> Self { let mut object = Object::default(); - object.kind = kind; + object.data = data; object - .internal_slots + .internal_slots_mut() .insert(INSTANCE_PROTOTYPE.to_string(), proto); Self::object(object) @@ -175,7 +186,7 @@ impl Value { .get_field("Array") .get_field(PROTOTYPE); let new_obj = - Value::new_object_from_prototype(global_array_prototype, ObjectKind::Array); + Value::new_object_from_prototype(global_array_prototype, ObjectData::Array); let length = vs.len(); for (idx, json) in vs.into_iter().enumerate() { new_obj.set_property( @@ -216,9 +227,9 @@ impl Value { ValueData::Null => Ok(JSONValue::Null), ValueData::Boolean(b) => Ok(JSONValue::Bool(b)), ValueData::Object(ref obj) => { - if obj.borrow().kind == ObjectKind::Array { + if obj.borrow().is_array() { let mut arr: Vec = Vec::new(); - for k in obj.borrow().properties.keys() { + for k in obj.borrow().properties().keys() { if k != "length" { let value = self.get_field(k.to_string()); if value.is_undefined() || value.is_function() { @@ -231,7 +242,7 @@ impl Value { Ok(JSONValue::Array(arr)) } else { let mut new_obj = Map::new(); - for k in obj.borrow().properties.keys() { + for k in obj.borrow().properties().keys() { let key = k.clone(); let value = self.get_field(k.to_string()); if !value.is_undefined() && !value.is_function() { @@ -246,9 +257,9 @@ impl Value { .map(JSONValue::Number) .unwrap_or(JSONValue::Null)), ValueData::Integer(val) => Ok(JSONValue::Number(JSONNumber::from(val))), - ValueData::BigInt(_) => Err(interpreter - .throw_type_error("BigInt value can't be serialized in JSON") - .expect_err("throw_type_error should always return an error")), + ValueData::BigInt(_) => { + Err(interpreter.construct_type_error("BigInt value can't be serialized in JSON")) + } ValueData::Symbol(_) | ValueData::Undefined => { unreachable!("Symbols and Undefined JSON Values depend on parent type"); } @@ -283,8 +294,8 @@ pub enum ValueData { BigInt(BigInt), /// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values. Object(Box>), - /// `Symbol` - A Symbol Type - Internally Symbols are similar to objects, except there are no properties, only internal slots. - Symbol(Box>), + /// `Symbol` - A Symbol Primitive type. + Symbol(Symbol), } impl ValueData { @@ -302,65 +313,65 @@ impl ValueData { } /// Returns true if the value is an object + #[inline] pub fn is_object(&self) -> bool { + matches!(self, Self::Object(_)) + } + + #[inline] + pub fn as_object(&self) -> Option> { match *self { - Self::Object(_) => true, - _ => false, + Self::Object(ref o) => Some(o.borrow()), + _ => None, } } - /// Returns true if the value is a symbol - pub fn is_symbol(&self) -> bool { + #[inline] + pub fn as_object_mut(&self) -> Option> { match *self { - Self::Symbol(_) => true, - _ => false, + Self::Object(ref o) => Some(o.borrow_mut()), + _ => None, } } + /// Returns true if the value is a symbol. + #[inline] + pub fn is_symbol(&self) -> bool { + matches!(self, Self::Symbol(_)) + } + /// Returns true if the value is a function + #[inline] pub fn is_function(&self) -> bool { - match *self { - Self::Object(ref o) => { - let borrowed_obj = o.borrow(); - borrowed_obj.is_callable() || borrowed_obj.is_constructable() - } - _ => false, - } + matches!(self, Self::Object(o) if o.borrow().is_function()) } /// Returns true if the value is undefined. + #[inline] pub fn is_undefined(&self) -> bool { - match *self { - Self::Undefined => true, - _ => false, - } + matches!(self, Self::Undefined) } /// Returns true if the value is null. + #[inline] pub fn is_null(&self) -> bool { - match *self { - Self::Null => true, - _ => false, - } + matches!(self, Self::Null) } /// Returns true if the value is null or undefined. + #[inline] pub fn is_null_or_undefined(&self) -> bool { - match *self { - Self::Null | Self::Undefined => true, - _ => false, - } + matches!(self, Self::Null | Self::Undefined) } /// Returns true if the value is a 64-bit floating-point number. + #[inline] pub fn is_double(&self) -> bool { - match *self { - Self::Rational(_) => true, - _ => false, - } + matches!(self, Self::Rational(_)) } /// Returns true if the value is integer. + #[inline] #[allow(clippy::float_cmp)] pub fn is_integer(&self) -> bool { // If it can fit in a i32 and the trucated version is @@ -374,39 +385,40 @@ impl ValueData { } } - /// Returns true if the value is a number + /// Returns true if the value is a number. + #[inline] pub fn is_number(&self) -> bool { - match self { - Self::Rational(_) | Self::Integer(_) => true, - _ => false, - } + matches!(self, Self::Rational(_) | Self::Integer(_)) } - /// Returns true if the value is a string + /// Returns true if the value is a string. + #[inline] pub fn is_string(&self) -> bool { - match *self { - Self::String(_) => true, - _ => false, - } + matches!(self, Self::String(_)) } - /// Returns true if the value is a boolean + /// Returns true if the value is a boolean. + #[inline] pub fn is_boolean(&self) -> bool { - match *self { - Self::Boolean(_) => true, - _ => false, - } + matches!(self, Self::Boolean(_)) } - /// Returns true if the value is a bigint + /// Returns true if the value is a bigint. + #[inline] pub fn is_bigint(&self) -> bool { - match *self { - Self::BigInt(_) => true, - _ => false, + matches!(self, Self::BigInt(_)) + } + + /// Returns an optional reference to a `BigInt` if the value is a BigInt primitive. + #[inline] + pub fn as_bigint(&self) -> Option<&BigInt> { + match self { + Self::BigInt(bigint) => Some(bigint), + _ => None, } } - /// Returns true if the value is true + /// Returns true if the value is true. /// /// [toBoolean](https://tc39.es/ecma262/#sec-toboolean) pub fn is_true(&self) -> bool { @@ -466,23 +478,27 @@ impl ValueData { } } - pub fn as_object(&self) -> Option> { + /// Creates a new boolean value from the input + pub fn to_boolean(&self) -> bool { match *self { - ValueData::Object(ref o) => Some(o.borrow()), - _ => None, + Self::Undefined | Self::Null => false, + Self::Symbol(_) | Self::Object(_) => true, + Self::String(ref s) if !s.is_empty() => true, + Self::Rational(n) if n != 0.0 && !n.is_nan() => true, + Self::Integer(n) if n != 0 => true, + Self::BigInt(ref n) if *n != 0 => true, + Self::Boolean(v) => v, + _ => false, } } /// Removes a property from a Value object. /// - /// It will return a boolean based on if the value was removed, if there was no value to remove false is returned + /// It will return a boolean based on if the value was removed, if there was no value to remove false is returned. pub fn remove_property(&self, field: &str) -> bool { - let removed = match *self { - Self::Object(ref obj) => obj.borrow_mut().deref_mut().properties.remove(field), - _ => None, - }; - - removed.is_some() + self.as_object_mut() + .and_then(|mut x| x.properties_mut().remove(field)) + .is_some() } /// Resolve the property in the object. @@ -492,36 +508,22 @@ impl ValueData { let _timer = BoaProfiler::global().start_event("Value::get_property", "value"); // Spidermonkey has its own GetLengthProperty: https://searchfox.org/mozilla-central/source/js/src/vm/Interpreter-inl.h#154 // This is only for primitive strings, String() objects have their lengths calculated in string.rs - if self.is_string() && field == "length" { - if let Self::String(ref s) = *self { - return Some(Property::default().value(Value::from(s.len()))); - } - } - - if self.is_undefined() { - return None; - } - - let obj: Object = match *self { - Self::Object(ref obj) => { - let hash = obj.clone(); - // TODO: This will break, we should return a GcCellRefMut instead - // into_inner will consume the wrapped value and remove it from the hashmap - hash.into_inner() + match self { + Self::Undefined => None, + Self::String(ref s) if field == "length" => { + Some(Property::default().value(Value::from(s.chars().count()))) } - Self::Symbol(ref obj) => { - let hash = obj.clone(); - hash.into_inner() + Self::Object(ref object) => { + let object = object.borrow(); + match object.properties().get(field) { + Some(value) => Some(value.clone()), + None => object + .internal_slots() + .get(INSTANCE_PROTOTYPE) + .and_then(|value| value.get_property(field)), + } } - _ => return None, - }; - - match obj.properties.get(field) { - Some(val) => Some(val.clone()), - None => match obj.internal_slots.get(&INSTANCE_PROTOTYPE.to_string()) { - Some(value) => value.get_property(field), - None => None, - }, + _ => None, } } @@ -537,18 +539,14 @@ impl ValueData { configurable: Option, ) { let _timer = BoaProfiler::global().start_event("Value::update_property", "value"); - let obj: Option = match self { - Self::Object(ref obj) => Some(obj.borrow_mut().deref_mut().clone()), - _ => None, - }; - if let Some(mut obj_data) = obj { + if let Some(ref mut object) = self.as_object_mut() { // Use value, or walk up the prototype chain - if let Some(ref mut prop) = obj_data.properties.get_mut(field) { - prop.value = value; - prop.enumerable = enumerable; - prop.writable = writable; - prop.configurable = configurable; + if let Some(ref mut property) = object.properties_mut().get_mut(field) { + property.value = value; + property.enumerable = enumerable; + property.writable = writable; + property.configurable = configurable; } } } @@ -556,24 +554,13 @@ impl ValueData { /// Resolve the property in the object. /// /// Returns a copy of the Property. + #[inline] pub fn get_internal_slot(&self, field: &str) -> Value { let _timer = BoaProfiler::global().start_event("Value::get_internal_slot", "value"); - let obj: Object = match *self { - Self::Object(ref obj) => { - let hash = obj.clone(); - hash.into_inner() - } - Self::Symbol(ref obj) => { - let hash = obj.clone(); - hash.into_inner() - } - _ => return Value::undefined(), - }; - match obj.internal_slots.get(field) { - Some(val) => val.clone(), - None => Value::undefined(), - } + self.as_object() + .and_then(|x| x.internal_slots().get(field).cloned()) + .unwrap_or_else(Value::undefined) } /// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist @@ -615,21 +602,15 @@ impl ValueData { } /// Check whether an object has an internal state set. + #[inline] pub fn has_internal_state(&self) -> bool { - if let Self::Object(ref obj) = *self { - obj.borrow().state.is_some() - } else { - false - } + matches!(self.as_object(), Some(object) if object.state().is_some()) } /// Get the internal state of an object. pub fn get_internal_state(&self) -> Option { - if let Self::Object(ref obj) = *self { - obj.borrow().state.as_ref().cloned() - } else { - None - } + self.as_object() + .and_then(|object| object.state().as_ref().cloned()) } /// Run a function with a reference to the internal state. @@ -638,14 +619,14 @@ impl ValueData { /// /// This will panic if this value doesn't have an internal state or if the internal state doesn't /// have the concrete type `S`. - pub fn with_internal_state_ref R>( - &self, - f: F, - ) -> R { - if let Self::Object(ref obj) = *self { - let o = obj.borrow(); - let state = o - .state + pub fn with_internal_state_ref(&self, f: F) -> R + where + S: Any + InternalState, + F: FnOnce(&S) -> R, + { + if let Some(object) = self.as_object() { + let state = object + .state() .as_ref() .expect("no state") .downcast_ref() @@ -662,14 +643,14 @@ impl ValueData { /// /// This will panic if this value doesn't have an internal state or if the internal state doesn't /// have the concrete type `S`. - pub fn with_internal_state_mut R>( - &self, - f: F, - ) -> R { - if let Self::Object(ref obj) = *self { - let mut o = obj.borrow_mut(); - let state = o - .state + pub fn with_internal_state_mut(&self, f: F) -> R + where + S: Any + InternalState, + F: FnOnce(&mut S) -> R, + { + if let Some(mut object) = self.as_object_mut() { + let state = object + .state_mut() .as_mut() .expect("no state") .downcast_mut() @@ -680,7 +661,8 @@ impl ValueData { } } - /// Check to see if the Value has the field, mainly used by environment records + /// Check to see if the Value has the field, mainly used by environment records. + #[inline] pub fn has_field(&self, field: &str) -> bool { let _timer = BoaProfiler::global().start_event("Value::has_field", "value"); self.get_property(field).is_some() @@ -698,7 +680,7 @@ impl ValueData { let val = val.into(); if let Self::Object(ref obj) = *self { - if obj.borrow().kind == ObjectKind::Array { + if obj.borrow().is_array() { if let Ok(num) = field.to_string().parse::() { if num > 0 { let len = i32::from(&self.get_field("length")); @@ -722,53 +704,50 @@ impl ValueData { } /// Set the private field in the value - pub fn set_internal_slot(&self, field: &str, val: Value) -> Value { + pub fn set_internal_slot(&self, field: &str, value: Value) -> Value { let _timer = BoaProfiler::global().start_event("Value::set_internal_slot", "exec"); - if let Self::Object(ref obj) = *self { - obj.borrow_mut() - .internal_slots - .insert(field.to_string(), val.clone()); + if let Some(mut object) = self.as_object_mut() { + object + .internal_slots_mut() + .insert(field.to_string(), value.clone()); } - val + value } - /// Set the kind of an object - pub fn set_kind(&self, kind: ObjectKind) { + /// Set the kind of an object. + #[inline] + pub fn set_data(&self, data: ObjectData) { if let Self::Object(ref obj) = *self { - (*obj.deref().borrow_mut()).kind = kind; + (*obj.deref().borrow_mut()).data = data; } } - /// Set the property in the value - pub fn set_property(&self, field: String, prop: Property) -> Property { - if let Self::Object(ref obj) = *self { - obj.borrow_mut().properties.insert(field, prop.clone()); + /// Set the property in the value. + pub fn set_property(&self, field: S, property: Property) -> Property + where + S: Into, + { + if let Some(mut object) = self.as_object_mut() { + object + .properties_mut() + .insert(field.into(), property.clone()); } - prop - } - - /// Set the property in the value - pub fn set_property_slice(&self, field: &str, prop: Property) -> Property { - self.set_property(field.to_string(), prop) + property } /// Set internal state of an Object. Discards the previous state if it was set. pub fn set_internal_state(&self, state: T) { - if let Self::Object(ref obj) = *self { - obj.borrow_mut() - .state - .replace(InternalStateCell::new(state)); + if let Some(mut object) = self.as_object_mut() { + object.state_mut().replace(InternalStateCell::new(state)); } } /// Consume the function and return a Value - pub fn from_func(native_func: Function) -> Value { - // Object with Kind set to function - let mut new_func = crate::builtins::object::Object::function(); + pub fn from_func(function: Function) -> Value { // Get Length - let length = native_func.params.len(); - // Set [[Call]] internal slot - new_func.set_func(native_func); + let length = function.params.len(); + // Object with Kind set to function + let new_func = Object::function(function); // Wrap Object in GC'd Value let new_func_val = Value::from(new_func); // Set length to parameters diff --git a/boa/src/builtins/value/tests.rs b/boa/src/builtins/value/tests.rs index 03975069c0..008b04076a 100644 --- a/boa/src/builtins/value/tests.rs +++ b/boa/src/builtins/value/tests.rs @@ -1,6 +1,9 @@ use super::*; use crate::{forward, forward_val, Interpreter, Realm}; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + #[test] fn check_is_object() { let val = Value::new_object(None); @@ -91,6 +94,67 @@ fn abstract_equality_comparison() { assert_eq!(forward(&mut engine, "0 == NaN"), "false"); assert_eq!(forward(&mut engine, "'foo' == NaN"), "false"); assert_eq!(forward(&mut engine, "NaN == NaN"), "false"); + + assert_eq!( + forward( + &mut engine, + "Number.POSITIVE_INFINITY === Number.POSITIVE_INFINITY" + ), + "true" + ); + assert_eq!( + forward( + &mut engine, + "Number.NEGAVIVE_INFINITY === Number.NEGAVIVE_INFINITY" + ), + "true" + ); +} + +/// Helper function to get the hash of a `Value`. +fn hash_value(value: &Value) -> u64 { + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + hasher.finish() +} + +#[test] +fn hash_undefined() { + let value1 = Value::undefined(); + let value_clone = value1.clone(); + assert_eq!(value1, value_clone); + + let value2 = Value::undefined(); + assert_eq!(value1, value2); + + assert_eq!(hash_value(&value1), hash_value(&value_clone)); + assert_eq!(hash_value(&value2), hash_value(&value_clone)); +} + +#[test] +fn hash_rational() { + let value1 = Value::rational(1.0); + let value2 = Value::rational(1.0); + assert_eq!(value1, value2); + assert_eq!(hash_value(&value1), hash_value(&value2)); + + let nan = Value::nan(); + assert_eq!(nan, nan); + assert_eq!(hash_value(&nan), hash_value(&nan)); + assert_ne!(hash_value(&nan), hash_value(&Value::rational(1.0))); +} + +#[test] +fn hash_object() { + let object1 = Value::object(Object::default()); + assert_eq!(object1, object1); + assert_eq!(object1, object1.clone()); + + let object2 = Value::object(Object::default()); + assert_ne!(object1, object2); + + assert_eq!(hash_value(&object1), hash_value(&object1.clone())); + assert_ne!(hash_value(&object1), hash_value(&object2)); } #[test] diff --git a/boa/src/environment/lexical_environment.rs b/boa/src/environment/lexical_environment.rs index 8e32c09945..3d6ff1c0a6 100644 --- a/boa/src/environment/lexical_environment.rs +++ b/boa/src/environment/lexical_environment.rs @@ -25,7 +25,7 @@ pub type Environment = Gc>>; /// Give each environment an easy way to declare its own type /// This helps with comparisons -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum EnvironmentType { Declarative, Function, @@ -34,7 +34,7 @@ pub enum EnvironmentType { } /// The scope of a given variable -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum VariableScope { /// The variable declaration is scoped to the current block (`let` and `const`) Block, @@ -42,13 +42,13 @@ pub enum VariableScope { Function, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct LexicalEnvironment { environment_stack: VecDeque, } /// An error that occurred during lexing or compiling of the source input. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct EnvironmentError { details: String, } diff --git a/boa/src/exec/exception.rs b/boa/src/exec/exception.rs index ddef2e82ee..d227662db4 100644 --- a/boa/src/exec/exception.rs +++ b/boa/src/exec/exception.rs @@ -8,8 +8,8 @@ use crate::{ }; impl Interpreter { - /// Throws a `RangeError` with the specified message. - pub fn throw_range_error(&mut self, message: M) -> ResultValue + /// Constructs a `RangeError` with the specified message. + pub fn construct_range_error(&mut self, message: M) -> Value where M: Into, { @@ -19,10 +19,19 @@ impl Interpreter { vec![Const::from(message.into()).into()], )) .run(self) + .expect_err("RangeError should always throw") } - /// Throws a `TypeError` with the specified message. - pub fn throw_type_error(&mut self, message: M) -> ResultValue + /// Throws a `RangeError` with the specified message. + pub fn throw_range_error(&mut self, message: M) -> ResultValue + where + M: Into, + { + Err(self.construct_range_error(message)) + } + + /// Constructs a `TypeError` with the specified message. + pub fn construct_type_error(&mut self, message: M) -> Value where M: Into, { @@ -32,5 +41,30 @@ impl Interpreter { vec![Const::from(message.into()).into()], )) .run(self) + .expect_err("TypeError should always throw") + } + + /// Throws a `TypeError` with the specified message. + pub fn throw_type_error(&mut self, message: M) -> ResultValue + where + M: Into, + { + Err(self.construct_type_error(message)) + } + + /// Constructs a `ReferenceError` with the specified message. + pub fn construct_reference_error(&mut self, _message: M) -> Value + where + M: Into, + { + unimplemented!("ReferenceError: is not implemented"); + } + + /// Throws a `ReferenceError` with the specified message. + pub fn throw_reference_error(&mut self, message: M) -> ResultValue + where + M: Into, + { + Err(self.construct_reference_error(message)) } } diff --git a/boa/src/exec/expression/mod.rs b/boa/src/exec/expression/mod.rs index 80d65236f5..59a59e1b5e 100644 --- a/boa/src/exec/expression/mod.rs +++ b/boa/src/exec/expression/mod.rs @@ -3,7 +3,7 @@ use super::{Executable, Interpreter, InterpreterState}; use crate::{ builtins::{ - object::{INSTANCE_PROTOTYPE, PROTOTYPE}, + object::{ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE}, value::{ResultValue, Type, Value, ValueData}, }, syntax::ast::node::{Call, New, Node}, @@ -71,12 +71,13 @@ impl Executable for New { this.set_internal_slot(INSTANCE_PROTOTYPE, func_object.get_field(PROTOTYPE)); match func_object.data() { - ValueData::Object(ref o) => o.clone().borrow_mut().func.as_ref().unwrap().construct( - &mut func_object.clone(), - &v_args, - interpreter, - &mut this, - ), + ValueData::Object(ref obj) => { + let obj = (**obj).borrow(); + if let ObjectData::Function(ref func) = obj.data { + return func.construct(func_object.clone(), &mut this, &v_args, interpreter); + } + interpreter.throw_type_error("not a constructor") + } _ => Ok(Value::undefined()), } } diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 407b6a1c9e..aca0cd6711 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -23,10 +23,7 @@ mod try_node; use crate::{ builtins::{ function::{Function as FunctionObject, FunctionBody, ThisMode}, - object::{ - internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, INSTANCE_PROTOTYPE, - PROTOTYPE, - }, + object::{Object, ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE}, property::Property, value::{ResultValue, Type, Value, ValueData}, BigInt, Number, @@ -62,31 +59,48 @@ pub enum PreferredType { /// A Javascript intepreter #[derive(Debug)] pub struct Interpreter { - current_state: InterpreterState, + /// the current state of the interpreter. + state: InterpreterState, /// realm holds both the global object and the environment pub realm: Realm, + + /// This is for generating an unique internal `Symbol` hash. + symbol_count: u32, } impl Interpreter { /// Creates a new interpreter. pub fn new(realm: Realm) -> Self { Self { - current_state: InterpreterState::Executing, + state: InterpreterState::Executing, realm, + symbol_count: 0, } } /// Retrieves the `Realm` of this executor. + #[inline] pub(crate) fn realm(&self) -> &Realm { &self.realm } /// Retrieves the `Realm` of this executor as a mutable reference. + #[inline] pub(crate) fn realm_mut(&mut self) -> &mut Realm { &mut self.realm } + /// Generates a new `Symbol` internal hash. + /// + /// This currently is an incremented value. + #[inline] + pub(crate) fn generate_hash(&mut self) -> u32 { + let hash = self.symbol_count; + self.symbol_count += 1; + hash + } + /// Utility to create a function Value for Function Declarations, Arrow Functions or Function Expressions pub(crate) fn create_function( &mut self, @@ -127,8 +141,8 @@ impl Interpreter { callable, ); - let mut new_func = Object::function(); - new_func.set_func(func); + let new_func = Object::function(func); + let val = Value::from(new_func); val.set_internal_slot(INSTANCE_PROTOTYPE, function_prototype.clone()); val.set_field(PROTOTYPE, proto); @@ -147,8 +161,10 @@ impl Interpreter { match *f.data() { ValueData::Object(ref obj) => { let obj = (**obj).borrow(); - let func = obj.func.as_ref().expect("Expected function"); - func.call(&mut f.clone(), arguments_list, self, this) + if let ObjectData::Function(ref func) = obj.data { + return func.call(f.clone(), this, arguments_list, self); + } + self.throw_type_error("not a function") } _ => Err(Value::undefined()), } @@ -165,8 +181,7 @@ impl Interpreter { ValueData::Integer(integer) => Ok(integer.to_string()), ValueData::String(string) => Ok(string.clone()), ValueData::Symbol(_) => { - self.throw_type_error("can't convert symbol to string")?; - unreachable!(); + Err(self.construct_type_error("can't convert symbol to string")) } ValueData::BigInt(ref bigint) => Ok(bigint.to_string()), ValueData::Object(_) => { @@ -180,13 +195,9 @@ impl Interpreter { #[allow(clippy::wrong_self_convention)] pub fn to_bigint(&mut self, value: &Value) -> Result { match value.data() { - ValueData::Null => { - self.throw_type_error("cannot convert null to a BigInt")?; - unreachable!(); - } + ValueData::Null => Err(self.construct_type_error("cannot convert null to a BigInt")), ValueData::Undefined => { - self.throw_type_error("cannot convert undefined to a BigInt")?; - unreachable!(); + Err(self.construct_type_error("cannot convert undefined to a BigInt")) } ValueData::String(ref string) => Ok(BigInt::from_string(string, self)?), ValueData::Boolean(true) => Ok(BigInt::from(1)), @@ -196,11 +207,10 @@ impl Interpreter { if let Ok(bigint) = BigInt::try_from(*num) { return Ok(bigint); } - self.throw_type_error(format!( + Err(self.construct_type_error(format!( "The number {} cannot be converted to a BigInt because it is not an integer", num - ))?; - unreachable!(); + ))) } ValueData::BigInt(b) => Ok(b.clone()), ValueData::Object(_) => { @@ -208,8 +218,7 @@ impl Interpreter { self.to_bigint(&primitive) } ValueData::Symbol(_) => { - self.throw_type_error("cannot convert Symbol to a BigInt")?; - unreachable!(); + Err(self.construct_type_error("cannot convert Symbol to a BigInt")) } } } @@ -226,13 +235,11 @@ impl Interpreter { let integer_index = self.to_integer(value)?; if integer_index < 0 { - self.throw_range_error("Integer index must be >= 0")?; - unreachable!(); + return Err(self.construct_range_error("Integer index must be >= 0")); } if integer_index > 2i64.pow(53) - 1 { - self.throw_range_error("Integer index must be less than 2**(53) - 1")?; - unreachable!() + return Err(self.construct_range_error("Integer index must be less than 2**(53) - 1")); } Ok(integer_index as usize) @@ -257,24 +264,16 @@ impl Interpreter { /// See: https://tc39.es/ecma262/#sec-tonumber #[allow(clippy::wrong_self_convention)] pub fn to_number(&mut self, value: &Value) -> Result { - match *value.deref().borrow() { + match *value.data() { 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::() { - 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 + // TODO: this is probably not 100% correct, see https://tc39.es/ecma262/#sec-tonumber-applied-to-the-string-type + ValueData::String(ref string) => Ok(string.parse().unwrap_or(f64::NAN)), 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::Symbol(_) => Err(self.construct_type_error("argument must not be a symbol")), + ValueData::BigInt(_) => Err(self.construct_type_error("argument must not be a bigint")), ValueData::Object(_) => { let prim_value = self.to_primitive(&mut (value.clone()), PreferredType::Number); self.to_number(&prim_value) @@ -282,13 +281,39 @@ impl Interpreter { } } + /// It returns value converted to a numeric value of type Number or BigInt. + /// + /// See: https://tc39.es/ecma262/#sec-tonumeric + #[allow(clippy::wrong_self_convention)] + pub fn to_numeric(&mut self, value: &Value) -> ResultValue { + let primitive = self.to_primitive(&mut value.clone(), PreferredType::Number); + if primitive.is_bigint() { + return Ok(primitive); + } + Ok(Value::from(self.to_number(&primitive)?)) + } + + /// This is a more specialized version of `to_numeric`. + /// + /// It returns value converted to a numeric value of type `Number`. + /// + /// See: https://tc39.es/ecma262/#sec-tonumeric + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_numeric_number(&mut self, value: &Value) -> Result { + let primitive = self.to_primitive(&mut value.clone(), PreferredType::Number); + if let Some(ref bigint) = primitive.as_bigint() { + return Ok(bigint.to_f64()); + } + Ok(self.to_number(&primitive)?) + } + /// 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 pub(crate) fn extract_array_properties(&mut self, value: &Value) -> Result, ()> { if let ValueData::Object(ref x) = *value.deref().borrow() { // Check if object is array - if x.deref().borrow().kind == ObjectKind::Array { + if let ObjectData::Array = x.deref().borrow().data { let length: i32 = self.value_to_rust_number(&value.get_field("length")) as i32; let values: Vec = (0..length) .map(|idx| value.get_field(idx.to_string())) @@ -389,58 +414,76 @@ impl Interpreter { pub(crate) fn to_object(&mut self, value: &Value) -> ResultValue { match value.data() { ValueData::Undefined | ValueData::Null => Err(Value::undefined()), - ValueData::Integer(_) => { + ValueData::Boolean(boolean) => { let proto = self .realm .environment - .get_binding_value("Number") + .get_binding_value("Boolean") .get_field(PROTOTYPE); - let number_obj = Value::new_object_from_prototype(proto, ObjectKind::Number); - number_obj.set_internal_slot("NumberData", value.clone()); - Ok(number_obj) + + Ok(Value::new_object_from_prototype( + proto, + ObjectData::Boolean(*boolean), + )) } - ValueData::Boolean(_) => { + ValueData::Integer(integer) => { let proto = self .realm .environment - .get_binding_value("Boolean") + .get_binding_value("Number") .get_field(PROTOTYPE); - - let bool_obj = Value::new_object_from_prototype(proto, ObjectKind::Boolean); - bool_obj.set_internal_slot("BooleanData", value.clone()); - Ok(bool_obj) + Ok(Value::new_object_from_prototype( + proto, + ObjectData::Number(f64::from(*integer)), + )) } - ValueData::Rational(_) => { + ValueData::Rational(rational) => { let proto = self .realm .environment .get_binding_value("Number") .get_field(PROTOTYPE); - let number_obj = Value::new_object_from_prototype(proto, ObjectKind::Number); - number_obj.set_internal_slot("NumberData", value.clone()); - Ok(number_obj) + + Ok(Value::new_object_from_prototype( + proto, + ObjectData::Number(*rational), + )) } - ValueData::String(_) => { + ValueData::String(ref string) => { let proto = self .realm .environment .get_binding_value("String") .get_field(PROTOTYPE); - let string_obj = Value::new_object_from_prototype(proto, ObjectKind::String); - string_obj.set_internal_slot("StringData", value.clone()); - Ok(string_obj) + + Ok(Value::new_object_from_prototype( + proto, + ObjectData::String(string.clone()), + )) + } + ValueData::Symbol(ref symbol) => { + let proto = self + .realm + .environment + .get_binding_value("Symbol") + .get_field(PROTOTYPE); + + Ok(Value::new_object_from_prototype( + proto, + ObjectData::Symbol(symbol.clone()), + )) } - ValueData::Object(_) | ValueData::Symbol(_) => Ok(value.clone()), - ValueData::BigInt(_) => { + ValueData::BigInt(ref bigint) => { let proto = self .realm .environment .get_binding_value("BigInt") .get_field(PROTOTYPE); - let bigint_obj = Value::new_object_from_prototype(proto, ObjectKind::BigInt); - bigint_obj.set_internal_slot("BigIntData", value.clone()); + let bigint_obj = + Value::new_object_from_prototype(proto, ObjectData::BigInt(bigint.clone())); Ok(bigint_obj) } + ValueData::Object(_) => Ok(value.clone()), } } @@ -493,12 +536,34 @@ impl Interpreter { } } + #[inline] pub(crate) fn set_current_state(&mut self, new_state: InterpreterState) { - self.current_state = new_state + self.state = new_state } + #[inline] pub(crate) fn get_current_state(&self) -> &InterpreterState { - &self.current_state + &self.state + } + + /// Check if the `Value` can be converted to an `Object` + /// + /// The abstract operation `RequireObjectCoercible` takes argument argument. + /// It throws an error if argument is a value that cannot be converted to an Object using `ToObject`. + /// It is defined by [Table 15][table] + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [table]: https://tc39.es/ecma262/#table-14 + /// [spec]: https://tc39.es/ecma262/#sec-requireobjectcoercible + #[inline] + pub fn require_object_coercible<'a>(&mut self, value: &'a Value) -> Result<&'a Value, Value> { + if value.is_null_or_undefined() { + Err(self.construct_type_error("cannot convert null or undefined to Object")) + } else { + Ok(value) + } } }