Browse Source

Adding object internal methods (#96)

* adding internal methods

* adding more methods

* Property refactored for optional fields, Docker container now working for debugging

* working through object internal methods
pull/97/head
Jason Williams 5 years ago committed by GitHub
parent
commit
2df9a24011
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 40
      .devcontainer/devcontainer.json
  2. 5
      Dockerfile
  3. 4
      src/lib/environment/global_environment_record.rs
  4. 11
      src/lib/environment/object_environment_record.rs
  5. 11
      src/lib/js/array.rs
  6. 6
      src/lib/js/console.rs
  7. 4
      src/lib/js/function.rs
  8. 228
      src/lib/js/object.rs
  9. 144
      src/lib/js/property.rs
  10. 14
      src/lib/js/regexp.rs
  11. 11
      src/lib/js/string.rs
  12. 112
      src/lib/js/value.rs
  13. 3
      src/lib/lib.rs

40
.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"
}

5
Dockerfile

@ -8,7 +8,10 @@ EXPOSE 9228
RUN apt-get -y update && \ RUN apt-get -y update && \
apt-get -y upgrade && \ 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 # https://askubuntu.com/questions/787383/how-to-install-llvm-3-9
# http://apt.llvm.org/ # http://apt.llvm.org/

4
src/lib/environment/global_environment_record.rs

