use crate::{
IntoJSValue, JSClass, JSObjectOps, JSResult, JSValueImpl, Promise, PromiseResolver, RongJSError,
};
use std::cell::RefCell;
use std::future::Future;
mod parameter;
pub use parameter::{FromParams, JSParameterType, Optional, ParamsAccessor, Rest, This, ThisMut};
pub(crate) struct RustFunc<V: JSValueImpl> {
func: JSCallable<V>,
required_params: u32,
}
type FnMutClosure<V> = dyn FnMut(&mut ParamsAccessor<V>) -> JSResult<V>;
type FnOnceClosure<V> = dyn FnOnce(&mut ParamsAccessor<V>) -> JSResult<V>;
pub enum JSCallable<V: JSValueImpl> {
FnMut(RefCell<Box<FnMutClosure<V>>>),
FnOnce(RefCell<Option<Box<FnOnceClosure<V>>>>),
}
pub trait IntoJSCallable<V: JSValueImpl, P, K> {
fn into_js_callable(self) -> JSCallable<V>;
}
pub trait IntoOnceJSCallable<V: JSValueImpl, P, K> {
fn into_js_callable(self) -> JSCallable<V>;
}
pub struct KFnMut;
pub struct KFnOnce;
pub struct KAsyncFnMut;
pub struct KAsyncFnOnce;
impl<V: JSValueImpl> RustFunc<V> {
pub(crate) fn new<F, P, K>(f: F) -> Self
where
F: IntoJSCallable<V, P, K>,
P: FromParams<V>,
{
let required_params = P::param_requirements().required_count() as u32;
Self {
func: f.into_js_callable(),
required_params,
}
}
pub(crate) fn new_once<F, P, K>(f: F) -> Self
where
F: IntoOnceJSCallable<V, P, K>,
P: FromParams<V>,
{
let required_params = P::param_requirements().required_count() as u32;
Self {
func: f.into_js_callable(),
required_params,
}
}
pub(crate) fn call(&mut self, accessor: &mut ParamsAccessor<V>) -> JSResult<V> {
let num_args = accessor.args_len() as u32;
if num_args < self.required_params {
return Err(RongJSError::InvalidParameter(
self.required_params,
num_args,
));
}
match &self.func {
JSCallable::FnMut(f) => f.borrow_mut()(accessor),
JSCallable::FnOnce(f) => f.take().ok_or_else(RongJSError::OnceFnCalled)?(accessor),
}
}
pub(crate) fn parameter_required_count(&self) -> u32 {
self.required_params
}
}
pub struct Constructor<V: JSValueImpl>(pub(crate) RustFunc<V>);
impl<V: JSValueImpl> Constructor<V> {
pub fn new<F, P, K>(f: F) -> Self
where
F: IntoJSCallable<V, P, K>,
P: FromParams<V>,
{
Self(RustFunc::new(f))
}
}
impl<V> JSClass<V> for RustFunc<V>
where
V: JSValueImpl + crate::JSObjectOps + 'static,
{
const NAME: &'static str = "RustFunc";
fn data_constructor() -> Constructor<V> {
panic!("Never 'new RustFunc()' in JS");
}
fn class_setup(class: &crate::ClassSetup<V>) -> JSResult<()> {
let fn_proto = class
.context()
.global()
.get::<_, crate::JSObject<V>>("Function")?
.get::<_, crate::JSObject<V>>("prototype")?;
class.prototype_object().prototype(fn_proto);
Ok(())
}
}
macro_rules! impl_js_callable_func {
($($t:ident),* $(,)?) => {
impl<V, R, Fun $(,$t)*> IntoJSCallable<V, ($($t,)*), KFnMut> for Fun
where
Fun: FnMut($($t),*) -> R + 'static,
V: JSValueImpl,
($($t,)*): FromParams<V>,
R: IntoJSValue<V>
{
fn into_js_callable(self) -> JSCallable<V> {
let mut f = self;
let closure = move |accessor: &mut ParamsAccessor<V>| {
let params = <($($t,)*)>::from_params(accessor)?;
#[allow(non_snake_case)]
let ($($t,)*) = params;
let result = f($($t),*);
Ok(<R as IntoJSValue<V>>::into_js_value(result, accessor.context()).into_value())
};
JSCallable::FnMut(RefCell::new(Box::new(closure)))
}
}
impl<V, R, Fut, Fun $(,$t)*> IntoJSCallable<V, ($($t,)*), KAsyncFnMut> for Fun
where
Fun: FnMut($($t),*) -> Fut + 'static,
Fut: Future<Output=R> +'static,
V: JSValueImpl + JSObjectOps + 'static,
($($t,)*): FromParams<V> ,
R: IntoJSValue<V> + PromiseResolver<V> + 'static,
{
fn into_js_callable(self) -> JSCallable<V> {
let mut f = self;
let closure = move |accessor: &mut ParamsAccessor<V>| {
let params = <($($t,)*)>::from_params(accessor)?;
#[allow(non_snake_case)]
let ($($t,)*) = params;
let this = accessor.get_this();
let fut = f($($t),*);
let ctx = accessor.context();
let promise = Promise::from_future(ctx, Some(this), fut)?;
Ok(<Promise<V> as IntoJSValue<V>>::into_js_value(promise, ctx).into_value())
};
JSCallable::FnMut(RefCell::new(Box::new(closure)))
}
}
};
}
macro_rules! impl_js_oncecallable_func {
($($t:ident),* $(,)?) => {
impl<V, R, Fun $(,$t)*> IntoOnceJSCallable<V, ($($t,)*), KFnOnce> for Fun
where
Fun: FnOnce($($t),*) -> R + 'static,
V: JSValueImpl,
($($t,)*): FromParams<V>,
R: IntoJSValue<V>
{
fn into_js_callable(self) -> JSCallable<V> {
let f = self;
let closure = move |accessor: &mut ParamsAccessor<V>| {
let params = <($($t,)*)>::from_params(accessor)?;
#[allow(non_snake_case)]
let ($($t,)*) = params;
let result = f($($t),*);
Ok(<R as IntoJSValue<V>>::into_js_value(result, accessor.context()).into_value())
};
JSCallable::FnOnce(RefCell::new(Some(Box::new(closure))))
}
}
impl<V, R,Fut, Fun $(,$t)*> IntoOnceJSCallable<V, ($($t,)*), KAsyncFnOnce> for Fun
where
Fun: FnOnce($($t),*) -> Fut + 'static,
Fut: Future<Output=R> +'static,
V: JSValueImpl + JSObjectOps + 'static,
($($t,)*): FromParams<V>,
R: IntoJSValue<V> + PromiseResolver<V> + 'static,
{
fn into_js_callable(self) -> JSCallable<V> {
let f = self;
let closure = move |accessor: &mut ParamsAccessor<V>| {
let params = <($($t,)*)>::from_params(accessor)?;
#[allow(non_snake_case)]
let ($($t,)*) = params;
let this = accessor.get_this();
let fut = f($($t),*);
let ctx = accessor.context();
let promise = Promise::from_future(ctx, Some(this), fut)?;
Ok(<Promise<V> as IntoJSValue<V>>::into_js_value(promise, ctx).into_value())
};
JSCallable::FnOnce(RefCell::new(Some(Box::new(closure))))
}
}
};
}
impl_js_callable_func!();
impl_js_callable_func!(P1);
impl_js_callable_func!(P1, P2);
impl_js_callable_func!(P1, P2, P3);
impl_js_callable_func!(P1, P2, P3, P4);
impl_js_callable_func!(P1, P2, P3, P4, P5);
impl_js_callable_func!(P1, P2, P3, P4, P5, P6);
impl_js_callable_func!(P1, P2, P3, P4, P5, P6, P7);
impl_js_callable_func!(P1, P2, P3, P4, P5, P6, P7, P8);
impl_js_oncecallable_func!();
impl_js_oncecallable_func!(P1);
impl_js_oncecallable_func!(P1, P2);
impl_js_oncecallable_func!(P1, P2, P3);
impl_js_oncecallable_func!(P1, P2, P3, P4);
impl_js_oncecallable_func!(P1, P2, P3, P4, P5);
impl_js_oncecallable_func!(P1, P2, P3, P4, P5, P6);
impl_js_oncecallable_func!(P1, P2, P3, P4, P5, P6, P7);
impl_js_oncecallable_func!(P1, P2, P3, P4, P5, P6, P7, P8);