Browse Source

passing primitives to methods (#86)

* passing primitives to methods

* finishing unboxing_primitives, tests added, using internal_slots for __proto__
pull/87/head
Jason Williams 5 years ago committed by GitHub
parent
commit
c5b75c55ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 54
      src/lib/exec.rs
  2. 12
      src/lib/js/function.rs
  3. 64
      src/lib/js/object.rs
  4. 16
      src/lib/js/string.rs
  5. 45
      src/lib/js/value.rs
  6. 2
      tests/js/test.js

54
src/lib/exec.rs

@ -86,7 +86,10 @@ impl Executor for Interpreter {
ExprDef::Call(ref callee, ref args) => { ExprDef::Call(ref callee, ref args) => {
let (this, func) = match callee.def { let (this, func) = match callee.def {
ExprDef::GetConstField(ref obj, ref field) => { 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)) (obj.clone(), obj.borrow().get_field(field))
} }
ExprDef::GetField(ref obj, ref field) => { ExprDef::GetField(ref obj, ref field) => {
@ -171,7 +174,7 @@ impl Executor for Interpreter {
arr_map.borrow().set_field(index.to_string(), val); arr_map.borrow().set_field(index.to_string(), val);
index += 1; index += 1;
} }
arr_map.borrow().set_field_slice( arr_map.borrow().set_internal_slot(
INSTANCE_PROTOTYPE, INSTANCE_PROTOTYPE,
self.environment self.environment
.get_binding_value("Array") .get_binding_value("Array")
@ -272,8 +275,10 @@ impl Executor for Interpreter {
} }
let this = ValueData::new_obj(None); let this = ValueData::new_obj(None);
// Create a blank object, then set its __proto__ property to the [Constructor].prototype // Create a blank object, then set its __proto__ property to the [Constructor].prototype
this.borrow() this.borrow().set_internal_slot(
.set_field_slice(INSTANCE_PROTOTYPE, func.borrow().get_field_slice(PROTOTYPE)); INSTANCE_PROTOTYPE,
func.borrow().get_field_slice(PROTOTYPE),
);
match *func { match *func {
ValueData::Function(ref inner_func) => match inner_func.clone().into_inner() { ValueData::Function(ref inner_func) => match inner_func.clone().into_inner() {
Function::NativeFunc(ref ntv) => { 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 /// value_to_rust_string() converts a value into a rust heap allocated string
pub fn value_to_rust_string(&mut self, value: &Value) -> String { pub fn value_to_rust_string(&mut self, value: &Value) -> String {
match *value.deref().borrow() { match *value.deref().borrow() {

12
src/lib/js/function.rs

@ -1,7 +1,7 @@
use crate::{ use crate::{
exec::Interpreter, exec::Interpreter,
js::{ js::{
object::{ObjectData, Property}, object::{Object, Property},
value::{to_value, ResultValue, Value, ValueData}, value::{to_value, ResultValue, Value, ValueData},
}, },
syntax::ast::expr::Expr, syntax::ast::expr::Expr,
@ -31,7 +31,7 @@ pub enum Function {
#[derive(Trace, Finalize, Debug, Clone)] #[derive(Trace, Finalize, Debug, Clone)]
pub struct RegularFunction { pub struct RegularFunction {
/// The fields associated with the function /// The fields associated with the function
pub object: ObjectData, pub object: Object,
/// This function's expression /// This function's expression
pub expr: Expr, pub expr: Expr,
/// The argument names of the function /// The argument names of the function
@ -42,7 +42,7 @@ impl RegularFunction {
/// Make a new regular function /// Make a new regular function
#[allow(clippy::cast_possible_wrap)] #[allow(clippy::cast_possible_wrap)]
pub fn new(expr: Expr, args: Vec<String>) -> Self { pub fn new(expr: Expr, args: Vec<String>) -> Self {
let mut object = ObjectData::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::new(Gc::new(ValueData::Integer(args.len() as i32))),
@ -55,7 +55,7 @@ impl RegularFunction {
/// Represents a native javascript function in memory /// Represents a native javascript function in memory
pub struct NativeFunction { pub struct NativeFunction {
/// The fields associated with the function /// The fields associated with the function
pub object: ObjectData, pub object: Object,
/// The callable function data /// The callable function data
pub data: NativeFunctionData, pub data: NativeFunctionData,
} }
@ -63,7 +63,7 @@ pub struct NativeFunction {
impl NativeFunction { impl NativeFunction {
/// Make a new native function with the given function data /// Make a new native function with the given function data
pub fn new(data: NativeFunctionData) -> Self { pub fn new(data: NativeFunctionData) -> Self {
let object = ObjectData::default(); let object = Object::default();
Self { object, data } Self { object, data }
} }
} }
@ -84,7 +84,7 @@ unsafe impl gc::Trace for NativeFunction {
/// Create a new `Function` object /// Create a new `Function` object
pub fn _create() -> Value { pub fn _create() -> Value {
let function: ObjectData = ObjectData::default(); let function: Object = Object::default();
to_value(function) to_value(function)
} }
/// Initialise the global object with the `Function` object /// Initialise the global object with the `Function` object

64
src/lib/js/object.rs

@ -7,7 +7,7 @@ use crate::{
}; };
use gc::Gc; use gc::Gc;
use gc_derive::{Finalize, Trace}; 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. /// 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 /// 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 /// `ObjectData` is the representation of an object in JavaScript
#[derive(Trace, Finalize, Debug, Clone)] #[derive(Trace, Finalize, Debug, Clone)]
pub struct ObjectData { pub struct Object {
/// Kind /// Kind
pub kind: ObjectKind, pub kind: ObjectKind,
/// Internal Slots /// Internal Slots
@ -30,16 +30,68 @@ pub struct ObjectData {
pub sym_properties: Box<HashMap<usize, Property>>, pub sym_properties: Box<HashMap<usize, Property>>,
} }
impl ObjectData { impl Object {
/// Return a new ObjectData struct, with `kind` set to Ordinary /// Return a new ObjectData struct, with `kind` set to Ordinary
pub fn default() -> Self { pub fn default() -> Self {
Self { Object {
kind: ObjectKind::Ordinary, kind: ObjectKind::Ordinary,
internal_slots: Box::new(HashMap::new()), internal_slots: Box::new(HashMap::new()),
properties: Box::new(HashMap::new()), properties: Box::new(HashMap::new()),
sym_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<Self, ()> {
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)] #[derive(Trace, Finalize, Clone, Debug)]
pub enum ObjectKind { pub enum ObjectKind {
@ -49,6 +101,8 @@ pub enum ObjectKind {
Symbol, Symbol,
Error, Error,
Ordinary, Ordinary,
Boolean,
Number,
} }
/// A Javascript Property AKA The Property Descriptor /// 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 { pub fn set_proto_of(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let obj = args.get(0).unwrap().clone(); let obj = args.get(0).unwrap().clone();
let proto = args.get(1).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) Ok(obj)
} }

16
src/lib/js/string.rs

@ -645,12 +645,20 @@ mod tests {
const empty = new String(''); const empty = new String('');
const en = new String('english'); const en = new String('english');
const zh = new String(''); const zh = new String('');
const emptyLiteral = '';
const enLiteral = 'english';
const zhLiteral = '';
"#; "#;
forward(&mut engine, init); forward(&mut engine, init);
let pass = String::from("true"); let pass = String::from("true");
assert_eq!(forward(&mut engine, "empty.startsWith('')"), pass); assert_eq!(forward(&mut engine, "empty.startsWith('')"), pass);
assert_eq!(forward(&mut engine, "en.startsWith('e')"), pass); assert_eq!(forward(&mut engine, "en.startsWith('e')"), pass);
assert_eq!(forward(&mut engine, "zh.startsWith('中')"), 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] #[test]
@ -660,11 +668,19 @@ mod tests {
const empty = new String(''); const empty = new String('');
const en = new String('english'); const en = new String('english');
const zh = new String(''); const zh = new String('');
const emptyLiteral = '';
const enLiteral = 'english';
const zhLiteral = '';
"#; "#;
forward(&mut engine, init); forward(&mut engine, init);
let pass = String::from("true"); let pass = String::from("true");
assert_eq!(forward(&mut engine, "empty.endsWith('')"), pass); assert_eq!(forward(&mut engine, "empty.endsWith('')"), pass);
assert_eq!(forward(&mut engine, "en.endsWith('h')"), pass); assert_eq!(forward(&mut engine, "en.endsWith('h')"), pass);
assert_eq!(forward(&mut engine, "zh.endsWith('文')"), 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);
} }
} }

45
src/lib/js/value.rs

@ -1,6 +1,6 @@
use crate::js::{ use crate::js::{
function::{Function, NativeFunction, NativeFunctionData}, function::{Function, NativeFunction, NativeFunctionData},
object::{ObjectData, ObjectKind, Property, INSTANCE_PROTOTYPE, PROTOTYPE}, object::{Object, ObjectKind, Property, INSTANCE_PROTOTYPE, PROTOTYPE},
}; };
use gc::{Gc, GcCell}; use gc::{Gc, GcCell};
use gc_derive::{Finalize, Trace}; use gc_derive::{Finalize, Trace};
@ -34,7 +34,7 @@ pub enum ValueData {
/// `Number` - A 32-bit integer, such as `42` /// `Number` - A 32-bit integer, such as `42`
Integer(i32), Integer(i32),
/// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values /// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values
Object(GcCell<ObjectData>), Object(GcCell<Object>),
/// `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` - 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<GcCell<Function>>), Function(Box<GcCell<Function>>),
} }
@ -42,25 +42,28 @@ 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 = ObjectData::default(); let mut obj = Object::default();
if global.is_some() { if global.is_some() {
let obj_proto = global let obj_proto = global
.expect("Expected global object in making-new-object") .expect("Expected global object in making-new-object")
.get_field_slice("Object") .get_field_slice("Object")
.get_field_slice(PROTOTYPE); .get_field_slice(PROTOTYPE);
obj.properties obj.internal_slots
.insert(INSTANCE_PROTOTYPE.to_string(), Property::new(obj_proto)); .insert(INSTANCE_PROTOTYPE.to_string(), obj_proto);
} }
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 /// Similar to `new_obj`, but you can pass a prototype to create from,
pub fn new_obj_from_prototype(proto: Value) -> Value { /// plus a kind
let mut obj = ObjectData::default(); pub fn new_obj_from_prototype(proto: Value, kind: ObjectKind) -> Value {
let mut obj = Object::default();
obj.kind = kind;
obj.internal_slots obj.internal_slots
.insert(INSTANCE_PROTOTYPE.to_string(), proto); .insert(INSTANCE_PROTOTYPE.to_string(), proto);
Gc::new(ValueData::Object(GcCell::new(obj))) 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) => { ValueData::Object(ref obj) => {
let hash = obj.clone(); let hash = obj.clone();
// TODO: This will break, we should return a GcCellRefMut instead // TODO: This will break, we should return a GcCellRefMut instead
@ -216,8 +219,8 @@ impl ValueData {
match obj.properties.get(field) { match obj.properties.get(field) {
Some(val) => Some(val.clone()), Some(val) => Some(val.clone()),
None => match obj.properties.get(&INSTANCE_PROTOTYPE.to_string()) { None => match obj.internal_slots.get(&INSTANCE_PROTOTYPE.to_string()) {
Some(prop) => prop.value.get_prop(field), Some(value) => value.get_prop(field),
None => None, None => None,
}, },
} }
@ -234,7 +237,7 @@ impl ValueData {
writable: Option<bool>, writable: Option<bool>,
configurable: Option<bool>, configurable: Option<bool>,
) { ) {
let obj: Option<ObjectData> = match self { let obj: Option<Object> = match self {
ValueData::Object(ref obj) => Some(obj.borrow_mut().deref_mut().clone()), 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 * // 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() { ValueData::Function(ref func) => match func.borrow_mut().deref_mut() {
@ -258,7 +261,7 @@ impl ValueData {
/// Resolve the property in the object /// Resolve the property in the object
/// Returns a copy of the Property /// Returns a copy of the Property
pub fn get_internal_slot(&self, field: &str) -> Value { pub fn get_internal_slot(&self, field: &str) -> Value {
let obj: ObjectData = match *self { let obj: Object = match *self {
ValueData::Object(ref obj) => { ValueData::Object(ref obj) => {
let hash = obj.clone(); let hash = obj.clone();
hash.into_inner() hash.into_inner()
@ -389,7 +392,7 @@ impl ValueData {
JSONValue::String(v) => ValueData::String(v), JSONValue::String(v) => ValueData::String(v),
JSONValue::Bool(v) => ValueData::Boolean(v), JSONValue::Bool(v) => ValueData::Boolean(v),
JSONValue::Array(vs) => { JSONValue::Array(vs) => {
let mut new_obj = ObjectData::default(); let mut new_obj = Object::default();
for (idx, json) in vs.iter().enumerate() { for (idx, json) in vs.iter().enumerate() {
new_obj new_obj
.properties .properties
@ -402,7 +405,7 @@ impl ValueData {
ValueData::Object(GcCell::new(new_obj)) ValueData::Object(GcCell::new(new_obj))
} }
JSONValue::Object(obj) => { JSONValue::Object(obj) => {
let mut new_obj = ObjectData::default(); let mut new_obj = Object::default();
for (key, json) in obj.iter() { for (key, json) in obj.iter() {
new_obj new_obj
.properties .properties
@ -421,9 +424,9 @@ impl ValueData {
ValueData::Boolean(b) => JSONValue::Bool(b), ValueData::Boolean(b) => JSONValue::Bool(b),
ValueData::Object(ref obj) => { ValueData::Object(ref obj) => {
let mut new_obj = Map::new(); 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 { 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) JSONValue::Object(new_obj)
@ -680,7 +683,7 @@ impl FromValue for bool {
impl<'s, T: ToValue> ToValue for &'s [T] { impl<'s, T: ToValue> ToValue for &'s [T] {
fn to_value(&self) -> Value { fn to_value(&self) -> Value {
let mut arr = ObjectData::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::new(item.to_value()));
@ -690,7 +693,7 @@ impl<'s, T: ToValue> ToValue for &'s [T] {
} }
impl<T: ToValue> ToValue for Vec<T> { impl<T: ToValue> ToValue for Vec<T> {
fn to_value(&self) -> Value { fn to_value(&self) -> Value {
let mut arr = ObjectData::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::new(item.to_value()));
@ -710,13 +713,13 @@ impl<T: FromValue> FromValue for Vec<T> {
} }
} }
impl ToValue for ObjectData { impl ToValue for Object {
fn to_value(&self) -> Value { fn to_value(&self) -> Value {
Gc::new(ValueData::Object(GcCell::new(self.clone()))) Gc::new(ValueData::Object(GcCell::new(self.clone())))
} }
} }
impl FromValue for ObjectData { impl FromValue for Object {
fn from_value(v: Value) -> Result<Self, &'static str> { fn from_value(v: Value) -> Result<Self, &'static str> {
match *v { match *v {
ValueData::Object(ref obj) => Ok(obj.clone().into_inner()), ValueData::Object(ref obj) => Ok(obj.clone().into_inner()),

2
tests/js/test.js

@ -1 +1 @@
"12345".charAt(2); ("Jason").charAt(2);

Loading…
Cancel
Save