Browse Source

implement "this" (#320)

* implement this
* remove construct/call from Object instead set func
* get_this_binding() was wrong, fixed
* BindingStatus is now properly set
* `this` now works on dynamic functions
* Migrates all builtins to use a single constructor/call fucntion to match the spec
* Ensure new object has an existing prototype
* create_function utility
* needing to clone before passing through
pull/407/head
Jason Williams 5 years ago committed by GitHub
parent
commit
63f37a2858
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .vscode/tasks.json
  2. 5
      boa/src/builtins/array/mod.rs
  3. 16
      boa/src/builtins/boolean/mod.rs
  4. 5
      boa/src/builtins/error.rs
  5. 55
      boa/src/builtins/function/mod.rs
  6. 62
      boa/src/builtins/mod.rs
  7. 15
      boa/src/builtins/number/mod.rs
  8. 44
      boa/src/builtins/object/mod.rs
  9. 5
      boa/src/builtins/regexp/mod.rs
  10. 21
      boa/src/builtins/string/mod.rs
  11. 3
      boa/src/builtins/symbol/mod.rs
  12. 2
      boa/src/builtins/value/mod.rs
  13. 4
      boa/src/environment/declarative_environment_record.rs
  14. 3
      boa/src/environment/environment_record_trait.rs
  15. 30
      boa/src/environment/function_environment_record.rs
  16. 8
      boa/src/environment/global_environment_record.rs
  17. 15
      boa/src/environment/lexical_environment.rs
  18. 4
      boa/src/environment/object_environment_record.rs
  19. 127
      boa/src/exec/mod.rs
  20. 34
      boa/src/exec/tests.rs

2
.vscode/tasks.json vendored

@ -7,7 +7,7 @@
"type": "process", "type": "process",
"label": "Cargo Run", "label": "Cargo Run",
"command": "cargo", "command": "cargo",
"args": ["run", "./tests/js/test.js"], "args": ["run", "--bin", "boa", "./tests/js/test.js"],
"problemMatcher": ["$rustc"], "problemMatcher": ["$rustc"],
"group": { "group": {
"kind": "build", "kind": "build",

5
boa/src/builtins/array/mod.rs

@ -12,9 +12,10 @@
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use super::function::make_constructor_fn;
use crate::{ use crate::{
builtins::{ builtins::{
object::{Object, ObjectInternalMethods, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, object::{ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE},
property::Property, property::Property,
value::{same_value_zero, ResultValue, Value, ValueData}, value::{same_value_zero, ResultValue, Value, ValueData},
}, },
@ -977,7 +978,7 @@ pub fn create(global: &Value) -> Value {
make_builtin_fn!(slice, named "slice", with length 2, of prototype); make_builtin_fn!(slice, named "slice", with length 2, of prototype);
make_builtin_fn!(some, named "some", with length 2, of prototype); make_builtin_fn!(some, named "some", with length 2, of prototype);
let array = make_constructor_fn!(make_array, make_array, global, prototype); let array = make_constructor_fn(make_array, global, prototype);
// Static Methods // Static Methods
make_builtin_fn!(is_array, named "isArray", with length 1, of array); make_builtin_fn!(is_array, named "isArray", with length 1, of array);

16
boa/src/builtins/boolean/mod.rs

@ -12,16 +12,19 @@
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use super::function::make_constructor_fn;
use crate::{ use crate::{
builtins::{ builtins::{
object::{internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, PROTOTYPE}, object::{internal_methods_trait::ObjectInternalMethods, ObjectKind},
value::{ResultValue, Value, ValueData}, value::{ResultValue, Value, ValueData},
}, },
exec::Interpreter, exec::Interpreter,
}; };
use std::{borrow::Borrow, ops::Deref}; use std::{borrow::Borrow, ops::Deref};
/// Create a new boolean object - [[Construct]] /// `[[Construct]]` Create a new boolean object
///
/// `[[Call]]` Creates a new boolean primitive
pub fn construct_boolean(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { pub fn construct_boolean(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
this.set_kind(ObjectKind::Boolean); this.set_kind(ObjectKind::Boolean);
@ -32,13 +35,6 @@ pub fn construct_boolean(this: &mut Value, args: &[Value], _: &mut Interpreter)
this.set_internal_slot("BooleanData", to_boolean(&Value::from(false))); this.set_internal_slot("BooleanData", to_boolean(&Value::from(false)));
} }
// no need to return `this` as its passed by reference
Ok(this.clone())
}
/// Return a boolean literal [[Call]]
pub fn call_boolean(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
// Get the argument, if any
match args.get(0) { match args.get(0) {
Some(ref value) => Ok(to_boolean(value)), Some(ref value) => Ok(to_boolean(value)),
None => Ok(to_boolean(&Value::from(false))), None => Ok(to_boolean(&Value::from(false))),
@ -108,7 +104,7 @@ pub fn create(global: &Value) -> Value {
make_builtin_fn!(to_string, named "toString", of prototype); make_builtin_fn!(to_string, named "toString", of prototype);
make_builtin_fn!(value_of, named "valueOf", of prototype); make_builtin_fn!(value_of, named "valueOf", of prototype);
make_constructor_fn!(construct_boolean, call_boolean, global, prototype) make_constructor_fn(construct_boolean, global, prototype)
} }
/// Initialise the `Boolean` object on the global object. /// Initialise the `Boolean` object on the global object.

5
boa/src/builtins/error.rs

@ -10,9 +10,10 @@
//! [spec]: https://tc39.es/ecma262/#sec-error-objects //! [spec]: https://tc39.es/ecma262/#sec-error-objects
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
use super::function::make_constructor_fn;
use crate::{ use crate::{
builtins::{ builtins::{
object::{internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, PROTOTYPE}, object::ObjectKind,
value::{ResultValue, Value}, value::{ResultValue, Value},
}, },
exec::Interpreter, exec::Interpreter,
@ -58,7 +59,7 @@ pub fn create(global: &Value) -> Value {
prototype.set_field_slice("message", Value::from("")); prototype.set_field_slice("message", Value::from(""));
prototype.set_field_slice("name", Value::from("Error")); prototype.set_field_slice("name", Value::from("Error"));
make_builtin_fn!(to_string, named "toString", of prototype); make_builtin_fn!(to_string, named "toString", of prototype);
make_constructor_fn!(make_error, global, prototype) make_constructor_fn(make_error, global, prototype)
} }
/// Initialise the global object with the `Error` object. /// Initialise the global object with the `Error` object.

55
boa/src/builtins/function/mod.rs

@ -18,7 +18,10 @@ use crate::{
property::Property, property::Property,
value::{ResultValue, Value}, value::{ResultValue, Value},
}, },
environment::lexical_environment::{new_function_environment, Environment}, environment::{
function_environment_record::BindingStatus,
lexical_environment::{new_function_environment, Environment},
},
exec::Executor, exec::Executor,
syntax::ast::node::{FormalParameter, Node}, syntax::ast::node::{FormalParameter, Node},
Interpreter, Interpreter,
@ -162,8 +165,9 @@ impl Function {
// <https://tc39.es/ecma262/#sec-prepareforordinarycall> // <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = new_function_environment( let local_env = new_function_environment(
this.clone(), this.clone(),
this_obj.clone(), None,
Some(self.environment.as_ref().unwrap().clone()), Some(self.environment.as_ref().unwrap().clone()),
BindingStatus::Uninitialized,
); );
// Add argument bindings to the function environment // Add argument bindings to the function environment
@ -203,6 +207,7 @@ impl Function {
} }
} }
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
pub fn construct( pub fn construct(
&self, &self,
this: &mut Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) this: &mut Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object)
@ -212,7 +217,10 @@ impl Function {
) -> ResultValue { ) -> ResultValue {
match self.kind { match self.kind {
FunctionKind::BuiltIn => match &self.body { FunctionKind::BuiltIn => match &self.body {
FunctionBody::BuiltIn(func) => func(this_obj, args_list, interpreter), FunctionBody::BuiltIn(func) => {
func(this_obj, args_list, interpreter).unwrap();
Ok(this_obj.clone())
}
FunctionBody::Ordinary(_) => { FunctionBody::Ordinary(_) => {
panic!("Builtin function should not have Ordinary Function body") panic!("Builtin function should not have Ordinary Function body")
} }
@ -222,8 +230,9 @@ impl Function {
// <https://tc39.es/ecma262/#sec-prepareforordinarycall> // <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = new_function_environment( let local_env = new_function_environment(
this.clone(), this.clone(),
this_obj.clone(), Some(this_obj.clone()),
Some(self.environment.as_ref().unwrap().clone()), Some(self.environment.as_ref().unwrap().clone()),
BindingStatus::Initialized,
); );
// Add argument bindings to the function environment // Add argument bindings to the function environment
@ -250,14 +259,14 @@ impl Function {
interpreter.realm.environment.push(local_env); interpreter.realm.environment.push(local_env);
// Call body should be set before reaching here // Call body should be set before reaching here
let result = match &self.body { let _ = match &self.body {
FunctionBody::Ordinary(ref body) => interpreter.run(body), FunctionBody::Ordinary(ref body) => interpreter.run(body),
_ => panic!("Ordinary function should not have BuiltIn Function body"), _ => panic!("Ordinary function should not have BuiltIn Function body"),
}; };
// local_env gets dropped here, its no longer needed // local_env gets dropped here, its no longer needed
interpreter.realm.environment.pop(); let binding = interpreter.realm.environment.get_this_binding();
result Ok(binding)
} }
} }
} }
@ -363,7 +372,37 @@ pub fn make_function(this: &mut Value, _: &[Value], _: &mut Interpreter) -> Resu
pub fn create(global: &Value) -> Value { pub fn create(global: &Value) -> Value {
let prototype = Value::new_object(Some(global)); let prototype = Value::new_object(Some(global));
make_constructor_fn!(make_function, make_function, global, prototype) make_constructor_fn(make_function, global, prototype)
}
/// Creates a new constructor function
///
/// This utility function handling linking the new Constructor to the prototype.
/// So far this is only used by internal functions
pub fn make_constructor_fn(body: NativeFunctionData, global: &Value, proto: Value) -> Value {
// Create the native function
let constructor_fn = crate::builtins::function::Function::create_builtin(
vec![],
crate::builtins::function::FunctionBody::BuiltIn(body),
);
// Get reference to Function.prototype
let func_prototype = global
.get_field_slice("Function")
.get_field_slice(PROTOTYPE);
// Create the function object and point its instance prototype to Function.prototype
let mut constructor_obj = Object::function();
constructor_obj.set_func(constructor_fn);
constructor_obj.set_internal_slot("__proto__", func_prototype);
let constructor_val = Value::from(constructor_obj);
// Set proto.constructor -> constructor_obj
proto.set_field_slice("constructor", constructor_val.clone());
constructor_val.set_field_slice(PROTOTYPE, proto);
constructor_val
} }
/// Initialise the `Function` object on the global object. /// Initialise the `Function` object on the global object.

62
boa/src/builtins/mod.rs

@ -11,7 +11,7 @@ macro_rules! make_builtin_fn {
); );
let mut new_func = crate::builtins::object::Object::function(); let mut new_func = crate::builtins::object::Object::function();
new_func.set_call(func); new_func.set_func(func);
let new_func_obj = Value::from(new_func); let new_func_obj = Value::from(new_func);
new_func_obj.set_field_slice("length", Value::from($l)); new_func_obj.set_field_slice("length", Value::from($l));
$p.set_field_slice($name, new_func_obj); $p.set_field_slice($name, new_func_obj);
@ -21,66 +21,6 @@ macro_rules! make_builtin_fn {
}; };
} }
/// Macro to create a new constructor function
///
/// Either (construct_body, global, prototype)
macro_rules! make_constructor_fn {
($body:ident, $global:ident, $proto:ident) => {{
// Create the native function
let constructor_fn = crate::builtins::function::Function::create_builtin(
vec![],
crate::builtins::function::FunctionBody::BuiltIn($body),
);
// Get reference to Function.prototype
let func_prototype = $global
.get_field_slice("Function")
.get_field_slice(PROTOTYPE);
// Create the function object and point its instance prototype to Function.prototype
let mut constructor_obj = Object::function();
constructor_obj.set_construct(constructor_fn);
constructor_obj.set_internal_slot("__proto__", func_prototype);
let constructor_val = Value::from(constructor_obj);
// Set proto.constructor -> constructor_obj
$proto.set_field_slice("constructor", constructor_val.clone());
constructor_val.set_field_slice(PROTOTYPE, $proto);
constructor_val
}};
($construct_body:ident, $call_body:ident, $global:ident, $proto:ident) => {{
// Create the native functions
let construct_fn = crate::builtins::function::Function::create_builtin(
vec![],
crate::builtins::function::FunctionBody::BuiltIn($construct_body),
);
let call_fn = crate::builtins::function::Function::create_builtin(
vec![],
crate::builtins::function::FunctionBody::BuiltIn($call_body),
);
// Get reference to Function.prototype
let func_prototype = $global
.get_field_slice("Function")
.get_field_slice(PROTOTYPE);
// Create the function object and point its instance prototype to Function.prototype
let mut constructor_obj = Object::function();
constructor_obj.set_construct(construct_fn);
constructor_obj.set_call(call_fn);
constructor_obj.set_internal_slot("__proto__", func_prototype);
let constructor_val = Value::from(constructor_obj);
// Set proto.constructor -> constructor_obj
$proto.set_field_slice("constructor", constructor_val.clone());
constructor_val.set_field_slice(PROTOTYPE, $proto);
constructor_val
}};
}
pub mod array; pub mod array;
pub mod boolean; pub mod boolean;
pub mod console; pub mod console;

