diff --git a/boa/src/builtins/boolean.rs b/boa/src/builtins/boolean.rs index 08ae55cc1d..28a42eb21d 100644 --- a/boa/src/builtins/boolean.rs +++ b/boa/src/builtins/boolean.rs @@ -1,7 +1,7 @@ use crate::{ builtins::{ function::NativeFunctionData, - object::{Object, ObjectKind, PROTOTYPE}, + object::{internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, PROTOTYPE}, value::{to_value, ResultValue, Value, ValueData}, }, exec::Interpreter, diff --git a/boa/src/builtins/function.rs b/boa/src/builtins/function.rs index 0cfa8efd64..d33e538b9c 100644 --- a/boa/src/builtins/function.rs +++ b/boa/src/builtins/function.rs @@ -1,6 +1,6 @@ use crate::{ builtins::{ - object::Object, + object::{internal_methods_trait::ObjectInternalMethods, Object}, property::Property, value::{to_value, ResultValue, Value, ValueData}, }, diff --git a/boa/src/builtins/number.rs b/boa/src/builtins/number.rs index 8426a17647..362ecd3d42 100644 --- a/boa/src/builtins/number.rs +++ b/boa/src/builtins/number.rs @@ -1,7 +1,7 @@ use crate::{ builtins::{ function::NativeFunctionData, - object::{Object, ObjectKind, PROTOTYPE}, + object::{internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, PROTOTYPE}, value::{to_value, ResultValue, Value, ValueData}, }, exec::Interpreter, diff --git a/boa/src/builtins/object/internal_methods_trait.rs b/boa/src/builtins/object/internal_methods_trait.rs new file mode 100644 index 0000000000..a2a1b47afc --- /dev/null +++ b/boa/src/builtins/object/internal_methods_trait.rs @@ -0,0 +1,167 @@ +use crate::builtins::{ + object::{Object, PROTOTYPE}, + property::Property, + value::{to_value, Value, ValueData}, +}; +use gc::Gc; +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 +/// +pub trait ObjectInternalMethods { + /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p + 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() { + let parent: Value = self.get_prototype_of(); + if !parent.is_null() { + // the parent value variant should be an object + // In the unlikely event it isn't return false + return match *parent { + ValueData::Object(ref obj) => obj.borrow().has_property(val), + _ => false, + }; + } + return false; + } + + true + } + + /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible + fn is_extensible(&self) -> bool { + let val = self.get_internal_slot("extensible"); + match *val.deref().borrow() { + ValueData::Boolean(b) => b, + _ => false, + } + } + + /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions + fn prevent_extensions(&mut self) -> bool { + self.set_internal_slot("extensible", to_value(false)); + true + } + + // [[Delete]] + 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 + .value + .clone() + .expect("unable to get value") + .is_undefined() + { + return true; + } + if desc.configurable.expect("unable to get value") { + self.remove_property(&prop_key.to_string()); + return true; + } + + false + } + + // [[Get]] + 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() + || desc + .value + .clone() + .expect("Failed to get object") + .is_undefined() + { + // parent will either be null or an Object + let parent = self.get_prototype_of(); + if parent.is_null() { + return Gc::new(ValueData::Undefined); + } + + let parent_obj = Object::from(&parent).expect("Failed to get object"); + + return parent_obj.get(val); + } + + if desc.is_data_descriptor() { + return desc.value.clone().expect("failed to extract value"); + } + + let getter = desc.get.clone(); + if getter.is_none() || getter.expect("Failed to get object").is_undefined() { + return Gc::new(ValueData::Undefined); + } + + // TODO!!!!! Call getter from here + Gc::new(ValueData::Undefined) + } + + /// [[Set]] + /// + fn set(&mut self, field: Value, val: Value) -> bool { + // [1] + debug_assert!(Property::is_property_key(&field)); + + // Fetch property key + let mut own_desc = self.get_own_property(&field); + // [2] + if own_desc.is_none() { + let parent = self.get_prototype_of(); + if !parent.is_null() { + // TODO: come back to this + } + own_desc = Property::new() + .writable(true) + .enumerable(true) + .configurable(true); + } + // [3] + if own_desc.is_data_descriptor() { + if !own_desc.writable.unwrap() { + return false; + } + + // Change value on the current descriptor + own_desc = own_desc.value(val); + return self.define_own_property(field.to_string(), own_desc); + } + // [4] + debug_assert!(own_desc.is_accessor_descriptor()); + match own_desc.set { + None => false, + Some(_) => { + unimplemented!(); + } + } + } + + /// 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; + + /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v + fn set_prototype_of(&mut self, val: Value) -> bool; + + /// 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 { + self.get_internal_slot(PROTOTYPE) + } + + fn define_own_property(&mut self, property_key: String, desc: Property) -> bool; + + /// Utility function to get an immutable internal slot or Null + fn get_internal_slot(&self, name: &str) -> Value; + + fn set_internal_slot(&mut self, name: &str, val: Value); + + fn insert_property(&mut self, name: String, p: Property); + + fn remove_property(&mut self, name: &str); +} diff --git a/boa/src/builtins/object.rs b/boa/src/builtins/object/mod.rs similarity index 75% rename from boa/src/builtins/object.rs rename to boa/src/builtins/object/mod.rs index 258f0a806e..f6b09d4f60 100644 --- a/boa/src/builtins/object.rs +++ b/boa/src/builtins/object/mod.rs @@ -10,8 +10,10 @@ use gc::Gc; use gc_derive::{Finalize, Trace}; use std::{borrow::Borrow, collections::HashMap, ops::Deref}; +pub use internal_methods_trait::ObjectInternalMethods; pub use internal_state::{InternalState, InternalStateCell}; +pub mod internal_methods_trait; mod internal_state; /// Static `prototype`, usually set on constructors as a key to point to their respective prototype object. @@ -35,126 +37,9 @@ pub struct Object { pub state: Option>, } -impl Object { - /// Return a new ObjectData struct, with `kind` set to Ordinary - pub fn default() -> Self { - let mut object = Object { - kind: ObjectKind::Ordinary, - internal_slots: Box::new(HashMap::new()), - properties: Box::new(HashMap::new()), - sym_properties: Box::new(HashMap::new()), - state: None, - }; - - object.set_internal_slot("extensible", to_value(true)); - object - } - - /// ObjectCreate is used to specify the runtime creation of new ordinary objects - /// - /// https://tc39.es/ecma262/#sec-objectcreate - // TODO: proto should be a &Value here - pub fn create(proto: Value) -> Object { - let mut obj = Object::default(); - obj.internal_slots - .insert(INSTANCE_PROTOTYPE.to_string(), proto); - obj.internal_slots - .insert("extensible".to_string(), to_value(true)); - obj - } - - /// Utility function to get an immutable internal slot or Null - pub fn get_internal_slot(&self, name: &str) -> Value { - match self.internal_slots.get(name) { - Some(v) => v.clone(), - None => Gc::new(ValueData::Null), - } - } - - /// Utility function to set an internal slot - pub fn set_internal_slot(&mut self, name: &str, val: Value) { - self.internal_slots.insert(name.to_string(), val); - } - - /// Utility function to set an internal slot which is a function - pub fn set_internal_method(&mut self, name: &str, val: NativeFunctionData) { - self.internal_slots.insert(name.to_string(), to_value(val)); - } - - /// Utility function to set a method on this object - /// The native function will live in the `properties` field of the Object - pub fn set_method(&mut self, name: &str, val: NativeFunctionData) { - self.properties - .insert(name.to_string(), Property::default().value(to_value(val))); - } - - /// Return a new Boolean object whose [[BooleanData]] internal slot is set to argument. - fn from_boolean(argument: &Value) -> Self { - let mut obj = Object { - kind: ObjectKind::Boolean, - internal_slots: Box::new(HashMap::new()), - properties: Box::new(HashMap::new()), - sym_properties: Box::new(HashMap::new()), - state: 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 = Object { - kind: ObjectKind::Number, - internal_slots: Box::new(HashMap::new()), - properties: Box::new(HashMap::new()), - sym_properties: Box::new(HashMap::new()), - state: 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 = Object { - kind: ObjectKind::String, - internal_slots: Box::new(HashMap::new()), - properties: Box::new(HashMap::new()), - sym_properties: Box::new(HashMap::new()), - state: None, - }; - - obj.internal_slots - .insert("StringData".to_string(), argument.clone()); - obj - } - - // https://tc39.es/ecma262/#sec-toobject - pub fn from(value: &Value) -> Result { - match *value.deref().borrow() { - ValueData::Boolean(_) => Ok(Self::from_boolean(value)), - ValueData::Number(_) => Ok(Self::from_number(value)), - ValueData::String(_) => Ok(Self::from_string(value)), - ValueData::Object(ref obj) => Ok(obj.borrow().clone()), - _ => Err(()), - } - } - - /// Returns either the prototype or null - /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof - pub fn get_prototype_of(&self) -> Value { - match self.internal_slots.get(PROTOTYPE) { - Some(v) => v.clone(), - None => Gc::new(ValueData::Null), - } - } - +impl ObjectInternalMethods for Object { /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v - pub fn set_prototype_of(&mut self, val: Value) -> bool { + 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 current == val { @@ -179,26 +64,30 @@ impl Object { true } - /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible - pub fn is_extensible(&self) -> bool { - match self.internal_slots.get("extensible") { - Some(ref v) => { - // try dereferencing it: `&(*v).clone()` - from_value((*v).clone()).expect("boolean expected") - } - None => false, - } + fn insert_property(&mut self, name: String, p: Property) { + self.properties.insert(name, p); } - /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions - pub fn prevent_extensions(&mut self) -> bool { - self.set_internal_slot("extensible", to_value(false)); - true + fn remove_property(&mut self, name: &str) { + self.properties.remove(name); + } + + /// Utility function to set an internal slot + fn set_internal_slot(&mut self, name: &str, val: Value) { + self.internal_slots.insert(name.to_string(), val); + } + + /// Utility function to get an immutable internal slot or Null + fn get_internal_slot(&self, name: &str) -> Value { + match self.internal_slots.get(name) { + Some(v) => v.clone(), + None => Gc::new(ValueData::Null), + } } /// 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. - pub fn get_own_property(&self, prop: &Value) -> Property { + fn get_own_property(&self, prop: &Value) -> Property { debug_assert!(Property::is_property_key(prop)); // Prop could either be a String or Symbol match *(*prop) { @@ -254,28 +143,8 @@ impl Object { } } - /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p - 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() { - let parent: Value = self.get_prototype_of(); - if !parent.is_null() { - // the parent value variant should be an object - // In the unlikely event it isn't return false - return match *parent { - ValueData::Object(ref obj) => obj.borrow().has_property(val), - _ => false, - }; - } - return false; - } - - true - } - #[allow(clippy::option_unwrap_used)] - pub fn define_own_property(&mut self, property_key: String, desc: Property) -> bool { + fn define_own_property(&mut self, property_key: String, desc: Property) -> bool { let mut current = self.get_own_property(&to_value(property_key.to_string())); let extensible = self.is_extensible(); @@ -399,98 +268,101 @@ impl Object { self.properties.insert(property_key, desc); true } +} - // [[Delete]] - 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 - .value - .clone() - .expect("unable to get value") - .is_undefined() - { - return true; - } - if desc.configurable.expect("unable to get value") { - self.properties.remove(&prop_key.to_string()); - return true; - } +impl Object { + /// Return a new ObjectData struct, with `kind` set to Ordinary + pub fn default() -> Self { + let mut object = Object { + kind: ObjectKind::Ordinary, + internal_slots: Box::new(HashMap::new()), + properties: Box::new(HashMap::new()), + sym_properties: Box::new(HashMap::new()), + state: None, + }; - false + object.set_internal_slot("extensible", to_value(true)); + object } - // [[Get]] - 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() - || desc - .value - .clone() - .expect("Failed to get object") - .is_undefined() - { - // parent will either be null or an Object - let parent = self.get_prototype_of(); - if parent.is_null() { - return Gc::new(ValueData::Undefined); - } + /// ObjectCreate is used to specify the runtime creation of new ordinary objects + /// + /// https://tc39.es/ecma262/#sec-objectcreate + // TODO: proto should be a &Value here + pub fn create(proto: Value) -> Object { + let mut obj = Object::default(); + obj.internal_slots + .insert(INSTANCE_PROTOTYPE.to_string(), proto); + obj.internal_slots + .insert("extensible".to_string(), to_value(true)); + obj + } - let parent_obj = Object::from(&parent).expect("Failed to get object"); + /// Utility function to set an internal slot which is a function + pub fn set_internal_method(&mut self, name: &str, val: NativeFunctionData) { + self.internal_slots.insert(name.to_string(), to_value(val)); + } - return parent_obj.get(val); - } + /// Utility function to set a method on this object + /// The native function will live in the `properties` field of the Object + pub fn set_method(&mut self, name: &str, val: NativeFunctionData) { + self.properties + .insert(name.to_string(), Property::default().value(to_value(val))); + } - if desc.is_data_descriptor() { - return desc.value.clone().expect("failed to extract value"); - } + /// Return a new Boolean object whose [[BooleanData]] internal slot is set to argument. + fn from_boolean(argument: &Value) -> Self { + let mut obj = Object { + kind: ObjectKind::Boolean, + internal_slots: Box::new(HashMap::new()), + properties: Box::new(HashMap::new()), + sym_properties: Box::new(HashMap::new()), + state: None, + }; - let getter = desc.get.clone(); - if getter.is_none() || getter.expect("Failed to get object").is_undefined() { - return Gc::new(ValueData::Undefined); - } + 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 = Object { + kind: ObjectKind::Number, + internal_slots: Box::new(HashMap::new()), + properties: Box::new(HashMap::new()), + sym_properties: Box::new(HashMap::new()), + state: None, + }; - // TODO!!!!! Call getter from here - Gc::new(ValueData::Undefined) + obj.internal_slots + .insert("NumberData".to_string(), argument.clone()); + obj } - /// [[Set]] - /// - pub fn set(&mut self, field: Value, val: Value) -> bool { - // [1] - debug_assert!(Property::is_property_key(&field)); - - // Fetch property key - let mut own_desc = self.get_own_property(&field); - // [2] - if own_desc.is_none() { - let parent = self.get_prototype_of(); - if !parent.is_null() { - // TODO: come back to this - } - own_desc = Property::new() - .writable(true) - .enumerable(true) - .configurable(true); - } - // [3] - if own_desc.is_data_descriptor() { - if !own_desc.writable.unwrap() { - return false; - } + /// Return a new String object whose [[StringData]] internal slot is set to argument. + fn from_string(argument: &Value) -> Self { + let mut obj = Object { + kind: ObjectKind::String, + internal_slots: Box::new(HashMap::new()), + properties: Box::new(HashMap::new()), + sym_properties: Box::new(HashMap::new()), + state: None, + }; - // Change value on the current descriptor - own_desc = own_desc.value(val); - return self.define_own_property(field.to_string(), own_desc); - } - // [4] - debug_assert!(own_desc.is_accessor_descriptor()); - match own_desc.set { - None => false, - Some(_) => { - unimplemented!(); - } + obj.internal_slots + .insert("StringData".to_string(), argument.clone()); + obj + } + + // https://tc39.es/ecma262/#sec-toobject + pub fn from(value: &Value) -> Result { + match *value.deref().borrow() { + ValueData::Boolean(_) => Ok(Self::from_boolean(value)), + ValueData::Number(_) => Ok(Self::from_number(value)), + ValueData::String(_) => Ok(Self::from_string(value)), + ValueData::Object(ref obj) => Ok(obj.borrow().clone()), + _ => Err(()), } } } diff --git a/boa/src/builtins/symbol.rs b/boa/src/builtins/symbol.rs index c05e008985..9cc192f103 100644 --- a/boa/src/builtins/symbol.rs +++ b/boa/src/builtins/symbol.rs @@ -1,6 +1,9 @@ use crate::{ builtins::{ - object::{Object, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, + object::{ + internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, INSTANCE_PROTOTYPE, + PROTOTYPE, + }, value::{to_value, ResultValue, Value, ValueData}, }, exec::Interpreter, diff --git a/boa/src/builtins/value.rs b/boa/src/builtins/value.rs index 3c60a00c5b..dcc6c4c697 100644 --- a/boa/src/builtins/value.rs +++ b/boa/src/builtins/value.rs @@ -1,6 +1,9 @@ use crate::builtins::{ function::{Function, NativeFunction, NativeFunctionData}, - object::{InternalState, InternalStateCell, Object, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, + object::{ + internal_methods_trait::ObjectInternalMethods, InternalState, InternalStateCell, Object, + ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE, + }, property::Property, }; use gc::{Gc, GcCell}; diff --git a/boa/src/exec.rs b/boa/src/exec.rs index f014c48a43..af4b80f72e 100644 --- a/boa/src/exec.rs +++ b/boa/src/exec.rs @@ -2,7 +2,10 @@ use crate::{ builtins::{ array, function::{create_unmapped_arguments_object, Function, RegularFunction}, - object::{ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, + object::{ + internal_methods_trait::ObjectInternalMethods, ObjectKind, INSTANCE_PROTOTYPE, + PROTOTYPE, + }, value::{from_value, to_value, ResultValue, Value, ValueData}, }, environment::lexical_environment::{