From c5b75c55ad4678d6ab09c7f699f0e386f01523d4 Mon Sep 17 00:00:00 2001 From: Jason Williams <936006+jasonwilliams@users.noreply.github.com> Date: Tue, 6 Aug 2019 00:13:04 +0100 Subject: [PATCH] passing primitives to methods (#86) * passing primitives to methods * finishing unboxing_primitives, tests added, using internal_slots for __proto__ --- src/lib/exec.rs | 54 ++++++++++++++++++++++++++++++++--- src/lib/js/function.rs | 12 ++++---- src/lib/js/object.rs | 64 ++++++++++++++++++++++++++++++++++++++---- src/lib/js/string.rs | 16 +++++++++++ src/lib/js/value.rs | 45 +++++++++++++++-------------- tests/js/test.js | 2 +- 6 files changed, 156 insertions(+), 37 deletions(-) diff --git a/src/lib/exec.rs b/src/lib/exec.rs index 86498fef7e..d921e77be9 100644 --- a/src/lib/exec.rs +++ b/src/lib/exec.rs @@ -86,7 +86,10 @@ impl Executor for Interpreter { ExprDef::Call(ref callee, ref args) => { let (this, func) = match callee.def { ExprDef::GetConstField(ref obj, ref field) => { - let obj = self.run(obj)?; + let mut obj = self.run(obj)?; + if obj.get_type() != "object" { + obj = self.to_object(&obj).expect("failed to convert to object"); + } (obj.clone(), obj.borrow().get_field(field)) } ExprDef::GetField(ref obj, ref field) => { @@ -171,7 +174,7 @@ impl Executor for Interpreter { arr_map.borrow().set_field(index.to_string(), val); index += 1; } - arr_map.borrow().set_field_slice( + arr_map.borrow().set_internal_slot( INSTANCE_PROTOTYPE, self.environment .get_binding_value("Array") @@ -272,8 +275,10 @@ impl Executor for Interpreter { } let this = ValueData::new_obj(None); // 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)); + this.borrow().set_internal_slot( + INSTANCE_PROTOTYPE, + func.borrow().get_field_slice(PROTOTYPE), + ); match *func { ValueData::Function(ref inner_func) => match inner_func.clone().into_inner() { Function::NativeFunc(ref ntv) => { @@ -480,6 +485,47 @@ impl Interpreter { } } + /// The abstract operation ToObject converts argument to a value of type Object + /// https://tc39.es/ecma262/#sec-toobject + #[allow(clippy::wrong_self_convention)] + pub fn to_object(&mut self, value: &Value) -> ResultValue { + match *value.deref().borrow() { + ValueData::Undefined + | ValueData::Function(_) + | ValueData::Integer(_) + | ValueData::Null => Err(Gc::new(ValueData::Undefined)), + ValueData::Boolean(_) => { + let proto = self + .environment + .get_binding_value("Boolean") + .get_field_slice(PROTOTYPE); + + let bool_obj = ValueData::new_obj_from_prototype(proto, ObjectKind::Boolean); + bool_obj.set_internal_slot("BooleanData", value.clone()); + Ok(bool_obj) + } + ValueData::Number(_) => { + let proto = self + .environment + .get_binding_value("Number") + .get_field_slice(PROTOTYPE); + let number_obj = ValueData::new_obj_from_prototype(proto, ObjectKind::Number); + number_obj.set_internal_slot("NumberData", value.clone()); + Ok(number_obj) + } + ValueData::String(_) => { + let proto = self + .environment + .get_binding_value("String") + .get_field_slice(PROTOTYPE); + let string_obj = ValueData::new_obj_from_prototype(proto, ObjectKind::String); + string_obj.set_internal_slot("StringData", value.clone()); + Ok(string_obj) + } + ValueData::Object(_) => Ok(value.clone()), + } + } + /// value_to_rust_string() converts a value into a rust heap allocated string pub fn value_to_rust_string(&mut self, value: &Value) -> String { match *value.deref().borrow() { diff --git a/src/lib/js/function.rs b/src/lib/js/function.rs index 7d575ceb26..642c5c8e57 100644 --- a/src/lib/js/function.rs +++ b/src/lib/js/function.rs @@ -1,7 +1,7 @@ use crate::{ exec::Interpreter, js::{ - object::{ObjectData, Property}, + object::{Object, Property}, value::{to_value, ResultValue, Value, ValueData}, }, syntax::ast::expr::Expr, @@ -31,7 +31,7 @@ pub enum Function { #[derive(Trace, Finalize, Debug, Clone)] pub struct RegularFunction { /// The fields associated with the function - pub object: ObjectData, + pub object: Object, /// This function's expression pub expr: Expr, /// The argument names of the function @@ -42,7 +42,7 @@ impl RegularFunction { /// Make a new regular function #[allow(clippy::cast_possible_wrap)] pub fn new(expr: Expr, args: Vec) -> Self { - let mut object = ObjectData::default(); + let mut object = Object::default(); object.properties.insert( "arguments".to_string(), Property::new(Gc::new(ValueData::Integer(args.len() as i32))), @@ -55,7 +55,7 @@ impl RegularFunction { /// Represents a native javascript function in memory pub struct NativeFunction { /// The fields associated with the function - pub object: ObjectData, + pub object: Object, /// The callable function data pub data: NativeFunctionData, } @@ -63,7 +63,7 @@ pub struct NativeFunction { impl NativeFunction { /// Make a new native function with the given function data pub fn new(data: NativeFunctionData) -> Self { - let object = ObjectData::default(); + let object = Object::default(); Self { object, data } } } @@ -84,7 +84,7 @@ unsafe impl gc::Trace for NativeFunction { /// Create a new `Function` object pub fn _create() -> Value { - let function: ObjectData = ObjectData::default(); + let function: Object = Object::default(); to_value(function) } /// Initialise the global object with the `Function` object diff --git a/src/lib/js/object.rs b/src/lib/js/object.rs index 73ad610c2e..bfe36fb28f 100644 --- a/src/lib/js/object.rs +++ b/src/lib/js/object.rs @@ -7,7 +7,7 @@ use crate::{ }; use gc::Gc; use gc_derive::{Finalize, Trace}; -use std::collections::HashMap; +use std::{borrow::Borrow, collections::HashMap, ops::Deref}; /// Static `prototype`, usually set on constructors as a key to point to their respective prototype object. /// As this string will be used a lot throughout the program, its best being a static global string which will be referenced @@ -19,7 +19,7 @@ pub static INSTANCE_PROTOTYPE: &str = "__proto__"; /// `ObjectData` is the representation of an object in JavaScript #[derive(Trace, Finalize, Debug, Clone)] -pub struct ObjectData { +pub struct Object { /// Kind pub kind: ObjectKind, /// Internal Slots @@ -30,16 +30,68 @@ pub struct ObjectData { pub sym_properties: Box>, } -impl ObjectData { +impl Object { /// Return a new ObjectData struct, with `kind` set to Ordinary pub fn default() -> Self { - Self { + Object { kind: ObjectKind::Ordinary, internal_slots: Box::new(HashMap::new()), properties: Box::new(HashMap::new()), sym_properties: Box::new(HashMap::new()), } } + + /// Return a new Boolean object whose [[BooleanData]] internal slot is set to argument. + fn from_boolean(argument: &Value) -> Self { + let mut obj = Object { + kind: ObjectKind::Boolean, + internal_slots: Box::new(HashMap::new()), + properties: Box::new(HashMap::new()), + sym_properties: Box::new(HashMap::new()), + }; + + obj.internal_slots + .insert("BooleanData".to_string(), argument.clone()); + obj + } + + /// Return a new Number object whose [[NumberData]] internal slot is set to argument. + fn from_number(argument: &Value) -> Self { + let mut obj = Object { + kind: ObjectKind::Number, + internal_slots: Box::new(HashMap::new()), + properties: Box::new(HashMap::new()), + sym_properties: Box::new(HashMap::new()), + }; + + obj.internal_slots + .insert("NumberData".to_string(), argument.clone()); + obj + } + + /// Return a new String object whose [[StringData]] internal slot is set to argument. + fn from_string(argument: &Value) -> Self { + let mut obj = Object { + kind: ObjectKind::String, + internal_slots: Box::new(HashMap::new()), + properties: Box::new(HashMap::new()), + sym_properties: Box::new(HashMap::new()), + }; + + obj.internal_slots + .insert("StringData".to_string(), argument.clone()); + obj + } + + // https://tc39.es/ecma262/#sec-toobject + pub fn from(value: &Value) -> Result { + match *value.deref().borrow() { + ValueData::Boolean(_) => Ok(Self::from_boolean(value)), + ValueData::Number(_) => Ok(Self::from_number(value)), + ValueData::String(_) => Ok(Self::from_string(value)), + _ => Err(()), + } + } } #[derive(Trace, Finalize, Clone, Debug)] pub enum ObjectKind { @@ -49,6 +101,8 @@ pub enum ObjectKind { Symbol, Error, Ordinary, + Boolean, + Number, } /// A Javascript Property AKA The Property Descriptor @@ -130,7 +184,7 @@ pub fn get_proto_of(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultVal 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(); - obj.set_field_slice(INSTANCE_PROTOTYPE, proto); + obj.set_internal_slot(INSTANCE_PROTOTYPE, proto); Ok(obj) } diff --git a/src/lib/js/string.rs b/src/lib/js/string.rs index 05538c84eb..0353c25c46 100644 --- a/src/lib/js/string.rs +++ b/src/lib/js/string.rs @@ -645,12 +645,20 @@ mod tests { const empty = new String(''); const en = new String('english'); const zh = new String('中文'); + + const emptyLiteral = ''; + const enLiteral = 'english'; + const zhLiteral = '中文'; "#; forward(&mut engine, init); let pass = String::from("true"); assert_eq!(forward(&mut engine, "empty.startsWith('')"), pass); assert_eq!(forward(&mut engine, "en.startsWith('e')"), pass); assert_eq!(forward(&mut engine, "zh.startsWith('中')"), pass); + + assert_eq!(forward(&mut engine, "emptyLiteral.startsWith('')"), pass); + assert_eq!(forward(&mut engine, "enLiteral.startsWith('e')"), pass); + assert_eq!(forward(&mut engine, "zhLiteral.startsWith('中')"), pass); } #[test] @@ -660,11 +668,19 @@ mod tests { const empty = new String(''); const en = new String('english'); const zh = new String('中文'); + + const emptyLiteral = ''; + const enLiteral = 'english'; + const zhLiteral = '中文'; "#; forward(&mut engine, init); let pass = String::from("true"); assert_eq!(forward(&mut engine, "empty.endsWith('')"), pass); assert_eq!(forward(&mut engine, "en.endsWith('h')"), pass); assert_eq!(forward(&mut engine, "zh.endsWith('文')"), pass); + + assert_eq!(forward(&mut engine, "emptyLiteral.endsWith('')"), pass); + assert_eq!(forward(&mut engine, "enLiteral.endsWith('h')"), pass); + assert_eq!(forward(&mut engine, "zhLiteral.endsWith('文')"), pass); } } diff --git a/src/lib/js/value.rs b/src/lib/js/value.rs index a54d3505c3..2ce37bc8a3 100644 --- a/src/lib/js/value.rs +++ b/src/lib/js/value.rs @@ -1,6 +1,6 @@ use crate::js::{ function::{Function, NativeFunction, NativeFunctionData}, - object::{ObjectData, ObjectKind, Property, INSTANCE_PROTOTYPE, PROTOTYPE}, + object::{Object, ObjectKind, Property, INSTANCE_PROTOTYPE, PROTOTYPE}, }; use gc::{Gc, GcCell}; use gc_derive::{Finalize, Trace}; @@ -34,7 +34,7 @@ pub enum ValueData { /// `Number` - A 32-bit integer, such as `42` Integer(i32), /// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values - Object(GcCell), + 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>), } @@ -42,25 +42,28 @@ pub enum ValueData { impl ValueData { /// Returns a new empty object pub fn new_obj(global: Option<&Value>) -> Value { - let mut obj = ObjectData::default(); + let mut obj = Object::default(); 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.properties - .insert(INSTANCE_PROTOTYPE.to_string(), Property::new(obj_proto)); + obj.internal_slots + .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 - pub fn new_obj_from_prototype(proto: Value) -> Value { - let mut obj = ObjectData::default(); + /// Similar to `new_obj`, but you can pass a prototype to create from, + /// plus a kind + pub fn new_obj_from_prototype(proto: Value, kind: ObjectKind) -> Value { + let mut obj = Object::default(); + obj.kind = kind; obj.internal_slots .insert(INSTANCE_PROTOTYPE.to_string(), proto); + Gc::new(ValueData::Object(GcCell::new(obj))) } @@ -199,7 +202,7 @@ impl ValueData { } } - let obj: ObjectData = match *self { + let obj: Object = match *self { ValueData::Object(ref obj) => { let hash = obj.clone(); // TODO: This will break, we should return a GcCellRefMut instead @@ -216,8 +219,8 @@ impl ValueData { match obj.properties.get(field) { Some(val) => Some(val.clone()), - None => match obj.properties.get(&INSTANCE_PROTOTYPE.to_string()) { - Some(prop) => prop.value.get_prop(field), + None => match obj.internal_slots.get(&INSTANCE_PROTOTYPE.to_string()) { + Some(value) => value.get_prop(field), None => None, }, } @@ -234,7 +237,7 @@ impl ValueData { writable: Option, configurable: Option, ) { - let obj: Option = match self { + let obj: Option = match self { ValueData::Object(ref obj) => Some(obj.borrow_mut().deref_mut().clone()), // Accesing .object on borrow() seems to automatically dereference it, so we don't need the * ValueData::Function(ref func) => match func.borrow_mut().deref_mut() { @@ -258,7 +261,7 @@ impl ValueData { /// Resolve the property in the object /// Returns a copy of the Property pub fn get_internal_slot(&self, field: &str) -> Value { - let obj: ObjectData = match *self { + let obj: Object = match *self { ValueData::Object(ref obj) => { let hash = obj.clone(); hash.into_inner() @@ -389,7 +392,7 @@ impl ValueData { JSONValue::String(v) => ValueData::String(v), JSONValue::Bool(v) => ValueData::Boolean(v), JSONValue::Array(vs) => { - let mut new_obj = ObjectData::default(); + let mut new_obj = Object::default(); for (idx, json) in vs.iter().enumerate() { new_obj .properties @@ -402,7 +405,7 @@ impl ValueData { ValueData::Object(GcCell::new(new_obj)) } JSONValue::Object(obj) => { - let mut new_obj = ObjectData::default(); + let mut new_obj = Object::default(); for (key, json) in obj.iter() { new_obj .properties @@ -421,9 +424,9 @@ impl ValueData { ValueData::Boolean(b) => JSONValue::Bool(b), ValueData::Object(ref obj) => { let mut new_obj = Map::new(); - for (k, v) in obj.borrow().properties.iter() { + for (k, v) in obj.borrow().internal_slots.iter() { if k != INSTANCE_PROTOTYPE { - new_obj.insert(k.clone(), v.value.to_json()); + new_obj.insert(k.clone(), v.to_json()); } } JSONValue::Object(new_obj) @@ -680,7 +683,7 @@ impl FromValue for bool { impl<'s, T: ToValue> ToValue for &'s [T] { fn to_value(&self) -> Value { - let mut arr = ObjectData::default(); + let mut arr = Object::default(); for (i, item) in self.iter().enumerate() { arr.properties .insert(i.to_string(), Property::new(item.to_value())); @@ -690,7 +693,7 @@ impl<'s, T: ToValue> ToValue for &'s [T] { } impl ToValue for Vec { fn to_value(&self) -> Value { - let mut arr = ObjectData::default(); + let mut arr = Object::default(); for (i, item) in self.iter().enumerate() { arr.properties .insert(i.to_string(), Property::new(item.to_value())); @@ -710,13 +713,13 @@ impl FromValue for Vec { } } -impl ToValue for ObjectData { +impl ToValue for Object { fn to_value(&self) -> Value { Gc::new(ValueData::Object(GcCell::new(self.clone()))) } } -impl FromValue for ObjectData { +impl FromValue for Object { fn from_value(v: Value) -> Result { match *v { ValueData::Object(ref obj) => Ok(obj.clone().into_inner()), diff --git a/tests/js/test.js b/tests/js/test.js index 022bf1a2fb..a9d33a6991 100644 --- a/tests/js/test.js +++ b/tests/js/test.js @@ -1 +1 @@ -"12345".charAt(2); +("Jason").charAt(2);