Browse Source

Object specialization (#419)

pull/499/head
HalidOdat 4 years ago committed by GitHub
parent
commit
df13272fc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 53
      boa/src/builtins/array/mod.rs
  2. 44
      boa/src/builtins/bigint/mod.rs
  3. 96
      boa/src/builtins/boolean/mod.rs
  4. 5
      boa/src/builtins/console/mod.rs
  5. 27
      boa/src/builtins/error/mod.rs
  6. 27
      boa/src/builtins/error/range.rs
  7. 27
      boa/src/builtins/error/type.rs
  8. 116
      boa/src/builtins/function/mod.rs
  9. 33
      boa/src/builtins/global_this/mod.rs
  10. 269
      boa/src/builtins/json/mod.rs
  11. 999
      boa/src/builtins/math/mod.rs
  12. 50
      boa/src/builtins/mod.rs
  13. 32
      boa/src/builtins/nan/mod.rs
  14. 138
      boa/src/builtins/number/mod.rs
  15. 12
      boa/src/builtins/number/tests.rs
  16. 215
      boa/src/builtins/object/internal_methods.rs
  17. 634
      boa/src/builtins/object/mod.rs
  18. 49
      boa/src/builtins/regexp/mod.rs
  19. 88
      boa/src/builtins/string/mod.rs
  20. 172
      boa/src/builtins/symbol/mod.rs
  21. 2
      boa/src/builtins/symbol/tests.rs
  22. 4
      boa/src/builtins/value/conversions.rs
  23. 46
      boa/src/builtins/value/display.rs
  24. 7
      boa/src/builtins/value/equality.rs
  25. 54
      boa/src/builtins/value/hash.rs
  26. 361
      boa/src/builtins/value/mod.rs
  27. 64
      boa/src/builtins/value/tests.rs
  28. 8
      boa/src/environment/lexical_environment.rs
  29. 42
      boa/src/exec/exception.rs
  30. 15
      boa/src/exec/expression/mod.rs
  31. 197
      boa/src/exec/mod.rs

53
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))
}
}

44
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))
}
}

96
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<bool, Value> {
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))
}
}

5
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))
}

27
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))
}
}

27
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))
}
}

27
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))
}
}

116
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 {
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
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)
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
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 {
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
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)
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
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<N>(function: NativeFunctionData, name: N, parent: &Value, length: i32)
pub fn make_builtin_fn<N>(function: NativeFunctionData, name: N, parent: &Value, length: usize)
where
N: Into<String>,
{
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))
}

33
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())
}
}

269
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::<JSONValue>(
&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::<JSONValue>(
&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))
}
}

999
boa/src/builtins/math/mod.rs

File diff suppressed because it is too large Load Diff

50
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);
}
}

32
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))
}
}

138
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<f64, Value> {
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::<f64>() {
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

12
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]

215
boa/src/builtins/object/internal_methods_trait.rs → 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]]
/// <https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver>
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(&current, &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<N>(&mut self, name: N, p: Property)
where
N: Into<String>,
{
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<N>(&mut self, name: N, value: Value) -> Option<Property>
where
N: Into<String>,
{
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())
}
}

