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},
Array,
},
environment::function_environment_record::BindingStatus,
environment::lexical_environment::{new_function_environment, Environment},
exec::{Executable, Interpreter},
environment::lexical_environment::Environment,
exec::Interpreter,
syntax::ast::node::{FormalParameter, StatementList},
BoaProfiler, Result,
};
@ -29,73 +28,33 @@ use gc::{unsafe_empty_trace, Finalize, Trace};
use std::fmt::{self, Debug};
/// _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(Debug, Copy, Clone)]
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(Clone, Copy, Finalize)]
pub struct BuiltInFunction(pub(crate) NativeFunction);
#[derive(Debug, Copy, Finalize, Clone, PartialEq, PartialOrd, Hash)]
pub enum ThisMode {
Lexical,
NonLexical,
}
unsafe impl Trace for ThisMode {
unsafe impl Trace for BuiltInFunction {
unsafe_empty_trace!();
}
/// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node)
#[derive(Clone, Finalize)]
pub enum FunctionBody {
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 From<NativeFunction> for BuiltInFunction {
fn from(function: NativeFunction) -> Self {
Self(function)
}
}
impl PartialEq for FunctionBody {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::BuiltIn(a), Self::BuiltIn(b)) => std::ptr::eq(a, b),
(Self::Ordinary(a), Self::Ordinary(b)) => a == b,
(_, _) => false,
}
}
impl Debug for BuiltInFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("[native]")
}
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! {
#[derive(Finalize, Default)]
struct FunctionFlags: u8 {
pub struct FunctionFlags: u8 {
const CALLABLE = 0b0000_0001;
const CONSTRUCTABLE = 0b0000_0010;
const LEXICAL_THIS_MODE = 0b0000_0100;
}
}
@ -114,14 +73,19 @@ impl FunctionFlags {
}
#[inline]
fn is_callable(&self) -> bool {
pub(crate) fn is_callable(&self) -> bool {
self.contains(Self::CALLABLE)
}
#[inline]
fn is_constructable(&self) -> bool {
pub(crate) fn is_constructable(&self) -> bool {
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 {
@ -130,220 +94,23 @@ unsafe impl Trace for FunctionFlags {
/// 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>
#[derive(Trace, Finalize, Clone)]
pub struct Function {
/// Call/Construct Function body
pub body: FunctionBody,
/// 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
#[derive(Debug, Clone, Finalize, Trace)]
pub enum Function {
BuiltIn(BuiltInFunction, FunctionFlags),
Ordinary {
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,
this_mode: ThisMode,
) -> Self
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())
params: Box<[FormalParameter]>,
environment: Environment,
},
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
fn add_rest_param(
pub(crate) fn add_rest_param(
&self,
param: &FormalParameter,
index: usize,
@ -367,7 +134,7 @@ impl Function {
}
// Adds an argument to the environment
fn add_arguments_to_environment(
pub(crate) fn add_arguments_to_environment(
&self,
param: &FormalParameter,
value: Value,
@ -386,20 +153,18 @@ impl Function {
/// Returns true if the function object is callable.
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.
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.
pub fn make_function(this: &Value, _: &[Value], _: &mut Interpreter) -> Result<Value> {
this.set_data(ObjectData::Function(Function::builtin(
Vec::new(),
|_, _, _| Ok(Value::undefined()),
this.set_data(ObjectData::Function(Function::BuiltIn(
BuiltInFunction(|_, _, _| Ok(Value::undefined())),
FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE,
)));
Ok(this.clone())
}
@ -450,7 +215,7 @@ pub fn make_function(this: &Value, _: &[Value], _: &mut Interpreter) -> Result<V
pub fn make_constructor_fn(
name: &str,
length: usize,
body: NativeFunctionData,
body: NativeFunction,
global: &Value,
prototype: Value,
constructable: bool,
@ -460,8 +225,10 @@ pub fn make_constructor_fn(
BoaProfiler::global().start_event(&format!("make_constructor_fn: {}", name), "init");
// Create the native function
let mut function = Function::builtin(Vec::new(), body);
function.flags = FunctionFlags::from_parameters(callable, constructable);
let function = Function::BuiltIn(
body.into(),
FunctionFlags::from_parameters(callable, constructable),
);
// Get reference 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.
pub fn make_builtin_fn<N>(
function: NativeFunctionData,
function: NativeFunction,
name: N,
parent: &Value,
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 mut function = Object::function(
Function::builtin(Vec::new(), function),
Function::BuiltIn(function.into(), FunctionFlags::CALLABLE),
interpreter
.global()
.get_field("Function")
.get_field("prototype"),
);
function.insert_field("length", Value::from(length));
parent

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

@ -228,10 +228,18 @@ fn recursive_display() {
}
#[test]
#[should_panic]
fn not_a_function() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let init = "let map = Map()";
forward(&mut engine, init);
let init = r"
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.
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 std::result::Result as StdResult;
use std::{
cell::RefCell,
collections::HashSet,
@ -31,12 +42,12 @@ impl GcObject {
}
#[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)
}
#[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)
}
@ -45,6 +56,151 @@ impl GcObject {
pub fn equals(lhs: &Self, rhs: &Self) -> bool {
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 {

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 crate::builtins::value::same_value;
pub use internal_state::{InternalState, InternalStateCell};
pub mod gcobject;
pub mod internal_methods;
mod internal_state;
mod gcobject;
mod internal_methods;
pub use gcobject::GcObject;
pub use internal_methods::*;
#[cfg(test)]
mod tests;
@ -45,9 +44,6 @@ mod tests;
/// Static `prototype`, usually set on constructors as a key to point to their respective prototype object.
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.
#[derive(Debug, Trace, Finalize, Clone)]
pub struct Object {
@ -59,8 +55,6 @@ pub struct Object {
symbol_properties: FxHashMap<u32, Property>,
/// Instance prototype `__proto__`.
prototype: Value,
/// Some rust object that stores internal state
state: Option<InternalStateCell>,
/// Whether it can have new properties added to it.
extensible: bool,
}
@ -70,7 +64,7 @@ pub struct Object {
pub enum ObjectData {
Array,
Map(OrderedMap<Value, Value>),
RegExp(RegExp),
RegExp(Box<RegExp>),
BigInt(RcBigInt),
Boolean(bool),
Function(Function),
@ -116,7 +110,6 @@ impl Default for Object {
properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype: Value::null(),
state: None,
extensible: true,
}
}
@ -137,7 +130,6 @@ impl Object {
properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype,
state: None,
extensible: true,
}
}
@ -162,7 +154,6 @@ impl Object {
properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype: Value::null(),
state: None,
extensible: true,
}
}
@ -174,7 +165,6 @@ impl Object {
properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype: Value::null(),
state: None,
extensible: true,
}
}
@ -189,7 +179,6 @@ impl Object {
properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype: Value::null(),
state: None,
extensible: true,
}
}
@ -201,7 +190,6 @@ impl Object {
properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype: Value::null(),
state: None,
extensible: true,
}
}
@ -420,16 +408,6 @@ impl Object {
&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 {
&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 crate::{
builtins::{
object::{InternalState, ObjectData},
object::ObjectData,
property::Property,
value::{RcString, Value},
},
@ -64,8 +64,6 @@ unsafe impl Trace for RegExp {
unsafe_empty_trace!();
}
impl InternalState for RegExp {}
impl RegExp {
/// The name of the object.
pub(crate) const NAME: &'static str = "RegExp";
@ -156,7 +154,7 @@ impl RegExp {
original_flags: regex_flags,
};
this.set_data(ObjectData::RegExp(regexp));
this.set_data(ObjectData::RegExp(Box::new(regexp)));
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 crate::builtins::{
function::Function,
object::{GcObject, InternalState, InternalStateCell, Object, ObjectData, PROTOTYPE},
object::{GcObject, Object, ObjectData, PROTOTYPE},
property::{Attribute, Property, PropertyKey},
BigInt, Number, Symbol,
};
@ -17,7 +16,6 @@ use crate::{BoaProfiler, Result};
use gc::{Finalize, GcCellRef, GcCellRefMut, Trace};
use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue};
use std::{
any::Any,
collections::HashSet,
convert::TryFrom,
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.
#[inline]
pub fn has_field(&self, field: &str) -> bool {
@ -646,27 +584,6 @@ impl Value {
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.
///
/// <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>
use crate::{
builtins::value::Value,
builtins::{object::GcObject, value::Value},
environment::{
declarative_environment_record::DeclarativeEnvironmentRecordBinding,
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.
pub this_binding_status: BindingStatus,
/// 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,
/// [[HomeObject]] is the object that the function is bound to as a method.
/// 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.
use crate::{
builtins::value::Value,
builtins::{object::GcObject, value::Value},
environment::{
declarative_environment_record::DeclarativeEnvironmentRecord,
environment_record_trait::EnvironmentRecordTrait,
@ -162,11 +162,7 @@ impl LexicalEnvironment {
})
.expect("No function or global environment");
#[allow(clippy::let_and_return)]
// 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
env.borrow_mut().create_immutable_binding(name, deletion)
}
}
}
@ -230,7 +226,7 @@ pub fn new_declarative_environment(env: Option<Environment>) -> Environment {
}
pub fn new_function_environment(
f: Value,
f: GcObject,
this: Option<Value>,
outer: Option<Environment>,
binding_status: BindingStatus,

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

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

81
boa/src/exec/mod.rs

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

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

@ -1,9 +1,6 @@
use super::{Executable, Interpreter};
use crate::{
builtins::{
object::{ObjectData, PROTOTYPE},
value::Value,
},
builtins::{object::PROTOTYPE, Value},
syntax::ast::node::New,
BoaProfiler, Result,
};
@ -11,10 +8,6 @@ use crate::{
impl Executable for New {
fn run(&self, interpreter: &mut Interpreter) -> Result<Value> {
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 mut v_args = Vec::with_capacity(self.args().len());
@ -28,13 +21,7 @@ impl Executable for New {
.set_prototype(func_object.get_field(PROTOTYPE));
match func_object {
Value::Object(ref obj) => {
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")
}
Value::Object(ref object) => object.construct(&this, &v_args, interpreter),
_ => 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.
use crate::{
builtins::{
function::{Function, NativeFunctionData},
value::Value,
},
builtins::value::Value,
environment::{
declarative_environment_record::DeclarativeEnvironmentRecord,
global_environment_record::GlobalEnvironmentRecord,
@ -49,14 +46,6 @@ impl Realm {
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

Loading…
Cancel
Save