Browse Source

Refactor `Function` (#626)

Co-authored-by: Iban Eguia <razican@protonmail.ch>
pull/603/head
HalidOdat 4 years ago committed by GitHub
parent
commit
c9afbf4221
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 332
      boa/src/builtins/function/mod.rs
  2. 14
      boa/src/builtins/map/tests.rs
  3. 160
      boa/src/builtins/object/gcobject.rs
  4. 64
      boa/src/builtins/object/internal_state.rs
  5. 30
      boa/src/builtins/object/mod.rs
  6. 6
      boa/src/builtins/regexp/mod.rs
  7. 85
      boa/src/builtins/value/mod.rs
  8. 4
      boa/src/environment/function_environment_record.rs
  9. 10
      boa/src/environment/lexical_environment.rs
  10. 16
      boa/src/exec/declaration/mod.rs
  11. 81
      boa/src/exec/mod.rs
  12. 17
      boa/src/exec/new/mod.rs
  13. 13
      boa/src/realm.rs

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

@ -18,9 +18,8 @@ use crate::{
value::{RcString, Value}, value::{RcString, Value},
Array, Array,
}, },
environment::function_environment_record::BindingStatus, environment::lexical_environment::Environment,
environment::lexical_environment::{new_function_environment, Environment}, exec::Interpreter,
exec::{Executable, Interpreter},
syntax::ast::node::{FormalParameter, StatementList}, syntax::ast::node::{FormalParameter, StatementList},
BoaProfiler, Result, BoaProfiler, Result,
}; };
@ -29,73 +28,33 @@ use gc::{unsafe_empty_trace, Finalize, Trace};
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
/// _fn(this, arguments, ctx) -> Result<Value>_ - The signature of a built-in function /// _fn(this, arguments, ctx) -> Result<Value>_ - The signature of a built-in function
pub type NativeFunctionData = fn(&Value, &[Value], &mut Interpreter) -> Result<Value>; pub type NativeFunction = fn(&Value, &[Value], &mut Interpreter) -> Result<Value>;
/// Sets the ConstructorKind #[derive(Clone, Copy, Finalize)]
#[derive(Debug, Copy, Clone)] pub struct BuiltInFunction(pub(crate) NativeFunction);
pub enum ConstructorKind {
Base,
Derived,
}
/// Defines how this references are interpreted within the formal parameters and code body of the function.
///
/// Arrow functions don't define a `this` and thus are lexical, `function`s do define a this and thus are NonLexical
#[derive(Debug, Copy, Finalize, Clone, PartialEq, PartialOrd, Hash)] unsafe impl Trace for BuiltInFunction {
pub enum ThisMode {
Lexical,
NonLexical,
}
unsafe impl Trace for ThisMode {
unsafe_empty_trace!(); unsafe_empty_trace!();
} }
/// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node) impl From<NativeFunction> for BuiltInFunction {
#[derive(Clone, Finalize)] fn from(function: NativeFunction) -> Self {
pub enum FunctionBody { Self(function)
BuiltIn(NativeFunctionData),
Ordinary(StatementList),
}
impl Debug for FunctionBody {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BuiltIn(_) => write!(f, "[native]"),
Self::Ordinary(statements) => write!(f, "{:?}", statements),
}
} }
} }
impl PartialEq for FunctionBody { impl Debug for BuiltInFunction {
fn eq(&self, other: &Self) -> bool { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match (self, other) { f.write_str("[native]")
(Self::BuiltIn(a), Self::BuiltIn(b)) => std::ptr::eq(a, b),
(Self::Ordinary(a), Self::Ordinary(b)) => a == b,
(_, _) => false,
}
}
} }
impl Eq for FunctionBody {}
/// `Trace` implementation for `FunctionBody`.
///
/// This is indeed safe, but we need to mark this as an empty trace because neither
// `NativeFunctionData` nor Node hold any GC'd objects, but Gc doesn't know that. So we need to
/// signal it manually. `rust-gc` does not have a `Trace` implementation for `fn(_, _, _)`.
///
/// <https://github.com/Manishearth/rust-gc/blob/master/gc/src/trace.rs>
unsafe impl Trace for FunctionBody {
unsafe_empty_trace!();
} }
bitflags! { bitflags! {
#[derive(Finalize, Default)] #[derive(Finalize, Default)]
struct FunctionFlags: u8 { pub struct FunctionFlags: u8 {
const CALLABLE = 0b0000_0001; const CALLABLE = 0b0000_0001;
const CONSTRUCTABLE = 0b0000_0010; const CONSTRUCTABLE = 0b0000_0010;
const LEXICAL_THIS_MODE = 0b0000_0100;
} }
} }
@ -114,14 +73,19 @@ impl FunctionFlags {
} }
#[inline] #[inline]
fn is_callable(&self) -> bool { pub(crate) fn is_callable(&self) -> bool {
self.contains(Self::CALLABLE) self.contains(Self::CALLABLE)
} }
#[inline] #[inline]
fn is_constructable(&self) -> bool { pub(crate) fn is_constructable(&self) -> bool {
self.contains(Self::CONSTRUCTABLE) self.contains(Self::CONSTRUCTABLE)
} }
#[inline]
pub(crate) fn is_lexical_this_mode(&self) -> bool {
self.contains(Self::LEXICAL_THIS_MODE)
}
} }
unsafe impl Trace for FunctionFlags { unsafe impl Trace for FunctionFlags {
@ -130,220 +94,23 @@ unsafe impl Trace for FunctionFlags {
/// Boa representation of a Function Object. /// Boa representation of a Function Object.
/// ///
/// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node)
///
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects> /// <https://tc39.es/ecma262/#sec-ecmascript-function-objects>
#[derive(Trace, Finalize, Clone)] #[derive(Debug, Clone, Finalize, Trace)]
pub struct Function { pub enum Function {
/// Call/Construct Function body BuiltIn(BuiltInFunction, FunctionFlags),
pub body: FunctionBody, Ordinary {
/// Formal Paramaters
pub params: Box<[FormalParameter]>,
/// This Mode
pub this_mode: ThisMode,
// Environment, built-in functions don't need Environments
pub environment: Option<Environment>,
/// Is it constructable or
flags: FunctionFlags, flags: FunctionFlags,
}
impl Function {
pub fn new<P>(
parameter_list: P,
scope: Option<Environment>,
body: FunctionBody,
this_mode: ThisMode,
constructable: bool,
callable: bool,
) -> Self
where
P: Into<Box<[FormalParameter]>>,
{
Self {
body,
environment: scope,
params: parameter_list.into(),
this_mode,
flags: FunctionFlags::from_parameters(callable, constructable),
}
}
/// This will create an ordinary function object
///
/// <https://tc39.es/ecma262/#sec-ordinaryfunctioncreate>
pub fn ordinary<P>(
parameter_list: P,
scope: Environment,
body: StatementList, body: StatementList,
this_mode: ThisMode, params: Box<[FormalParameter]>,
) -> Self environment: Environment,
where
P: Into<Box<[FormalParameter]>>,
{
Self::new(
parameter_list.into(),
Some(scope),
FunctionBody::Ordinary(body),
this_mode,
true,
true,
)
}
/// This will create a built-in function object
///
/// <https://tc39.es/ecma262/#sec-createbuiltinfunction>
pub fn builtin<P>(parameter_list: P, body: NativeFunctionData) -> Self
where
P: Into<Box<[FormalParameter]>>,
{
let _timer = BoaProfiler::global().start_event("function::builtin", "function");
Self::new(
parameter_list.into(),
None,
FunctionBody::BuiltIn(body),
ThisMode::NonLexical,
false,
true,
)
}
/// This will handle calls for both ordinary and built-in functions
///
/// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
pub fn call(
&self,
function: Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object)
this: &Value,
args_list: &[Value],
interpreter: &mut Interpreter,
) -> Result<Value> {
let _timer = BoaProfiler::global().start_event("function::call", "function");
if self.flags.is_callable() {
match self.body {
FunctionBody::BuiltIn(func) => func(this, args_list, interpreter),
FunctionBody::Ordinary(ref body) => {
// Create a new Function environment who's parent is set to the scope of the function declaration (self.environment)
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = new_function_environment(
function,
if let ThisMode::Lexical = self.this_mode {
None
} else {
Some(this.clone())
}, },
self.environment.as_ref().cloned(),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
if let ThisMode::Lexical = self.this_mode {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
);
// Add argument bindings to the function environment
for (i, param) in self.params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
self.add_rest_param(param, i, args_list, interpreter, &local_env);
break;
}
let value = args_list.get(i).cloned().unwrap_or_else(Value::undefined);
self.add_arguments_to_environment(param, value, &local_env);
}
// Add arguments object
let arguments_obj = create_unmapped_arguments_object(args_list);
local_env
.borrow_mut()
.create_mutable_binding("arguments".to_string(), false);
local_env
.borrow_mut()
.initialize_binding("arguments", arguments_obj);
interpreter.realm.environment.push(local_env);
// Call body should be set before reaching here
let result = body.run(interpreter);
// local_env gets dropped here, its no longer needed
interpreter.realm.environment.pop();
result
}
}
} else {
panic!("TypeError: class constructors must be invoked with 'new'");
}
}
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
pub fn construct(
&self,
function: Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object)
this: &Value,
args_list: &[Value],
interpreter: &mut Interpreter,
) -> Result<Value> {
if self.flags.is_constructable() {
match self.body {
FunctionBody::BuiltIn(func) => {
func(this, args_list, interpreter)?;
Ok(this.clone())
}
FunctionBody::Ordinary(ref body) => {
// Create a new Function environment who's parent is set to the scope of the function declaration (self.environment)
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = new_function_environment(
function,
Some(this.clone()),
self.environment.as_ref().cloned(),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
if let ThisMode::Lexical = self.this_mode {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
);
// Add argument bindings to the function environment
for (i, param) in self.params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
self.add_rest_param(param, i, args_list, interpreter, &local_env);
break;
}
let value = args_list.get(i).cloned().unwrap_or_else(Value::undefined);
self.add_arguments_to_environment(param, value, &local_env);
}
// Add arguments object
let arguments_obj = create_unmapped_arguments_object(args_list);
local_env
.borrow_mut()
.create_mutable_binding("arguments".to_string(), false);
local_env
.borrow_mut()
.initialize_binding("arguments", arguments_obj);
interpreter.realm.environment.push(local_env);
// Call body should be set before reaching here
let _ = body.run(interpreter);
// local_env gets dropped here, its no longer needed
let binding = interpreter.realm.environment.get_this_binding();
Ok(binding)
}
}
} else {
let name = this.get_field("name").display().to_string();
panic!("TypeError: {} is not a constructor", name);
}
} }
impl Function {
// Adds the final rest parameters to the Environment as an array // Adds the final rest parameters to the Environment as an array
fn add_rest_param( pub(crate) fn add_rest_param(
&self, &self,
param: &FormalParameter, param: &FormalParameter,
index: usize, index: usize,
@ -367,7 +134,7 @@ impl Function {
} }
// Adds an argument to the environment // Adds an argument to the environment
fn add_arguments_to_environment( pub(crate) fn add_arguments_to_environment(
&self, &self,
param: &FormalParameter, param: &FormalParameter,
value: Value, value: Value,
@ -386,20 +153,18 @@ impl Function {
/// Returns true if the function object is callable. /// Returns true if the function object is callable.
pub fn is_callable(&self) -> bool { pub fn is_callable(&self) -> bool {
self.flags.is_callable() match self {
Self::BuiltIn(_, flags) => flags.is_callable(),
Self::Ordinary { flags, .. } => flags.is_callable(),
}
} }
/// Returns true if the function object is constructable. /// Returns true if the function object is constructable.
pub fn is_constructable(&self) -> bool { pub fn is_constructable(&self) -> bool {
self.flags.is_constructable() match self {
} Self::BuiltIn(_, flags) => flags.is_constructable(),
Self::Ordinary { flags, .. } => flags.is_constructable(),
} }
impl Debug for Function {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{{")?;
write!(f, "[Not implemented]")?;
write!(f, "}}")
} }
} }
@ -436,9 +201,9 @@ pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value {
/// ///
// This gets called when a new Function() is created. // This gets called when a new Function() is created.
pub fn make_function(this: &Value, _: &[Value], _: &mut Interpreter) -> Result<Value> { pub fn make_function(this: &Value, _: &[Value], _: &mut Interpreter) -> Result<Value> {
this.set_data(ObjectData::Function(Function::builtin( this.set_data(ObjectData::Function(Function::BuiltIn(
Vec::new(), BuiltInFunction(|_, _, _| Ok(Value::undefined())),
|_, _, _| Ok(Value::undefined()), FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE,
))); )));
Ok(this.clone()) Ok(this.clone())
} }
@ -450,7 +215,7 @@ pub fn make_function(this: &Value, _: &[Value], _: &mut Interpreter) -> Result<V
pub fn make_constructor_fn( pub fn make_constructor_fn(
name: &str, name: &str,
length: usize, length: usize,
body: NativeFunctionData, body: NativeFunction,
global: &Value, global: &Value,
prototype: Value, prototype: Value,
constructable: bool, constructable: bool,
@ -460,8 +225,10 @@ pub fn make_constructor_fn(
BoaProfiler::global().start_event(&format!("make_constructor_fn: {}", name), "init"); BoaProfiler::global().start_event(&format!("make_constructor_fn: {}", name), "init");
// Create the native function // Create the native function
let mut function = Function::builtin(Vec::new(), body); let function = Function::BuiltIn(
function.flags = FunctionFlags::from_parameters(callable, constructable); body.into(),
FunctionFlags::from_parameters(callable, constructable),
);
// Get reference to Function.prototype // Get reference to Function.prototype
// Create the function object and point its instance prototype to Function.prototype // Create the function object and point its instance prototype to Function.prototype
@ -514,7 +281,7 @@ pub fn make_constructor_fn(
/// ///
/// If no length is provided, the length will be set to 0. /// If no length is provided, the length will be set to 0.
pub fn make_builtin_fn<N>( pub fn make_builtin_fn<N>(
function: NativeFunctionData, function: NativeFunction,
name: N, name: N,
parent: &Value, parent: &Value,
length: usize, length: usize,
@ -526,13 +293,12 @@ pub fn make_builtin_fn<N>(
let _timer = BoaProfiler::global().start_event(&format!("make_builtin_fn: {}", &name), "init"); let _timer = BoaProfiler::global().start_event(&format!("make_builtin_fn: {}", &name), "init");
let mut function = Object::function( let mut function = Object::function(
Function::builtin(Vec::new(), function), Function::BuiltIn(function.into(), FunctionFlags::CALLABLE),
interpreter interpreter
.global() .global()
.get_field("Function") .get_field("Function")
.get_field("prototype"), .get_field("prototype"),
); );
function.insert_field("length", Value::from(length)); function.insert_field("length", Value::from(length));
parent parent

14
boa/src/builtins/map/tests.rs

@ -228,10 +228,18 @@ fn recursive_display() {
} }
#[test] #[test]
#[should_panic]
fn not_a_function() { fn not_a_function() {
let realm = Realm::create(); let realm = Realm::create();
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
let init = "let map = Map()"; let init = r"
forward(&mut engine, init); try {
let map = Map()
} catch(e) {
e.toString()
}
";
assert_eq!(
forward(&mut engine, init),
"\"TypeError: function object is not callable\""
);
} }

160
boa/src/builtins/object/gcobject.rs

@ -3,7 +3,18 @@
//! The `GcObject` is a garbage collected Object. //! The `GcObject` is a garbage collected Object.
use super::Object; use super::Object;
use crate::{
builtins::{
function::{create_unmapped_arguments_object, BuiltInFunction, Function},
Value,
},
environment::{
function_environment_record::BindingStatus, lexical_environment::new_function_environment,
},
Executable, Interpreter, Result,
};
use gc::{Finalize, Gc, GcCell, GcCellRef, GcCellRefMut, Trace}; use gc::{Finalize, Gc, GcCell, GcCellRef, GcCellRefMut, Trace};
use std::result::Result as StdResult;
use std::{ use std::{
cell::RefCell, cell::RefCell,
collections::HashSet, collections::HashSet,
@ -31,12 +42,12 @@ impl GcObject {
} }
#[inline] #[inline]
pub fn try_borrow(&self) -> Result<GcCellRef<'_, Object>, BorrowError> { pub fn try_borrow(&self) -> StdResult<GcCellRef<'_, Object>, BorrowError> {
self.0.try_borrow().map_err(|_| BorrowError) self.0.try_borrow().map_err(|_| BorrowError)
} }
#[inline] #[inline]
pub fn try_borrow_mut(&self) -> Result<GcCellRefMut<'_, Object>, BorrowMutError> { pub fn try_borrow_mut(&self) -> StdResult<GcCellRefMut<'_, Object>, BorrowMutError> {
self.0.try_borrow_mut().map_err(|_| BorrowMutError) self.0.try_borrow_mut().map_err(|_| BorrowMutError)
} }
@ -45,6 +56,151 @@ impl GcObject {
pub fn equals(lhs: &Self, rhs: &Self) -> bool { pub fn equals(lhs: &Self, rhs: &Self) -> bool {
std::ptr::eq(lhs.as_ref(), rhs.as_ref()) std::ptr::eq(lhs.as_ref(), rhs.as_ref())
} }
/// This will handle calls for both ordinary and built-in functions
///
/// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
pub fn call(&self, this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result<Value> {
let this_function_object = self.clone();
let object = self.borrow();
if let Some(function) = object.as_function() {
if function.is_callable() {
match function {
Function::BuiltIn(BuiltInFunction(function), _) => function(this, args, ctx),
Function::Ordinary {
body,
params,
environment,
flags,
} => {
// Create a new Function environment who's parent is set to the scope of the function declaration (self.environment)
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = new_function_environment(
this_function_object,
if flags.is_lexical_this_mode() {
None
} else {
Some(this.clone())
},
Some(environment.clone()),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
if flags.is_lexical_this_mode() {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
);
// Add argument bindings to the function environment
for (i, param) in params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
function.add_rest_param(param, i, args, ctx, &local_env);
break;
}
let value = args.get(i).cloned().unwrap_or_else(Value::undefined);
function.add_arguments_to_environment(param, value, &local_env);
}
// Add arguments object
let arguments_obj = create_unmapped_arguments_object(args);
local_env
.borrow_mut()
.create_mutable_binding("arguments".to_string(), false);
local_env
.borrow_mut()
.initialize_binding("arguments", arguments_obj);
ctx.realm.environment.push(local_env);
// Call body should be set before reaching here
let result = body.run(ctx);
// local_env gets dropped here, its no longer needed
ctx.realm.environment.pop();
result
}
}
} else {
ctx.throw_type_error("function object is not callable")
}
} else {
ctx.throw_type_error("not a function")
}
}
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
pub fn construct(&self, this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result<Value> {
let this_function_object = self.clone();
let object = self.borrow();
if let Some(function) = object.as_function() {
if function.is_constructable() {
match function {
Function::BuiltIn(BuiltInFunction(function), _) => {
function(this, args, ctx)?;
Ok(this.clone())
}
Function::Ordinary {
body,
params,
environment,
flags,
} => {
// Create a new Function environment who's parent is set to the scope of the function declaration (self.environment)
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = new_function_environment(
this_function_object,
Some(this.clone()),
Some(environment.clone()),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
if flags.is_lexical_this_mode() {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
);
// Add argument bindings to the function environment
for (i, param) in params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
function.add_rest_param(param, i, args, ctx, &local_env);
break;
}
let value = args.get(i).cloned().unwrap_or_else(Value::undefined);
function.add_arguments_to_environment(param, value, &local_env);
}
// Add arguments object
let arguments_obj = create_unmapped_arguments_object(args);
local_env
.borrow_mut()
.create_mutable_binding("arguments".to_string(), false);
local_env
.borrow_mut()
.initialize_binding("arguments", arguments_obj);
ctx.realm.environment.push(local_env);
// Call body should be set before reaching here
let _ = body.run(ctx);
// local_env gets dropped here, its no longer needed
let binding = ctx.realm.environment.get_this_binding();
Ok(binding)
}
}
} else {
let name = this.get_field("name").display().to_string();
ctx.throw_type_error(format!("{} is not a constructor", name))
}
} else {
ctx.throw_type_error("not a function")
}
}
} }
impl AsRef<GcCell<Object>> for GcObject { impl AsRef<GcCell<Object>> for GcObject {

64
boa/src/builtins/object/internal_state.rs

@ -1,64 +0,0 @@
//! Implementations for storing normal rust structs inside any object as internal state.
use std::{
any::Any,
fmt::{self, Debug},
ops::{Deref, DerefMut},
rc::Rc,
};
use gc::{unsafe_empty_trace, Finalize, Trace};
/// Wrapper around `Rc` to implement `Trace` and `Finalize`.
#[derive(Clone)]
pub struct InternalStateCell {
/// The internal state.
state: Rc<dyn Any>,
}
impl Finalize for InternalStateCell {}
unsafe impl Trace for InternalStateCell {
unsafe_empty_trace!();
}
impl Deref for InternalStateCell {
type Target = dyn Any;
fn deref(&self) -> &Self::Target {
Deref::deref(&self.state)
}
}
impl DerefMut for InternalStateCell {
fn deref_mut(&mut self) -> &mut Self::Target {
Rc::get_mut(&mut self.state).expect("failed to get mutable")
}
}
/// The derived version would print 'InternalStateCell { state: ... }', this custom implementation
/// only prints the actual internal state.
impl Debug for InternalStateCell {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt(&self.state, f)
}
}
impl InternalStateCell {
/// Create new `InternalStateCell` from a value.
pub fn new<T: Any + InternalState>(value: T) -> Self {
Self {
state: Rc::new(value),
}
}
/// Get a reference to the stored value and cast it to `T`.
pub fn downcast_ref<T: Any + InternalState>(&self) -> Option<&T> {
self.deref().downcast_ref::<T>()
}
/// Get a mutable reference to the stored value and cast it to `T`.
pub fn downcast_mut<T: Any + InternalState>(&mut self) -> Option<&mut T> {
self.deref_mut().downcast_mut::<T>()
}
}
/// This trait must be implemented by all structs used for internal state.
pub trait InternalState: Debug {}

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

@ -31,13 +31,12 @@ use std::result::Result as StdResult;
use super::function::{make_builtin_fn, make_constructor_fn}; use super::function::{make_builtin_fn, make_constructor_fn};
use crate::builtins::value::same_value; use crate::builtins::value::same_value;
pub use internal_state::{InternalState, InternalStateCell};
pub mod gcobject; mod gcobject;
pub mod internal_methods; mod internal_methods;
mod internal_state;
pub use gcobject::GcObject; pub use gcobject::GcObject;
pub use internal_methods::*;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -45,9 +44,6 @@ mod tests;
/// Static `prototype`, usually set on constructors as a key to point to their respective prototype object. /// Static `prototype`, usually set on constructors as a key to point to their respective prototype object.
pub static PROTOTYPE: &str = "prototype"; pub static PROTOTYPE: &str = "prototype";
// /// Static `__proto__`, usually set on Object instances as a key to point to their respective prototype object.
// pub static INSTANCE_PROTOTYPE: &str = "__proto__";
/// The internal representation of an JavaScript object. /// The internal representation of an JavaScript object.
#[derive(Debug, Trace, Finalize, Clone)] #[derive(Debug, Trace, Finalize, Clone)]
pub struct Object { pub struct Object {
@ -59,8 +55,6 @@ pub struct Object {
symbol_properties: FxHashMap<u32, Property>, symbol_properties: FxHashMap<u32, Property>,
/// Instance prototype `__proto__`. /// Instance prototype `__proto__`.
prototype: Value, prototype: Value,
/// Some rust object that stores internal state
state: Option<InternalStateCell>,
/// Whether it can have new properties added to it. /// Whether it can have new properties added to it.
extensible: bool, extensible: bool,
} }
@ -70,7 +64,7 @@ pub struct Object {
pub enum ObjectData { pub enum ObjectData {
Array, Array,
Map(OrderedMap<Value, Value>), Map(OrderedMap<Value, Value>),
RegExp(RegExp), RegExp(Box<RegExp>),
BigInt(RcBigInt), BigInt(RcBigInt),
Boolean(bool), Boolean(bool),
Function(Function), Function(Function),
@ -116,7 +110,6 @@ impl Default for Object {
properties: FxHashMap::default(), properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(),
prototype: Value::null(), prototype: Value::null(),
state: None,
extensible: true, extensible: true,
} }
} }
@ -137,7 +130,6 @@ impl Object {
properties: FxHashMap::default(), properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(),
prototype, prototype,
state: None,
extensible: true, extensible: true,
} }
} }
@ -162,7 +154,6 @@ impl Object {
properties: FxHashMap::default(), properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(),
prototype: Value::null(), prototype: Value::null(),
state: None,
extensible: true, extensible: true,
} }
} }
@ -174,7 +165,6 @@ impl Object {
properties: FxHashMap::default(), properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(),
prototype: Value::null(), prototype: Value::null(),
state: None,
extensible: true, extensible: true,
} }
} }
@ -189,7 +179,6 @@ impl Object {
properties: FxHashMap::default(), properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(),
prototype: Value::null(), prototype: Value::null(),
state: None,
extensible: true, extensible: true,
} }
} }
@ -201,7 +190,6 @@ impl Object {
properties: FxHashMap::default(), properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(),
prototype: Value::null(), prototype: Value::null(),
state: None,
extensible: true, extensible: true,
} }
} }
@ -420,16 +408,6 @@ impl Object {
&mut self.symbol_properties &mut self.symbol_properties
} }
#[inline]
pub fn state(&self) -> &Option<InternalStateCell> {
&self.state
}
#[inline]
pub fn state_mut(&mut self) -> &mut Option<InternalStateCell> {
&mut self.state
}
pub fn prototype(&self) -> &Value { pub fn prototype(&self) -> &Value {
&self.prototype &self.prototype
} }

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

