use crate::{
function::{Exhaustive, Flat, FuncArg, Opt, Rest, This},
qjs, Ctx, FromJs, Result, Value,
};
use std::slice;
pub struct Params<'a, 'js> {
ctx: Ctx<'js>,
function: qjs::JSValue,
this: qjs::JSValue,
args: &'a [qjs::JSValue],
is_constructor: bool,
}
impl<'a, 'js> Params<'a, 'js> {
pub(crate) unsafe fn from_ffi_class(
ctx: *mut qjs::JSContext,
function: qjs::JSValue,
this: qjs::JSValue,
argc: qjs::c_int,
argv: *mut qjs::JSValue,
_flags: qjs::c_int,
) -> Self {
let args = if argv.is_null() {
assert_eq!(
argc, 0,
"got a null pointer from quickjs for a non-zero number of args"
);
[].as_slice()
} else {
let argc = usize::try_from(argc).expect("invalid argument number");
slice::from_raw_parts(argv, argc)
};
Self {
ctx: Ctx::from_ptr(ctx),
function,
this,
args,
is_constructor: false,
}
}
pub fn check_params(&self, num: ParamRequirement) -> Result<()> {
if self.args.len() < num.min {
return Err(crate::Error::MissingArgs {
expected: num.min,
given: self.args.len(),
});
}
if num.exhaustive && self.args.len() > num.max {
return Err(crate::Error::TooManyArgs {
expected: num.max,
given: self.args.len(),
});
}
Ok(())
}
pub fn ctx(&self) -> &Ctx<'js> {
&self.ctx
}
pub fn function(&self) -> Value<'js> {
unsafe { Value::from_js_value_const(self.ctx.clone(), self.function) }
}
pub fn this(&self) -> Value<'js> {
unsafe { Value::from_js_value_const(self.ctx.clone(), self.this) }
}
pub fn arg(&self, index: usize) -> Option<Value<'js>> {
self.args
.get(index)
.map(|arg| unsafe { Value::from_js_value_const(self.ctx.clone(), *arg) })
}
pub fn len(&self) -> usize {
self.args.len()
}
pub fn is_empty(&self) -> bool {
self.args.is_empty()
}
pub fn is_constructor(&self) -> bool {
self.is_constructor
}
pub fn access(self) -> ParamsAccessor<'a, 'js> {
ParamsAccessor {
params: self,
offset: 0,
}
}
}
pub struct ParamsAccessor<'a, 'js> {
params: Params<'a, 'js>,
offset: usize,
}
impl<'a, 'js> ParamsAccessor<'a, 'js> {
pub fn ctx(&self) -> &Ctx<'js> {
self.params.ctx()
}
pub fn this(&self) -> Value<'js> {
self.params.this()
}
pub fn function(&self) -> Value<'js> {
self.params.function()
}
pub fn arg(&mut self) -> Value<'js> {
assert!(
self.offset < self.params.args.len(),
"arg called too many times"
);
let res = self.params.args[self.offset];
self.offset += 1;
unsafe { Value::from_js_value_const(self.params.ctx.clone(), res) }
}
pub fn len(&self) -> usize {
self.params.args.len() - self.offset
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub struct ParamRequirement {
min: usize,
max: usize,
exhaustive: bool,
}
impl ParamRequirement {
pub const fn single() -> Self {
ParamRequirement {
min: 1,
max: 1,
exhaustive: false,
}
}
pub const fn exhaustive() -> Self {
ParamRequirement {
min: 0,
max: 0,
exhaustive: true,
}
}
pub const fn optional() -> Self {
ParamRequirement {
min: 0,
max: 1,
exhaustive: false,
}
}
pub const fn any() -> Self {
ParamRequirement {
min: 0,
max: usize::MAX,
exhaustive: false,
}
}
pub const fn none() -> Self {
ParamRequirement {
min: 0,
max: 0,
exhaustive: false,
}
}
pub const fn combine(self, other: Self) -> ParamRequirement {
Self {
min: self.min.saturating_add(other.min),
max: self.max.saturating_add(other.max),
exhaustive: self.exhaustive || other.exhaustive,
}
}
pub fn min(&self) -> usize {
self.min
}
pub fn max(&self) -> usize {
self.max
}
pub fn is_exhaustive(&self) -> bool {
self.exhaustive
}
}
pub trait FromParam<'js>: Sized {
fn param_requirement() -> ParamRequirement;
fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self>;
}
impl<'js, T: FromJs<'js>> FromParam<'js> for T {
fn param_requirement() -> ParamRequirement {
ParamRequirement::single()
}
fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
let ctx = params.ctx().clone();
T::from_js(&ctx, params.arg())
}
}
impl<'js> FromParam<'js> for Ctx<'js> {
fn param_requirement() -> ParamRequirement {
ParamRequirement::none()
}
fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
Ok(params.ctx().clone())
}
}
impl<'js, T: FromJs<'js>> FromParam<'js> for Opt<T> {
fn param_requirement() -> ParamRequirement {
ParamRequirement::optional()
}
fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
if !params.is_empty() {
let ctx = params.ctx().clone();
Ok(Opt(Some(T::from_js(&ctx, params.arg())?)))
} else {
Ok(Opt(None))
}
}
}
impl<'js, T: FromJs<'js>> FromParam<'js> for This<T> {
fn param_requirement() -> ParamRequirement {
ParamRequirement::any()
}
fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
T::from_js(params.ctx(), params.this()).map(This)
}
}
impl<'js, T: FromJs<'js>> FromParam<'js> for FuncArg<T> {
fn param_requirement() -> ParamRequirement {
ParamRequirement::any()
}
fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
T::from_js(params.ctx(), params.function()).map(FuncArg)
}
}
impl<'js, T: FromJs<'js>> FromParam<'js> for Rest<T> {
fn param_requirement() -> ParamRequirement {
ParamRequirement::any()
}
fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
let mut res = Vec::with_capacity(params.len());
for _ in 0..params.len() {
let p = params.arg();
res.push(T::from_js(params.ctx(), p)?);
}
Ok(Rest(res))
}
}
impl<'js, T: FromParams<'js>> FromParam<'js> for Flat<T> {
fn param_requirement() -> ParamRequirement {
T::param_requirements()
}
fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
T::from_params(params).map(Flat)
}
}
impl<'js> FromParam<'js> for Exhaustive {
fn param_requirement() -> ParamRequirement {
ParamRequirement::exhaustive()
}
fn from_param<'a>(_params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
Ok(Exhaustive)
}
}
pub trait FromParams<'js>: Sized {
fn param_requirements() -> ParamRequirement;
fn from_params<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self>;
}
macro_rules! impl_from_params{
($($t:ident),*) => {
#[allow(non_snake_case)]
impl<'js $(,$t)*> FromParams<'js> for ($($t,)*)
where
$($t : FromParam<'js>,)*
{
fn param_requirements() -> ParamRequirement{
ParamRequirement::none()
$(.combine($t::param_requirement()))*
}
fn from_params<'a>(_args: &mut ParamsAccessor<'a,'js>) -> Result<Self>{
Ok((
$($t::from_param(_args)?,)*
))
}
}
};
}
impl_from_params!();
impl_from_params!(A);
impl_from_params!(A, B);
impl_from_params!(A, B, C);
impl_from_params!(A, B, C, D);
impl_from_params!(A, B, C, D, E);
impl_from_params!(A, B, C, D, E, F);
impl_from_params!(A, B, C, D, E, F, G);