diff --git a/benches/exec.rs b/benches/exec.rs index 41477be6d5..380dcffa8b 100644 --- a/benches/exec.rs +++ b/benches/exec.rs @@ -1,12 +1,23 @@ #[macro_use] extern crate criterion; +use boa::exec; use boa::realm::Realm; -use criterion::Criterion; +use criterion::{black_box, Criterion}; + +static SRC: &str = r#" +let a = Symbol(); +let b = Symbol(); +let c = Symbol(); +"#; + +fn symbol_creation(c: &mut Criterion) { + c.bench_function("Symbol Creation", move |b| b.iter(|| exec(black_box(SRC)))); +} fn create_realm(c: &mut Criterion) { c.bench_function("Create Realm", move |b| b.iter(|| Realm::create())); } -criterion_group!(benches, create_realm); +criterion_group!(benches, create_realm, symbol_creation); criterion_main!(benches); diff --git a/src/lib/builtins/array.rs b/src/lib/builtins/array.rs index 8252272dbb..0b1dc7ddf2 100644 --- a/src/lib/builtins/array.rs +++ b/src/lib/builtins/array.rs @@ -35,7 +35,7 @@ pub(crate) fn new_array(interpreter: &Interpreter) -> ResultValue { /// Utility function for creating array objects: `array_obj` can be any array with /// prototype already set (it will be wiped and recreated from `array_contents`) -fn construct_array(array_obj: &Value, array_contents: &[Value]) -> ResultValue { +pub fn construct_array(array_obj: &Value, array_contents: &[Value]) -> ResultValue { let array_obj_ptr = array_obj.clone(); // Wipe existing contents of the array object @@ -45,9 +45,17 @@ fn construct_array(array_obj: &Value, array_contents: &[Value]) -> ResultValue { array_obj_ptr.remove_prop(&n.to_string()); } - array_obj_ptr.set_field_slice("length", to_value(array_contents.len() as i32)); + // Create length + let length = Property::new() + .value(to_value(array_contents.len() as i32)) + .writable(true) + .configurable(false) + .enumerable(false); + + array_obj_ptr.set_prop("length".to_string(), length); + for (n, value) in array_contents.iter().enumerate() { - array_obj_ptr.set_field(n.to_string(), value.clone()); + array_obj_ptr.set_field_slice(&n.to_string(), value.clone()); } Ok(array_obj_ptr) } @@ -60,7 +68,7 @@ pub(crate) fn add_to_array_object(array_ptr: &Value, add_values: &[Value]) -> Re for (n, value) in add_values.iter().enumerate() { let new_index = orig_length.wrapping_add(n as i32); - array_ptr.set_field(new_index.to_string(), value.clone()); + array_ptr.set_field_slice(&new_index.to_string(), value.clone()); } array_ptr.set_field_slice( @@ -72,24 +80,37 @@ pub(crate) fn add_to_array_object(array_ptr: &Value, add_values: &[Value]) -> Re } /// Create a new array -pub fn make_array(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn make_array(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // Make a new Object which will internally represent the Array (mapping // between indices and values): this creates an Object with no prototype - this.set_field_slice("length", to_value(0_i32)); + + // Create length + let length = Property::new() + .value(to_value(args.len() as i32)) + .writable(true) + .configurable(false) + .enumerable(false); + + this.set_prop("length".to_string(), length); + + // Set Prototype + let array_prototype = ctx + .realm + .global_obj + .get_field_slice("Array") + .get_field_slice(PROTOTYPE); + + this.set_internal_slot(INSTANCE_PROTOTYPE, array_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); - match args.len() { - 0 => construct_array(this, &[]), - 1 => { - let array = construct_array(this, &[]).expect("Could not construct array"); - let size: i32 = from_value(args.get(0).expect("Could not get argument").clone()) - .expect("Could not convert argument to i32"); - array.set_field_slice("length", to_value(size)); - Ok(array) - } - _ => construct_array(this, args), + + // And finally add our arguments in + for (n, value) in args.iter().enumerate() { + this.set_field_slice(&n.to_string(), value.clone()); } + + Ok(this.clone()) } /// Get an array's length @@ -118,14 +139,14 @@ pub fn concat(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue let this_length: i32 = from_value(this.get_field_slice("length")).expect("Could not convert argument to i32"); for n in 0..this_length { - new_values.push(this.get_field(&n.to_string())); + new_values.push(this.get_field_slice(&n.to_string())); } for concat_array in args { let concat_length: i32 = from_value(concat_array.get_field_slice("length")) .expect("Could not convert argument to i32"); for n in 0..concat_length { - new_values.push(concat_array.get_field(&n.to_string())); + new_values.push(concat_array.get_field_slice(&n.to_string())); } } @@ -154,7 +175,7 @@ pub fn pop(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { return Ok(Gc::new(ValueData::Undefined)); } let pop_index = curr_length.wrapping_sub(1); - let pop_value: Value = this.get_field(&pop_index.to_string()); + let pop_value: Value = this.get_field_slice(&pop_index.to_string()); this.remove_prop(&pop_index.to_string()); this.set_field_slice("length", to_value(pop_index)); Ok(pop_value) @@ -177,7 +198,7 @@ pub fn join(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let length: i32 = from_value(this.get_field_slice("length")).expect("Could not convert argument to i32"); for n in 0..length { - let elem_str: String = this.get_field(&n.to_string()).to_string(); + let elem_str: String = this.get_field_slice(&n.to_string()).to_string(); elem_strs.push(elem_str); } @@ -201,17 +222,17 @@ pub fn reverse(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { let upper_exists = this.has_field(&upper.to_string()); let lower_exists = this.has_field(&lower.to_string()); - let upper_value = this.get_field(&upper.to_string()); - let lower_value = this.get_field(&lower.to_string()); + let upper_value = this.get_field_slice(&upper.to_string()); + let lower_value = this.get_field_slice(&lower.to_string()); if upper_exists && lower_exists { - this.set_field(upper.to_string(), lower_value); - this.set_field(lower.to_string(), upper_value); + this.set_field_slice(&upper.to_string(), lower_value); + this.set_field_slice(&lower.to_string(), upper_value); } else if upper_exists { - this.set_field(lower.to_string(), upper_value); + this.set_field_slice(&lower.to_string(), upper_value); this.remove_prop(&upper.to_string()); } else if lower_exists { - this.set_field(upper.to_string(), lower_value); + this.set_field_slice(&upper.to_string(), lower_value); this.remove_prop(&lower.to_string()); } } @@ -230,20 +251,20 @@ pub fn shift(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { if len == 0 { this.set_field_slice("length", to_value(0_i32)); // Since length is 0, this will be an Undefined value - return Ok(this.get_field(&0.to_string())); + return Ok(this.get_field_slice(&0.to_string())); } - let first: Value = this.get_field(&0.to_string()); + let first: Value = this.get_field_slice(&0.to_string()); for k in 1..len { let from = k.to_string(); let to = (k.wrapping_sub(1)).to_string(); - let from_value = this.get_field(&from); + let from_value = this.get_field_slice(&from); if from_value == Gc::new(ValueData::Undefined) { this.remove_prop(&to); } else { - this.set_field(to, from_value); + this.set_field_slice(&to, from_value); } } @@ -270,11 +291,11 @@ pub fn unshift(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue let from = (k.wrapping_sub(1)).to_string(); let to = (k.wrapping_add(arg_c).wrapping_sub(1)).to_string(); - let from_value = this.get_field(&from); + let from_value = this.get_field_slice(&from); if from_value == Gc::new(ValueData::Undefined) { this.remove_prop(&to); } else { - this.set_field(to, from_value); + this.set_field_slice(&to, from_value); } } for j in 0..arg_c { @@ -315,7 +336,7 @@ pub fn every(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Res let max_len: i32 = from_value(this.get_field_slice("length")).unwrap(); let mut len = max_len; while i < len { - let element = this.get_field(&i.to_string()); + let element = this.get_field_slice(&i.to_string()); let arguments = vec![element.clone(), to_value(i), this.clone()]; let result = interpreter.call(callback, &this_arg, arguments)?.is_true(); if !result { @@ -349,7 +370,7 @@ pub fn map(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Resul let values = (0..length) .map(|idx| { - let element = this.get_field(&idx.to_string()); + let element = this.get_field_slice(&idx.to_string()); let args = vec![element, to_value(idx), new.clone()]; @@ -400,7 +421,7 @@ pub fn index_of(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValu }; while idx < len { - let check_element = this.get_field(&idx.to_string()).clone(); + let check_element = this.get_field_slice(&idx.to_string()).clone(); if check_element == search_element { return Ok(to_value(idx)); @@ -449,7 +470,7 @@ pub fn last_index_of(this: &Value, args: &[Value], _: &mut Interpreter) -> Resul }; while idx >= 0 { - let check_element = this.get_field(&idx.to_string()).clone(); + let check_element = this.get_field_slice(&idx.to_string()).clone(); if check_element == search_element { return Ok(to_value(idx)); @@ -481,7 +502,7 @@ pub fn find(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Resu }; let len: i32 = from_value(this.get_field_slice("length")).unwrap(); for i in 0..len { - let element = this.get_field(&i.to_string()); + let element = this.get_field_slice(&i.to_string()); let arguments = vec![element.clone(), to_value(i), this.clone()]; let result = interpreter.call(callback, &this_arg, arguments)?; if result.is_true() { @@ -515,7 +536,7 @@ pub fn find_index(this: &Value, args: &[Value], interpreter: &mut Interpreter) - from_value(this.get_field_slice("length")).expect("Could not get `length` property."); for i in 0..length { - let element = this.get_field(&i.to_string()); + let element = this.get_field_slice(&i.to_string()); let arguments = vec![element.clone(), to_value(i), this.clone()]; let result = interpreter.call(predicate_arg, &this_arg, arguments)?; @@ -556,7 +577,7 @@ pub fn fill(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { }; for i in start..fin { - this.set_field(i.to_string(), value.clone()); + this.set_field_slice(&i.to_string(), value.clone()); } Ok(this.clone()) @@ -572,7 +593,7 @@ pub fn includes_value(this: &Value, args: &[Value], _: &mut Interpreter) -> Resu from_value(this.get_field_slice("length")).expect("Could not get `length` property."); for idx in 0..length { - let check_element = this.get_field(&idx.to_string()).clone(); + let check_element = this.get_field_slice(&idx.to_string()).clone(); if check_element == search_element { return Ok(to_value(true)); @@ -618,7 +639,10 @@ pub fn slice(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Res let span = max(to.wrapping_sub(from), 0); let mut new_array_len: i32 = 0; for i in from..from.wrapping_add(span) { - new_array.set_field(new_array_len.to_string(), this.get_field(&i.to_string())); + new_array.set_field_slice( + &new_array_len.to_string(), + this.get_field_slice(&i.to_string()), + ); new_array_len = new_array_len.wrapping_add(1); } new_array.set_field_slice("length", to_value(new_array_len)); @@ -628,14 +652,15 @@ pub fn slice(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Res /// Create a new `Array` object pub fn create_constructor(global: &Value) -> Value { // Create Constructor - let mut array_constructor = Object::default(); + let object_prototype = global.get_field_slice("Object").get_field_slice(PROTOTYPE); + let mut array_constructor = Object::create(object_prototype); array_constructor.kind = ObjectKind::Function; array_constructor.set_internal_method("construct", make_array); // Todo: add call function array_constructor.set_internal_method("call", make_array); // Create prototype - let array_prototype = ValueData::new_obj(Some(global)); + let array_prototype = ValueData::new_obj(None); let length = Property::default().get(to_value(get_array_length as NativeFunctionData)); @@ -1127,7 +1152,7 @@ mod tests { // test object reference forward(&mut engine, "a = (new Array(3)).fill({});"); forward(&mut engine, "a[0].hi = 'hi';"); - assert_eq!(forward(&mut engine, "a[1].hi"), String::from("hi")); + assert_eq!(forward(&mut engine, "a[0].hi"), String::from("hi")); } #[test] diff --git a/src/lib/builtins/console.rs b/src/lib/builtins/console.rs index 6f9057056a..58dca49161 100644 --- a/src/lib/builtins/console.rs +++ b/src/lib/builtins/console.rs @@ -94,6 +94,13 @@ fn log_string_from(x: Value) -> String { } s } + 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()"), + } + } _ => from_value::(x.clone()).expect("Could not convert value to String"), } diff --git a/src/lib/builtins/mod.rs b/src/lib/builtins/mod.rs index 3d549fa445..a1c89dada9 100644 --- a/src/lib/builtins/mod.rs +++ b/src/lib/builtins/mod.rs @@ -1,5 +1,7 @@ /// The global `Array` object pub mod array; +/// the global `Symbol` Object +pub mod symbol; // The global `Boolean` object pub mod boolean; /// The global `console` object @@ -16,11 +18,11 @@ pub mod math; pub mod number; /// The global `Object` object pub mod object; +/// Property, used by `Object` +pub mod property; /// The global 'RegExp' object pub mod regexp; /// The global `String` object pub mod string; /// Javascript values, utility methods and conversion between Javascript values and Rust values pub mod value; -// Property, used by `Object` -pub mod property; diff --git a/src/lib/builtins/number.rs b/src/lib/builtins/number.rs index a0762d9aa0..6b99046235 100644 --- a/src/lib/builtins/number.rs +++ b/src/lib/builtins/number.rs @@ -20,7 +20,7 @@ fn to_number(value: &Value) -> Value { to_value(0) } } - ValueData::Function(_) | ValueData::Undefined => to_value(f64::NAN), + ValueData::Function(_) | ValueData::Symbol(_) | ValueData::Undefined => to_value(f64::NAN), ValueData::Integer(i) => to_value(f64::from(i)), ValueData::Object(ref o) => (o).deref().borrow().get_internal_slot("NumberData"), ValueData::Null => to_value(0), diff --git a/src/lib/builtins/object.rs b/src/lib/builtins/object.rs index 9c42c4f7d3..6bac66b72b 100644 --- a/src/lib/builtins/object.rs +++ b/src/lib/builtins/object.rs @@ -30,7 +30,7 @@ pub struct Object { /// Properties pub properties: Box>, /// Symbol Properties - pub sym_properties: Box>, + pub sym_properties: Box>, /// Some rust object that stores internal state pub state: Option>, } @@ -38,18 +38,22 @@ pub struct Object { impl Object { /// Return a new ObjectData struct, with `kind` set to Ordinary pub fn default() -> Self { - Object { + 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 @@ -77,6 +81,13 @@ impl Object { 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 { @@ -184,28 +195,62 @@ impl Object { self.set_internal_slot("extensible", to_value(false)); 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. pub fn get_own_property(&self, prop: &Value) -> Property { debug_assert!(Property::is_property_key(prop)); - match self.properties.get(&prop.to_string()) { - // 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(); + // Prop could either be a String or Symbol + match *(*prop) { + ValueData::String(ref st) => { + match self.properties.get(st) { + // If O does not have an own property with key P, return undefined. + // In this case we return a new empty Property + None => Property::default(), + Some(ref v) => { + let mut d = Property::default(); + if v.is_data_descriptor() { + d.value = v.value.clone(); + d.writable = v.writable; + } else { + debug_assert!(v.is_accessor_descriptor()); + d.get = v.get.clone(); + d.set = v.set.clone(); + } + d.enumerable = v.enumerable; + d.configurable = v.configurable; + d + } + } + } + ValueData::Symbol(ref sym) => { + let sym_id = sym + .borrow() + .get_internal_slot("SymbolData") + .to_string() + .parse::() + .expect("Could not get Symbol ID"); + match self.sym_properties.get(&sym_id) { + // If O does not have an own property with key P, return undefined. + // In this case we return a new empty Property + None => Property::default(), + Some(ref v) => { + let mut d = Property::default(); + if v.is_data_descriptor() { + d.value = v.value.clone(); + d.writable = v.writable; + } else { + debug_assert!(v.is_accessor_descriptor()); + d.get = v.get.clone(); + d.set = v.set.clone(); + } + d.enumerable = v.enumerable; + d.configurable = v.configurable; + d + } } - d.enumerable = v.enumerable; - d.configurable = v.configurable; - d } + _ => Property::default(), } } @@ -240,20 +285,18 @@ impl Object { if !extensible { return false; } - - let mut p = Property::new(); - if desc.is_generic_descriptor() || desc.is_data_descriptor() { - p.value = Some(desc.value.clone().unwrap_or_default()); - p.writable = Some(desc.writable.unwrap_or_default()); - p.configurable = Some(desc.configurable.unwrap_or_default()); - p.enumerable = Some(desc.enumerable.unwrap_or_default()); + if desc.value.is_some() && desc.value.clone().unwrap().is_symbol() { + let sym_id = desc + .value + .clone() + .unwrap() + .to_string() + .parse::() + .expect("parsing failed"); + self.sym_properties.insert(sym_id, desc); } else { - p.get = Some(desc.get.clone().unwrap_or_default()); - p.set = Some(desc.set.clone().unwrap_or_default()); - p.configurable = Some(desc.configurable.unwrap_or_default()); - p.enumerable = Some(desc.enumerable.unwrap_or_default()); - }; - self.properties.insert(property_key, p); + self.properties.insert(property_key, desc); + } return true; } // If every field is absent we don't need to set anything @@ -262,13 +305,13 @@ impl Object { } // 4 - if current.configurable.unwrap_or(false) { + 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()) + && (desc.enumerable.as_ref().unwrap() != current.enumerable.as_ref().unwrap()) { return false; } @@ -276,7 +319,6 @@ impl Object { // 5 if desc.is_generic_descriptor() { - // 6 } else if current.is_data_descriptor() != desc.is_data_descriptor() { // a @@ -294,7 +336,20 @@ impl Object { current.get = None; current.set = None; } - self.properties.insert(property_key, current.clone()); + + if current.value.is_some() && current.value.clone().unwrap().is_symbol() { + let sym_id = current + .value + .clone() + .unwrap() + .to_string() + .parse::() + .expect("parsing failed"); + self.sym_properties.insert(sym_id, current.clone()); + } else { + self.properties + .insert(property_key.clone(), current.clone()); + } // 7 } else if current.is_data_descriptor() && desc.is_data_descriptor() { // a @@ -342,7 +397,7 @@ impl Object { return true; } // 9 - Property::assign(&mut current, &desc); + self.properties.insert(property_key, desc); true } @@ -400,6 +455,45 @@ impl Object { // TODO!!!!! Call getter from here Gc::new(ValueData::Undefined) } + + /// [[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; + } + + // 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!(); + } + } + } } #[derive(Trace, Finalize, Clone, Debug, Eq, PartialEq)] @@ -462,16 +556,15 @@ pub fn has_own_prop(this: &Value, args: &[Value], _: &mut Interpreter) -> Result } /// Create a new `Object` object -pub fn create_constructor(global: &Value) -> Value { +pub fn create_constructor(_: &Value) -> Value { let object = to_value(make_object as NativeFunctionData); - let prototype = ValueData::new_obj(Some(global)); - prototype.set_field_slice( - "hasOwnProperty", - to_value(has_own_prop as NativeFunctionData), - ); - prototype.set_field_slice("toString", to_value(to_string as NativeFunctionData)); + // Prototype chain ends here VV + let mut prototype = Object::default(); + prototype.set_method("hasOwnProperty", has_own_prop); + prototype.set_method("toString", to_string); + object.set_field_slice("length", to_value(1_i32)); - object.set_field_slice(PROTOTYPE, prototype); + object.set_field_slice(PROTOTYPE, to_value(prototype)); object.set_field_slice( "setPrototypeOf", to_value(set_proto_of as NativeFunctionData), diff --git a/src/lib/builtins/property.rs b/src/lib/builtins/property.rs index 484926e5a1..1ea3fae662 100644 --- a/src/lib/builtins/property.rs +++ b/src/lib/builtins/property.rs @@ -1,5 +1,4 @@ use crate::builtins::value::{from_value, to_value, FromValue, ToValue, Value, ValueData}; -use gc::Gc; use gc_derive::{Finalize, Trace}; /// A Javascript Property AKA The Property Descriptor @@ -26,7 +25,7 @@ pub struct Property { impl Property { /// Checks if the provided Value can be used as a property key. pub fn is_property_key(value: &Value) -> bool { - value.is_string() // || value.is_symbol() // Uncomment this when we are handeling symbols. + value.is_string() || value.is_symbol() // Uncomment this when we are handeling symbols. } /// Make a new property with the given value @@ -92,43 +91,22 @@ impl Property { && self.enumerable.is_none() } - // https://tc39.es/ecma262/#sec-isaccessordescriptor + /// An accessor Property Descriptor is one that includes any fields named either [[Get]] or [[Set]]. + /// pub fn is_accessor_descriptor(&self) -> bool { - self.get.is_some() && self.set.is_some() + self.get.is_some() || self.set.is_some() } - // https://tc39.es/ecma262/#sec-isdatadescriptor + /// A data Property Descriptor is one that includes any fields named either [[Value]] or [[Writable]]. + /// https://tc39.es/ecma262/#sec-isdatadescriptor pub fn is_data_descriptor(&self) -> bool { - self.value.is_some() && self.writable.is_some() + self.value.is_some() || self.writable.is_some() } - // https://tc39.es/ecma262/#sec-isgenericdescriptor + /// https://tc39.es/ecma262/#sec-isgenericdescriptor pub fn is_generic_descriptor(&self) -> bool { !self.is_accessor_descriptor() && !self.is_data_descriptor() } - - /// This copies only present property fields from B to A - pub fn assign(a: &mut Property, b: &Property) { - if b.get.is_some() { - a.get = b.get.clone(); - } - - if b.set.is_some() { - a.set = b.set.clone(); - } - - if b.configurable.is_some() { - a.configurable = b.configurable; - } - - if b.writable.is_some() { - a.writable = b.writable; - } - - if b.enumerable.is_some() { - a.enumerable = b.enumerable; - } - } } impl Default for Property { @@ -136,12 +114,12 @@ impl Default for Property { /// https://tc39.es/ecma262/#table-default-attribute-values fn default() -> Self { Self { - configurable: Some(false), - enumerable: Some(false), - writable: Some(false), - value: Some(Gc::new(ValueData::Undefined)), - get: Some(Gc::new(ValueData::Undefined)), - set: Some(Gc::new(ValueData::Undefined)), + configurable: None, + enumerable: None, + writable: None, + value: None, + get: None, + set: None, } } } diff --git a/src/lib/builtins/regexp.rs b/src/lib/builtins/regexp.rs index 1c41711101..5fd97217a3 100644 --- a/src/lib/builtins/regexp.rs +++ b/src/lib/builtins/regexp.rs @@ -189,7 +189,8 @@ fn _make_prop(getter: NativeFunctionData) -> Property { /// Search for a match between this regex and a specified string pub fn test(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let arg_str = get_argument::(args, 0)?; - let mut last_index = from_value::(this.get_field("lastIndex")).map_err(to_value)?; + let mut last_index = + from_value::(this.get_field_slice("lastIndex")).map_err(to_value)?; let result = this.with_internal_state_ref(|regex: &RegExp| { let result = match regex.matcher.find_at(arg_str.as_str(), last_index) { Some(m) => { @@ -214,7 +215,8 @@ pub fn test(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// Search for a match between this regex and a specified string pub fn exec(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let arg_str = get_argument::(args, 0)?; - let mut last_index = from_value::(this.get_field("lastIndex")).map_err(to_value)?; + let mut last_index = + from_value::(this.get_field_slice("lastIndex")).map_err(to_value)?; let result = this.with_internal_state_ref(|regex: &RegExp| { let mut locations = regex.matcher.capture_locations(); let result = diff --git a/src/lib/builtins/symbol.rs b/src/lib/builtins/symbol.rs new file mode 100644 index 0000000000..e2250c4877 --- /dev/null +++ b/src/lib/builtins/symbol.rs @@ -0,0 +1,113 @@ +use crate::{ + builtins::{ + object::{Object, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, + value::{to_value, ResultValue, Value, ValueData}, + }, + exec::Interpreter, +}; +use gc::{Gc, GcCell}; +use rand::random; + +/// https://tc39.es/ecma262/#sec-symbol-description +/// 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. +pub fn call_symbol(_: &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) => to_value(value.to_string()), + None => Gc::new(ValueData::Undefined), + }; + + sym_instance.set_internal_slot("Description", desc_string); + sym_instance.set_internal_slot("SymbolData", to_value(random::())); + + // Set __proto__ internal slot + let proto = ctx + .realm + .global_obj + .get_field_slice("Symbol") + .get_field_slice(PROTOTYPE); + sym_instance.set_internal_slot(INSTANCE_PROTOTYPE, proto); + + Ok(Gc::new(ValueData::Symbol(GcCell::new(sym_instance)))) +} + +/// +pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + let s: Value = this.get_internal_slot("Description"); + let full_string = format!(r#"Symbol({})"#, s.to_string()); + Ok(to_value(full_string)) +} + +/// +pub fn create_constructor(global: &Value) -> Value { + // Create Symbol constructor (or function in Symbol's case) + let mut symbol_constructor = Object::default(); + symbol_constructor.set_internal_method("call", call_symbol); + + // Create prototype + let mut symbol_prototype = Object::default(); + + // Symbol.prototype[[Prototype]] points to Object.prototype + // Symbol Constructor -> Symbol Prototype -> Object Prototype + let object_prototype = global.get_field_slice("Object").get_field_slice(PROTOTYPE); + symbol_prototype.set_internal_slot(INSTANCE_PROTOTYPE, object_prototype.clone()); + symbol_prototype.set_method("toString", to_string); + + let symbol_prototype_val = to_value(symbol_prototype); + + let symbol_constructor_value = to_value(symbol_constructor); + symbol_prototype_val.set_field_slice("construcotor", symbol_constructor_value.clone()); + symbol_constructor_value.set_field_slice(PROTOTYPE, symbol_prototype_val.clone()); + + symbol_constructor_value +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::exec::Executor; + use crate::realm::Realm; + use crate::{forward, forward_val}; + + #[test] + fn check_symbol_constructor_is_function() { + let global: Gc = ValueData::new_obj(None); + let symbol_constructor = create_constructor(&global); + assert_eq!(symbol_constructor.is_function(), true); + } + + #[test] + fn call_symbol_and_check_return_type() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var sym = Symbol(); + "#; + forward(&mut engine, init); + let sym = forward_val(&mut engine, "sym").unwrap(); + assert_eq!(sym.is_symbol(), true); + } + + #[test] + fn print_symbol_expect_description() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var sym = Symbol("Hello"); + "#; + forward(&mut engine, init); + let sym = forward_val(&mut engine, "sym.toString()").unwrap(); + assert_eq!(sym.to_string(), "Symbol(Hello)"); + } +} diff --git a/src/lib/builtins/value.rs b/src/lib/builtins/value.rs index 1aeb5bbb1b..7f2dce1fd2 100644 --- a/src/lib/builtins/value.rs +++ b/src/lib/builtins/value.rs @@ -43,6 +43,8 @@ pub enum ValueData { Object(GcCell), /// `Function` - A runnable block of code, such as `Math.sqrt`, which can take some variables and return a useful value or act upon an object Function(Box>), + /// `Symbol` - A Symbol Type - Internally Symbols are similar to objects, except there are no properties, only internal slots + Symbol(GcCell), } impl ValueData { @@ -90,6 +92,14 @@ impl ValueData { } } + /// Returns true if the value is a symbol + pub fn is_symbol(&self) -> bool { + match *self { + ValueData::Symbol(_) => true, + _ => false, + } + } + /// Returns true if the value is a function pub fn is_function(&self) -> bool { match *self { @@ -168,7 +178,10 @@ impl ValueData { /// Converts the value into a 64-bit floating point number pub fn to_num(&self) -> f64 { match *self { - ValueData::Object(_) | ValueData::Undefined | ValueData::Function(_) => NAN, + ValueData::Object(_) + | ValueData::Symbol(_) + | ValueData::Undefined + | ValueData::Function(_) => NAN, ValueData::String(ref str) => match FromStr::from_str(str) { Ok(num) => num, Err(_) => NAN, @@ -185,6 +198,7 @@ impl ValueData { match *self { ValueData::Object(_) | ValueData::Undefined + | ValueData::Symbol(_) | ValueData::Null | ValueData::Boolean(false) | ValueData::Function(_) => 0, @@ -235,6 +249,10 @@ impl ValueData { Function::NativeFunc(ref func) => func.object.clone(), Function::RegularFunc(ref func) => func.object.clone(), }, + ValueData::Symbol(ref obj) => { + let hash = obj.clone(); + hash.into_inner() + } _ => return None, }; @@ -287,6 +305,10 @@ impl ValueData { let hash = obj.clone(); hash.into_inner() } + ValueData::Symbol(ref obj) => { + let hash = obj.clone(); + hash.into_inner() + } _ => return Gc::new(ValueData::Undefined), }; @@ -299,28 +321,35 @@ impl ValueData { /// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist /// get_field recieves a Property from get_prop(). It should then return the [[Get]] result value if that's set, otherwise fall back to [[Value]] /// TODO: this function should use the get Value if its set - pub fn get_field(&self, field: &str) -> Value { - match self.get_prop(field) { - Some(prop) => { - // If the Property has [[Get]] set to a function, we should run that and return the Value - let prop_getter = match prop.get { - Some(_) => None, - None => None, - }; - - // If the getter is populated, use that. If not use [[Value]] instead - match prop_getter { - Some(val) => val, - None => { - let val = prop - .value - .as_ref() - .expect("Could not get property as reference"); - val.clone() + pub fn get_field(&self, field: Value) -> Value { + match *field { + // Our field will either be a String or a Symbol + ValueData::String(ref s) => { + match self.get_prop(s) { + Some(prop) => { + // If the Property has [[Get]] set to a function, we should run that and return the Value + let prop_getter = match prop.get { + Some(_) => None, + None => None, + }; + + // If the getter is populated, use that. If not use [[Value]] instead + match prop_getter { + Some(val) => val, + None => { + let val = prop + .value + .as_ref() + .expect("Could not get property as reference"); + val.clone() + } + } } + None => Gc::new(ValueData::Undefined), } } - None => Gc::new(ValueData::Undefined), + ValueData::Symbol(_) => unimplemented!(), + _ => Gc::new(ValueData::Undefined), } } @@ -400,15 +429,19 @@ impl ValueData { /// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist pub fn get_field_slice(&self, field: &str) -> Value { - self.get_field(field) + // get_field used to accept strings, but now Symbols accept it needs to accept a value + // So this function will now need to Box strings back into values (at least for now) + let f = Gc::new(ValueData::String(field.to_string())); + self.get_field(f) } /// Set the field in the value - pub fn set_field(&self, field: String, val: Value) -> Value { + /// Field could be a Symbol, so we need to accept a Value (not a string) + pub fn set_field(&self, field: Value, val: Value) -> Value { match *self { ValueData::Object(ref obj) => { if obj.borrow().kind == ObjectKind::Array { - if let Ok(num) = field.parse::() { + if let Ok(num) = field.to_string().parse::() { if num > 0 { let len: i32 = from_value(self.get_field_slice("length")) .expect("Could not convert argument to i32"); @@ -418,20 +451,25 @@ impl ValueData { } } } - obj.borrow_mut() - .properties - .insert(field, Property::default().value(val.clone())); + + // Symbols get saved into a different bucket to general properties + if field.is_symbol() { + obj.borrow_mut().set(field.clone(), val.clone()); + } else { + obj.borrow_mut() + .set(to_value(field.to_string()), val.clone()); + } } ValueData::Function(ref func) => { match *func.borrow_mut().deref_mut() { Function::NativeFunc(ref mut f) => f .object .properties - .insert(field, Property::default().value(val.clone())), + .insert(field.to_string(), Property::default().value(val.clone())), Function::RegularFunc(ref mut f) => f .object .properties - .insert(field, Property::default().value(val.clone())), + .insert(field.to_string(), Property::default().value(val.clone())), }; } _ => (), @@ -441,7 +479,10 @@ impl ValueData { /// Set the field in the value pub fn set_field_slice<'a>(&self, field: &'a str, val: Value) -> Value { - self.set_field(field.to_string(), val) + // set_field used to accept strings, but now Symbols accept it needs to accept a value + // So this function will now need to Box strings back into values (at least for now) + let f = Gc::new(ValueData::String(field.to_string())); + self.set_field(f, val) } /// Set the private field in the value @@ -536,7 +577,10 @@ impl ValueData { pub fn to_json(&self) -> JSONValue { match *self { - ValueData::Null | ValueData::Undefined | ValueData::Function(_) => JSONValue::Null, + ValueData::Null + | ValueData::Symbol(_) + | ValueData::Undefined + | ValueData::Function(_) => JSONValue::Null, ValueData::Boolean(b) => JSONValue::Bool(b), ValueData::Object(ref obj) => { let mut new_obj = Map::new(); @@ -562,6 +606,7 @@ impl ValueData { ValueData::Number(_) | ValueData::Integer(_) => "number", ValueData::String(_) => "string", ValueData::Boolean(_) => "boolean", + ValueData::Symbol(_) => "symbol", ValueData::Null => "null", ValueData::Undefined => "undefined", ValueData::Function(_) => "function", @@ -592,6 +637,11 @@ impl Display for ValueData { ValueData::Null => write!(f, "null"), ValueData::Undefined => write!(f, "undefined"), ValueData::Boolean(v) => write!(f, "{}", v), + ValueData::Symbol(ref v) => match *v.borrow().get_internal_slot("Description") { + // If a description exists use it + ValueData::String(ref v) => write!(f, "{}", format!("Symbol({})", v)), + _ => write!(f, "Symbol()"), + }, ValueData::String(ref v) => write!(f, "{}", v), ValueData::Number(v) => write!( f, @@ -842,7 +892,7 @@ impl FromValue for Vec { let len = v.get_field_slice("length").to_int(); let mut vec = Self::with_capacity(len as usize); for i in 0..len { - vec.push(from_value(v.get_field(&i.to_string()))?) + vec.push(from_value(v.get_field_slice(&i.to_string()))?) } Ok(vec) } diff --git a/src/lib/environment/object_environment_record.rs b/src/lib/environment/object_environment_record.rs index e3ce3e9623..d007ae1153 100644 --- a/src/lib/environment/object_environment_record.rs +++ b/src/lib/environment/object_environment_record.rs @@ -72,7 +72,7 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord { fn get_binding_value(&self, name: &str, strict: bool) -> Value { if self.bindings.has_field(name) { - self.bindings.get_field(name) + self.bindings.get_field_slice(name) } else { if strict { // TODO: throw error here diff --git a/src/lib/exec.rs b/src/lib/exec.rs index d00a04097e..76d4ea4714 100644 --- a/src/lib/exec.rs +++ b/src/lib/exec.rs @@ -34,7 +34,7 @@ pub trait Executor { pub struct Interpreter { is_return: bool, /// realm holds both the global object and the environment - realm: Realm, + pub realm: Realm, } fn exec_assign_op(op: &AssignOp, v_a: ValueData, v_b: ValueData) -> Value { @@ -105,28 +105,30 @@ impl Executor for Interpreter { } ExprDef::GetConstField(ref obj, ref field) => { let val_obj = self.run(obj)?; - Ok(val_obj.borrow().get_field(field)) + Ok(val_obj.borrow().get_field_slice(field)) } ExprDef::GetField(ref obj, ref field) => { let val_obj = self.run(obj)?; let val_field = self.run(field)?; - Ok(val_obj.borrow().get_field(&val_field.borrow().to_string())) + Ok(val_obj + .borrow() + .get_field_slice(&val_field.borrow().to_string())) } ExprDef::Call(ref callee, ref args) => { let (this, func) = match callee.def { ExprDef::GetConstField(ref obj, ref field) => { let mut obj = self.run(obj)?; - if obj.get_type() != "object" { + if obj.get_type() != "object" || obj.get_type() != "symbol" { obj = self.to_object(&obj).expect("failed to convert to object"); } - (obj.clone(), obj.borrow().get_field(field)) + (obj.clone(), obj.borrow().get_field_slice(field)) } ExprDef::GetField(ref obj, ref field) => { let obj = self.run(obj)?; let field = self.run(field)?; ( obj.clone(), - obj.borrow().get_field(&field.borrow().to_string()), + obj.borrow().get_field_slice(&field.borrow().to_string()), ) } _ => (self.realm.global_obj.clone(), self.run(&callee.clone())?), // 'this' binding should come from the function's self-contained environment @@ -199,7 +201,7 @@ impl Executor for Interpreter { .expect("Could not get the global object"); let obj = ValueData::new_obj(Some(global_val)); for (key, val) in map.iter() { - obj.borrow().set_field(key.clone(), self.run(val)?); + obj.borrow().set_field_slice(&key.clone(), self.run(val)?); } Ok(obj) } @@ -320,10 +322,12 @@ impl Executor for Interpreter { } ExprDef::GetConstField(ref obj, ref field) => { let v_r_a = self.run(obj)?; - let v_a = (*v_r_a.borrow().get_field(field)).clone(); + let v_a = (*v_r_a.borrow().get_field_slice(field)).clone(); let v_b = (*self.run(b)?).clone(); let value = exec_assign_op(op, v_a, v_b.clone()); - v_r_a.borrow().set_field(field.clone(), value.clone()); + v_r_a + .borrow() + .set_field_slice(&field.clone(), value.clone()); Ok(value) } _ => Ok(Gc::new(ValueData::Undefined)), @@ -409,14 +413,14 @@ impl Executor for Interpreter { } ExprDef::GetConstField(ref obj, ref field) => { let val_obj = self.run(obj)?; - val_obj.borrow().set_field(field.clone(), val.clone()); + val_obj + .borrow() + .set_field_slice(&field.clone(), val.clone()); } ExprDef::GetField(ref obj, ref field) => { let val_obj = self.run(obj)?; let val_field = self.run(field)?; - val_obj - .borrow() - .set_field(val_field.to_string(), val.clone()); + val_obj.borrow().set_field(val_field, val.clone()); } _ => (), } @@ -470,6 +474,7 @@ impl Executor for Interpreter { let val = self.run(val_e)?; Ok(to_value(match *val { ValueData::Undefined => "undefined", + ValueData::Symbol(_) => "symbol", ValueData::Null | ValueData::Object(_) => "object", ValueData::Boolean(_) => "boolean", ValueData::Number(_) | ValueData::Integer(_) => "number", @@ -663,7 +668,7 @@ impl Interpreter { string_obj.set_internal_slot("StringData", value.clone()); Ok(string_obj) } - ValueData::Object(_) => Ok(value.clone()), + ValueData::Object(_) | ValueData::Symbol(_) => Ok(value.clone()), } } @@ -701,7 +706,7 @@ impl Interpreter { self.to_string(&prim_value) .to_string() .parse::() - .unwrap() + .expect("cannot parse valur to x64") } _ => { // TODO: Make undefined? diff --git a/src/lib/realm.rs b/src/lib/realm.rs index 372c7eac13..6f9fba0c3e 100644 --- a/src/lib/realm.rs +++ b/src/lib/realm.rs @@ -5,7 +5,7 @@ //!A realm is represented in this implementation as a Realm struct with the fields specified from the spec use crate::{ builtins::{ - array, boolean, console, function, json, math, number, object, regexp, string, + array, boolean, console, function, json, math, number, object, regexp, string, symbol, value::{Value, ValueData}, }, environment::{ @@ -62,6 +62,7 @@ impl Realm { global.set_field_slice("Object", object::create_constructor(global)); global.set_field_slice("RegExp", regexp::create_constructor(global)); global.set_field_slice("String", string::create_constructor(global)); + global.set_field_slice("Symbol", symbol::create_constructor(global)); global.set_field_slice("console", console::create_constructor(global)); } } diff --git a/tests/js/test.js b/tests/js/test.js index e69de29bb2..d0559ac44d 100644 --- a/tests/js/test.js +++ b/tests/js/test.js @@ -0,0 +1 @@ +// Test your JS here