Browse Source

creating String() constructor function

pull/8/head
Jason Williams 6 years ago
parent
commit
7c2aa5601d
  1. 1
      src/bin/bin.rs
  2. 17
      src/lib/exec.rs
  3. 2
      src/lib/js/object.rs
  4. 20
      src/lib/js/string.rs
  5. 132
      src/lib/js/value.rs
  6. 2
      tests/js/test.js

1
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);

17
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",

2
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__";

20
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<Value>) -> 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<Value>) -> 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::<i32>(this_str.len() as i32))
}
/// Get the string representation of the error
pub fn to_string(_: Value, _: Value, _: Vec<Value>) -> 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
}

132
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<ObjectData>),
/// The second object storage is optional for now
Object(GcCell<ObjectData>, GcCell<ObjectData>),
/// `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<Function>),
}
@ -46,6 +47,7 @@ impl ValueData {
/// Returns a new empty object
pub fn new_obj(global: Option<Value>) -> 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<Property> {
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<T: FromValue> FromValue for Vec<T> {
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<ObjectData, &'static str> {
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(),

2
tests/js/test.js

@ -1,2 +1,2 @@
var a = new String("test");
a;
a.length;
Loading…
Cancel
Save