634
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<String, Value>,
internal_slots: FxHashMap<String, Value>,
/// Properties
pub properties: FxHashMap<String, Property>,
properties: FxHashMap<String, Property>,
/// Symbol Properties
pub sym_properties: FxHashMap<i32, Property>,
symbol_properties: FxHashMap<u32, Property>,
/// Some rust object that stores internal state
pub state: Option<InternalStateCell>,
/// Function
pub func: Option<Function>,
state: Option<InternalStateCell>,
}
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(&current, &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::<i32>()
.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::<i32>()
.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::<i32>()
.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(),
&current.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(),
&current.set.clone().unwrap(),
false,
)
{
return false;
}
if desc.get.is_some()
&& !same_value(
&desc.get.clone().unwrap(),
&current.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<Self, ()> {
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(_, _, _)`.
///
/// <https://github.com/Manishearth/rust-gc/blob/master/gc/src/trace.rs>
/// Waiting on <https://github.com/Manishearth/rust-gc/issues/87> 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<bool> {
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<f64> {
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<String, Value> {
&self.internal_slots
}
#[inline]
pub fn internal_slots_mut(&mut self) -> &mut FxHashMap<String, Value> {
&mut self.internal_slots
}
#[inline]
pub fn properties(&self) -> &FxHashMap<String, Property> {
&self.properties
}
#[inline]
pub fn properties_mut(&mut self) -> &mut FxHashMap<String, Property> {
&mut self.properties
}
#[inline]
pub fn symbol_properties(&self) -> &FxHashMap<u32, Property> {
&self.symbol_properties
}
#[inline]
pub fn symbol_properties_mut(&mut self) -> &mut FxHashMap<u32, Property> {
&mut self.symbol_properties
}
#[inline]
pub fn state(&self) -> &Option<InternalStateCell> {
&self.state
}
#[inline]
pub fn state_mut(&mut self) -> &mut Option<InternalStateCell> {
&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))
}

49
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))
}
}

88
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<StdString, Value> {
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))
}
}

172
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::<i32>()));
// 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<Box<str>>, 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<Self, Value> {
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))
}
}

2
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);
}

4
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)

46
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::<Vec<String>>()
@ -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!(

7
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,
}
}

54
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<H: Hasher>(&self, state: &mut H) {
self.0.to_bits().hash(state);
}
}
impl Hash for Value {
fn hash<H: Hasher>(&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),
}
}
}

361
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<Value, Value>;
/// A Garbage-collected Javascript value as represented in the interpreter.
#[derive(Debug, Clone, Trace, Finalize, Default)]
pub struct Value(pub(crate) Gc<ValueData>);
pub struct Value(Gc<ValueData>);
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<S>(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<JSONValue> = 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<GcCell<Object>>),
/// `Symbol` - A Symbol Type - Internally Symbols are similar to objects, except there are no properties, only internal slots.
Symbol(Box<GcCell<Object>>),
/// `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<GcCellRef<'_, Object>> {
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<GcCellRefMut<'_, Object>> {
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<GcCellRef<'_, Object>> {
/// 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<bool>,
) {
let _timer = BoaProfiler::global().start_event("Value::update_property", "value");
let obj: Option<Object> = 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<InternalStateCell> {
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<S: Any + InternalState, R, F: FnOnce(&S) -> 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<S, R, F>(&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<S: Any + InternalState, R, F: FnOnce(&mut S) -> 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<S, R, F>(&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::<usize>() {
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<S>(&self, field: S, property: Property) -> Property
where
S: Into<String>,
{
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<T: Any + InternalState>(&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

64
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]

8
boa/src/environment/lexical_environment.rs

@ -25,7 +25,7 @@ pub type Environment = Gc<GcCell<Box<dyn EnvironmentRecordTrait>>>;
/// 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<Environment>,
}
/// 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,
}

42
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<M>(&mut self, message: M) -> ResultValue
/// Constructs a `RangeError` with the specified message.
pub fn construct_range_error<M>(&mut self, message: M) -> Value
where
M: Into<String>,
{
@ -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<M>(&mut self, message: M) -> ResultValue
/// Throws a `RangeError` with the specified message.
pub fn throw_range_error<M>(&mut self, message: M) -> ResultValue
where
M: Into<String>,
{
Err(self.construct_range_error(message))
}
/// Constructs a `TypeError` with the specified message.
pub fn construct_type_error<M>(&mut self, message: M) -> Value
where
M: Into<String>,
{
@ -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<M>(&mut self, message: M) -> ResultValue
where
M: Into<String>,
{
Err(self.construct_type_error(message))
}
/// Constructs a `ReferenceError` with the specified message.
pub fn construct_reference_error<M>(&mut self, _message: M) -> Value
where
M: Into<String>,
{
unimplemented!("ReferenceError: is not implemented");
}
/// Throws a `ReferenceError` with the specified message.
pub fn throw_reference_error<M>(&mut self, message: M) -> ResultValue
where
M: Into<String>,
{
Err(self.construct_reference_error(message))
}
}

15
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()),
}
}

197
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<P, B>(
&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<BigInt, Value> {
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<f64, Value> {
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::<f64>() {
Ok(number) => Ok(number),
Err(_) => Ok(0.0),
}, // this is probably not 100% correct, see https://tc39.es/ecma262/#sec-tonumber-applied-to-the-string-type
// 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<f64, Value> {
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<Vec<Value>, ()> {
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<Value> = (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)
}
}
}

Loading…
Cancel
Save