diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..a565f93893 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,40 @@ +// For format details, see https://aka.ms/vscode-remote/devcontainer.json or the definition README at +// https://github.com/microsoft/vscode-dev-containers/tree/master/containers/docker-existing-dockerfile +{ + "name": "Existing Dockerfile", + // Sets the run context to one level up instead of the .devcontainer folder. + "context": "..", + // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. + "dockerFile": "..\\Dockerfile", + // The optional 'runArgs' property can be used to specify additional runtime arguments. + "runArgs": [ + // Uncomment the next line to use Docker from inside the container. See https://aka.ms/vscode-remote/samples/docker-in-docker for details. + // "-v","/var/run/docker.sock:/var/run/docker.sock", + // Uncomment the next line if you will be using a ptrace-based debugger like C++, Go, and Rust. + "--cap-add=SYS_PTRACE", + "--security-opt", + "seccomp=unconfined" + // You may want to add a non-root user to your Dockerfile. On Linux, this will prevent + // new files getting created as root. See https://aka.ms/vscode-remote/containers/non-root-user + // for the needed Dockerfile updates and then uncomment the next line. + // "-u", "vscode" + ], + // Use 'settings' to set *default* container specific settings.json values on container create. + // You can edit these settings after create using File > Preferences > Settings > Remote. + "settings": { + // This will ignore your local shell user setting for Linux since shells like zsh are typically + // not in base container images. You can also update this to an specific shell to ensure VS Code + // uses the right one for terminals and tasks. For example, /bin/bash (or /bin/ash for Alpine). + "terminal.integrated.shell.linux": null + }, + // Uncomment the next line if you want to publish any ports. + // "appPort": [], + // Uncomment the next line to run commands after the container is created - for example installing git. + // "postCreateCommand": "apt-get update && apt-get install -y git", + // Add the IDs of extensions you want installed when the container is created in the array below. + "extensions": [ + "vadimcn.vscode-lldb", + "rust-lang.rust" + ], + "postCreateCommand": "rustup toolchain install stable && rustup component add rls rust-src rust-analysis --toolchain stable" +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 7752305e00..7c79aade53 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,10 @@ EXPOSE 9228 RUN apt-get -y update && \ apt-get -y upgrade && \ - apt-get install -y sudo software-properties-common + apt-get install -y sudo software-properties-common libpython2.7 + +# codelldb depends on libpython2.7 +# https://stackoverflow.com/questions/20842732/libpython2-7-so-1-0-cannot-open-shared-object-file-no-such-file-or-directory # https://askubuntu.com/questions/787383/how-to-install-llvm-3-9 # http://apt.llvm.org/ diff --git a/src/lib/environment/global_environment_record.rs b/src/lib/environment/global_environment_record.rs index ad9348330e..8c3aa8a82a 100644 --- a/src/lib/environment/global_environment_record.rs +++ b/src/lib/environment/global_environment_record.rs @@ -46,7 +46,7 @@ impl GlobalEnvironmentRecord { let existing_prop = global_object.get_prop(name); match existing_prop { Some(prop) => { - if prop.value.is_undefined() || prop.configurable { + if prop.value.is_none() || prop.configurable.unwrap_or(false) { return false; } true @@ -75,7 +75,7 @@ impl GlobalEnvironmentRecord { let global_object = &mut self.object_record.bindings; let existing_prop = global_object.get_prop(&name); if let Some(prop) = existing_prop { - if prop.value.is_undefined() || prop.configurable { + if prop.value.is_none() || prop.configurable.unwrap_or(false) { global_object.update_prop( name, Some(value), diff --git a/src/lib/environment/object_environment_record.rs b/src/lib/environment/object_environment_record.rs index 5fe3201a33..7a1483cef6 100644 --- a/src/lib/environment/object_environment_record.rs +++ b/src/lib/environment/object_environment_record.rs @@ -42,11 +42,12 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord { // TODO: could save time here and not bother generating a new undefined object, // only for it to be replace with the real value later. We could just add the name to a Vector instead let bindings = &mut self.bindings; - let uninitialized = Gc::new(ValueData::Undefined); - let mut prop = Property::new(uninitialized); - prop.enumerable = true; - prop.writable = true; - prop.configurable = deletion; + let prop = Property::default() + .value(Gc::new(ValueData::Undefined)) + .writable(true) + .enumerable(true) + .configurable(deletion); + bindings.set_prop(name, prop); } diff --git a/src/lib/js/array.rs b/src/lib/js/array.rs index d06817cec2..5cf3fff5f5 100644 --- a/src/lib/js/array.rs +++ b/src/lib/js/array.rs @@ -255,14 +255,9 @@ pub fn unshift(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue pub fn _create(global: &Value) -> Value { let array = to_value(make_array as NativeFunctionData); let proto = ValueData::new_obj(Some(global)); - let length = Property { - configurable: false, - enumerable: false, - writable: false, - value: Gc::new(ValueData::Undefined), - get: to_value(get_array_length as NativeFunctionData), - set: Gc::new(ValueData::Undefined), - }; + let length = Property::default() + .get(to_value(get_array_length as NativeFunctionData)); + proto.set_prop_slice("length", length); let concat_func = to_value(concat as NativeFunctionData); concat_func.set_field_slice("length", to_value(1_i32)); diff --git a/src/lib/js/console.rs b/src/lib/js/console.rs index 662b133fd7..39cd593de9 100644 --- a/src/lib/js/console.rs +++ b/src/lib/js/console.rs @@ -32,7 +32,7 @@ fn log_string_from(x: Value) -> String { ObjectKind::Array => { write!(s, "[").unwrap(); let len: i32 = - from_value(v.borrow().properties.get("length").unwrap().value.clone()) + from_value(v.borrow().properties.get("length").unwrap().value.clone().unwrap().clone()) .unwrap(); for i in 0..len { // Introduce recursive call to stringify any objects @@ -43,6 +43,8 @@ fn log_string_from(x: Value) -> String { .get(&i.to_string()) .unwrap() .value + .clone() + .unwrap() .clone(), ); write!(s, "{}", arr_str).unwrap(); @@ -62,7 +64,7 @@ fn log_string_from(x: Value) -> String { } // Introduce recursive call to stringify any objects // which are keys of the object - write!(s, "{}: {}", key, log_string_from(val.value.clone())).unwrap(); + write!(s, "{}: {}", key, log_string_from(val.value.clone().unwrap().clone())).unwrap(); if key != last_key { write!(s, ", ").unwrap(); } diff --git a/src/lib/js/function.rs b/src/lib/js/function.rs index 936bb917b1..2417ea4452 100644 --- a/src/lib/js/function.rs +++ b/src/lib/js/function.rs @@ -46,7 +46,7 @@ impl RegularFunction { let mut object = Object::default(); object.properties.insert( "arguments".to_string(), - Property::new(Gc::new(ValueData::Integer(args.len() as i32))), + Property::default().value(Gc::new(ValueData::Integer(args.len() as i32))), ); Self { object, expr, args } } @@ -73,7 +73,7 @@ impl Debug for NativeFunction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{{")?; for (key, val) in self.object.properties.iter() { - write!(f, "{}: {}", key, val.value.clone())?; + write!(f, "{}: {}", key, val.value.as_ref().unwrap_or(&Gc::new(ValueData::Undefined)).clone())?; } write!(f, "}}") } diff --git a/src/lib/js/object.rs b/src/lib/js/object.rs index e716cbd197..ab5c7aaecb 100644 --- a/src/lib/js/object.rs +++ b/src/lib/js/object.rs @@ -3,7 +3,7 @@ use crate::{ js::{ function::NativeFunctionData, property::Property, - value::{from_value, to_value, ResultValue, Value, ValueData}, + value::{from_value, same_value, to_value, ResultValue, Value, ValueData}, }, }; use gc::Gc; @@ -47,6 +47,31 @@ impl Object { } } + /// ObjectCreate is used to specify the runtime creation of new ordinary objects + /// + /// https://tc39.es/ecma262/#sec-objectcreate + pub fn create(proto: Value) -> Object { + let mut obj = Object::default(); + obj.internal_slots + .insert(INSTANCE_PROTOTYPE.to_string(), proto.clone()); + 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); + } + /// Return a new Boolean object whose [[BooleanData]] internal slot is set to argument. fn from_boolean(argument: &Value) -> Self { let mut obj = Object { @@ -101,7 +126,190 @@ impl Object { _ => 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), + } + } + + /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v + 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 current == val { + 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(&to_value(self.clone()), &p) { + return false; + } else { + p = p.get_internal_slot(PROTOTYPE); + } + } + self.set_internal_slot(PROTOTYPE, val); + 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, + } + } + + /// 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 + } + /// 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(); + } + d.enumerable = v.enumerable; + d.configurable = v.configurable; + d + } + } + } + + /// 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 + } + + pub 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(); + + // 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; + } + + 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()); + } 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); + 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; + } + self.properties.insert(property_key, current); + // 7 + } else if current.is_data_descriptor() && desc.is_data_descriptor() { + // a + if !current.configurable.unwrap()&& !current.writable.unwrap() { + if desc.writable.is_some() && desc.writable.unwrap() { + return false; + } + + if desc.value.is_some() && !same_value(&desc.value.clone().unwrap(), ¤t.value.clone().unwrap()) { + return false; + } + + return true; + } + // 8 + } else { + + } + true + } } + #[derive(Trace, Finalize, Clone, Debug)] pub enum ObjectKind { Function, @@ -121,23 +329,25 @@ pub fn make_object(_: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { /// Get the prototype of an object pub fn get_proto_of(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let obj = args.get(0).unwrap(); + let obj = args.get(0).expect("Cannot get object"); Ok(obj.get_field_slice(INSTANCE_PROTOTYPE)) } /// Set the prototype of an object pub fn set_proto_of(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let obj = args.get(0).unwrap().clone(); - let proto = args.get(1).unwrap().clone(); + let obj = args.get(0).expect("Cannot get object").clone(); + let proto = args.get(1).expect("Cannot get object").clone(); obj.set_internal_slot(INSTANCE_PROTOTYPE, proto); Ok(obj) } /// Define a property in an object pub fn define_prop(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let obj = args.get(0).unwrap(); - let prop = from_value::(args.get(1).unwrap().clone()).unwrap(); - let desc = from_value::(args.get(2).unwrap().clone()).unwrap(); + let obj = args.get(0).expect("Cannot get object"); + let prop = from_value::(args.get(1).expect("Cannot get object").clone()) + .expect("Cannot get object"); + let desc = from_value::(args.get(2).expect("Cannot get object").clone()) + .expect("Cannot get object"); obj.set_prop(prop, desc); Ok(Gc::new(ValueData::Undefined)) } @@ -152,10 +362,10 @@ pub fn has_own_prop(this: &Value, args: &[Value], _: &mut Interpreter) -> Result let prop = if args.is_empty() { None } else { - from_value::(args.get(0).unwrap().clone()).ok() + from_value::(args.get(0).expect("Cannot get object").clone()).ok() }; Ok(to_value( - prop.is_some() && this.get_prop(&prop.unwrap()).is_some(), + prop.is_some() && this.get_prop(&prop.expect("Cannot get object")).is_some(), )) } diff --git a/src/lib/js/property.rs b/src/lib/js/property.rs index 852c88a5da..18dff5780d 100644 --- a/src/lib/js/property.rs +++ b/src/lib/js/property.rs @@ -5,20 +5,22 @@ use gc_derive::{Finalize, Trace}; /// A Javascript Property AKA The Property Descriptor /// [[SPEC] - The Property Descriptor Specification Type](https://tc39.github.io/ecma262/#sec-property-descriptor-specification-type) /// [[SPEC] - Default Attribute Values](https://tc39.github.io/ecma262/#table-4) +/// +/// Any field in a JavaScript Property may be present or absent. #[derive(Trace, Finalize, Clone, Debug)] pub struct Property { /// If the type of this can be changed and this can be deleted - pub configurable: bool, + pub configurable: Option, /// If the property shows up in enumeration of the object - pub enumerable: bool, + pub enumerable: Option, /// If this property can be changed with an assignment - pub writable: bool, + pub writable: Option, /// The value associated with the property - pub value: Value, + pub value: Option, /// The function serving as getter - pub get: Value, + pub get: Option, /// The function serving as setter - pub set: Value, + pub set: Option, } impl Property { @@ -28,14 +30,95 @@ impl Property { } /// Make a new property with the given value - pub fn new(value: Value) -> Self { + /// The difference between New and Default: + /// + /// New: zeros everything to make an empty object + /// Default: Defaults according to the spec + pub fn new() -> Self { Self { - configurable: false, - enumerable: false, - writable: false, - value, - get: Gc::new(ValueData::Undefined), - set: Gc::new(ValueData::Undefined), + configurable: None, + enumerable: None, + writable: None, + value: None, + get: None, + set: None, + } + } + + /// Set configurable + pub fn configurable(mut self, configurable: bool) -> Self { + self.configurable = Some(configurable); + self + } + + /// Set enumerable + pub fn enumerable(mut self, enumerable: bool) -> Self { + self.enumerable = Some(enumerable); + self + } + + /// Set writable + pub fn writable(mut self, writable: bool) -> Self { + self.writable = Some(writable); + self + } + + /// Set value + pub fn value(mut self, value: Value) -> Self { + self.value = Some(value); + self + } + + /// Set get + pub fn get(mut self, get: Value) -> Self { + self.get = Some(get); + self + } + + /// Set set + pub fn set(mut self, set: Value) -> Self { + self.set = Some(set); + self + } + + /// Is this an empty Property? + /// + /// `true` if all fields are set to none + pub fn is_none(&self) -> bool { + self.get.is_none() + && self.set.is_none() + && self.writable.is_none() + && self.configurable.is_none() + && self.enumerable.is_none() + } + + // https://tc39.es/ecma262/#sec-isaccessordescriptor + pub fn is_accessor_descriptor(&self) -> bool { + self.get.is_some() && self.set.is_some() + } + + // https://tc39.es/ecma262/#sec-isdatadescriptor + pub fn is_data_descriptor(&self) -> bool { + self.value.is_some() && self.writable.is_some() + } + + // https://tc39.es/ecma262/#sec-isgenericdescriptor + pub fn is_generic_descriptor(&self) -> bool { + !self.is_accessor_descriptor() && !self.is_data_descriptor() + } +} + +impl Default for Property { + /// Make a default 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)), } } } @@ -46,22 +129,39 @@ impl ToValue for Property { prop.set_field_slice("configurable", to_value(self.configurable)); prop.set_field_slice("enumerable", to_value(self.enumerable)); prop.set_field_slice("writable", to_value(self.writable)); - prop.set_field_slice("value", self.value.clone()); - prop.set_field_slice("get", self.get.clone()); - prop.set_field_slice("set", self.set.clone()); + prop.set_field_slice("value", to_value(self.value.clone())); + prop.set_field_slice("get", to_value(self.get.clone())); + prop.set_field_slice("set", to_value(self.set.clone())); prop } } impl FromValue for Property { + /// Attempt to fetch values "configurable", "enumerable", "writable" from the value, + /// if they're not there default to false fn from_value(v: Value) -> Result { Ok(Self { - configurable: from_value(v.get_field_slice("configurable")).unwrap(), - enumerable: from_value(v.get_field_slice("enumerable")).unwrap(), - writable: from_value(v.get_field_slice("writable")).unwrap(), - value: v.get_field_slice("value"), - get: v.get_field_slice("get"), - set: v.get_field_slice("set"), + configurable: { + match from_value::(v.get_field_slice("configurable")) { + Ok(v) => Some(v), + Err(_) => Some(false), + } + }, + enumerable: { + match from_value::(v.get_field_slice("enumerable")) { + Ok(v) => Some(v), + Err(_) => Some(false), + } + }, + writable: { + match from_value(v.get_field_slice("writable")) { + Ok(v) => Some(v), + Err(_) => Some(false), + } + }, + value: Some(v.get_field_slice("value")), + get: Some(v.get_field_slice("get")), + set: Some(v.get_field_slice("set")), }) } } diff --git a/src/lib/js/regexp.rs b/src/lib/js/regexp.rs index 7f901d5cf0..f7f3295ac3 100644 --- a/src/lib/js/regexp.rs +++ b/src/lib/js/regexp.rs @@ -181,14 +181,8 @@ fn get_unicode(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { } fn _make_prop(getter: NativeFunctionData) -> Property { - Property { - writable: false, - enumerable: false, - configurable: true, - value: Gc::new(ValueData::Undefined), - get: to_value(getter), - set: Gc::new(ValueData::Undefined), - } + Property::default() + .get(to_value(getter)) } /// Search for a match between this regex and a specified string @@ -240,8 +234,8 @@ pub fn exec(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { } } let result = to_value(result); - result.set_prop_slice("index", Property::new(to_value(m.start()))); - result.set_prop_slice("input", Property::new(to_value(arg_str))); + result.set_prop_slice("index", Property::default().value(to_value(m.start()))); + result.set_prop_slice("input", Property::default().value(to_value(arg_str))); result } None => { diff --git a/src/lib/js/string.rs b/src/lib/js/string.rs index 4ccd4bedda..35339ce97b 100644 --- a/src/lib/js/string.rs +++ b/src/lib/js/string.rs @@ -525,14 +525,9 @@ pub fn trim_end(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue pub fn _create(global: &Value) -> Value { let string = to_value(make_string as NativeFunctionData); let proto = ValueData::new_obj(Some(global)); - let prop = Property { - configurable: false, - enumerable: false, - writable: false, - value: Gc::new(ValueData::Undefined), - get: to_value(get_string_length as NativeFunctionData), - set: Gc::new(ValueData::Undefined), - }; + let prop = Property::default() + .get(to_value(get_string_length as NativeFunctionData)); + proto.set_prop_slice("length", prop); proto.set_field_slice("charAt", to_value(char_at as NativeFunctionData)); proto.set_field_slice("charCodeAt", to_value(char_code_at as NativeFunctionData)); diff --git a/src/lib/js/value.rs b/src/lib/js/value.rs index 4d7525cbef..5f3e3a941b 100644 --- a/src/lib/js/value.rs +++ b/src/lib/js/value.rs @@ -20,6 +20,10 @@ pub type ResultValue = Result; /// A Garbage-collected Javascript value as represented in the interpreter pub type Value = Gc; +pub fn undefined() -> Value { + Gc::new(ValueData::Undefined) +} + /// A Javascript value #[derive(Trace, Finalize, Debug, Clone)] pub enum ValueData { @@ -44,17 +48,18 @@ pub enum ValueData { impl ValueData { /// Returns a new empty object pub fn new_obj(global: Option<&Value>) -> Value { - let mut obj = Object::default(); + match global { + Some(glob) => { + let obj_proto = glob.get_field_slice("Object").get_field_slice(PROTOTYPE); - if global.is_some() { - let obj_proto = global - .expect("Expected global object in making-new-object") - .get_field_slice("Object") - .get_field_slice(PROTOTYPE); - obj.internal_slots - .insert(INSTANCE_PROTOTYPE.to_string(), obj_proto); + let obj = Object::create(obj_proto); + Gc::new(ValueData::Object(GcCell::new(obj))) + } + None => { + let obj = Object::default(); + Gc::new(ValueData::Object(GcCell::new(obj))) + } } - Gc::new(ValueData::Object(GcCell::new(obj))) } /// Similar to `new_obj`, but you can pass a prototype to create from, @@ -200,7 +205,7 @@ impl ValueData { // This is only for primitive strings, String() objects have their lengths calculated in string.rs if self.is_string() && field == "length" { if let ValueData::String(ref s) = *self { - return Some(Property::new(to_value(s.len() as i32))); + return Some(Property::default().value(to_value(s.len() as i32))); } } @@ -252,10 +257,10 @@ impl ValueData { if let Some(mut obj_data) = obj { // Use value, or walk up the prototype chain if let Some(ref mut prop) = obj_data.properties.get_mut(field) { - prop.value = value.unwrap_or_else(|| prop.value.clone()); - prop.enumerable = enumerable.unwrap_or(prop.enumerable); - prop.writable = writable.unwrap_or(prop.writable); - prop.configurable = configurable.unwrap_or(prop.configurable); + prop.value = value; + prop.enumerable = enumerable; + prop.writable = writable; + prop.configurable = configurable; } } } @@ -279,24 +284,23 @@ 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 { - ValueData::Function(ref v) => match *v.borrow() { - Function::NativeFunc(ref _ntv) => { - None // this never worked properly anyway - } - _ => None, - }, - _ => None, + 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 => prop.value.clone(), + None => { + let val = prop.value.as_ref().unwrap(); + val.clone() + } } } None => Gc::new(ValueData::Undefined), @@ -388,18 +392,18 @@ impl ValueData { ValueData::Object(ref obj) => { obj.borrow_mut() .properties - .insert(field, Property::new(val.clone())); + .insert(field, Property::default().value(val.clone())); } ValueData::Function(ref func) => { match *func.borrow_mut().deref_mut() { Function::NativeFunc(ref mut f) => f .object .properties - .insert(field, Property::new(val.clone())), + .insert(field, Property::default().value(val.clone())), Function::RegularFunc(ref mut f) => f .object .properties - .insert(field, Property::new(val.clone())), + .insert(field, Property::default().value(val.clone())), }; } _ => (), @@ -476,11 +480,11 @@ impl ValueData { for (idx, json) in vs.iter().enumerate() { new_obj .properties - .insert(idx.to_string(), Property::new(to_value(json.clone()))); + .insert(idx.to_string(), Property::default().value(to_value(json.clone()))); } new_obj.properties.insert( "length".to_string(), - Property::new(to_value(vs.len() as i32)), + Property::default().value(to_value(vs.len() as i32)), ); ValueData::Object(GcCell::new(new_obj)) } @@ -489,7 +493,7 @@ impl ValueData { for (key, json) in obj.iter() { new_obj .properties - .insert(key.clone(), Property::new(to_value(json.clone()))); + .insert(key.clone(), Property::default().value(to_value(json.clone()))); } ValueData::Object(GcCell::new(new_obj)) @@ -530,6 +534,12 @@ impl ValueData { } } +impl Default for ValueData { + fn default() -> Self { + ValueData::Undefined + } +} + impl Display for ValueData { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -552,7 +562,7 @@ impl Display for ValueData { // Print public properties if let Some((last_key, _)) = v.borrow().properties.iter().last() { for (key, val) in v.borrow().properties.iter() { - write!(f, "{}: {}", key, val.value.clone())?; + write!(f, "{}: {}", key, val.value.as_ref().unwrap_or(&Gc::new(ValueData::Undefined)).clone())?; if key != last_key { write!(f, ", ")?; } @@ -778,7 +788,7 @@ impl<'s, T: ToValue> ToValue for &'s [T] { let mut arr = Object::default(); for (i, item) in self.iter().enumerate() { arr.properties - .insert(i.to_string(), Property::new(item.to_value())); + .insert(i.to_string(), Property::default().value(item.to_value())); } to_value(arr) } @@ -788,7 +798,7 @@ impl ToValue for Vec { let mut arr = Object::default(); for (i, item) in self.iter().enumerate() { arr.properties - .insert(i.to_string(), Property::new(item.to_value())); + .insert(i.to_string(), Property::default().value(item.to_value())); } to_value(arr) } @@ -894,6 +904,44 @@ pub fn to_value(v: A) -> Value { v.to_value() } +/// The internal comparison abstract operation SameValue(x, y), +/// where x and y are ECMAScript language values, produces true or false. +/// Such a comparison is performed as follows: +/// +/// https://tc39.es/ecma262/#sec-samevalue +pub fn same_value(x: &Value, y: &Value) -> bool { + if x.get_type() != y.get_type() { + return false; + } + + if x.get_type() == "number" { + let native_x: f64 = from_value(x.clone()).expect("failed to get value"); + let native_y: f64 = from_value(y.clone()).expect("failed to get value"); + return native_x.abs() - native_y.abs() == 0.0; + } + + same_value_non_number(x, y) +} + +pub fn same_value_non_number(x: &Value, y: &Value) -> bool { + debug_assert!(x.get_type() == y.get_type()); + match x.get_type() { + "undefined" => true, + "null" => true, + "string" => { + if x.to_string() == y.to_string() { + return true; + } + false + } + "boolean" => { + from_value::(x.clone()).expect("failed to get value") + == from_value::(y.clone()).expect("failed to get value") + } + _ => false, + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/lib/lib.rs b/src/lib/lib.rs index 7db021799b..52ff9a9141 100644 --- a/src/lib/lib.rs +++ b/src/lib/lib.rs @@ -29,7 +29,8 @@ clippy::print_stdout, clippy::cast_possible_truncation, clippy::cast_possible_wrap, - clippy::non_ascii_literal + clippy::non_ascii_literal, + clippy::float_arithmetic )] pub mod environment;