use crate::object::PROTOTYPE;
use crate::{
builtins::{Array, BuiltIn},
environment::lexical_environment::Environment,
gc::{empty_trace, Finalize, Trace},
object::{ConstructorBuilder, FunctionBuilder, GcObject, Object, ObjectData},
property::{Attribute, DataDescriptor},
syntax::ast::node::{FormalParameter, RcStatementList},
BoaProfiler, Context, Result, Value,
};
use bitflags::bitflags;
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 {
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 {
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],
context: &mut Context,
local_env: &Environment,
) {
let array = Array::new_array(context).unwrap();
Array::add_to_array_object(&array, &args_list[index..], context).unwrap();
local_env
.borrow_mut()
.create_mutable_binding(param.name().to_owned(), false, true)
.expect("Failed to create binding for rest param");
local_env
.borrow_mut()
.initialize_binding(param.name(), array)
.expect("Failed to initialize rest param");
}
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, true)
.expect("Failed to create binding");
local_env
.borrow_mut()
.initialize_binding(param.name(), value)
.expect("Failed to intialize binding");
}
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 = GcObject::new(Object::default());
let length = DataDescriptor::new(
len,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
obj.ordinary_define_own_property("length", length.into());
let mut index: usize = 0;
while index < len {
let val = arguments_list.get(index).expect("Could not get argument");
let prop = DataDescriptor::new(
val.clone(),
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
);
obj.insert(index, prop);
index += 1;
}
Value::from(obj)
}
pub fn make_builtin_fn<N>(
function: NativeFunction,
name: N,
parent: &GcObject,
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
.standard_objects()
.function_object()
.prototype()
.into(),
);
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
function.insert_property("length", length, attribute);
function.insert_property("name", name.as_str(), attribute);
parent.clone().insert_property(
name,
function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
}
#[derive(Debug, Clone, Copy)]
pub struct BuiltInFunctionObject;
impl BuiltInFunctionObject {
pub const LENGTH: usize = 1;
fn constructor(new_target: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
let prototype = new_target
.as_object()
.and_then(|obj| {
obj.get(&PROTOTYPE.into(), obj.clone().into(), context)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or_else(|| context.standard_objects().object_object().prototype());
let this = Value::new_object(context);
this.as_object()
.expect("this should be an object")
.set_prototype_instance(prototype.into());
this.set_data(ObjectData::Function(Function::BuiltIn(
BuiltInFunction(|_, _, _| Ok(Value::undefined())),
FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE,
)));
Ok(this)
}
fn prototype(_: &Value, _: &[Value], _: &mut Context) -> Result<Value> {
Ok(Value::undefined())
}
fn call(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
if !this.is_function() {
return context.throw_type_error(format!("{} is not a function", this.display()));
}
let this_arg: Value = args.get(0).cloned().unwrap_or_default();
let start = if !args.is_empty() { 1 } else { 0 };
context.call(this, &this_arg, &args[start..])
}
fn apply(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
if !this.is_function() {
return context.throw_type_error(format!("{} is not a function", this.display()));
}
let this_arg = args.get(0).cloned().unwrap_or_default();
let arg_array = args.get(1).cloned().unwrap_or_default();
if arg_array.is_null_or_undefined() {
return context.call(this, &this_arg, &[]);
}
let arg_list = context
.extract_array_properties(&arg_array)?
.map_err(|()| arg_array)?;
context.call(this, &this_arg, &arg_list)
}
}
impl BuiltIn for BuiltInFunctionObject {
const NAME: &'static str = "Function";
fn attribute() -> Attribute {
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE
}
fn init(context: &mut Context) -> (&'static str, Value, Attribute) {
let _timer = BoaProfiler::global().start_event("function", "init");
let function_prototype = context.standard_objects().function_object().prototype();
FunctionBuilder::new(context, Self::prototype)
.name("")
.length(0)
.callable(true)
.constructable(false)
.build_function_prototype(&function_prototype);
let function_object = ConstructorBuilder::with_standard_object(
context,
Self::constructor,
context.standard_objects().function_object().clone(),
)
.name(Self::NAME)
.length(Self::LENGTH)
.method(Self::call, "call", 1)
.method(Self::apply, "apply", 1)
.build();
(Self::NAME, function_object.into(), Self::attribute())
}
}