Skip to content

Commit

Permalink
implement "this" (#320)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jasonwilliams authored May 16, 2020
1 parent 402041d commit 63f37a2
Show file tree
Hide file tree
Showing 20 changed files with 224 additions and 236 deletions.
2 changes: 1 addition & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"type": "process",
"label": "Cargo Run",
"command": "cargo",
"args": ["run", "./tests/js/test.js"],
"args": ["run", "--bin", "boa", "./tests/js/test.js"],
"problemMatcher": ["$rustc"],
"group": {
"kind": "build",
Expand Down
5 changes: 3 additions & 2 deletions boa/src/builtins/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
#[cfg(test)]
mod tests;

use super::function::make_constructor_fn;
use crate::{
builtins::{
object::{Object, ObjectInternalMethods, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE},
object::{ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE},
property::Property,
value::{same_value_zero, ResultValue, Value, ValueData},
},
Expand Down Expand Up @@ -977,7 +978,7 @@ pub fn create(global: &Value) -> Value {
make_builtin_fn!(slice, named "slice", 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
make_builtin_fn!(is_array, named "isArray", with length 1, of array);
Expand Down
16 changes: 6 additions & 10 deletions boa/src/builtins/boolean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@
#[cfg(test)]
mod tests;

use super::function::make_constructor_fn;
use crate::{
builtins::{
object::{internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, PROTOTYPE},
object::{internal_methods_trait::ObjectInternalMethods, ObjectKind},
value::{ResultValue, Value, ValueData},
},
exec::Interpreter,
};
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 {
this.set_kind(ObjectKind::Boolean);

Expand All @@ -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)));
}

// 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) {
Some(ref value) => Ok(to_boolean(value)),
None => Ok(to_boolean(&Value::from(false))),
Expand Down Expand Up @@ -108,7 +104,7 @@ pub fn create(global: &Value) -> Value {
make_builtin_fn!(to_string, named "toString", 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.
Expand Down
5 changes: 3 additions & 2 deletions boa/src/builtins/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
//! [spec]: https://tc39.es/ecma262/#sec-error-objects
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
use super::function::make_constructor_fn;
use crate::{
builtins::{
object::{internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, PROTOTYPE},
object::ObjectKind,
value::{ResultValue, Value},
},
exec::Interpreter,
Expand Down Expand Up @@ -58,7 +59,7 @@ pub fn create(global: &Value) -> Value {
prototype.set_field_slice("message", Value::from(""));
prototype.set_field_slice("name", Value::from("Error"));
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.
Expand Down
55 changes: 47 additions & 8 deletions boa/src/builtins/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ use crate::{
property::Property,
value::{ResultValue, Value},
},
environment::lexical_environment::{new_function_environment, Environment},
environment::{
function_environment_record::BindingStatus,
lexical_environment::{new_function_environment, Environment},
},
exec::Executor,
syntax::ast::node::{FormalParameter, Node},
Interpreter,
Expand Down Expand Up @@ -162,8 +165,9 @@ impl Function {
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = new_function_environment(
this.clone(),
this_obj.clone(),
None,
Some(self.environment.as_ref().unwrap().clone()),
BindingStatus::Uninitialized,
);

// Add argument bindings to the function environment
Expand Down Expand Up @@ -203,6 +207,7 @@ impl Function {
}
}

/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
pub fn construct(
&self,
this: &mut Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object)
Expand All @@ -212,7 +217,10 @@ impl Function {
) -> ResultValue {
match self.kind {
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(_) => {
panic!("Builtin function should not have Ordinary Function body")
}
Expand All @@ -222,8 +230,9 @@ impl Function {
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = new_function_environment(
this.clone(),
this_obj.clone(),
Some(this_obj.clone()),
Some(self.environment.as_ref().unwrap().clone()),
BindingStatus::Initialized,
);

// Add argument bindings to the function environment
Expand All @@ -250,14 +259,14 @@ impl Function {
interpreter.realm.environment.push(local_env);

// Call body should be set before reaching here
let result = match &self.body {
let _ = match &self.body {
FunctionBody::Ordinary(ref body) => interpreter.run(body),
_ => panic!("Ordinary function should not have BuiltIn Function body"),
};

// local_env gets dropped here, its no longer needed
interpreter.realm.environment.pop();
result
let binding = interpreter.realm.environment.get_this_binding();
Ok(binding)
}
}
}
Expand Down Expand Up @@ -363,7 +372,37 @@ pub fn make_function(this: &mut Value, _: &[Value], _: &mut Interpreter) -> Resu
pub fn create(global: &Value) -> Value {
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.
Expand Down
62 changes: 1 addition & 61 deletions boa/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ macro_rules! make_builtin_fn {
);

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);
new_func_obj.set_field_slice("length", Value::from($l));
$p.set_field_slice($name, new_func_obj);
Expand All @@ -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 boolean;
pub mod console;
Expand Down
15 changes: 10 additions & 5 deletions boa/src/builtins/number/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
#[cfg(test)]
mod tests;

use super::{function::make_constructor_fn, object::ObjectKind};
use crate::{
builtins::{
object::{internal_methods_trait::ObjectInternalMethods, Object, PROTOTYPE},
object::internal_methods_trait::ObjectInternalMethods,
value::{ResultValue, Value, ValueData},
},
exec::Interpreter,
Expand Down Expand Up @@ -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 {
let data = match args.get(0) {
Some(ref value) => to_number(value),
None => to_number(&Value::from(0)),
};
this.set_internal_slot("NumberData", data);
Ok(this.clone())
this.set_kind(ObjectKind::Number);
this.set_internal_slot("NumberData", data.clone());

Ok(data)
}

/// `Number()` function.
Expand Down Expand Up @@ -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!(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.
Expand Down
Loading

0 comments on commit 63f37a2

Please sign in to comment.