Browse Source

[WIP] - addition of Symbols (#191)

* Addition of Symbols
* Addition of Symbol benchmark
pull/212/head
Jason Williams 5 years ago committed by GitHub
parent
commit
d4791837db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      benches/exec.rs
  2. 113
      src/lib/builtins/array.rs
  3. 7
      src/lib/builtins/console.rs
  4. 6
      src/lib/builtins/mod.rs
  5. 2
      src/lib/builtins/number.rs
  6. 183
      src/lib/builtins/object.rs
  7. 50
      src/lib/builtins/property.rs
  8. 6
      src/lib/builtins/regexp.rs
  9. 113
      src/lib/builtins/symbol.rs
  10. 112
      src/lib/builtins/value.rs
  11. 2
      src/lib/environment/object_environment_record.rs
  12. 35
      src/lib/exec.rs
  13. 3
      src/lib/realm.rs
  14. 1
      tests/js/test.js

15
benches/exec.rs

@ -1,12 +1,23 @@
#[macro_use]
extern crate criterion;
use boa::exec;
use boa::realm::Realm;
use criterion::Criterion;
use criterion::{black_box, Criterion};
static SRC: &str = r#"
let a = Symbol();
let b = Symbol();
let c = Symbol();
"#;
fn symbol_creation(c: &mut Criterion) {
c.bench_function("Symbol Creation", move |b| b.iter(|| exec(black_box(SRC))));
}
fn create_realm(c: &mut Criterion) {
c.bench_function("Create Realm", move |b| b.iter(|| Realm::create()));
}
criterion_group!(benches, create_realm);
criterion_group!(benches, create_realm, symbol_creation);
criterion_main!(benches);

113
src/lib/builtins/array.rs

@ -35,7 +35,7 @@ pub(crate) fn new_array(interpreter: &Interpreter) -> ResultValue {
/// Utility function for creating array objects: `array_obj` can be any array with
/// prototype already set (it will be wiped and recreated from `array_contents`)
fn construct_array(array_obj: &Value, array_contents: &[Value]) -> ResultValue {
pub fn construct_array(array_obj: &Value, array_contents: &[Value]) -> ResultValue {
let array_obj_ptr = array_obj.clone();
// Wipe existing contents of the array object
@ -45,9 +45,17 @@ fn construct_array(array_obj: &Value, array_contents: &[Value]) -> ResultValue {
array_obj_ptr.remove_prop(&n.to_string());
}
array_obj_ptr.set_field_slice("length", to_value(array_contents.len() as i32));
// Create length
let length = Property::new()
.value(to_value(array_contents.len() as i32))
.writable(true)
.configurable(false)
.enumerable(false);
array_obj_ptr.set_prop("length".to_string(), length);
for (n, value) in array_contents.iter().enumerate() {
array_obj_ptr.set_field(n.to_string(), value.clone());
array_obj_ptr.set_field_slice(&n.to_string(), value.clone());
}
Ok(array_obj_ptr)
}
@ -60,7 +68,7 @@ pub(crate) fn add_to_array_object(array_ptr: &Value, add_values: &[Value]) -> Re
for (n, value) in add_values.iter().enumerate() {
let new_index = orig_length.wrapping_add(n as i32);
array_ptr.set_field(new_index.to_string(), value.clone());
array_ptr.set_field_slice(&new_index.to_string(), value.clone());
}
array_ptr.set_field_slice(
@ -72,24 +80,37 @@ pub(crate) fn add_to_array_object(array_ptr: &Value, add_values: &[Value]) -> Re
}
/// Create a new array
pub fn make_array(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
pub fn make_array(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
// Make a new Object which will internally represent the Array (mapping
// between indices and values): this creates an Object with no prototype
this.set_field_slice("length", to_value(0_i32));
// Create length
let length = Property::new()
.value(to_value(args.len() as i32))
.writable(true)
.configurable(false)
.enumerable(false);
this.set_prop("length".to_string(), length);
// Set Prototype
let array_prototype = ctx
.realm
.global_obj
.get_field_slice("Array")
.get_field_slice(PROTOTYPE);
this.set_internal_slot(INSTANCE_PROTOTYPE, array_prototype);
// This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name)
this.set_kind(ObjectKind::Array);
match args.len() {
0 => construct_array(this, &[]),
1 => {
let array = construct_array(this, &[]).expect("Could not construct array");
let size: i32 = from_value(args.get(0).expect("Could not get argument").clone())
.expect("Could not convert argument to i32");
array.set_field_slice("length", to_value(size));
Ok(array)
}
_ => construct_array(this, args),
// And finally add our arguments in
for (n, value) in args.iter().enumerate() {
this.set_field_slice(&n.to_string(), value.clone());
}
Ok(this.clone())
}
/// Get an array's length
@ -118,14 +139,14 @@ pub fn concat(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue
let this_length: i32 =
from_value(this.get_field_slice("length")).expect("Could not convert argument to i32");
for n in 0..this_length {
new_values.push(this.get_field(&n.to_string()));
new_values.push(this.get_field_slice(&n.to_string()));
}
for concat_array in args {
let concat_length: i32 = from_value(concat_array.get_field_slice("length"))
.expect("Could not convert argument to i32");
for n in 0..concat_length {
new_values.push(concat_array.get_field(&n.to_string()));
new_values.push(concat_array.get_field_slice(&n.to_string()));
}
}
@ -154,7 +175,7 @@ pub fn pop(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
return Ok(Gc::new(ValueData::Undefined));
}
let pop_index = curr_length.wrapping_sub(1);
let pop_value: Value = this.get_field(&pop_index.to_string());
let pop_value: Value = this.get_field_slice(&pop_index.to_string());
this.remove_prop(&pop_index.to_string());
this.set_field_slice("length", to_value(pop_index));
Ok(pop_value)
@ -177,7 +198,7 @@ pub fn join(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let length: i32 =
from_value(this.get_field_slice("length")).expect("Could not convert argument to i32");
for n in 0..length {
let elem_str: String = this.get_field(&n.to_string()).to_string();
let elem_str: String = this.get_field_slice(&n.to_string()).to_string();
elem_strs.push(elem_str);
}
@ -201,17 +222,17 @@ pub fn reverse(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let upper_exists = this.has_field(&upper.to_string());
let lower_exists = this.has_field(&lower.to_string());
let upper_value = this.get_field(&upper.to_string());
let lower_value = this.get_field(&lower.to_string());
let upper_value = this.get_field_slice(&upper.to_string());
let lower_value = this.get_field_slice(&lower.to_string());
if upper_exists && lower_exists {
this.set_field(upper.to_string(), lower_value);
this.set_field(lower.to_string(), upper_value);
this.set_field_slice(&upper.to_string(), lower_value);
this.set_field_slice(&lower.to_string(), upper_value);
} else if upper_exists {
this.set_field(lower.to_string(), upper_value);
this.set_field_slice(&lower.to_string(), upper_value);
this.remove_prop(&upper.to_string());
} else if lower_exists {
this.set_field(upper.to_string(), lower_value);
this.set_field_slice(&upper.to_string(), lower_value);
this.remove_prop(&lower.to_string());
}
}
@ -230,20 +251,20 @@ pub fn shift(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
if len == 0 {
this.set_field_slice("length", to_value(0_i32));
// Since length is 0, this will be an Undefined value
return Ok(this.get_field(&0.to_string()));
return Ok(this.get_field_slice(&0.to_string()));
}
let first: Value = this.get_field(&0.to_string());
let first: Value = this.get_field_slice(&0.to_string());
for k in 1..len {
let from = k.to_string();
let to = (k.wrapping_sub(1)).to_string();
let from_value = this.get_field(&from);
let from_value = this.get_field_slice(&from);
if from_value == Gc::new(ValueData::Undefined) {
this.remove_prop(&to);
} else {
this.set_field(to, from_value);
this.set_field_slice(&to, from_value);
}
}
@ -270,11 +291,11 @@ pub fn unshift(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue
let from = (k.wrapping_sub(1)).to_string();
let to = (k.wrapping_add(arg_c).wrapping_sub(1)).to_string();
let from_value = this.get_field(&from);
let from_value = this.get_field_slice(&from);
if from_value == Gc::new(ValueData::Undefined) {
this.remove_prop(&to);
} else {
this.set_field(to, from_value);
this.set_field_slice(&to, from_value);
}
}
for j in 0..arg_c {
@ -315,7 +336,7 @@ pub fn every(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Res
let max_len: i32 = from_value(this.get_field_slice("length")).unwrap();
let mut len = max_len;
while i < len {
let element = this.get_field(&i.to_string());
let element = this.get_field_slice(&i.to_string());
let arguments = vec![element.clone(), to_value(i), this.clone()];
let result = interpreter.call(callback, &this_arg, arguments)?.is_true();
if !result {
@ -349,7 +370,7 @@ pub fn map(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Resul
let values = (0..length)
.map(|idx| {
let element = this.get_field(&idx.to_string());
let element = this.get_field_slice(&idx.to_string());
let args = vec![element, to_value(idx), new.clone()];
@ -400,7 +421,7 @@ pub fn index_of(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValu
};
while idx < len {
let check_element = this.get_field(&idx.to_string()).clone();
let check_element = this.get_field_slice(&idx.to_string()).clone();
if check_element == search_element {
return Ok(to_value(idx));
@ -449,7 +470,7 @@ pub fn last_index_of(this: &Value, args: &[Value], _: &mut Interpreter) -> Resul
};
while idx >= 0 {
let check_element = this.get_field(&idx.to_string()).clone();
let check_element = this.get_field_slice(&idx.to_string()).clone();
if check_element == search_element {
return Ok(to_value(idx));
@ -481,7 +502,7 @@ pub fn find(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Resu
};
let len: i32 = from_value(this.get_field_slice("length")).unwrap();
for i in 0..len {
let element = this.get_field(&i.to_string());
let element = this.get_field_slice(&i.to_string());
let arguments = vec![element.clone(), to_value(i), this.clone()];
let result = interpreter.call(callback, &this_arg, arguments)?;
if result.is_true() {
@ -515,7 +536,7 @@ pub fn find_index(this: &Value, args: &[Value], interpreter: &mut Interpreter) -
from_value(this.get_field_slice("length")).expect("Could not get `length` property.");
for i in 0..length {
let element = this.get_field(&i.to_string());
let element = this.get_field_slice(&i.to_string());
let arguments = vec![element.clone(), to_value(i), this.clone()];
let result = interpreter.call(predicate_arg, &this_arg, arguments)?;
@ -556,7 +577,7 @@ pub fn fill(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
};
for i in start..fin {
this.set_field(i.to_string(), value.clone());
this.set_field_slice(&i.to_string(), value.clone());
}
Ok(this.clone())
@ -572,7 +593,7 @@ pub fn includes_value(this: &Value, args: &[Value], _: &mut Interpreter) -> Resu
from_value(this.get_field_slice("length")).expect("Could not get `length` property.");
for idx in 0..length {
let check_element = this.get_field(&idx.to_string()).clone();
let check_element = this.get_field_slice(&idx.to_string()).clone();
if check_element == search_element {
return Ok(to_value(true));
@ -618,7 +639,10 @@ pub fn slice(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Res
let span = max(to.wrapping_sub(from), 0);
let mut new_array_len: i32 = 0;
for i in from..from.wrapping_add(span) {
new_array.set_field(new_array_len.to_string(), this.get_field(&i.to_string()));
new_array.set_field_slice(
&new_array_len.to_string(),
this.get_field_slice(&i.to_string()),
);
new_array_len = new_array_len.wrapping_add(1);
}
new_array.set_field_slice("length", to_value(new_array_len));
@ -628,14 +652,15 @@ pub fn slice(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Res
/// Create a new `Array` object
pub fn create_constructor(global: &Value) -> Value {
// Create Constructor
let mut array_constructor = Object::default();
let object_prototype = global.get_field_slice("Object").get_field_slice(PROTOTYPE);
let mut array_constructor = Object::create(object_prototype);
array_constructor.kind = ObjectKind::Function;
array_constructor.set_internal_method("construct", make_array);
// Todo: add call function
array_constructor.set_internal_method("call", make_array);
// Create prototype
let array_prototype = ValueData::new_obj(Some(global));
let array_prototype = ValueData::new_obj(None);
let length = Property::default().get(to_value(get_array_length as NativeFunctionData));
@ -1127,7 +1152,7 @@ mod tests {
// test object reference
forward(&mut engine, "a = (new Array(3)).fill({});");
forward(&mut engine, "a[0].hi = 'hi';");
assert_eq!(forward(&mut engine, "a[1].hi"), String::from("hi"));
assert_eq!(forward(&mut engine, "a[0].hi"), String::from("hi"));
}
#[test]

7
src/lib/builtins/console.rs

@ -94,6 +94,13 @@ fn log_string_from(x: Value) -> String {
}
s
}
ValueData::Symbol(ref sym) => {
let desc: Value = sym.borrow().get_internal_slot("Description");
match *desc {
ValueData::String(ref st) => format!("Symbol(\"{}\")", st.to_string()),
_ => String::from("Symbol()"),
}
}
_ => from_value::<String>(x.clone()).expect("Could not convert value to String"),
}

6
src/lib/builtins/mod.rs

@ -1,5 +1,7 @@
/// The global `Array` object
pub mod array;
/// the global `Symbol` Object
pub mod symbol;
// The global `Boolean` object
pub mod boolean;
/// The global `console` object
@ -16,11 +18,11 @@ pub mod math;
pub mod number;
/// The global `Object` object
pub mod object;
/// Property, used by `Object`
pub mod property;
/// The global 'RegExp' object
pub mod regexp;
/// The global `String` object
pub mod string;
/// Javascript values, utility methods and conversion between Javascript values and Rust values
pub mod value;
// Property, used by `Object`
pub mod property;

2
src/lib/builtins/number.rs

@ -20,7 +20,7 @@ fn to_number(value: &Value) -> Value {
to_value(0)
}
}
ValueData::Function(_) | ValueData::Undefined => to_value(f64::NAN),
ValueData::Function(_) | ValueData::Symbol(_) | ValueData::Undefined => to_value(f64::NAN),
ValueData::Integer(i) => to_value(f64::from(i)),
ValueData::Object(ref o) => (o).deref().borrow().get_internal_slot("NumberData"),
ValueData::Null => to_value(0),

183
src/lib/builtins/object.rs

@ -30,7 +30,7 @@ pub struct Object {
/// Properties
pub properties: Box<HashMap<String, Property>>,
/// Symbol Properties
pub sym_properties: Box<HashMap<usize, Property>>,
pub sym_properties: Box<HashMap<i32, Property>>,
/// Some rust object that stores internal state
pub state: Option<Box<InternalStateCell>>,
}
@ -38,18 +38,22 @@ pub struct Object {
impl Object {
/// Return a new ObjectData struct, with `kind` set to Ordinary
pub fn default() -> Self {
Object {
let mut object = Object {
kind: ObjectKind::Ordinary,
internal_slots: Box::new(HashMap::new()),
properties: Box::new(HashMap::new()),
sym_properties: Box::new(HashMap::new()),
state: None,
}
};
object.set_internal_slot("extensible", to_value(true));
object
}
/// ObjectCreate is used to specify the runtime creation of new ordinary objects
///
/// https://tc39.es/ecma262/#sec-objectcreate
// TODO: proto should be a &Value here
pub fn create(proto: Value) -> Object {
let mut obj = Object::default();
obj.internal_slots
@ -77,6 +81,13 @@ impl Object {
self.internal_slots.insert(name.to_string(), to_value(val));
}
/// Utility function to set a method on this object
/// The native function will live in the `properties` field of the Object
pub fn set_method(&mut self, name: &str, val: NativeFunctionData) {
self.properties
.insert(name.to_string(), Property::default().value(to_value(val)));
}
/// Return a new Boolean object whose [[BooleanData]] internal slot is set to argument.
fn from_boolean(argument: &Value) -> Self {
let mut obj = Object {
@ -184,28 +195,62 @@ impl Object {
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();
// Prop could either be a String or Symbol
match *(*prop) {
ValueData::String(ref st) => {
match self.properties.get(st) {
// 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
}
}
}
ValueData::Symbol(ref sym) => {
let sym_id = sym
.borrow()
.get_internal_slot("SymbolData")
.to_string()
.parse::<i32>()
.expect("Could not get Symbol ID");
match self.sym_properties.get(&sym_id) {
// 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
}
}
d.enumerable = v.enumerable;
d.configurable = v.configurable;
d
}
_ => Property::default(),
}
}
@ -240,20 +285,18 @@ impl Object {
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());
if desc.value.is_some() && desc.value.clone().unwrap().is_symbol() {
let sym_id = desc
.value
.clone()
.unwrap()
.to_string()
.parse::<i32>()
.expect("parsing failed");
self.sym_properties.insert(sym_id, desc);
} 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);
self.properties.insert(property_key, desc);
}
return true;
}
// If every field is absent we don't need to set anything
@ -262,13 +305,13 @@ impl Object {
}
// 4
if current.configurable.unwrap_or(false) {
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())
&& (desc.enumerable.as_ref().unwrap() != current.enumerable.as_ref().unwrap())
{
return false;
}
@ -276,7 +319,6 @@ impl Object {
// 5
if desc.is_generic_descriptor() {
// 6
} else if current.is_data_descriptor() != desc.is_data_descriptor() {
// a
@ -294,7 +336,20 @@ impl Object {
current.get = None;
current.set = None;
}
self.properties.insert(property_key, current.clone());
if current.value.is_some() && current.value.clone().unwrap().is_symbol() {
let sym_id = current
.value
.clone()
.unwrap()
.to_string()
.parse::<i32>()
.expect("parsing failed");
self.sym_properties.insert(sym_id, current.clone());
} else {
self.properties
.insert(property_key.clone(), current.clone());
}
// 7
} else if current.is_data_descriptor() && desc.is_data_descriptor() {
// a
@ -342,7 +397,7 @@ impl Object {
return true;
}
// 9
Property::assign(&mut current, &desc);
self.properties.insert(property_key, desc);
true
}
@ -400,6 +455,45 @@ impl Object {
// TODO!!!!! Call getter from here
Gc::new(ValueData::Undefined)
}
/// [[Set]]
/// <https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver>
pub fn set(&mut self, field: Value, val: Value) -> bool {
// [1]
debug_assert!(Property::is_property_key(&field));
// Fetch property key
let mut own_desc = self.get_own_property(&field);
// [2]
if own_desc.is_none() {
let parent = self.get_prototype_of();
if !parent.is_null() {
// TODO: come back to this
}
own_desc = Property::new()
.writable(true)
.enumerable(true)
.configurable(true);
}
// [3]
if own_desc.is_data_descriptor() {
if !own_desc.writable.unwrap() {
return false;
}
// Change value on the current descriptor
own_desc = own_desc.value(val);
return self.define_own_property(field.to_string(), own_desc);
}
// [4]
debug_assert!(own_desc.is_accessor_descriptor());
match own_desc.set {
None => false,
Some(_) => {
unimplemented!();
}
}
}
}
#[derive(Trace, Finalize, Clone, Debug, Eq, PartialEq)]
@ -462,16 +556,15 @@ pub fn has_own_prop(this: &Value, args: &[Value], _: &mut Interpreter) -> Result
}
/// Create a new `Object` object
pub fn create_constructor(global: &Value) -> Value {
pub fn create_constructor(_: &Value) -> Value {
let object = to_value(make_object as NativeFunctionData);
let prototype = ValueData::new_obj(Some(global));
prototype.set_field_slice(
"hasOwnProperty",
to_value(has_own_prop as NativeFunctionData),
);
prototype.set_field_slice("toString", to_value(to_string as NativeFunctionData));
// Prototype chain ends here VV
let mut prototype = Object::default();
prototype.set_method("hasOwnProperty", has_own_prop);
prototype.set_method("toString", to_string);
object.set_field_slice("length", to_value(1_i32));
object.set_field_slice(PROTOTYPE, prototype);
object.set_field_slice(PROTOTYPE, to_value(prototype));
object.set_field_slice(
"setPrototypeOf",
to_value(set_proto_of as NativeFunctionData),

50
src/lib/builtins/property.rs

@ -1,5 +1,4 @@
use crate::builtins::value::{from_value, to_value, FromValue, ToValue, Value, ValueData};
use gc::Gc;
use gc_derive::{Finalize, Trace};
/// A Javascript Property AKA The Property Descriptor
@ -26,7 +25,7 @@ pub struct Property {
impl Property {
/// Checks if the provided Value can be used as a property key.
pub fn is_property_key(value: &Value) -> bool {
value.is_string() // || value.is_symbol() // Uncomment this when we are handeling symbols.
value.is_string() || value.is_symbol() // Uncomment this when we are handeling symbols.
}
/// Make a new property with the given value
@ -92,43 +91,22 @@ impl Property {
&& self.enumerable.is_none()
}
// https://tc39.es/ecma262/#sec-isaccessordescriptor
/// An accessor Property Descriptor is one that includes any fields named either [[Get]] or [[Set]].
/// <https://tc39.es/ecma262/#sec-isaccessordescriptor>
pub fn is_accessor_descriptor(&self) -> bool {
self.get.is_some() && self.set.is_some()
self.get.is_some() || self.set.is_some()
}
// https://tc39.es/ecma262/#sec-isdatadescriptor
/// A data Property Descriptor is one that includes any fields named either [[Value]] or [[Writable]].
/// https://tc39.es/ecma262/#sec-isdatadescriptor
pub fn is_data_descriptor(&self) -> bool {
self.value.is_some() && self.writable.is_some()
self.value.is_some() || self.writable.is_some()
}
// https://tc39.es/ecma262/#sec-isgenericdescriptor
/// https://tc39.es/ecma262/#sec-isgenericdescriptor
pub fn is_generic_descriptor(&self) -> bool {
!self.is_accessor_descriptor() && !self.is_data_descriptor()
}
/// This copies only present property fields from B to A
pub fn assign(a: &mut Property, b: &Property) {
if b.get.is_some() {
a.get = b.get.clone();
}
if b.set.is_some() {
a.set = b.set.clone();
}
if b.configurable.is_some() {
a.configurable = b.configurable;
}
if b.writable.is_some() {
a.writable = b.writable;
}
if b.enumerable.is_some() {
a.enumerable = b.enumerable;
}
}
}
impl Default for Property {
@ -136,12 +114,12 @@ impl Default for 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)),
configurable: None,
enumerable: None,
writable: None,
value: None,
get: None,
set: None,
}
}
}

6
src/lib/builtins/regexp.rs

@ -189,7 +189,8 @@ fn _make_prop(getter: NativeFunctionData) -> Property {
/// Search for a match between this regex and a specified string
pub fn test(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let arg_str = get_argument::<String>(args, 0)?;
let mut last_index = from_value::<usize>(this.get_field("lastIndex")).map_err(to_value)?;
let mut last_index =
from_value::<usize>(this.get_field_slice("lastIndex")).map_err(to_value)?;
let result = this.with_internal_state_ref(|regex: &RegExp| {
let result = match regex.matcher.find_at(arg_str.as_str(), last_index) {
Some(m) => {
@ -214,7 +215,8 @@ pub fn test(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
/// Search for a match between this regex and a specified string
pub fn exec(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let arg_str = get_argument::<String>(args, 0)?;
let mut last_index = from_value::<usize>(this.get_field("lastIndex")).map_err(to_value)?;
let mut last_index =
from_value::<usize>(this.get_field_slice("lastIndex")).map_err(to_value)?;
let result = this.with_internal_state_ref(|regex: &RegExp| {
let mut locations = regex.matcher.capture_locations();
let result =

113
src/lib/builtins/symbol.rs

@ -0,0 +1,113 @@
use crate::{
builtins::{
object::{Object, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE},
value::{to_value, ResultValue, Value, ValueData},
},
exec::Interpreter,
};
use gc::{Gc, GcCell};
use rand::random;
/// https://tc39.es/ecma262/#sec-symbol-description
/// Creates Symbol instances.
///
/// Symbol instances are ordinary objects that inherit properties from the Symbol prototype object.
/// Symbol instances have a [[SymbolData]] internal slot.
/// The [[SymbolData]] internal slot is the Symbol value represented by this Symbol object.
pub fn call_symbol(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
// From an implementation and specificaition perspective Symbols are similar to Objects.
// They have internal slots to hold the SymbolData and Description, they also have methods and a prototype.
// So we start by creating an Object
// TODO: Set prototype to Symbol.prototype (by changing to Object::create(), use interpreter to get Symbol.prototype)
let mut sym_instance = Object::default();
sym_instance.kind = ObjectKind::Symbol;
// Set description which should either be undefined or a string
let desc_string = match args.get(0) {
Some(value) => to_value(value.to_string()),
None => Gc::new(ValueData::Undefined),
};
sym_instance.set_internal_slot("Description", desc_string);
sym_instance.set_internal_slot("SymbolData", to_value(random::<i32>()));
// Set __proto__ internal slot
let proto = ctx
.realm
.global_obj
.get_field_slice("Symbol")
.get_field_slice(PROTOTYPE);
sym_instance.set_internal_slot(INSTANCE_PROTOTYPE, proto);
Ok(Gc::new(ValueData::Symbol(GcCell::new(sym_instance))))
}
/// <https://tc39.es/ecma262/#sec-symbol.prototype.tostring>
pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let s: Value = this.get_internal_slot("Description");
let full_string = format!(r#"Symbol({})"#, s.to_string());
Ok(to_value(full_string))
}
/// <https://tc39.es/ecma262/#sec-symbol-constructor>
pub fn create_constructor(global: &Value) -> Value {
// Create Symbol constructor (or function in Symbol's case)
let mut symbol_constructor = Object::default();
symbol_constructor.set_internal_method("call", call_symbol);
// Create prototype
let mut symbol_prototype = Object::default();
// Symbol.prototype[[Prototype]] points to Object.prototype
// Symbol Constructor -> Symbol Prototype -> Object Prototype
let object_prototype = global.get_field_slice("Object").get_field_slice(PROTOTYPE);
symbol_prototype.set_internal_slot(INSTANCE_PROTOTYPE, object_prototype.clone());
symbol_prototype.set_method("toString", to_string);
let symbol_prototype_val = to_value(symbol_prototype);
let symbol_constructor_value = to_value(symbol_constructor);
symbol_prototype_val.set_field_slice("construcotor", symbol_constructor_value.clone());
symbol_constructor_value.set_field_slice(PROTOTYPE, symbol_prototype_val.clone());
symbol_constructor_value
}
#[cfg(test)]
mod tests {
use super::*;
use crate::exec::Executor;
use crate::realm::Realm;
use crate::{forward, forward_val};
#[test]
fn check_symbol_constructor_is_function() {
let global: Gc<ValueData> = ValueData::new_obj(None);
let symbol_constructor = create_constructor(&global);
assert_eq!(symbol_constructor.is_function(), true);
}
#[test]
fn call_symbol_and_check_return_type() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var sym = Symbol();
"#;
forward(&mut engine, init);
let sym = forward_val(&mut engine, "sym").unwrap();
assert_eq!(sym.is_symbol(), true);
}
#[test]
fn print_symbol_expect_description() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var sym = Symbol("Hello");
"#;
forward(&mut engine, init);
let sym = forward_val(&mut engine, "sym.toString()").unwrap();
assert_eq!(sym.to_string(), "Symbol(Hello)");
}
}

112
src/lib/builtins/value.rs

@ -43,6 +43,8 @@ pub enum ValueData {
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(Box<GcCell<Function>>),
/// `Symbol` - A Symbol Type - Internally Symbols are similar to objects, except there are no properties, only internal slots
Symbol(GcCell<Object>),
}
impl ValueData {
@ -90,6 +92,14 @@ impl ValueData {
}
}
/// Returns true if the value is a symbol
pub fn is_symbol(&self) -> bool {
match *self {
ValueData::Symbol(_) => true,
_ => false,
}
}
/// Returns true if the value is a function
pub fn is_function(&self) -> bool {
match *self {
@ -168,7 +178,10 @@ 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::Symbol(_)
| ValueData::Undefined
| ValueData::Function(_) => NAN,
ValueData::String(ref str) => match FromStr::from_str(str) {
Ok(num) => num,
Err(_) => NAN,
@ -185,6 +198,7 @@ impl ValueData {
match *self {
ValueData::Object(_)
| ValueData::Undefined
| ValueData::Symbol(_)
| ValueData::Null
| ValueData::Boolean(false)
| ValueData::Function(_) => 0,
@ -235,6 +249,10 @@ impl ValueData {
Function::NativeFunc(ref func) => func.object.clone(),
Function::RegularFunc(ref func) => func.object.clone(),
},
ValueData::Symbol(ref obj) => {
let hash = obj.clone();
hash.into_inner()
}
_ => return None,
};
@ -287,6 +305,10 @@ impl ValueData {
let hash = obj.clone();
hash.into_inner()
}
ValueData::Symbol(ref obj) => {
let hash = obj.clone();
hash.into_inner()
}
_ => return Gc::new(ValueData::Undefined),
};
@ -299,28 +321,35 @@ 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]]
/// TODO: this function should use the get Value if its set
pub fn get_field(&self, field: &str) -> Value {
match self.get_prop(field) {
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 {
Some(_) => None,
None => None,
};
// If the getter is populated, use that. If not use [[Value]] instead
match prop_getter {
Some(val) => val,
None => {
let val = prop
.value
.as_ref()
.expect("Could not get property as reference");
val.clone()
pub fn get_field(&self, field: Value) -> Value {
match *field {
// Our field will either be a String or a Symbol
ValueData::String(ref s) => {
match self.get_prop(s) {
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 {
Some(_) => None,
None => None,
};
// If the getter is populated, use that. If not use [[Value]] instead
match prop_getter {
Some(val) => val,
None => {
let val = prop
.value
.as_ref()
.expect("Could not get property as reference");
val.clone()
}
}
}
None => Gc::new(ValueData::Undefined),
}
}
None => Gc::new(ValueData::Undefined),
ValueData::Symbol(_) => unimplemented!(),
_ => Gc::new(ValueData::Undefined),
}
}
@ -400,15 +429,19 @@ 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
pub fn get_field_slice(&self, field: &str) -> Value {
self.get_field(field)
// get_field used to accept strings, but now Symbols accept it needs to accept a value
// So this function will now need to Box strings back into values (at least for now)
let f = Gc::new(ValueData::String(field.to_string()));
self.get_field(f)
}
/// Set the field in the value
pub fn set_field(&self, field: String, val: Value) -> Value {
/// Field could be a Symbol, so we need to accept a Value (not a string)
pub fn set_field(&self, field: Value, val: Value) -> Value {
match *self {
ValueData::Object(ref obj) => {
if obj.borrow().kind == ObjectKind::Array {
if let Ok(num) = field.parse::<usize>() {
if let Ok(num) = field.to_string().parse::<usize>() {
if num > 0 {
let len: i32 = from_value(self.get_field_slice("length"))
.expect("Could not convert argument to i32");
@ -418,20 +451,25 @@ impl ValueData {
}
}
}
obj.borrow_mut()
.properties
.insert(field, Property::default().value(val.clone()));
// Symbols get saved into a different bucket to general properties
if field.is_symbol() {
obj.borrow_mut().set(field.clone(), val.clone());
} else {
obj.borrow_mut()
.set(to_value(field.to_string()), val.clone());
}
}
ValueData::Function(ref func) => {
match *func.borrow_mut().deref_mut() {
Function::NativeFunc(ref mut f) => f
.object
.properties
.insert(field, Property::default().value(val.clone())),
.insert(field.to_string(), Property::default().value(val.clone())),
Function::RegularFunc(ref mut f) => f
.object
.properties
.insert(field, Property::default().value(val.clone())),
.insert(field.to_string(), Property::default().value(val.clone())),
};
}
_ => (),
@ -441,7 +479,10 @@ impl ValueData {
/// Set the field in the value
pub fn set_field_slice<'a>(&self, field: &'a str, val: Value) -> Value {
self.set_field(field.to_string(), val)
// set_field used to accept strings, but now Symbols accept it needs to accept a value
// So this function will now need to Box strings back into values (at least for now)
let f = Gc::new(ValueData::String(field.to_string()));
self.set_field(f, val)
}
/// Set the private field in the value
@ -536,7 +577,10 @@ impl ValueData {
pub fn to_json(&self) -> JSONValue {
match *self {
ValueData::Null | ValueData::Undefined | ValueData::Function(_) => JSONValue::Null,
ValueData::Null
| ValueData::Symbol(_)
| ValueData::Undefined
| ValueData::Function(_) => JSONValue::Null,
ValueData::Boolean(b) => JSONValue::Bool(b),
ValueData::Object(ref obj) => {
let mut new_obj = Map::new();
@ -562,6 +606,7 @@ impl ValueData {
ValueData::Number(_) | ValueData::Integer(_) => "number",
ValueData::String(_) => "string",
ValueData::Boolean(_) => "boolean",
ValueData::Symbol(_) => "symbol",
ValueData::Null => "null",
ValueData::Undefined => "undefined",
ValueData::Function(_) => "function",
@ -592,6 +637,11 @@ impl Display for ValueData {
ValueData::Null => write!(f, "null"),
ValueData::Undefined => write!(f, "undefined"),
ValueData::Boolean(v) => write!(f, "{}", v),
ValueData::Symbol(ref v) => match *v.borrow().get_internal_slot("Description") {
// If a description exists use it
ValueData::String(ref v) => write!(f, "{}", format!("Symbol({})", v)),
_ => write!(f, "Symbol()"),
},
ValueData::String(ref v) => write!(f, "{}", v),
ValueData::Number(v) => write!(
f,
@ -842,7 +892,7 @@ impl<T: FromValue> FromValue for Vec<T> {
let len = v.get_field_slice("length").to_int();
let mut vec = Self::with_capacity(len as usize);
for i in 0..len {
vec.push(from_value(v.get_field(&i.to_string()))?)
vec.push(from_value(v.get_field_slice(&i.to_string()))?)
}
Ok(vec)
}

2
src/lib/environment/object_environment_record.rs

@ -72,7 +72,7 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord {
fn get_binding_value(&self, name: &str, strict: bool) -> Value {
if self.bindings.has_field(name) {
self.bindings.get_field(name)
self.bindings.get_field_slice(name)
} else {
if strict {
// TODO: throw error here

35
src/lib/exec.rs

@ -34,7 +34,7 @@ pub trait Executor {
pub struct Interpreter {
is_return: bool,
/// realm holds both the global object and the environment
realm: Realm,
pub realm: Realm,
}
fn exec_assign_op(op: &AssignOp, v_a: ValueData, v_b: ValueData) -> Value {
@ -105,28 +105,30 @@ impl Executor for Interpreter {
}
ExprDef::GetConstField(ref obj, ref field) => {
let val_obj = self.run(obj)?;
Ok(val_obj.borrow().get_field(field))
Ok(val_obj.borrow().get_field_slice(field))
}
ExprDef::GetField(ref obj, ref field) => {
let val_obj = self.run(obj)?;
let val_field = self.run(field)?;
Ok(val_obj.borrow().get_field(&val_field.borrow().to_string()))
Ok(val_obj
.borrow()
.get_field_slice(&val_field.borrow().to_string()))
}
ExprDef::Call(ref callee, ref args) => {
let (this, func) = match callee.def {
ExprDef::GetConstField(ref obj, ref field) => {
let mut obj = self.run(obj)?;
if obj.get_type() != "object" {
if obj.get_type() != "object" || obj.get_type() != "symbol" {
obj = self.to_object(&obj).expect("failed to convert to object");
}
(obj.clone(), obj.borrow().get_field(field))
(obj.clone(), obj.borrow().get_field_slice(field))
}
ExprDef::GetField(ref obj, ref field) => {
let obj = self.run(obj)?;
let field = self.run(field)?;
(
obj.clone(),
obj.borrow().get_field(&field.borrow().to_string()),
obj.borrow().get_field_slice(&field.borrow().to_string()),
)
}
_ => (self.realm.global_obj.clone(), self.run(&callee.clone())?), // 'this' binding should come from the function's self-contained environment
@ -199,7 +201,7 @@ impl Executor for Interpreter {
.expect("Could not get the global object");
let obj = ValueData::new_obj(Some(global_val));
for (key, val) in map.iter() {
obj.borrow().set_field(key.clone(), self.run(val)?);
obj.borrow().set_field_slice(&key.clone(), self.run(val)?);
}
Ok(obj)
}
@ -320,10 +322,12 @@ impl Executor for Interpreter {
}
ExprDef::GetConstField(ref obj, ref field) => {
let v_r_a = self.run(obj)?;
let v_a = (*v_r_a.borrow().get_field(field)).clone();
let v_a = (*v_r_a.borrow().get_field_slice(field)).clone();
let v_b = (*self.run(b)?).clone();
let value = exec_assign_op(op, v_a, v_b.clone());
v_r_a.borrow().set_field(field.clone(), value.clone());
v_r_a
.borrow()
.set_field_slice(&field.clone(), value.clone());
Ok(value)
}
_ => Ok(Gc::new(ValueData::Undefined)),
@ -409,14 +413,14 @@ impl Executor for Interpreter {
}
ExprDef::GetConstField(ref obj, ref field) => {
let val_obj = self.run(obj)?;
val_obj.borrow().set_field(field.clone(), val.clone());
val_obj
.borrow()
.set_field_slice(&field.clone(), val.clone());
}
ExprDef::GetField(ref obj, ref field) => {
let val_obj = self.run(obj)?;
let val_field = self.run(field)?;
val_obj
.borrow()
.set_field(val_field.to_string(), val.clone());
val_obj.borrow().set_field(val_field, val.clone());
}
_ => (),
}
@ -470,6 +474,7 @@ impl Executor for Interpreter {
let val = self.run(val_e)?;
Ok(to_value(match *val {
ValueData::Undefined => "undefined",
ValueData::Symbol(_) => "symbol",
ValueData::Null | ValueData::Object(_) => "object",
ValueData::Boolean(_) => "boolean",
ValueData::Number(_) | ValueData::Integer(_) => "number",
@ -663,7 +668,7 @@ impl Interpreter {
string_obj.set_internal_slot("StringData", value.clone());
Ok(string_obj)
}
ValueData::Object(_) => Ok(value.clone()),
ValueData::Object(_) | ValueData::Symbol(_) => Ok(value.clone()),
}
}
@ -701,7 +706,7 @@ impl Interpreter {
self.to_string(&prim_value)
.to_string()
.parse::<f64>()
.unwrap()
.expect("cannot parse valur to x64")
}
_ => {
// TODO: Make undefined?

3
src/lib/realm.rs

@ -5,7 +5,7 @@
//!A realm is represented in this implementation as a Realm struct with the fields specified from the spec
use crate::{
builtins::{
array, boolean, console, function, json, math, number, object, regexp, string,
array, boolean, console, function, json, math, number, object, regexp, string, symbol,
value::{Value, ValueData},
},
environment::{
@ -62,6 +62,7 @@ impl Realm {
global.set_field_slice("Object", object::create_constructor(global));
global.set_field_slice("RegExp", regexp::create_constructor(global));
global.set_field_slice("String", string::create_constructor(global));
global.set_field_slice("Symbol", symbol::create_constructor(global));
global.set_field_slice("console", console::create_constructor(global));
}
}

1
tests/js/test.js

@ -0,0 +1 @@
// Test your JS here
Loading…
Cancel
Save