@ -14,7 +14,7 @@ use regex::Regex;
use super::function::{make_builtin_fn, make_constructor_fn}; use super::function::{make_builtin_fn, make_constructor_fn};
use crate::{ use crate::{
builtins::{ builtins::{
object::{InternalState, ObjectData}, object::ObjectData,
property::Property, property::Property,
value::{RcString, Value}, value::{RcString, Value},
}, },
@ -64,8 +64,6 @@ unsafe impl Trace for RegExp {
unsafe_empty_trace!(); unsafe_empty_trace!();
} }
impl InternalState for RegExp {}
impl RegExp { impl RegExp {
/// The name of the object. /// The name of the object.
pub(crate) const NAME: &'static str = "RegExp"; pub(crate) const NAME: &'static str = "RegExp";
@ -156,7 +154,7 @@ impl RegExp {
original_flags: regex_flags, original_flags: regex_flags,
}; };
this.set_data(ObjectData::RegExp(regexp)); this.set_data(ObjectData::RegExp(Box::new(regexp)));
Ok(this.clone()) Ok(this.clone())
} }

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

@ -7,8 +7,7 @@ mod tests;
use super::number::{f64_to_int32, f64_to_uint32}; use super::number::{f64_to_int32, f64_to_uint32};
use crate::builtins::{ use crate::builtins::{
function::Function, object::{GcObject, Object, ObjectData, PROTOTYPE},
object::{GcObject, InternalState, InternalStateCell, Object, ObjectData, PROTOTYPE},
property::{Attribute, Property, PropertyKey}, property::{Attribute, Property, PropertyKey},
BigInt, Number, Symbol, BigInt, Number, Symbol,
}; };
@ -17,7 +16,6 @@ use crate::{BoaProfiler, Result};
use gc::{Finalize, GcCellRef, GcCellRefMut, Trace}; use gc::{Finalize, GcCellRef, GcCellRefMut, Trace};
use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue}; use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue};
use std::{ use std::{
any::Any,
collections::HashSet, collections::HashSet,
convert::TryFrom, convert::TryFrom,
f64::NAN, f64::NAN,
@ -530,66 +528,6 @@ impl Value {
} }
} }
/// Check whether an object has an internal state set.
#[inline]
pub fn has_internal_state(&self) -> bool {
matches!(self.as_object(), Some(object) if object.state().is_some())
}
/// Get the internal state of an object.
pub fn get_internal_state(&self) -> Option<InternalStateCell> {
self.as_object()
.and_then(|object| object.state().as_ref().cloned())
}
/// Run a function with a reference to the internal state.
///
/// # Panics
///
/// This will panic if this value doesn't have an internal state or if the internal state doesn't
/// have the concrete type `S`.
pub fn with_internal_state_ref<S, R, F>(&self, f: F) -> R
where
S: Any + InternalState,
F: FnOnce(&S) -> R,
{
if let Some(object) = self.as_object() {
let state = object
.state()
.as_ref()
.expect("no state")
.downcast_ref()
.expect("wrong state type");
f(state)
} else {
panic!("not an object");
}
}
/// Run a function with a mutable reference to the internal state.
///
/// # Panics
///
/// This will panic if this value doesn't have an internal state or if the internal state doesn't
/// have the concrete type `S`.
pub fn with_internal_state_mut<S, R, F>(&self, f: F) -> R
where
S: Any + InternalState,
F: FnOnce(&mut S) -> R,
{
if let Some(mut object) = self.as_object_mut() {
let state = object
.state_mut()
.as_mut()
.expect("no state")
.downcast_mut()
.expect("wrong state type");
f(state)
} else {
panic!("not an object");
}
}
/// Check to see if the Value has the field, mainly used by environment records. /// Check to see if the Value has the field, mainly used by environment records.
#[inline] #[inline]
pub fn has_field(&self, field: &str) -> bool { pub fn has_field(&self, field: &str) -> bool {
@ -646,27 +584,6 @@ impl Value {
property property
} }
/// Set internal state of an Object. Discards the previous state if it was set.
pub fn set_internal_state<T: Any + InternalState>(&self, state: T) {
if let Some(mut object) = self.as_object_mut() {
object.state_mut().replace(InternalStateCell::new(state));
}
}
/// Consume the function and return a Value
pub fn from_func(function: Function) -> Value {
// Get Length
let length = function.params.len();
// Object with Kind set to function
// TODO: FIXME: Add function prototype
let new_func = Object::function(function, Value::null());
// Wrap Object in GC'd Value
let new_func_val = Value::from(new_func);
// Set length to parameters
new_func_val.set_field("length", Value::from(length));
new_func_val
}
/// The abstract operation ToPrimitive takes an input argument and an optional argument PreferredType. /// The abstract operation ToPrimitive takes an input argument and an optional argument PreferredType.
/// ///
/// <https://tc39.es/ecma262/#sec-toprimitive> /// <https://tc39.es/ecma262/#sec-toprimitive>

4
boa/src/environment/function_environment_record.rs

@ -9,7 +9,7 @@
//! More info: <https://tc39.es/ecma262/#sec-function-environment-records> //! More info: <https://tc39.es/ecma262/#sec-function-environment-records>
use crate::{ use crate::{
builtins::value::Value, builtins::{object::GcObject, value::Value},
environment::{ environment::{
declarative_environment_record::DeclarativeEnvironmentRecordBinding, declarative_environment_record::DeclarativeEnvironmentRecordBinding,
environment_record_trait::EnvironmentRecordTrait, environment_record_trait::EnvironmentRecordTrait,
@ -44,7 +44,7 @@ pub struct FunctionEnvironmentRecord {
/// If the value is "lexical", this is an ArrowFunction and does not have a local this value. /// If the value is "lexical", this is an ArrowFunction and does not have a local this value.
pub this_binding_status: BindingStatus, pub this_binding_status: BindingStatus,
/// The function object whose invocation caused this Environment Record to be created. /// The function object whose invocation caused this Environment Record to be created.
pub function: Value, pub function: GcObject,
/// If the associated function has super property accesses and is not an ArrowFunction, /// If the associated function has super property accesses and is not an ArrowFunction,
/// [[HomeObject]] is the object that the function is bound to as a method. /// [[HomeObject]] is the object that the function is bound to as a method.
/// The default value for [[HomeObject]] is undefined. /// The default value for [[HomeObject]] is undefined.

10
boa/src/environment/lexical_environment.rs

@ -6,7 +6,7 @@
//! This is the entrypoint to lexical environments. //! This is the entrypoint to lexical environments.
use crate::{ use crate::{
builtins::value::Value, builtins::{object::GcObject, value::Value},
environment::{ environment::{
declarative_environment_record::DeclarativeEnvironmentRecord, declarative_environment_record::DeclarativeEnvironmentRecord,
environment_record_trait::EnvironmentRecordTrait, environment_record_trait::EnvironmentRecordTrait,
@ -162,11 +162,7 @@ impl LexicalEnvironment {
}) })
.expect("No function or global environment"); .expect("No function or global environment");
#[allow(clippy::let_and_return)] env.borrow_mut().create_immutable_binding(name, deletion)
// FIXME need to assign result to a variable to avoid borrow checker error
// (borrowed value `env` does not live long enough)
let b = env.borrow_mut().create_immutable_binding(name, deletion);
b
} }
} }
} }
@ -230,7 +226,7 @@ pub fn new_declarative_environment(env: Option<Environment>) -> Environment {
} }
pub fn new_function_environment( pub fn new_function_environment(
f: Value, f: GcObject,
this: Option<Value>, this: Option<Value>,
outer: Option<Environment>, outer: Option<Environment>,
binding_status: BindingStatus, binding_status: BindingStatus,

16
boa/src/exec/declaration/mod.rs

@ -2,7 +2,7 @@
use super::{Executable, Interpreter}; use super::{Executable, Interpreter};
use crate::{ use crate::{
builtins::{function::ThisMode, value::Value}, builtins::{function::FunctionFlags, Value},
environment::lexical_environment::VariableScope, environment::lexical_environment::VariableScope,
syntax::ast::node::{ syntax::ast::node::{
ArrowFunctionDecl, ConstDeclList, FunctionDecl, FunctionExpr, LetDeclList, VarDeclList, ArrowFunctionDecl, ConstDeclList, FunctionDecl, FunctionExpr, LetDeclList, VarDeclList,
@ -16,9 +16,7 @@ impl Executable for FunctionDecl {
let val = interpreter.create_function( let val = interpreter.create_function(
self.parameters().to_vec(), self.parameters().to_vec(),
self.body().to_vec(), self.body().to_vec(),
ThisMode::NonLexical, FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE,
true,
true,
); );
// Set the name and assign it in the current environment // Set the name and assign it in the current environment
@ -43,9 +41,7 @@ impl Executable for FunctionExpr {
let val = interpreter.create_function( let val = interpreter.create_function(
self.parameters().to_vec(), self.parameters().to_vec(),
self.body().to_vec(), self.body().to_vec(),
ThisMode::NonLexical, FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE,
true,
true,
); );
if let Some(name) = self.name() { if let Some(name) = self.name() {
@ -127,9 +123,9 @@ impl Executable for ArrowFunctionDecl {
Ok(interpreter.create_function( Ok(interpreter.create_function(
self.params().to_vec(), self.params().to_vec(),
self.body().to_vec(), self.body().to_vec(),
ThisMode::Lexical, FunctionFlags::CALLABLE
false, | FunctionFlags::CONSTRUCTABLE
true, | FunctionFlags::LEXICAL_THIS_MODE,
)) ))
} }
} }

81
boa/src/exec/mod.rs

@ -25,8 +25,8 @@ mod try_node;
use crate::{ use crate::{
builtins, builtins,
builtins::{ builtins::{
function::{Function as FunctionObject, FunctionBody, ThisMode}, function::{Function, FunctionFlags, NativeFunction},
object::{Object, ObjectData, PROTOTYPE}, object::{GcObject, Object, ObjectData, PROTOTYPE},
property::PropertyKey, property::PropertyKey,
value::{PreferredType, Type, Value}, value::{PreferredType, Type, Value},
Console, Console,
@ -125,40 +125,25 @@ impl Interpreter {
&mut self, &mut self,
params: P, params: P,
body: B, body: B,
this_mode: ThisMode, flags: FunctionFlags,
constructable: bool,
callable: bool,
) -> Value ) -> Value
where where
P: Into<Box<[FormalParameter]>>, P: Into<Box<[FormalParameter]>>,
B: Into<StatementList>, B: Into<StatementList>,
{ {
let function_prototype = self let function_prototype = self.global().get_field("Function").get_field(PROTOTYPE);
.realm
.environment
.get_global_object()
.expect("Could not get the global object")
.get_field("Function")
.get_field(PROTOTYPE);
// Every new function has a prototype property pre-made // Every new function has a prototype property pre-made
let global_val = &self let proto = Value::new_object(Some(self.global()));
.realm
.environment
.get_global_object()
.expect("Could not get the global object");
let proto = Value::new_object(Some(global_val));
let params = params.into(); let params = params.into();
let params_len = params.len(); let params_len = params.len();
let func = FunctionObject::new( let func = Function::Ordinary {
flags,
body: body.into(),
params, params,
Some(self.realm.environment.get_current_environment().clone()), environment: self.realm.environment.get_current_environment().clone(),
FunctionBody::Ordinary(body.into()), };
this_mode,
constructable,
callable,
);
let new_func = Object::function(func, function_prototype); let new_func = Object::function(func, function_prototype);
@ -169,21 +154,43 @@ impl Interpreter {
val val
} }
/// <https://tc39.es/ecma262/#sec-call> /// Utility to create a function Value for Function Declarations, Arrow Functions or Function Expressions
pub(crate) fn call( pub fn create_builtin_function(
&mut self, &mut self,
f: &Value, name: &str,
this: &Value, length: usize,
arguments_list: &[Value], body: NativeFunction,
) -> Result<Value> { ) -> Result<GcObject> {
match *f { let function_prototype = self.global().get_field("Function").get_field(PROTOTYPE);
Value::Object(ref obj) => {
let obj = obj.borrow(); // Every new function has a prototype property pre-made
if let ObjectData::Function(ref func) = obj.data { let proto = Value::new_object(Some(self.global()));
return func.call(f.clone(), this, arguments_list, self); let mut function = Object::function(
Function::BuiltIn(body.into(), FunctionFlags::CALLABLE),
function_prototype,
);
function.set(&PROTOTYPE.into(), proto);
function.set(&"length".into(), length.into());
function.set(&"name".into(), name.into());
Ok(GcObject::new(function))
} }
self.throw_type_error("not a function")
pub fn register_global_function(
&mut self,
name: &str,
length: usize,
body: NativeFunction,
) -> Result<()> {
let function = self.create_builtin_function(name, length, body)?;
self.global().set_field(name, function);
Ok(())
} }
/// <https://tc39.es/ecma262/#sec-call>
pub(crate) fn call(&mut self, f: &Value, this: &Value, args: &[Value]) -> Result<Value> {
match *f {
Value::Object(ref object) => object.call(this, args, self),
_ => self.throw_type_error("not a function"), _ => self.throw_type_error("not a function"),
} }
} }

17
boa/src/exec/new/mod.rs

@ -1,9 +1,6 @@
use super::{Executable, Interpreter}; use super::{Executable, Interpreter};
use crate::{ use crate::{
builtins::{ builtins::{object::PROTOTYPE, Value},
object::{ObjectData, PROTOTYPE},
value::Value,
},
syntax::ast::node::New, syntax::ast::node::New,
BoaProfiler, Result, BoaProfiler, Result,
}; };
@ -11,10 +8,6 @@ use crate::{
impl Executable for New { impl Executable for New {
fn run(&self, interpreter: &mut Interpreter) -> Result<Value> { fn run(&self, interpreter: &mut Interpreter) -> Result<Value> {
let _timer = BoaProfiler::global().start_event("New", "exec"); let _timer = BoaProfiler::global().start_event("New", "exec");
// let (callee, args) = match call.as_ref() {
// Node::Call(callee, args) => (callee, args),
// _ => unreachable!("Node::New(ref call): 'call' must only be Node::Call type."),
// };
let func_object = self.expr().run(interpreter)?; let func_object = self.expr().run(interpreter)?;
let mut v_args = Vec::with_capacity(self.args().len()); let mut v_args = Vec::with_capacity(self.args().len());
@ -28,13 +21,7 @@ impl Executable for New {
.set_prototype(func_object.get_field(PROTOTYPE)); .set_prototype(func_object.get_field(PROTOTYPE));
match func_object { match func_object {
Value::Object(ref obj) => { Value::Object(ref object) => object.construct(&this, &v_args, interpreter),
let obj = obj.borrow();
if let ObjectData::Function(ref func) = obj.data {
return func.construct(func_object.clone(), &this, &v_args, interpreter);
}
interpreter.throw_type_error("not a constructor")
}
_ => Ok(Value::undefined()), _ => Ok(Value::undefined()),
} }
} }

13
boa/src/realm.rs

@ -5,10 +5,7 @@
//! A realm is represented in this implementation as a Realm struct with the fields specified from the spec. //! A realm is represented in this implementation as a Realm struct with the fields specified from the spec.
use crate::{ use crate::{
builtins::{ builtins::value::Value,
function::{Function, NativeFunctionData},
value::Value,
},
environment::{ environment::{
declarative_environment_record::DeclarativeEnvironmentRecord, declarative_environment_record::DeclarativeEnvironmentRecord,
global_environment_record::GlobalEnvironmentRecord, global_environment_record::GlobalEnvironmentRecord,
@ -49,14 +46,6 @@ impl Realm {
environment: LexicalEnvironment::new(global), environment: LexicalEnvironment::new(global),
} }
} }
/// Utility to add a function to the global object
pub fn register_global_func(self, func_name: &str, func: NativeFunctionData) -> Self {
let func = Function::builtin(Vec::new(), func);
self.global_obj.set_field(func_name, Value::from_func(func));
self
}
} }
// Similar to new_global_environment in lexical_environment, except we need to return a GlobalEnvirionment // Similar to new_global_environment in lexical_environment, except we need to return a GlobalEnvirionment

Loading…
Cancel
Save