use crate::{
builtins::{
array::Array,
object::{Object, ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE},
property::Property,
value::{RcString, ResultValue, Value},
},
environment::function_environment_record::BindingStatus,
environment::lexical_environment::{new_function_environment, Environment},
exec::{Executable, Interpreter},
syntax::ast::node::{FormalParameter, StatementList},
BoaProfiler,
};
use gc::{unsafe_empty_trace, Finalize, Trace};
use std::fmt::{self, Debug};
pub type NativeFunctionData = fn(&Value, &[Value], &mut Interpreter) -> ResultValue;
#[derive(Debug, Copy, Clone)]
pub enum ConstructorKind {
Base,
Derived,
}
#[derive(Debug, Copy, Finalize, Clone, PartialEq, PartialOrd, Hash)]
pub enum ThisMode {
Lexical,
NonLexical,
}
unsafe impl Trace for ThisMode {
unsafe_empty_trace!();
}
#[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 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 Eq for FunctionBody {}
unsafe impl Trace for FunctionBody {
unsafe_empty_trace!();
}
#[derive(Trace, Finalize, Clone)]
pub struct Function {
pub body: FunctionBody,
pub params: Box<[FormalParameter]>,
pub this_mode: ThisMode,
pub environment: Option<Environment>,
constructable: bool,
callable: bool,
}
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,
constructable,
callable,
}
}
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,
)
}
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,
)
}
pub fn call(
&self,
function: Value,
this: &Value,
args_list: &[Value],
interpreter: &mut Interpreter,
) -> ResultValue {
let _timer = BoaProfiler::global().start_event("function::call", "function");
if self.callable {
match self.body {
FunctionBody::BuiltIn(func) => func(this, args_list, interpreter),
FunctionBody::Ordinary(ref body) => {
let local_env = new_function_environment(
function,
if let ThisMode::Lexical = self.this_mode {
None
} else {
Some(this.clone())
},
self.environment.as_ref().cloned(),
if let ThisMode::Lexical = self.this_mode {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
);
for (i, param) in self.params.iter().enumerate() {
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);
}
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);
let result = body.run(interpreter);
interpreter.realm.environment.pop();
result
}
}
} else {
panic!("TypeError: class constructors must be invoked with 'new'");
}
}
pub fn construct(
&self,
function: Value,
this: &Value,
args_list: &[Value],
interpreter: &mut Interpreter,
) -> ResultValue {
if self.constructable {
match self.body {
FunctionBody::BuiltIn(func) => {
func(this, args_list, interpreter)?;
Ok(this.clone())
}
FunctionBody::Ordinary(ref body) => {
let local_env = new_function_environment(
function,
Some(this.clone()),
self.environment.as_ref().cloned(),
if let ThisMode::Lexical = self.this_mode {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
);
for (i, param) in self.params.iter().enumerate() {
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);
}
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);
let _ = body.run(interpreter);
let binding = interpreter.realm.environment.get_this_binding();
Ok(binding)
}
}
} else {
let name = this.get_field("name").to_string();
panic!("TypeError: {} is not a constructor", name);
}
}
fn add_rest_param(
&self,
param: &FormalParameter,
index: usize,
args_list: &[Value],
interpreter: &mut Interpreter,
local_env: &Environment,
) {
let array = Array::new_array(interpreter).unwrap();
Array::add_to_array_object(&array, &args_list[index..]).unwrap();
local_env
.borrow_mut()
.create_mutable_binding(param.name().to_owned(), false);
local_env
.borrow_mut()
.initialize_binding(param.name(), array);
}
fn add_arguments_to_environment(
&self,
param: &FormalParameter,
value: Value,
local_env: &Environment,
) {
local_env
.borrow_mut()
.create_mutable_binding(param.name().to_owned(), false);
local_env
.borrow_mut()
.initialize_binding(param.name(), value);
}
pub fn is_callable(&self) -> bool {
self.callable
}
pub fn is_constructable(&self) -> bool {
self.constructable
}
}
impl Debug for Function {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{{")?;
write!(f, "[Not implemented]")?;
write!(f, "}}")
}
}
pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value {
let len = arguments_list.len();
let mut obj = Object::default();
obj.set_internal_slot("ParameterMap", Value::undefined());
let mut length = Property::default();
length = length.writable(true).value(Value::from(len));
obj.define_own_property("length".to_string(), length);
let mut index: usize = 0;
while index < len {
let val = arguments_list.get(index).expect("Could not get argument");
let mut prop = Property::default();
prop = prop
.value(val.clone())
.enumerable(true)
.writable(true)
.configurable(true);
obj.properties_mut()
.insert(RcString::from(index.to_string()), prop);
index += 1;
}
Value::from(obj)
}
pub fn make_function(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
this.set_data(ObjectData::Function(Function::builtin(
Vec::new(),
|_, _, _| Ok(Value::undefined()),
)));
Ok(this.clone())
}
pub fn create(global: &Value) -> Value {
let prototype = Value::new_object(Some(global));
make_constructor_fn("Function", 1, make_function, global, prototype, true)
}
pub fn make_constructor_fn(
name: &str,
length: usize,
body: NativeFunctionData,
global: &Value,
prototype: Value,
constructable: bool,
) -> Value {
let _timer =
BoaProfiler::global().start_event(&format!("make_constructor_fn: {}", name), "init");
let mut function = Function::builtin(Vec::new(), body);
function.constructable = constructable;
let mut constructor = Object::function(function);
constructor.set_internal_slot(
INSTANCE_PROTOTYPE,
global.get_field("Function").get_field(PROTOTYPE),
);
let length = Property::new()
.value(Value::from(length))
.writable(false)
.configurable(false)
.enumerable(false);
constructor.insert_property("length", length);
let name = Property::new()
.value(Value::from(name))
.writable(false)
.configurable(false)
.enumerable(false);
constructor.insert_property("name", name);
let constructor = Value::from(constructor);
prototype
.as_object_mut()
.unwrap()
.insert_field("constructor", constructor.clone());
constructor
.as_object_mut()
.expect("constructor object")
.insert_field(PROTOTYPE, prototype);
constructor
}
pub fn make_builtin_fn<N>(function: NativeFunctionData, name: N, parent: &Value, length: usize)
where
N: Into<String>,
{
let name = name.into();
let _timer = BoaProfiler::global().start_event(&format!("make_builtin_fn: {}", &name), "init");
let mut function = Object::function(Function::builtin(Vec::new(), function));
function.insert_field("length", Value::from(length));
parent
.as_object_mut()
.unwrap()
.insert_field(name, Value::from(function));
}
#[inline]
pub fn init(global: &Value) -> (&str, Value) {
let _timer = BoaProfiler::global().start_event("function", "init");
("Function", create(global))
}