From 7c2aa5601d294d6f3434817e11c6f4a1d53d480f Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Wed, 12 Dec 2018 00:48:24 +0000 Subject: [PATCH 1/2] creating String() constructor function --- src/bin/bin.rs | 1 + src/lib/exec.rs | 17 +++--- src/lib/js/object.rs | 2 +- src/lib/js/string.rs | 20 +++++-- src/lib/js/value.rs | 132 +++++++++++++++++++++++++++++++++++-------- tests/js/test.js | 2 +- 6 files changed, 139 insertions(+), 35 deletions(-) diff --git a/src/bin/bin.rs b/src/bin/bin.rs index d628694f1e..2fd2289089 100644 --- a/src/bin/bin.rs +++ b/src/bin/bin.rs @@ -12,6 +12,7 @@ pub fn main() { // Setup executor let expr = Parser::new(tokens).parse_all().unwrap(); + // print!("{:#?}", expr); let mut engine: Interpreter = Executor::new(); let result = engine.run(&expr); diff --git a/src/lib/exec.rs b/src/lib/exec.rs index 8c8a495c86..13b7965219 100644 --- a/src/lib/exec.rs +++ b/src/lib/exec.rs @@ -1,13 +1,13 @@ -use gc::{Gc, GcCell}; use crate::js::function::{Function, RegularFunction}; use crate::js::object::{INSTANCE_PROTOTYPE, PROTOTYPE}; use crate::js::value::{from_value, to_value, ResultValue, Value, ValueData}; use crate::js::{array, console, function, json, math, object, string}; -use std::borrow::Borrow; -use std::collections::HashMap; use crate::syntax::ast::constant::Const; use crate::syntax::ast::expr::{Expr, ExprDef}; use crate::syntax::ast::op::{BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}; +use gc::{Gc, GcCell}; +use std::borrow::Borrow; +use std::collections::HashMap; /// A variable scope #[derive(Trace, Finalize, Clone, Debug)] pub struct Scope { @@ -117,7 +117,7 @@ impl Executor for Interpreter { let vars = scope.vars.clone(); let vars_ptr = vars.borrow(); match *vars_ptr.clone() { - ValueData::Object(ref obj) => match obj.borrow().get(name) { + ValueData::Object(ref obj, _) => match obj.borrow().get(name) { Some(v) => { val = v.value.clone(); break; @@ -335,14 +335,17 @@ impl Executor for Interpreter { for arg in args.iter() { v_args.push(r#try!(self.run(arg))); } - let this = Gc::new(ValueData::Object(GcCell::new(HashMap::new()))); + let this = Gc::new(ValueData::Object( + GcCell::new(HashMap::new()), + GcCell::new(HashMap::new()), + )); + // Create a blank object, then set its __proto__ property to the [Constructor].prototype this.borrow() .set_field_slice(INSTANCE_PROTOTYPE, func.borrow().get_field_slice(PROTOTYPE)); match *func { ValueData::Function(ref func) => match func.clone().into_inner() { Function::NativeFunc(ref ntv) => { let func = ntv.data; - println!("{:#?}", this); func(this, self.run(callee)?, v_args) } Function::RegularFunc(ref data) => { @@ -400,7 +403,7 @@ impl Executor for Interpreter { let val = r#try!(self.run(val_e)); Ok(to_value(match *val { ValueData::Undefined => "undefined", - ValueData::Null | ValueData::Object(_) => "object", + ValueData::Null | ValueData::Object(_, _) => "object", ValueData::Boolean(_) => "boolean", ValueData::Number(_) | ValueData::Integer(_) => "number", ValueData::String(_) => "string", diff --git a/src/lib/js/object.rs b/src/lib/js/object.rs index d6be01a95e..26ca85d4a5 100644 --- a/src/lib/js/object.rs +++ b/src/lib/js/object.rs @@ -1,6 +1,6 @@ -use gc::Gc; use crate::js::function::NativeFunctionData; use crate::js::value::{from_value, to_value, FromValue, ResultValue, ToValue, Value, ValueData}; +use gc::Gc; use std::collections::HashMap; pub static PROTOTYPE: &'static str = "prototype"; pub static INSTANCE_PROTOTYPE: &'static str = "__proto__"; diff --git a/src/lib/js/string.rs b/src/lib/js/string.rs index fafda08630..a165b25fe7 100644 --- a/src/lib/js/string.rs +++ b/src/lib/js/string.rs @@ -1,22 +1,33 @@ -use gc::Gc; use crate::js::function::NativeFunctionData; use crate::js::object::{Property, PROTOTYPE}; use crate::js::value::{from_value, to_value, ResultValue, Value, ValueData}; +use gc::Gc; /// Create new string /// https://searchfox.org/mozilla-central/source/js/src/vm/StringObject.h#19 +// This gets called when a new String() is created, it's called by exec:346 pub fn make_string(this: Value, _: Value, args: Vec) -> ResultValue { // If we're constructing a string, we should set the initial length // To do this we need to convert the string back to a Rust String, then get the .len() - let a: String = from_value(args[0].clone()).unwrap(); - this.set_field_slice("length", to_value(a.len() as i32)); + // let a: String = from_value(args[0].clone()).unwrap(); + // this.set_field_slice("length", to_value(a.len() as i32)); + + this.set_private_field_slice("PrimitiveValue", args[0].clone()); Ok(this) } + /// Get a string's length pub fn get_string_length(this: Value, _: Value, _: Vec) -> ResultValue { - let this_str: String = from_value(this).unwrap(); + let this_str: String = + from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap(); Ok(to_value::(this_str.len() as i32)) } + +/// Get the string representation of the error +pub fn to_string(_: Value, _: Value, _: Vec) -> ResultValue { + Ok(to_value(format!("{}", String::from("test")).to_string())) +} + /// Create a new `String` object pub fn _create(global: Value) -> Value { let string = to_value(make_string as NativeFunctionData); @@ -30,6 +41,7 @@ pub fn _create(global: Value) -> Value { set: Gc::new(ValueData::Undefined), }; proto.set_prop_slice("length", prop); + proto.set_field_slice("toString", to_value(to_string as NativeFunctionData)); string.set_field_slice(PROTOTYPE, proto); string } diff --git a/src/lib/js/value.rs b/src/lib/js/value.rs index c8aa5bd665..a90b830795 100644 --- a/src/lib/js/value.rs +++ b/src/lib/js/value.rs @@ -1,6 +1,6 @@ -use gc::{Gc, GcCell}; use crate::js::function::{Function, NativeFunction, NativeFunctionData}; use crate::js::object::{ObjectData, Property, INSTANCE_PROTOTYPE, PROTOTYPE}; +use gc::{Gc, GcCell}; use serde_json::map::Map; use serde_json::Number as JSONNumber; use serde_json::Value as JSONValue; @@ -37,7 +37,8 @@ pub enum ValueData { Integer(i32), /// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values /// Some Objects will need an internal slot to hold private values, so our second ObjectData is for that - Object(GcCell), + /// The second object storage is optional for now + Object(GcCell, 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(GcCell), } @@ -46,6 +47,7 @@ impl ValueData { /// Returns a new empty object pub fn new_obj(global: Option) -> Value { let mut obj: ObjectData = HashMap::new(); + let mut private_obj: ObjectData = HashMap::new(); if global.is_some() { let obj_proto = global .unwrap() @@ -53,20 +55,27 @@ impl ValueData { .get_field_slice(PROTOTYPE); obj.insert(INSTANCE_PROTOTYPE.to_string(), Property::new(obj_proto)); } - Gc::new(ValueData::Object(GcCell::new(obj))) + Gc::new(ValueData::Object( + GcCell::new(obj), + GcCell::new(private_obj), + )) } /// Similar to `new_obj`, but you can pass a prototype to create from pub fn new_obj_from_prototype(proto: Value) -> Value { let mut obj: ObjectData = HashMap::new(); + let mut private_obj: ObjectData = HashMap::new(); obj.insert(INSTANCE_PROTOTYPE.to_string(), Property::new(proto)); - Gc::new(ValueData::Object(GcCell::new(obj))) + Gc::new(ValueData::Object( + GcCell::new(obj), + GcCell::new(private_obj), + )) } /// Returns true if the value is an object pub fn is_object(&self) -> bool { match *self { - ValueData::Object(_) => true, + ValueData::Object(_, _) => true, _ => false, } } @@ -115,7 +124,7 @@ impl ValueData { /// [toBoolean](https://tc39.github.io/ecma262/#sec-toboolean) pub fn is_true(&self) -> bool { match *self { - ValueData::Object(_) => true, + ValueData::Object(_, _) => true, ValueData::String(ref s) if !s.is_empty() => true, ValueData::Number(n) if n >= 1.0 && n % 1.0 == 0.0 => true, ValueData::Integer(n) if n > 1 => true, @@ -127,7 +136,7 @@ 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::Undefined | ValueData::Function(_) => NAN, ValueData::String(ref str) => match FromStr::from_str(str) { Ok(num) => num, Err(_) => NAN, @@ -142,7 +151,7 @@ impl ValueData { /// Converts the value into a 32-bit integer pub fn to_int(&self) -> i32 { match *self { - ValueData::Object(_) + ValueData::Object(_, _) | ValueData::Undefined | ValueData::Null | ValueData::Boolean(false) @@ -164,25 +173,46 @@ 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::new(to_value(s.len() as i32))); } } let obj: ObjectData = match *self { - ValueData::Object(ref obj) => { + ValueData::Object(ref obj, _) => { let hash = obj.clone(); hash.into_inner() } // Accesing .object on borrow() seems to automatically dereference it, so we don't need the * ValueData::Function(ref func) => match func.clone().into_inner() { Function::NativeFunc(ref func) => func.object.clone(), - Function::RegularFunc(ref func) => func.object.clone() + Function::RegularFunc(ref func) => func.object.clone(), + }, + _ => return None, + }; + + match obj.get(&field) { + Some(val) => Some(val.clone()), + None => match obj.get(&INSTANCE_PROTOTYPE.to_string()) { + Some(prop) => prop.value.get_prop(field), + None => None, + }, + } + } + + /// Resolve the property in the object + /// Returns a copy of the Property + pub fn get_private_prop(&self, field: String) -> Option { + let obj: ObjectData = match *self { + ValueData::Object(_, ref obj) => { + let hash = obj.clone(); + hash.into_inner() } _ => return None, }; + match obj.get(&field) { Some(val) => Some(val.clone()), - None => match obj.get(&PROTOTYPE.to_string()) { + None => match obj.get(&INSTANCE_PROTOTYPE.to_string()) { Some(prop) => prop.value.get_prop(field), None => None, }, @@ -190,9 +220,44 @@ 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]] + pub fn get_private_field(&self, field: String) -> Value { + match self.get_private_prop(field) { + Some(prop) => prop.value.clone(), + None => Gc::new(ValueData::Undefined), + } + } + + /// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist + /// 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]] pub fn get_field(&self, field: String) -> Value { match self.get_prop(field) { - Some(prop) => prop.value.clone(), + 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) => { + let func = ntv.data; + Some( + func( + Gc::new(self.clone()), + Gc::new(self.clone()), + vec![Gc::new(self.clone())], + ) + .unwrap(), + ) + } + _ => 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 => Gc::new(ValueData::Undefined), } } @@ -205,7 +270,7 @@ impl ValueData { /// Set the field in the value pub fn set_field(&self, field: String, val: Value) -> Value { match *self { - ValueData::Object(ref obj) => { + ValueData::Object(ref obj, _) => { obj.borrow_mut() .insert(field.clone(), Property::new(val.clone())); } @@ -229,10 +294,27 @@ impl ValueData { self.set_field(field.to_string(), val) } + /// Set the private field in the value + pub fn set_private_field(&self, field: String, val: Value) -> Value { + match *self { + ValueData::Object(_, ref obj) => { + obj.borrow_mut() + .insert(field.clone(), Property::new(val.clone())); + } + _ => (), + } + val + } + + /// Set the private field in the value + pub fn set_private_field_slice<'a>(&self, field: &'a str, val: Value) -> Value { + self.set_private_field(field.to_string(), val) + } + /// Set the property in the value pub fn set_prop(&self, field: String, prop: Property) -> Property { match *self { - ValueData::Object(ref obj) => { + ValueData::Object(ref obj, _) => { obj.borrow_mut().insert(field.clone(), prop.clone()); } ValueData::Function(ref func) => { @@ -261,6 +343,7 @@ impl ValueData { JSONValue::Bool(v) => ValueData::Boolean(v), JSONValue::Array(vs) => { let mut i = 0; + let mut private_data: ObjectData = HashMap::new(); let mut data: ObjectData = FromIterator::from_iter(vs.iter().map(|json| { i += 1; ( @@ -272,14 +355,15 @@ impl ValueData { "length".to_string(), Property::new(to_value(vs.len() as i32)), ); - ValueData::Object(GcCell::new(data)) + ValueData::Object(GcCell::new(data), GcCell::new(private_data)) } JSONValue::Object(obj) => { + let mut private_data: ObjectData = HashMap::new(); let data: ObjectData = FromIterator::from_iter( obj.iter() .map(|(key, json)| (key.clone(), Property::new(to_value(json.clone())))), ); - ValueData::Object(GcCell::new(data)) + ValueData::Object(GcCell::new(data), GcCell::new(private_data)) } JSONValue::Null => ValueData::Null, } @@ -289,7 +373,7 @@ impl ValueData { match *self { ValueData::Null | ValueData::Undefined => JSONValue::Null, ValueData::Boolean(b) => JSONValue::Bool(b), - ValueData::Object(ref obj) => { + ValueData::Object(ref obj, _) => { let mut nobj = Map::new(); for (k, v) in obj.borrow().iter() { if k != INSTANCE_PROTOTYPE { @@ -335,8 +419,8 @@ impl Display for ValueData { _ => v.to_string(), } ), - ValueData::Object(ref v) => { - r#try!(write!(f, "{}", "{")); + ValueData::Object(ref v, _) => { + write!(f, "{}", "{")?; match v.borrow().iter().last() { Some((last_key, _)) => { for (key, val) in v.borrow().iter() { @@ -567,14 +651,18 @@ impl FromValue for Vec { impl ToValue for ObjectData { fn to_value(&self) -> Value { - Gc::new(ValueData::Object(GcCell::new(self.clone()))) + let mut private_obj: ObjectData = HashMap::new(); + Gc::new(ValueData::Object( + GcCell::new(self.clone()), + GcCell::new(private_obj), + )) } } impl FromValue for ObjectData { fn from_value(v: Value) -> Result { match *v { - ValueData::Object(ref obj) => Ok(obj.clone().into_inner()), + ValueData::Object(ref obj, _) => Ok(obj.clone().into_inner()), ValueData::Function(ref func) => Ok(match *func.borrow().deref() { Function::NativeFunc(ref data) => data.object.clone(), Function::RegularFunc(ref data) => data.object.clone(), diff --git a/tests/js/test.js b/tests/js/test.js index b71ec58ac8..593d1c2e86 100644 --- a/tests/js/test.js +++ b/tests/js/test.js @@ -1,2 +1,2 @@ var a = new String("test"); -a; \ No newline at end of file +a.length; \ No newline at end of file From 226ba4dcf394b1bddf4cf8dcf57221b6d4d6f63a Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Thu, 7 Mar 2019 22:13:35 +0000 Subject: [PATCH 2/2] no need for mutable in private_obj --- src/lib/js/value.rs | 10 +++++----- tests/js/test.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/js/value.rs b/src/lib/js/value.rs index a90b830795..bfc439e208 100644 --- a/src/lib/js/value.rs +++ b/src/lib/js/value.rs @@ -47,7 +47,7 @@ impl ValueData { /// Returns a new empty object pub fn new_obj(global: Option) -> Value { let mut obj: ObjectData = HashMap::new(); - let mut private_obj: ObjectData = HashMap::new(); + let private_obj: ObjectData = HashMap::new(); if global.is_some() { let obj_proto = global .unwrap() @@ -64,7 +64,7 @@ impl ValueData { /// Similar to `new_obj`, but you can pass a prototype to create from pub fn new_obj_from_prototype(proto: Value) -> Value { let mut obj: ObjectData = HashMap::new(); - let mut private_obj: ObjectData = HashMap::new(); + let private_obj: ObjectData = HashMap::new(); obj.insert(INSTANCE_PROTOTYPE.to_string(), Property::new(proto)); Gc::new(ValueData::Object( GcCell::new(obj), @@ -343,7 +343,7 @@ impl ValueData { JSONValue::Bool(v) => ValueData::Boolean(v), JSONValue::Array(vs) => { let mut i = 0; - let mut private_data: ObjectData = HashMap::new(); + let private_data: ObjectData = HashMap::new(); let mut data: ObjectData = FromIterator::from_iter(vs.iter().map(|json| { i += 1; ( @@ -358,7 +358,7 @@ impl ValueData { ValueData::Object(GcCell::new(data), GcCell::new(private_data)) } JSONValue::Object(obj) => { - let mut private_data: ObjectData = HashMap::new(); + let private_data: ObjectData = HashMap::new(); let data: ObjectData = FromIterator::from_iter( obj.iter() .map(|(key, json)| (key.clone(), Property::new(to_value(json.clone())))), @@ -651,7 +651,7 @@ impl FromValue for Vec { impl ToValue for ObjectData { fn to_value(&self) -> Value { - let mut private_obj: ObjectData = HashMap::new(); + let private_obj: ObjectData = HashMap::new(); Gc::new(ValueData::Object( GcCell::new(self.clone()), GcCell::new(private_obj), diff --git a/tests/js/test.js b/tests/js/test.js index 593d1c2e86..eaf7c65929 100644 --- a/tests/js/test.js +++ b/tests/js/test.js @@ -1,2 +1,2 @@ var a = new String("test"); -a.length; \ No newline at end of file +a.toString();