mirror of https://github.com/boa-dev/boa.git
Browse Source
- Separate builtin function from rust JavaScript function - Removed BuiltInFunction structpull/1612/head
Halid Odat
3 years ago
committed by
GitHub
23 changed files with 1446 additions and 713 deletions
@ -0,0 +1,376 @@
|
||||
//! This module implements the global `Function` object as well as creates Native Functions.
|
||||
//!
|
||||
//! Objects wrap `Function`s and expose them via call/construct slots.
|
||||
//!
|
||||
//! `The `Function` object is used for matching text with a pattern.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-function-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
|
||||
|
||||
use crate::{ |
||||
environment::lexical_environment::Environment, |
||||
gc::{Finalize, Trace}, |
||||
object::{JsObject, Object}, |
||||
property::PropertyDescriptor, |
||||
syntax::ast::node::{FormalParameter, RcStatementList}, |
||||
BoaProfiler, Context, JsResult, JsValue, |
||||
}; |
||||
|
||||
use dyn_clone::DynClone; |
||||
use std::{ |
||||
fmt, |
||||
ops::{Deref, DerefMut}, |
||||
}; |
||||
|
||||
use super::NativeObject; |
||||
|
||||
/// Type representing a native built-in function a.k.a. function pointer.
|
||||
///
|
||||
/// Native functions need to have this signature in order to
|
||||
/// be callable from Javascript.
|
||||
pub type NativeFunctionSignature = fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue>; |
||||
|
||||
// Allows restricting closures to only `Copy` ones.
|
||||
// Used the sealed pattern to disallow external implementations
|
||||
// of `DynCopy`.
|
||||
mod sealed { |
||||
pub trait Sealed {} |
||||
impl<T: Copy> Sealed for T {} |
||||
} |
||||
pub trait DynCopy: sealed::Sealed {} |
||||
impl<T: Copy> DynCopy for T {} |
||||
|
||||
/// Trait representing a native built-in closure.
|
||||
///
|
||||
/// Closures need to have this signature in order to
|
||||
/// be callable from Javascript, but most of the time the compiler
|
||||
/// is smart enough to correctly infer the types.
|
||||
pub trait ClosureFunctionSignature: |
||||
Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult<JsValue> + DynCopy + DynClone + 'static |
||||
{ |
||||
} |
||||
|
||||
// The `Copy` bound automatically infers `DynCopy` and `DynClone`
|
||||
impl<T> ClosureFunctionSignature for T where |
||||
T: Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult<JsValue> + Copy + 'static |
||||
{ |
||||
} |
||||
|
||||
// Allows cloning Box<dyn ClosureFunctionSignature>
|
||||
dyn_clone::clone_trait_object!(ClosureFunctionSignature); |
||||
|
||||
#[derive(Debug, Trace, Finalize, PartialEq, Clone)] |
||||
pub enum ThisMode { |
||||
Lexical, |
||||
Strict, |
||||
Global, |
||||
} |
||||
|
||||
impl ThisMode { |
||||
/// Returns `true` if the this mode is `Lexical`.
|
||||
pub fn is_lexical(&self) -> bool { |
||||
matches!(self, Self::Lexical) |
||||
} |
||||
|
||||
/// Returns `true` if the this mode is `Strict`.
|
||||
pub fn is_strict(&self) -> bool { |
||||
matches!(self, Self::Strict) |
||||
} |
||||
|
||||
/// Returns `true` if the this mode is `Global`.
|
||||
pub fn is_global(&self) -> bool { |
||||
matches!(self, Self::Global) |
||||
} |
||||
} |
||||
|
||||
#[derive(Debug, Trace, Finalize, PartialEq, Clone)] |
||||
pub enum ConstructorKind { |
||||
Base, |
||||
Derived, |
||||
} |
||||
|
||||
impl ConstructorKind { |
||||
/// Returns `true` if the constructor kind is `Base`.
|
||||
pub fn is_base(&self) -> bool { |
||||
matches!(self, Self::Base) |
||||
} |
||||
|
||||
/// Returns `true` if the constructor kind is `Derived`.
|
||||
pub fn is_derived(&self) -> bool { |
||||
matches!(self, Self::Derived) |
||||
} |
||||
} |
||||
// We don't use a standalone `NativeObject` for `Captures` because it doesn't
|
||||
// guarantee that the internal type implements `Clone`.
|
||||
// This private trait guarantees that the internal type passed to `Captures`
|
||||
// implements `Clone`, and `DynClone` allows us to implement `Clone` for
|
||||
// `Box<dyn CapturesObject>`.
|
||||
trait CapturesObject: NativeObject + DynClone {} |
||||
impl<T: NativeObject + Clone> CapturesObject for T {} |
||||
dyn_clone::clone_trait_object!(CapturesObject); |
||||
|
||||
/// Wrapper for `Box<dyn NativeObject + Clone>` that allows passing additional
|
||||
/// captures through a `Copy` closure.
|
||||
///
|
||||
/// Any type implementing `Trace + Any + Debug + Clone`
|
||||
/// can be used as a capture context, so you can pass e.g. a String,
|
||||
/// a tuple or even a full struct.
|
||||
///
|
||||
/// You can downcast to any type and handle the fail case as you like
|
||||
/// with `downcast_ref` and `downcast_mut`, or you can use `try_downcast_ref`
|
||||
/// and `try_downcast_mut` to automatically throw a `TypeError` if the downcast
|
||||
/// fails.
|
||||
#[derive(Debug, Clone, Trace, Finalize)] |
||||
pub struct Captures(Box<dyn CapturesObject>); |
||||
|
||||
impl Captures { |
||||
/// Creates a new capture context.
|
||||
pub(crate) fn new<T>(captures: T) -> Self |
||||
where |
||||
T: NativeObject + Clone, |
||||
{ |
||||
Self(Box::new(captures)) |
||||
} |
||||
|
||||
/// Downcasts `Captures` to the specified type, returning a reference to the
|
||||
/// downcasted type if successful or `None` otherwise.
|
||||
pub fn downcast_ref<T>(&self) -> Option<&T> |
||||
where |
||||
T: NativeObject + Clone, |
||||
{ |
||||
self.0.deref().as_any().downcast_ref::<T>() |
||||
} |
||||
|
||||
/// Mutably downcasts `Captures` to the specified type, returning a
|
||||
/// mutable reference to the downcasted type if successful or `None` otherwise.
|
||||
pub fn downcast_mut<T>(&mut self) -> Option<&mut T> |
||||
where |
||||
T: NativeObject + Clone, |
||||
{ |
||||
self.0.deref_mut().as_mut_any().downcast_mut::<T>() |
||||
} |
||||
|
||||
/// Downcasts `Captures` to the specified type, returning a reference to the
|
||||
/// downcasted type if successful or a `TypeError` otherwise.
|
||||
pub fn try_downcast_ref<T>(&self, context: &mut Context) -> JsResult<&T> |
||||
where |
||||
T: NativeObject + Clone, |
||||
{ |
||||
self.0 |
||||
.deref() |
||||
.as_any() |
||||
.downcast_ref::<T>() |
||||
.ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type")) |
||||
} |
||||
|
||||
/// Downcasts `Captures` to the specified type, returning a reference to the
|
||||
/// downcasted type if successful or a `TypeError` otherwise.
|
||||
pub fn try_downcast_mut<T>(&mut self, context: &mut Context) -> JsResult<&mut T> |
||||
where |
||||
T: NativeObject + Clone, |
||||
{ |
||||
self.0 |
||||
.deref_mut() |
||||
.as_mut_any() |
||||
.downcast_mut::<T>() |
||||
.ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type")) |
||||
} |
||||
} |
||||
|
||||
/// 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(Clone, Trace, Finalize)] |
||||
pub enum Function { |
||||
Native { |
||||
#[unsafe_ignore_trace] |
||||
function: NativeFunctionSignature, |
||||
constructable: bool, |
||||
}, |
||||
Closure { |
||||
#[unsafe_ignore_trace] |
||||
function: Box<dyn ClosureFunctionSignature>, |
||||
constructable: bool, |
||||
captures: Captures, |
||||
}, |
||||
Ordinary { |
||||
constructable: bool, |
||||
this_mode: ThisMode, |
||||
body: RcStatementList, |
||||
params: Box<[FormalParameter]>, |
||||
environment: Environment, |
||||
}, |
||||
#[cfg(feature = "vm")] |
||||
VmOrdinary { |
||||
code: gc::Gc<crate::vm::CodeBlock>, |
||||
environment: Environment, |
||||
}, |
||||
} |
||||
|
||||
impl fmt::Debug for Function { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
write!(f, "Function {{ ... }}") |
||||
} |
||||
} |
||||
|
||||
impl Function { |
||||
// Adds the final rest parameters to the Environment as an array
|
||||
#[cfg(not(feature = "vm"))] |
||||
pub(crate) fn add_rest_param( |
||||
param: &FormalParameter, |
||||
index: usize, |
||||
args_list: &[JsValue], |
||||
context: &mut Context, |
||||
local_env: &Environment, |
||||
) { |
||||
use crate::builtins::Array; |
||||
// Create array of values
|
||||
let array = Array::new_array(context); |
||||
Array::add_to_array_object(&array, args_list.get(index..).unwrap_or_default(), context) |
||||
.unwrap(); |
||||
|
||||
// Create binding
|
||||
local_env |
||||
// Function parameters can share names in JavaScript...
|
||||
.create_mutable_binding(param.name(), false, true, context) |
||||
.expect("Failed to create binding for rest param"); |
||||
|
||||
// Set Binding to value
|
||||
local_env |
||||
.initialize_binding(param.name(), array, context) |
||||
.expect("Failed to initialize rest param"); |
||||
} |
||||
|
||||
// Adds an argument to the environment
|
||||
pub(crate) fn add_arguments_to_environment( |
||||
param: &FormalParameter, |
||||
value: JsValue, |
||||
local_env: &Environment, |
||||
context: &mut Context, |
||||
) { |
||||
// Create binding
|
||||
local_env |
||||
.create_mutable_binding(param.name(), false, true, context) |
||||
.expect("Failed to create binding"); |
||||
|
||||
// Set Binding to value
|
||||
local_env |
||||
.initialize_binding(param.name(), value, context) |
||||
.expect("Failed to intialize binding"); |
||||
} |
||||
|
||||
/// Returns true if the function object is constructable.
|
||||
pub fn is_constructable(&self) -> bool { |
||||
match self { |
||||
Self::Native { constructable, .. } => *constructable, |
||||
Self::Closure { constructable, .. } => *constructable, |
||||
Self::Ordinary { constructable, .. } => *constructable, |
||||
#[cfg(feature = "vm")] |
||||
Self::VmOrdinary { code, .. } => code.constructable, |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Arguments.
|
||||
///
|
||||
/// <https://tc39.es/ecma262/#sec-createunmappedargumentsobject>
|
||||
pub fn create_unmapped_arguments_object( |
||||
arguments_list: &[JsValue], |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
let len = arguments_list.len(); |
||||
let obj = JsObject::new(Object::default()); |
||||
// Set length
|
||||
let length = PropertyDescriptor::builder() |
||||
.value(len) |
||||
.writable(true) |
||||
.enumerable(false) |
||||
.configurable(true) |
||||
.build(); |
||||
// Define length as a property
|
||||
crate::object::internal_methods::ordinary_define_own_property( |
||||
&obj, |
||||
"length".into(), |
||||
length, |
||||
context, |
||||
)?; |
||||
let mut index: usize = 0; |
||||
while index < len { |
||||
let val = arguments_list.get(index).expect("Could not get argument"); |
||||
let prop = PropertyDescriptor::builder() |
||||
.value(val.clone()) |
||||
.writable(true) |
||||
.enumerable(true) |
||||
.configurable(true); |
||||
|
||||
obj.insert(index, prop); |
||||
index += 1; |
||||
} |
||||
|
||||
Ok(JsValue::new(obj)) |
||||
} |
||||
|
||||
/// Creates a new member function of a `Object` or `prototype`.
|
||||
///
|
||||
/// A function registered using this macro can then be called from Javascript using:
|
||||
///
|
||||
/// parent.name()
|
||||
///
|
||||
/// See the javascript 'Number.toString()' as an example.
|
||||
///
|
||||
/// # Arguments
|
||||
/// function: The function to register as a built in function.
|
||||
/// name: The name of the function (how it will be called but without the ()).
|
||||
/// parent: The object to register the function on, if the global object is used then the function is instead called as name()
|
||||
/// without requiring the parent, see parseInt() as an example.
|
||||
/// length: As described at <https://tc39.es/ecma262/#sec-function-instances-length>, The value of the "length" property is an integer that
|
||||
/// indicates the typical number of arguments expected by the function. However, the language permits the function to be invoked with
|
||||
/// some other number of arguments.
|
||||
///
|
||||
/// If no length is provided, the length will be set to 0.
|
||||
// TODO: deprecate/remove this.
|
||||
pub(crate) fn make_builtin_fn<N>( |
||||
function: NativeFunctionSignature, |
||||
name: N, |
||||
parent: &JsObject, |
||||
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::Native { |
||||
function, |
||||
constructable: false, |
||||
}, |
||||
interpreter |
||||
.standard_objects() |
||||
.function_object() |
||||
.prototype() |
||||
.into(), |
||||
); |
||||
let attribute = PropertyDescriptor::builder() |
||||
.writable(false) |
||||
.enumerable(false) |
||||
.configurable(true); |
||||
function.insert_property("length", attribute.clone().value(length)); |
||||
function.insert_property("name", attribute.value(name.as_str())); |
||||
|
||||
parent.clone().insert_property( |
||||
name, |
||||
PropertyDescriptor::builder() |
||||
.value(function) |
||||
.writable(true) |
||||
.enumerable(false) |
||||
.configurable(true), |
||||
); |
||||
} |
Loading…
Reference in new issue