use crate::{
builtins::Array,
environment::lexical_environment::Environment,
object::{Object, ObjectData, PROTOTYPE},
property::{Attribute, Property},
syntax::ast::node::{FormalParameter, RcStatementList},
BoaProfiler, Context, Result, Value,
};
use bitflags::bitflags;
use gc::{unsafe_empty_trace, Finalize, Trace};
use std::fmt::{self, Debug};
#[cfg(test)]
mod tests;
pub type NativeFunction = fn(&Value, &[Value], &mut Context) -> Result<Value>;
#[derive(Clone, Copy, Finalize)]
pub struct BuiltInFunction(pub(crate) NativeFunction);
unsafe impl Trace for BuiltInFunction {
unsafe_empty_trace!();
}
impl From<NativeFunction> for BuiltInFunction {
fn from(function: NativeFunction) -> Self {
Self(function)
}
}
impl Debug for BuiltInFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("[native]")
}
}
bitflags! {
#[derive(Finalize, Default)]
pub struct FunctionFlags: u8 {
const CALLABLE = 0b0000_0001;
const CONSTRUCTABLE = 0b0000_0010;
const LEXICAL_THIS_MODE = 0b0000_0100;
}
}
impl FunctionFlags {
pub(crate) fn from_parameters(callable: bool, constructable: bool) -> Self {
let mut flags = Self::default();
if callable {
flags |= Self::CALLABLE;
}
if constructable {
flags |= Self::CONSTRUCTABLE;
}
flags
}
#[inline]
pub(crate) fn is_callable(&self) -> bool {
self.contains(Self::CALLABLE)
}
#[inline]
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 {
unsafe_empty_trace!();
}
#[derive(Debug, Clone, Finalize, Trace)]
pub enum Function {
BuiltIn(BuiltInFunction, FunctionFlags),
Ordinary {
flags: FunctionFlags,
body: RcStatementList,
params: Box<[FormalParameter]>,
environment: Environment,
},
}
impl Function {
pub(crate) fn add_rest_param(
&self,
param: &FormalParameter,
index: usize,
args_list: &[Value],
interpreter: &mut Context,
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);
}
pub(crate) 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 {
match self {
Self::BuiltIn(_, flags) => flags.is_callable(),
Self::Ordinary { flags, .. } => flags.is_callable(),
}
}
pub fn is_constructable(&self) -> bool {
match self {
Self::BuiltIn(_, flags) => flags.is_constructable(),
Self::Ordinary { flags, .. } => flags.is_constructable(),
}
}
}
pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value {
let len = arguments_list.len();
let mut obj = Object::default();
let length = Property::data_descriptor(
len.into(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
);
obj.define_own_property("length", length);
let mut index: usize = 0;
while index < len {
let val = arguments_list.get(index).expect("Could not get argument");
let prop = Property::data_descriptor(
val.clone(),
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
);
obj.insert_property(index, prop);
index += 1;
}
Value::from(obj)
}
pub fn make_function(this: &Value, _: &[Value], _: &mut Context) -> Result<Value> {
this.set_data(ObjectData::Function(Function::BuiltIn(
BuiltInFunction(|_, _, _| Ok(Value::undefined())),
FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE,
)));
Ok(this.clone())
}
pub fn make_constructor_fn(
name: &str,
length: usize,
body: NativeFunction,
global: &Value,
prototype: Value,
constructable: bool,
callable: bool,
) -> Value {
let _timer =
BoaProfiler::global().start_event(&format!("make_constructor_fn: {}", name), "init");
let function = Function::BuiltIn(
body.into(),
FunctionFlags::from_parameters(callable, constructable),
);
let mut constructor =
Object::function(function, global.get_field("Function").get_field(PROTOTYPE));
let length = Property::data_descriptor(
length.into(),
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
);
constructor.insert_property("length", length);
let name = Property::data_descriptor(
name.into(),
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
);
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: NativeFunction,
name: N,
parent: &Value,
length: usize,
interpreter: &Context,
) 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(function.into(), FunctionFlags::CALLABLE),
interpreter
.global_object()
.get_field("Function")
.get_field("prototype"),
);
function.insert_field("length", Value::from(length));
parent
.as_object_mut()
.unwrap()
.insert_field(name, Value::from(function));
}
#[inline]
pub fn init(interpreter: &mut Context) -> (&'static str, Value) {
let global = interpreter.global_object();
let _timer = BoaProfiler::global().start_event("function", "init");
let prototype = Value::new_object(Some(global));
let function_object =
make_constructor_fn("Function", 1, make_function, global, prototype, true, true);
("Function", function_object)
}