15
boa/src/builtins/number/mod.rs

@ -16,9 +16,10 @@
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use super::{function::make_constructor_fn, object::ObjectKind};
use crate::{ use crate::{
builtins::{ builtins::{
object::{internal_methods_trait::ObjectInternalMethods, Object, PROTOTYPE}, object::internal_methods_trait::ObjectInternalMethods,
value::{ResultValue, Value, ValueData}, value::{ResultValue, Value, ValueData},
}, },
exec::Interpreter, exec::Interpreter,
@ -57,14 +58,18 @@ fn num_to_exponential(n: f64) -> String {
} }
} }
/// Create a new number `[[Construct]]` /// `[[Construct]]` - Creates a Number instance
///
/// `[[Call]]` - Creates a number primitive
pub fn make_number(this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { pub fn make_number(this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue {
let data = match args.get(0) { let data = match args.get(0) {
Some(ref value) => to_number(value), Some(ref value) => to_number(value),
None => to_number(&Value::from(0)), None => to_number(&Value::from(0)),
}; };
this.set_internal_slot("NumberData", data); this.set_kind(ObjectKind::Number);
Ok(this.clone()) this.set_internal_slot("NumberData", data.clone());
Ok(data)
} }
/// `Number()` function. /// `Number()` function.
@ -360,7 +365,7 @@ pub fn create(global: &Value) -> Value {
make_builtin_fn!(to_string, named "toString", with length 1, of prototype); make_builtin_fn!(to_string, named "toString", with length 1, of prototype);
make_builtin_fn!(value_of, named "valueOf", of prototype); make_builtin_fn!(value_of, named "valueOf", of prototype);
make_constructor_fn!(make_number, call_number, global, prototype) make_constructor_fn(make_number, global, prototype)
} }
/// Initialise the `Number` object on the global object. /// Initialise the `Number` object on the global object.

44
boa/src/builtins/object/mod.rs

@ -29,6 +29,7 @@ use std::{
ops::Deref, ops::Deref,
}; };
use super::function::make_constructor_fn;
pub use internal_methods_trait::ObjectInternalMethods; pub use internal_methods_trait::ObjectInternalMethods;
pub use internal_state::{InternalState, InternalStateCell}; pub use internal_state::{InternalState, InternalStateCell};
@ -46,7 +47,7 @@ pub static INSTANCE_PROTOTYPE: &str = "__proto__";
pub struct Object { pub struct Object {
/// The type of the object. /// The type of the object.
pub kind: ObjectKind, pub kind: ObjectKind,
/// Intfiernal Slots /// Internal Slots
pub internal_slots: FxHashMap<String, Value>, pub internal_slots: FxHashMap<String, Value>,
/// Properties /// Properties
pub properties: FxHashMap<String, Property>, pub properties: FxHashMap<String, Property>,
@ -54,10 +55,8 @@ pub struct Object {
pub sym_properties: FxHashMap<i32, Property>, pub sym_properties: FxHashMap<i32, Property>,
/// Some rust object that stores internal state /// Some rust object that stores internal state
pub state: Option<InternalStateCell>, pub state: Option<InternalStateCell>,
/// [[Call]] /// Function
pub call: Option<Function>, pub func: Option<Function>,
/// [[Construct]]
pub construct: Option<Function>,
} }
impl Debug for Object { impl Debug for Object {
@ -65,8 +64,7 @@ impl Debug for Object {
writeln!(f, "{{")?; writeln!(f, "{{")?;
writeln!(f, "\tkind: {}", self.kind)?; writeln!(f, "\tkind: {}", self.kind)?;
writeln!(f, "\tstate: {:?}", self.state)?; writeln!(f, "\tstate: {:?}", self.state)?;
writeln!(f, "\tcall: {:?}", self.call)?; writeln!(f, "\tfunc: {:?}", self.func)?;
writeln!(f, "\tconstruct: {:?}", self.construct)?;
writeln!(f, "\tproperties: {{")?; writeln!(f, "\tproperties: {{")?;
for (key, _) in self.properties.iter() { for (key, _) in self.properties.iter() {
writeln!(f, "\t\t{}", key)?; writeln!(f, "\t\t{}", key)?;
@ -342,8 +340,7 @@ impl Object {
properties: FxHashMap::default(), properties: FxHashMap::default(),
sym_properties: FxHashMap::default(), sym_properties: FxHashMap::default(),
state: None, state: None,
call: None, func: None,
construct: None,
}; };
object.set_internal_slot("extensible", Value::from(true)); object.set_internal_slot("extensible", Value::from(true));
@ -358,8 +355,7 @@ impl Object {
properties: FxHashMap::default(), properties: FxHashMap::default(),
sym_properties: FxHashMap::default(), sym_properties: FxHashMap::default(),
state: None, state: None,
call: None, func: None,
construct: None,
}; };
object.set_internal_slot("extensible", Value::from(true)); object.set_internal_slot("extensible", Value::from(true));
@ -382,14 +378,9 @@ impl Object {
obj obj
} }
/// Set [[Call]] /// Set the function this object wraps
pub fn set_call(&mut self, val: Function) { pub fn set_func(&mut self, val: Function) {
self.call = Some(val); self.func = Some(val);
}
/// set [[Construct]]
pub fn set_construct(&mut self, val: Function) {
self.construct = Some(val);
} }
/// Return a new Boolean object whose `[[BooleanData]]` internal slot is set to argument. /// Return a new Boolean object whose `[[BooleanData]]` internal slot is set to argument.
@ -400,8 +391,7 @@ impl Object {
properties: FxHashMap::default(), properties: FxHashMap::default(),
sym_properties: FxHashMap::default(), sym_properties: FxHashMap::default(),
state: None, state: None,
call: None, func: None,
construct: None,
}; };
obj.internal_slots obj.internal_slots
@ -417,8 +407,7 @@ impl Object {
properties: FxHashMap::default(), properties: FxHashMap::default(),
sym_properties: FxHashMap::default(), sym_properties: FxHashMap::default(),
state: None, state: None,
call: None, func: None,
construct: None,
}; };
obj.internal_slots obj.internal_slots
@ -434,8 +423,7 @@ impl Object {
properties: FxHashMap::default(), properties: FxHashMap::default(),
sym_properties: FxHashMap::default(), sym_properties: FxHashMap::default(),
state: None, state: None,
call: None, func: None,
construct: None,
}; };
obj.internal_slots obj.internal_slots
@ -466,7 +454,7 @@ impl Object {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-iscallable /// [spec]: https://tc39.es/ecma262/#sec-iscallable
pub fn is_callable(&self) -> bool { pub fn is_callable(&self) -> bool {
self.call.is_some() self.func.is_some()
} }
/// It determines if Object is a function object with a [[Construct]] internal method. /// It determines if Object is a function object with a [[Construct]] internal method.
@ -476,7 +464,7 @@ impl Object {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-isconstructor /// [spec]: https://tc39.es/ecma262/#sec-isconstructor
pub fn is_constructor(&self) -> bool { pub fn is_constructor(&self) -> bool {
self.construct.is_some() self.func.is_some()
} }
} }
@ -607,7 +595,7 @@ pub fn create(global: &Value) -> Value {
make_builtin_fn!(has_own_property, named "hasOwnProperty", of prototype); make_builtin_fn!(has_own_property, named "hasOwnProperty", of prototype);
make_builtin_fn!(to_string, named "toString", of prototype); make_builtin_fn!(to_string, named "toString", of prototype);
let object = make_constructor_fn!(make_object, make_object, global, prototype); let object = make_constructor_fn(make_object, global, prototype);
object.set_field_slice("length", Value::from(1)); object.set_field_slice("length", Value::from(1));
make_builtin_fn!(set_prototype_of, named "setPrototypeOf", with length 2, of object); make_builtin_fn!(set_prototype_of, named "setPrototypeOf", with length 2, of object);

5
boa/src/builtins/regexp/mod.rs

@ -13,9 +13,10 @@ use std::ops::Deref;
use regex::Regex; use regex::Regex;
use super::function::make_constructor_fn;
use crate::{ use crate::{
builtins::{ builtins::{
object::{InternalState, Object, ObjectInternalMethods, ObjectKind, PROTOTYPE}, object::{InternalState, ObjectKind},
property::Property, property::Property,
value::{ResultValue, Value, ValueData}, value::{ResultValue, Value, ValueData},
}, },
@ -475,7 +476,7 @@ pub fn create(global: &Value) -> Value {
make_builtin_fn!(get_sticky, named "sticky", of prototype); make_builtin_fn!(get_sticky, named "sticky", of prototype);
make_builtin_fn!(get_unicode, named "unicode", of prototype); make_builtin_fn!(get_unicode, named "unicode", of prototype);
make_constructor_fn!(make_regexp, make_regexp, global, prototype) make_constructor_fn(make_regexp, global, prototype)
} }
/// Initialise the `RegExp` object on the global object. /// Initialise the `RegExp` object on the global object.

21
boa/src/builtins/string/mod.rs

@ -12,9 +12,10 @@
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use super::function::make_constructor_fn;
use crate::{ use crate::{
builtins::{ builtins::{
object::{internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, PROTOTYPE}, object::{Object, ObjectKind},
property::Property, property::Property,
regexp::{make_regexp, match_all as regexp_match_all, r#match as regexp_match}, regexp::{make_regexp, match_all as regexp_match_all, r#match as regexp_match},
value::{ResultValue, Value, ValueData}, value::{ResultValue, Value, ValueData},
@ -28,14 +29,10 @@ use std::{
ops::Deref, ops::Deref,
}; };
/// Create new string [[Construct]] /// [[Construct]] - Creates a new instance `this`
// This gets called when a new String() is created, it's called by exec:346 ///
/// [[Call]] - Returns a new native `string`
pub fn make_string(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { pub fn make_string(this: &mut Value, args: &[Value], _: &mut Interpreter) -> 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.get(0).expect("failed to get argument for String method").clone()).unwrap();
// this.set_field_slice("length", to_value(a.len() as i32));
// This value is used by console.log and other routines to match Obexpecty"failed to parse argument for String method"pe // This value is used by console.log and other routines to match Obexpecty"failed to parse argument for String method"pe
// to its Javascript Identifier (global constructor method name) // to its Javascript Identifier (global constructor method name)
this.set_kind(ObjectKind::String); this.set_kind(ObjectKind::String);
@ -45,13 +42,7 @@ pub fn make_string(this: &mut Value, args: &[Value], _: &mut Interpreter) -> Res
.expect("failed to get StringData for make_string()") .expect("failed to get StringData for make_string()")
.clone(), .clone(),
); );
Ok(this.clone())
}
/// Call new string [[Call]]
///
/// More information: [ECMAScript reference](https://tc39.es/ecma262/#sec-string-constructor-string-value)
pub fn call_string(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let arg = match args.get(0) { let arg = match args.get(0) {
Some(v) => v.clone(), Some(v) => v.clone(),
None => Value::undefined(), None => Value::undefined(),
@ -1029,7 +1020,7 @@ pub fn create(global: &Value) -> Value {
make_builtin_fn!(match_all, named "matchAll", with length 1, of prototype); make_builtin_fn!(match_all, named "matchAll", with length 1, of prototype);
make_builtin_fn!(replace, named "replace", with length 2, of prototype); make_builtin_fn!(replace, named "replace", with length 2, of prototype);
make_constructor_fn!(make_string, call_string, global, prototype) make_constructor_fn(make_string, global, prototype)
} }
/// Initialise the `String` object on the global object. /// Initialise the `String` object on the global object.

3
boa/src/builtins/symbol/mod.rs

@ -18,6 +18,7 @@
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use super::function::make_constructor_fn;
use crate::{ use crate::{
builtins::{ builtins::{
object::{ object::{
@ -92,7 +93,7 @@ pub fn create(global: &Value) -> Value {
// Create prototype object // Create prototype object
let prototype = Value::new_object(Some(global)); let prototype = Value::new_object(Some(global));
make_builtin_fn!(to_string, named "toString", of prototype); make_builtin_fn!(to_string, named "toString", of prototype);
make_constructor_fn!(call_symbol, call_symbol, global, prototype) make_constructor_fn(call_symbol, global, prototype)
} }
/// Initialise the `Symbol` object on the global object. /// Initialise the `Symbol` object on the global object.

2
boa/src/builtins/value/mod.rs

@ -634,7 +634,7 @@ impl ValueData {
// Get Length // Get Length
let length = native_func.params.len(); let length = native_func.params.len();
// Set [[Call]] internal slot // Set [[Call]] internal slot
new_func.set_call(native_func); new_func.set_func(native_func);
// Wrap Object in GC'd Value // Wrap Object in GC'd Value
let new_func_val = Value::from(new_func); let new_func_val = Value::from(new_func);
// Set length to parameters // Set length to parameters

4
boa/src/environment/declarative_environment_record.rs

@ -149,6 +149,10 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord {
false false
} }
fn get_this_binding(&self) -> Value {
Value::undefined()
}
fn has_super_binding(&self) -> bool { fn has_super_binding(&self) -> bool {
false false
} }

3
boa/src/environment/environment_record_trait.rs

@ -60,6 +60,9 @@ pub trait EnvironmentRecordTrait: Debug + Trace + Finalize {
/// Return true if it does and false if it does not. /// Return true if it does and false if it does not.
fn has_this_binding(&self) -> bool; fn has_this_binding(&self) -> bool;
/// Return the `this` binding from the environment
fn get_this_binding(&self) -> Value;
/// Determine if an Environment Record establishes a super method binding. /// Determine if an Environment Record establishes a super method binding.
/// Return true if it does and false if it does not. /// Return true if it does and false if it does not.
fn has_super_binding(&self) -> bool; fn has_super_binding(&self) -> bool;

30
boa/src/environment/function_environment_record.rs

@ -74,21 +74,6 @@ impl FunctionEnvironmentRecord {
} }
} }
} }
pub fn get_this_binding(&self) -> Value {
match self.this_binding_status {
BindingStatus::Lexical => {
// TODO: change this when error handling comes into play
panic!("There is no this for a lexical function record");
}
BindingStatus::Uninitialized => {
// TODO: change this when error handling comes into play
panic!("Reference Error: Unitialised binding for this function");
}
BindingStatus::Initialized => self.this_value.clone(),
}
}
} }
impl EnvironmentRecordTrait for FunctionEnvironmentRecord { impl EnvironmentRecordTrait for FunctionEnvironmentRecord {
@ -115,6 +100,21 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord {
); );
} }
fn get_this_binding(&self) -> Value {
match self.this_binding_status {
BindingStatus::Lexical => {
// TODO: change this when error handling comes into play
panic!("There is no this for a lexical function record");
}
BindingStatus::Uninitialized => {
// TODO: change this when error handling comes into play
panic!("Reference Error: Unitialised binding for this function");
}
BindingStatus::Initialized => self.this_value.clone(),
}
}
fn create_immutable_binding(&mut self, name: String, strict: bool) -> bool { fn create_immutable_binding(&mut self, name: String, strict: bool) -> bool {
if self.env_rec.contains_key(&name) { if self.env_rec.contains_key(&name) {
// TODO: change this when error handling comes into play // TODO: change this when error handling comes into play

8
boa/src/environment/global_environment_record.rs

@ -28,10 +28,6 @@ pub struct GlobalEnvironmentRecord {
} }
impl GlobalEnvironmentRecord { impl GlobalEnvironmentRecord {
pub fn get_this_binding(&self) -> Value {
self.global_this_binding.clone()
}
pub fn has_var_declaration(&self, name: &str) -> bool { pub fn has_var_declaration(&self, name: &str) -> bool {
self.var_names.contains(name) self.var_names.contains(name)
} }
@ -96,6 +92,10 @@ impl GlobalEnvironmentRecord {
} }
impl EnvironmentRecordTrait for GlobalEnvironmentRecord { impl EnvironmentRecordTrait for GlobalEnvironmentRecord {
fn get_this_binding(&self) -> Value {
self.global_this_binding.clone()
}
fn has_binding(&self, name: &str) -> bool { fn has_binding(&self, name: &str) -> bool {
if self.declarative_record.has_binding(name) { if self.declarative_record.has_binding(name) {
return true; return true;

15
boa/src/environment/lexical_environment.rs

@ -113,6 +113,11 @@ impl LexicalEnvironment {
.get_global_object() .get_global_object()
} }
pub fn get_this_binding(&self) -> Value {
let env = self.environment_stack.back().expect("").borrow();
env.get_this_binding()
}
pub fn create_mutable_binding(&mut self, name: String, deletion: bool, scope: VariableScope) { pub fn create_mutable_binding(&mut self, name: String, deletion: bool, scope: VariableScope) {
match scope { match scope {
VariableScope::Block => self VariableScope::Block => self
@ -226,18 +231,18 @@ pub fn new_declarative_environment(env: Option<Environment>) -> Environment {
pub fn new_function_environment( pub fn new_function_environment(
f: Value, f: Value,
new_target: Value, this: Option<Value>,
outer: Option<Environment>, outer: Option<Environment>,
binding_status: BindingStatus,
) -> Environment { ) -> Environment {
debug_assert!(new_target.is_object() || new_target.is_undefined());
Gc::new(GcCell::new(Box::new(FunctionEnvironmentRecord { Gc::new(GcCell::new(Box::new(FunctionEnvironmentRecord {
env_rec: FxHashMap::default(), env_rec: FxHashMap::default(),
function: f, function: f,
this_binding_status: BindingStatus::Uninitialized, // hardcoding to unitialized for now until short functions are properly supported this_binding_status: binding_status,
home_object: Value::undefined(), home_object: Value::undefined(),
new_target, new_target: Value::undefined(),
outer_env: outer, // this will come from Environment set as a private property of F - https://tc39.es/ecma262/#sec-ecmascript-function-objects outer_env: outer, // this will come from Environment set as a private property of F - https://tc39.es/ecma262/#sec-ecmascript-function-objects
this_value: Value::undefined(), // TODO: this_value should start as an Option as its not always there to begin with this_value: this.unwrap_or_else(Value::undefined),
}))) })))
} }

4
boa/src/environment/object_environment_record.rs

@ -87,6 +87,10 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord {
false false
} }
fn get_this_binding(&self) -> Value {
Value::undefined()
}
fn has_super_binding(&self) -> bool { fn has_super_binding(&self) -> bool {
false false
} }

127
boa/src/exec/mod.rs

@ -18,7 +18,7 @@ use crate::{
realm::Realm, realm::Realm,
syntax::ast::{ syntax::ast::{
constant::Const, constant::Const,
node::{MethodDefinitionKind, Node, PropertyDefinition}, node::{FormalParameter, MethodDefinitionKind, Node, PropertyDefinition},
op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}, op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp},
}, },
}; };
@ -274,28 +274,9 @@ impl Executor for Interpreter {
} }
// <https://tc39.es/ecma262/#sec-createdynamicfunction> // <https://tc39.es/ecma262/#sec-createdynamicfunction>
Node::FunctionDecl(ref name, ref args, ref expr) => { Node::FunctionDecl(ref name, ref args, ref expr) => {
// Todo: Function.prototype doesn't exist yet, so the prototype right now is the Object.prototype let val = self.create_function(args.clone(), expr, ThisMode::NonLexical);
// let proto = &self
// .realm
// .environment
// .get_global_object()
// .expect("Could not get the global object")
// .get_field_slice("Object")
// .get_field_slice("Prototype");
let func = FunctionObject::create_ordinary(
args.clone(), // TODO: args shouldn't need to be a reference it should be passed by value
self.realm.environment.get_current_environment().clone(),
FunctionBody::Ordinary(*expr.clone()),
ThisMode::NonLexical,
);
let mut new_func = Object::function();
new_func.set_call(func);
let val = Value::from(new_func);
val.set_field_slice("length", Value::from(args.len()));
// Set the name and assign it in the current environment // Set the name and assign it in the current environment
val.set_field_slice("name", Value::from(name.clone())); val.set_field_slice("name", Value::from(name.clone()));
self.realm.environment.create_mutable_binding( self.realm.environment.create_mutable_binding(
name.clone(), name.clone(),
@ -309,26 +290,7 @@ impl Executor for Interpreter {
} }
// <https://tc39.es/ecma262/#sec-createdynamicfunction> // <https://tc39.es/ecma262/#sec-createdynamicfunction>
Node::FunctionExpr(ref name, ref args, ref expr) => { Node::FunctionExpr(ref name, ref args, ref expr) => {
// Todo: Function.prototype doesn't exist yet, so the prototype right now is the Object.prototype let val = self.create_function(args.clone(), expr, ThisMode::NonLexical);
// let proto = &self
// .realm
// .environment
// .get_global_object()
// .expect("Could not get the global object")
// .get_field_slice("Object")
// .get_field_slice("Prototype");
let func = FunctionObject::create_ordinary(
args.clone(), // TODO: args shouldn't need to be a reference it should be passed by value
self.realm.environment.get_current_environment().clone(),
FunctionBody::Ordinary(*expr.clone()),
ThisMode::NonLexical,
);
let mut new_func = Object::function();
new_func.set_call(func);
let val = Value::from(new_func);
val.set_field_slice("length", Value::from(args.len()));
if let Some(name) = name { if let Some(name) = name {
val.set_field_slice("name", Value::from(name.clone())); val.set_field_slice("name", Value::from(name.clone()));
@ -337,28 +299,7 @@ impl Executor for Interpreter {
Ok(val) Ok(val)
} }
Node::ArrowFunctionDecl(ref args, ref expr) => { Node::ArrowFunctionDecl(ref args, ref expr) => {
// Todo: Function.prototype doesn't exist yet, so the prototype right now is the Object.prototype Ok(self.create_function(args.clone(), expr, ThisMode::Lexical))
// let proto = &self
// .realm
// .environment
// .get_global_object()
// .expect("Could not get the global object")
// .get_field_slice("Object")
// .get_field_slice("Prototype");
let func = FunctionObject::create_ordinary(
args.clone(), // TODO: args shouldn't need to be a reference it should be passed by value
self.realm.environment.get_current_environment().clone(),
FunctionBody::Ordinary(*expr.clone()),
ThisMode::Lexical,
);
let mut new_func = Object::function();
new_func.set_call(func);
let val = Value::from(new_func);
val.set_field_slice("length", Value::from(args.len()));
Ok(val)
} }
Node::BinOp(BinOp::Num(ref op), ref a, ref b) => { Node::BinOp(BinOp::Num(ref op), ref a, ref b) => {
let v_a = self.run(a)?; let v_a = self.run(a)?;
@ -513,7 +454,7 @@ impl Executor for Interpreter {
match *(func_object.borrow()).deref() { match *(func_object.borrow()).deref() {
ValueData::Object(ref o) => (*o.deref().clone().borrow_mut()) ValueData::Object(ref o) => (*o.deref().clone().borrow_mut())
.construct .func
.as_ref() .as_ref()
.unwrap() .unwrap()
.construct(&mut func_object.clone(), &v_args, self, &mut this), .construct(&mut func_object.clone(), &v_args, self, &mut this),
@ -626,13 +567,6 @@ impl Executor for Interpreter {
})) }))
} }
Node::StatementList(ref list) => { Node::StatementList(ref list) => {
{
let env = &mut self.realm.environment;
env.push(new_declarative_environment(Some(
env.get_current_environment_ref().clone(),
)));
}
let mut obj = Value::null(); let mut obj = Value::null();
for (i, item) in list.iter().enumerate() { for (i, item) in list.iter().enumerate() {
let val = self.run(item)?; let val = self.run(item)?;
@ -646,15 +580,16 @@ impl Executor for Interpreter {
} }
} }
// pop the block env
let _ = self.realm.environment.pop();
Ok(obj) Ok(obj)
} }
Node::Spread(ref node) => { Node::Spread(ref node) => {
// TODO: for now we can do nothing but return the value as-is // TODO: for now we can do nothing but return the value as-is
self.run(node) self.run(node)
} }
Node::This => {
// Will either return `this` binding or undefined
Ok(self.realm.environment.get_this_binding())
}
ref i => unimplemented!("{}", i), ref i => unimplemented!("{}", i),
} }
} }
@ -666,6 +601,46 @@ impl Interpreter {
&self.realm &self.realm
} }
/// Utility to create a function Value for Function Declarations, Arrow Functions or Function Expressions
pub(crate) fn create_function(
&mut self,
args: Box<[FormalParameter]>,
expr: &Node,
this_mode: ThisMode,
) -> Value {
let function_prototype = &self
.realm
.environment
.get_global_object()
.expect("Could not get the global object")
.get_field_slice("Function")
.get_field_slice("Prototype");
// Every new function has a prototype property pre-made
let global_val = &self
.realm
.environment
.get_global_object()
.expect("Could not get the global object");
let proto = Value::new_object(Some(global_val));
let func = FunctionObject::create_ordinary(
args.clone(),
self.realm.environment.get_current_environment().clone(),
FunctionBody::Ordinary(expr.clone()),
this_mode,
);
let mut new_func = Object::function();
new_func.set_func(func);
let val = Value::from(new_func);
val.set_internal_slot(INSTANCE_PROTOTYPE, function_prototype.clone());
val.set_field_slice(PROTOTYPE, proto);
val.set_field_slice("length", Value::from(args.len()));
val
}
/// https://tc39.es/ecma262/#sec-call /// https://tc39.es/ecma262/#sec-call
pub(crate) fn call( pub(crate) fn call(
&mut self, &mut self,
@ -676,7 +651,7 @@ impl Interpreter {
// All functions should be objects, and eventually will be. // All functions should be objects, and eventually will be.
// During this transition call will support both native functions and function objects // During this transition call will support both native functions and function objects
match (*f).deref() { match (*f).deref() {
ValueData::Object(ref obj) => match (*obj).deref().borrow().call { ValueData::Object(ref obj) => match (*obj).deref().borrow().func {
Some(ref func) => func.call(&mut f.clone(), arguments_list, self, this), Some(ref func) => func.call(&mut f.clone(), arguments_list, self, this),
None => panic!("Expected function"), None => panic!("Expected function"),
}, },

34
boa/src/exec/tests.rs

@ -500,6 +500,7 @@ fn unary_delete() {
#[cfg(test)] #[cfg(test)]
mod in_operator { mod in_operator {
use super::*; use super::*;
use crate::{builtins::object::INSTANCE_PROTOTYPE, forward_val};
#[test] #[test]
fn propery_in_object() { fn propery_in_object() {
let p_in_o = r#" let p_in_o = r#"
@ -564,4 +565,37 @@ mod in_operator {
"#; "#;
exec(scenario); exec(scenario);
} }
#[test]
fn should_set_this_value() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let scenario = r#"
function Foo() {
this.a = "a";
this.b = "b";
}
var bar = new Foo();
"#;
forward(&mut engine, scenario);
assert_eq!(forward(&mut engine, "bar.a"), "a");
assert_eq!(forward(&mut engine, "bar.b"), "b");
}
#[test]
fn new_instance_should_point_to_prototype() {
// A new instance should point to a prototype object created with the constructor function
let realm = Realm::create();
let mut engine = Executor::new(realm);
let scenario = r#"
function Foo() {}
var bar = new Foo();
"#;
forward(&mut engine, scenario);
let a = forward_val(&mut engine, "bar").unwrap();
assert!(a.get_internal_slot(INSTANCE_PROTOTYPE).is_object(), true);
}
} }

Loading…
Cancel
Save