@ -46,7 +46,7 @@ impl GlobalEnvironmentRecord {
let existing_prop = global_object.get_prop(name); let existing_prop = global_object.get_prop(name);
match existing_prop { match existing_prop {
Some(prop) => { Some(prop) => {
if prop.value.is_undefined() || prop.configurable { if prop.value.is_none() || prop.configurable.unwrap_or(false) {
return false; return false;
} }
true true
@ -75,7 +75,7 @@ impl GlobalEnvironmentRecord {
let global_object = &mut self.object_record.bindings; let global_object = &mut self.object_record.bindings;
let existing_prop = global_object.get_prop(&name); let existing_prop = global_object.get_prop(&name);
if let Some(prop) = existing_prop { 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( global_object.update_prop(
name, name,
Some(value), Some(value),

11
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, // 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 // 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 bindings = &mut self.bindings;
let uninitialized = Gc::new(ValueData::Undefined); let prop = Property::default()
let mut prop = Property::new(uninitialized); .value(Gc::new(ValueData::Undefined))
prop.enumerable = true; .writable(true)
prop.writable = true; .enumerable(true)
prop.configurable = deletion; .configurable(deletion);
bindings.set_prop(name, prop); bindings.set_prop(name, prop);
} }

11
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 { pub fn _create(global: &Value) -> Value {
let array = to_value(make_array as NativeFunctionData); let array = to_value(make_array as NativeFunctionData);
let proto = ValueData::new_obj(Some(global)); let proto = ValueData::new_obj(Some(global));
let length = Property { let length = Property::default()
configurable: false, .get(to_value(get_array_length as NativeFunctionData));
enumerable: false,
writable: false,
value: Gc::new(ValueData::Undefined),
get: to_value(get_array_length as NativeFunctionData),
set: Gc::new(ValueData::Undefined),
};
proto.set_prop_slice("length", length); proto.set_prop_slice("length", length);
let concat_func = to_value(concat as NativeFunctionData); let concat_func = to_value(concat as NativeFunctionData);
concat_func.set_field_slice("length", to_value(1_i32)); concat_func.set_field_slice("length", to_value(1_i32));

6
src/lib/js/console.rs

@ -32,7 +32,7 @@ fn log_string_from(x: Value) -> String {
ObjectKind::Array => { ObjectKind::Array => {
write!(s, "[").unwrap(); write!(s, "[").unwrap();
let len: i32 = 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(); .unwrap();
for i in 0..len { for i in 0..len {
// Introduce recursive call to stringify any objects // Introduce recursive call to stringify any objects
@ -43,6 +43,8 @@ fn log_string_from(x: Value) -> String {
.get(&i.to_string()) .get(&i.to_string())
.unwrap() .unwrap()
.value .value
.clone()
.unwrap()
.clone(), .clone(),
); );
write!(s, "{}", arr_str).unwrap(); write!(s, "{}", arr_str).unwrap();
@ -62,7 +64,7 @@ fn log_string_from(x: Value) -> String {
} }
// Introduce recursive call to stringify any objects // Introduce recursive call to stringify any objects
// which are keys of the object // 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 { if key != last_key {
write!(s, ", ").unwrap(); write!(s, ", ").unwrap();
} }

4
src/lib/js/function.rs

@ -46,7 +46,7 @@ impl RegularFunction {
let mut object = Object::default(); let mut object = Object::default();
object.properties.insert( object.properties.insert(
"arguments".to_string(), "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 } Self { object, expr, args }
} }
@ -73,7 +73,7 @@ impl Debug for NativeFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{{")?; write!(f, "{{")?;
for (key, val) in self.object.properties.iter() { 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, "}}") write!(f, "}}")
} }

228
src/lib/js/object.rs

@ -3,7 +3,7 @@ use crate::{
js::{ js::{
function::NativeFunctionData, function::NativeFunctionData,
property::Property, property::Property,
value::{from_value, to_value, ResultValue, Value, ValueData}, value::{from_value, same_value, to_value, ResultValue, Value, ValueData},
}, },
}; };
use gc::Gc; 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. /// Return a new Boolean object whose [[BooleanData]] internal slot is set to argument.
fn from_boolean(argument: &Value) -> Self { fn from_boolean(argument: &Value) -> Self {
let mut obj = Object { let mut obj = Object {
@ -101,7 +126,190 @@ impl Object {
_ => Err(()), _ => 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(), &current.value.clone().unwrap()) {
return false;
}
return true;
}
// 8
} else {
}
true
}
} }
#[derive(Trace, Finalize, Clone, Debug)] #[derive(Trace, Finalize, Clone, Debug)]
pub enum ObjectKind { pub enum ObjectKind {
Function, Function,
@ -121,23 +329,25 @@ pub fn make_object(_: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
/// Get the prototype of an object /// Get the prototype of an object
pub fn get_proto_of(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { 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)) Ok(obj.get_field_slice(INSTANCE_PROTOTYPE))
} }
/// Set the prototype of an object /// Set the prototype of an object
pub fn set_proto_of(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { pub fn set_proto_of(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let obj = args.get(0).unwrap().clone(); let obj = args.get(0).expect("Cannot get object").clone();
let proto = args.get(1).unwrap().clone(); let proto = args.get(1).expect("Cannot get object").clone();
obj.set_internal_slot(INSTANCE_PROTOTYPE, proto); obj.set_internal_slot(INSTANCE_PROTOTYPE, proto);
Ok(obj) Ok(obj)
} }
/// Define a property in an object /// Define a property in an object
pub fn define_prop(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { pub fn define_prop(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let obj = args.get(0).unwrap(); let obj = args.get(0).expect("Cannot get object");
let prop = from_value::<String>(args.get(1).unwrap().clone()).unwrap(); let prop = from_value::<String>(args.get(1).expect("Cannot get object").clone())
let desc = from_value::<Property>(args.get(2).unwrap().clone()).unwrap(); .expect("Cannot get object");
let desc = from_value::<Property>(args.get(2).expect("Cannot get object").clone())
.expect("Cannot get object");
obj.set_prop(prop, desc); obj.set_prop(prop, desc);
Ok(Gc::new(ValueData::Undefined)) 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() { let prop = if args.is_empty() {
None None
} else { } else {
from_value::<String>(args.get(0).unwrap().clone()).ok() from_value::<String>(args.get(0).expect("Cannot get object").clone()).ok()
}; };
Ok(to_value( 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(),
)) ))
} }

144
src/lib/js/property.rs

@ -5,20 +5,22 @@ use gc_derive::{Finalize, Trace};
/// A Javascript Property AKA The Property Descriptor /// A Javascript Property AKA The Property Descriptor
/// [[SPEC] - The Property Descriptor Specification Type](https://tc39.github.io/ecma262/#sec-property-descriptor-specification-type) /// [[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) /// [[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)] #[derive(Trace, Finalize, Clone, Debug)]
pub struct Property { pub struct Property {
/// If the type of this can be changed and this can be deleted /// If the type of this can be changed and this can be deleted
pub configurable: bool, pub configurable: Option<bool>,
/// If the property shows up in enumeration of the object /// If the property shows up in enumeration of the object
pub enumerable: bool, pub enumerable: Option<bool>,
/// If this property can be changed with an assignment /// If this property can be changed with an assignment
pub writable: bool, pub writable: Option<bool>,
/// The value associated with the property /// The value associated with the property
pub value: Value, pub value: Option<Value>,
/// The function serving as getter /// The function serving as getter
pub get: Value, pub get: Option<Value>,
/// The function serving as setter /// The function serving as setter
pub set: Value, pub set: Option<Value>,
} }
impl Property { impl Property {
@ -28,14 +30,95 @@ impl Property {
} }
/// Make a new property with the given value /// 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 { Self {
configurable: false, configurable: None,
enumerable: false, enumerable: None,
writable: false, writable: None,
value, value: None,
get: Gc::new(ValueData::Undefined), get: None,
set: Gc::new(ValueData::Undefined), 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("configurable", to_value(self.configurable));
prop.set_field_slice("enumerable", to_value(self.enumerable)); prop.set_field_slice("enumerable", to_value(self.enumerable));
prop.set_field_slice("writable", to_value(self.writable)); prop.set_field_slice("writable", to_value(self.writable));
prop.set_field_slice("value", self.value.clone()); prop.set_field_slice("value", to_value(self.value.clone()));
prop.set_field_slice("get", self.get.clone()); prop.set_field_slice("get", to_value(self.get.clone()));
prop.set_field_slice("set", self.set.clone()); prop.set_field_slice("set", to_value(self.set.clone()));
prop prop
} }
} }
impl FromValue for Property { 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<Self, &'static str> { fn from_value(v: Value) -> Result<Self, &'static str> {
Ok(Self { Ok(Self {
configurable: from_value(v.get_field_slice("configurable")).unwrap(), configurable: {
enumerable: from_value(v.get_field_slice("enumerable")).unwrap(), match from_value::<bool>(v.get_field_slice("configurable")) {
writable: from_value(v.get_field_slice("writable")).unwrap(), Ok(v) => Some(v),
value: v.get_field_slice("value"), Err(_) => Some(false),
get: v.get_field_slice("get"), }
set: v.get_field_slice("set"), },
enumerable: {
match from_value::<bool>(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")),
}) })
} }
} }

14
src/lib/js/regexp.rs

@ -181,14 +181,8 @@ fn get_unicode(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
} }
fn _make_prop(getter: NativeFunctionData) -> Property { fn _make_prop(getter: NativeFunctionData) -> Property {
Property { Property::default()
writable: false, .get(to_value(getter))
enumerable: false,
configurable: true,
value: Gc::new(ValueData::Undefined),
get: to_value(getter),
set: Gc::new(ValueData::Undefined),
}
} }
/// Search for a match between this regex and a specified string /// 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); let result = to_value(result);
result.set_prop_slice("index", Property::new(to_value(m.start()))); result.set_prop_slice("index", Property::default().value(to_value(m.start())));
result.set_prop_slice("input", Property::new(to_value(arg_str))); result.set_prop_slice("input", Property::default().value(to_value(arg_str)));
result result
} }
None => { None => {

11
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 { pub fn _create(global: &Value) -> Value {
let string = to_value(make_string as NativeFunctionData); let string = to_value(make_string as NativeFunctionData);
let proto = ValueData::new_obj(Some(global)); let proto = ValueData::new_obj(Some(global));
let prop = Property { let prop = Property::default()
configurable: false, .get(to_value(get_string_length as NativeFunctionData));
enumerable: false,
writable: false,
value: Gc::new(ValueData::Undefined),
get: to_value(get_string_length as NativeFunctionData),
set: Gc::new(ValueData::Undefined),
};
proto.set_prop_slice("length", prop); proto.set_prop_slice("length", prop);
proto.set_field_slice("charAt", to_value(char_at as NativeFunctionData)); proto.set_field_slice("charAt", to_value(char_at as NativeFunctionData));
proto.set_field_slice("charCodeAt", to_value(char_code_at as NativeFunctionData)); proto.set_field_slice("charCodeAt", to_value(char_code_at as NativeFunctionData));

112
src/lib/js/value.rs

@ -20,6 +20,10 @@ pub type ResultValue = Result<Value, Value>;
/// A Garbage-collected Javascript value as represented in the interpreter /// A Garbage-collected Javascript value as represented in the interpreter
pub type Value = Gc<ValueData>; pub type Value = Gc<ValueData>;
pub fn undefined() -> Value {
Gc::new(ValueData::Undefined)
}
/// A Javascript value /// A Javascript value
#[derive(Trace, Finalize, Debug, Clone)] #[derive(Trace, Finalize, Debug, Clone)]
pub enum ValueData { pub enum ValueData {
@ -44,17 +48,18 @@ pub enum ValueData {
impl ValueData { impl ValueData {
/// Returns a new empty object /// Returns a new empty object
pub fn new_obj(global: Option<&Value>) -> Value { 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 = Object::create(obj_proto);
let obj_proto = global Gc::new(ValueData::Object(GcCell::new(obj)))
.expect("Expected global object in making-new-object") }
.get_field_slice("Object") None => {
.get_field_slice(PROTOTYPE); let obj = Object::default();
obj.internal_slots Gc::new(ValueData::Object(GcCell::new(obj)))
.insert(INSTANCE_PROTOTYPE.to_string(), obj_proto); }
} }
Gc::new(ValueData::Object(GcCell::new(obj)))
} }
/// Similar to `new_obj`, but you can pass a prototype to create from, /// 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 // This is only for primitive strings, String() objects have their lengths calculated in string.rs
if self.is_string() && field == "length" { if self.is_string() && field == "length" {
if let ValueData::String(ref s) = *self { 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 { if let Some(mut obj_data) = obj {
// Use value, or walk up the prototype chain // Use value, or walk up the prototype chain
if let Some(ref mut prop) = obj_data.properties.get_mut(field) { if let Some(ref mut prop) = obj_data.properties.get_mut(field) {
prop.value = value.unwrap_or_else(|| prop.value.clone()); prop.value = value;
prop.enumerable = enumerable.unwrap_or(prop.enumerable); prop.enumerable = enumerable;
prop.writable = writable.unwrap_or(prop.writable); prop.writable = writable;
prop.configurable = configurable.unwrap_or(prop.configurable); 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 /// 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]] /// 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 { pub fn get_field(&self, field: &str) -> Value {
match self.get_prop(field) { match self.get_prop(field) {
Some(prop) => { Some(prop) => {
// If the Property has [[Get]] set to a function, we should run that and return the Value // If the Property has [[Get]] set to a function, we should run that and return the Value
let prop_getter = match *prop.get { let prop_getter = match prop.get {
ValueData::Function(ref v) => match *v.borrow() { Some(_) => None,
Function::NativeFunc(ref _ntv) => { None => None
None // this never worked properly anyway
}
_ => None,
},
_ => None,
}; };
// If the getter is populated, use that. If not use [[Value]] instead // If the getter is populated, use that. If not use [[Value]] instead
match prop_getter { match prop_getter {
Some(val) => val, Some(val) => val,
None => prop.value.clone(), None => {
let val = prop.value.as_ref().unwrap();
val.clone()
}
} }
} }
None => Gc::new(ValueData::Undefined), None => Gc::new(ValueData::Undefined),
@ -388,18 +392,18 @@ impl ValueData {
ValueData::Object(ref obj) => { ValueData::Object(ref obj) => {
obj.borrow_mut() obj.borrow_mut()
.properties .properties
.insert(field, Property::new(val.clone())); .insert(field, Property::default().value(val.clone()));
} }
ValueData::Function(ref func) => { ValueData::Function(ref func) => {
match *func.borrow_mut().deref_mut() { match *func.borrow_mut().deref_mut() {
Function::NativeFunc(ref mut f) => f Function::NativeFunc(ref mut f) => f
.object .object
.properties .properties
.insert(field, Property::new(val.clone())), .insert(field, Property::default().value(val.clone())),
Function::RegularFunc(ref mut f) => f Function::RegularFunc(ref mut f) => f
.object .object
.properties .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() { for (idx, json) in vs.iter().enumerate() {
new_obj new_obj
.properties .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( new_obj.properties.insert(
"length".to_string(), "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)) ValueData::Object(GcCell::new(new_obj))
} }
@ -489,7 +493,7 @@ impl ValueData {
for (key, json) in obj.iter() { for (key, json) in obj.iter() {
new_obj new_obj
.properties .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)) 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 { impl Display for ValueData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
@ -552,7 +562,7 @@ impl Display for ValueData {
// Print public properties // Print public properties
if let Some((last_key, _)) = v.borrow().properties.iter().last() { if let Some((last_key, _)) = v.borrow().properties.iter().last() {
for (key, val) in v.borrow().properties.iter() { 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 { if key != last_key {
write!(f, ", ")?; write!(f, ", ")?;
} }
@ -778,7 +788,7 @@ impl<'s, T: ToValue> ToValue for &'s [T] {
let mut arr = Object::default(); let mut arr = Object::default();
for (i, item) in self.iter().enumerate() { for (i, item) in self.iter().enumerate() {
arr.properties arr.properties
.insert(i.to_string(), Property::new(item.to_value())); .insert(i.to_string(), Property::default().value(item.to_value()));
} }
to_value(arr) to_value(arr)
} }
@ -788,7 +798,7 @@ impl<T: ToValue> ToValue for Vec<T> {
let mut arr = Object::default(); let mut arr = Object::default();
for (i, item) in self.iter().enumerate() { for (i, item) in self.iter().enumerate() {
arr.properties arr.properties
.insert(i.to_string(), Property::new(item.to_value())); .insert(i.to_string(), Property::default().value(item.to_value()));
} }
to_value(arr) to_value(arr)
} }
@ -894,6 +904,44 @@ pub fn to_value<A: ToValue>(v: A) -> Value {
v.to_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::<bool>(x.clone()).expect("failed to get value")
== from_value::<bool>(y.clone()).expect("failed to get value")
}
_ => false,
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

3
src/lib/lib.rs

@ -29,7 +29,8 @@
clippy::print_stdout, clippy::print_stdout,
clippy::cast_possible_truncation, clippy::cast_possible_truncation,
clippy::cast_possible_wrap, clippy::cast_possible_wrap,
clippy::non_ascii_literal clippy::non_ascii_literal,
clippy::float_arithmetic
)] )]
pub mod environment; pub mod environment;

Loading…
Cancel
Save