Skip to main content

rquickjs_core/value/function/
args.rs

1use crate::{
2    function::{Flat, Opt, Rest, This},
3    qjs, Ctx, FromJs, Function, IntoJs, Result, Value,
4};
5use alloc::vec::Vec;
6
7use super::{ffi::defer_call_job, Constructor};
8
9const ARGS_ON_STACK: usize = 4;
10
11pub enum ArgsSlice {
12    Stack {
13        slice: [qjs::JSValue; ARGS_ON_STACK],
14        offset: u8,
15    },
16    Heap(Vec<qjs::JSValue>),
17}
18
19/// Argument input for a functions
20///
21/// Arguments on the Rust side for calling into the JavaScript context.
22pub struct Args<'js> {
23    ctx: Ctx<'js>,
24    pub(crate) this: qjs::JSValue,
25    pub(crate) args: ArgsSlice,
26}
27
28impl<'js> Args<'js> {
29    /// Returns a new args with space for a give number of arguments
30    pub fn new(ctx: Ctx<'js>, args: usize) -> Args<'js> {
31        Args {
32            ctx,
33            this: qjs::JS_UNDEFINED,
34            args: if args <= ARGS_ON_STACK {
35                ArgsSlice::Stack {
36                    slice: [qjs::JS_UNDEFINED; ARGS_ON_STACK],
37                    offset: 0,
38                }
39            } else {
40                ArgsSlice::Heap(Vec::with_capacity(args))
41            },
42        }
43    }
44
45    /// Returns a new args with space for any number of arguments
46    pub fn new_unsized(ctx: Ctx<'js>) -> Args<'js> {
47        Args {
48            ctx,
49            this: qjs::JS_UNDEFINED,
50            args: ArgsSlice::Heap(Vec::new()),
51        }
52    }
53
54    /// Returns the context associated with these arguments.
55    pub fn ctx(&self) -> &Ctx<'js> {
56        &self.ctx
57    }
58
59    /// Add an argument to the list.
60    pub fn push_arg<T: IntoJs<'js>>(&mut self, arg: T) -> Result<()> {
61        let v = arg.into_js(&self.ctx)?;
62
63        match self.args {
64            ArgsSlice::Stack {
65                ref mut slice,
66                ref mut offset,
67            } => {
68                if *offset >= 8 {
69                    panic!("pushed more arguments than num_args returned");
70                }
71                slice[*offset as usize] = v.into_js_value();
72                *offset += 1;
73            }
74            ArgsSlice::Heap(ref mut h) => h.push(v.into_js_value()),
75        }
76
77        Ok(())
78    }
79
80    /// Add multiple arguments to the list.
81    pub fn push_args<T, I>(&mut self, iter: I) -> Result<()>
82    where
83        T: IntoJs<'js>,
84        I: IntoIterator<Item = T>,
85    {
86        for a in iter.into_iter() {
87            self.push_arg(a)?
88        }
89        Ok(())
90    }
91
92    /// Add a this arguments.
93    pub fn this<T>(&mut self, this: T) -> Result<()>
94    where
95        T: IntoJs<'js>,
96    {
97        let v = this.into_js(&self.ctx)?;
98        let v = core::mem::replace(&mut self.this, v.into_js_value());
99        unsafe { qjs::JS_FreeValue(self.ctx.as_ptr(), v) };
100        Ok(())
101    }
102
103    /// Replace the this value with 'Undefined' and return the original value.
104    pub fn take_this(&mut self) -> Value<'js> {
105        let value = core::mem::replace(&mut self.this, qjs::JS_UNDEFINED);
106        Value {
107            ctx: self.ctx().clone(),
108            value,
109        }
110    }
111
112    /// The number of arguments currently in the list.
113    fn len(&self) -> usize {
114        match self.args {
115            ArgsSlice::Stack { offset, .. } => offset as usize,
116            ArgsSlice::Heap(ref h) => h.len(),
117        }
118    }
119
120    fn as_ptr(&self) -> *const qjs::JSValue {
121        match self.args {
122            ArgsSlice::Stack { ref slice, .. } => slice.as_ptr(),
123            ArgsSlice::Heap(ref h) => h.as_ptr(),
124        }
125    }
126
127    /// Call a function with the current set of arguments.
128    pub fn apply<R>(self, func: &Function<'js>) -> Result<R>
129    where
130        R: FromJs<'js>,
131    {
132        let val = unsafe {
133            #[cfg(feature = "parallel")]
134            qjs::JS_UpdateStackTop(qjs::JS_GetRuntime(self.ctx.as_ptr()));
135
136            let val = qjs::JS_Call(
137                self.ctx.as_ptr(),
138                func.as_js_value(),
139                self.this,
140                self.len() as _,
141                self.as_ptr() as _,
142            );
143            let val = self.ctx.handle_exception(val)?;
144            Value::from_js_value(self.ctx.clone(), val)
145        };
146        R::from_js(&self.ctx, val)
147    }
148
149    pub fn defer(mut self, func: Function<'js>) -> Result<()> {
150        let this = self.take_this();
151        self.push_arg(this)?;
152        self.push_arg(func)?;
153        let ctx = self.ctx();
154        unsafe {
155            if qjs::JS_EnqueueJob(
156                ctx.as_ptr(),
157                Some(defer_call_job),
158                self.len() as _,
159                self.as_ptr() as _,
160            ) < 0
161            {
162                return Err(ctx.raise_exception());
163            }
164        }
165        Ok(())
166    }
167
168    pub fn construct<R>(self, constructor: &Constructor<'js>) -> Result<R>
169    where
170        R: FromJs<'js>,
171    {
172        let value = if unsafe { qjs::JS_VALUE_GET_TAG(self.this) != qjs::JS_TAG_UNDEFINED } {
173            unsafe {
174                #[cfg(feature = "parallel")]
175                qjs::JS_UpdateStackTop(qjs::JS_GetRuntime(self.ctx.as_ptr()));
176
177                qjs::JS_CallConstructor2(
178                    self.ctx.as_ptr(),
179                    constructor.as_js_value(),
180                    self.this,
181                    self.len() as _,
182                    self.as_ptr() as _,
183                )
184            }
185        } else {
186            unsafe {
187                #[cfg(feature = "parallel")]
188                qjs::JS_UpdateStackTop(qjs::JS_GetRuntime(self.ctx.as_ptr()));
189
190                qjs::JS_CallConstructor(
191                    self.ctx.as_ptr(),
192                    constructor.as_js_value(),
193                    self.len() as _,
194                    self.as_ptr() as _,
195                )
196            }
197        };
198        let value = unsafe { self.ctx.handle_exception(value)? };
199        let v = unsafe { Value::from_js_value(self.ctx.clone(), value) };
200        R::from_js(&self.ctx, v)
201    }
202}
203
204impl Drop for Args<'_> {
205    fn drop(&mut self) {
206        match self.args {
207            ArgsSlice::Heap(ref h) => h.iter().for_each(|v| {
208                unsafe { qjs::JS_FreeValue(self.ctx.as_ptr(), *v) };
209            }),
210            ArgsSlice::Stack { ref slice, offset } => {
211                slice[..(offset as usize)].iter().for_each(|v| {
212                    unsafe { qjs::JS_FreeValue(self.ctx.as_ptr(), *v) };
213                })
214            }
215        }
216        unsafe { qjs::JS_FreeValue(self.ctx.as_ptr(), self.this) };
217    }
218}
219
220/// A trait for converting values into arguments.
221pub trait IntoArg<'js> {
222    /// The number of arguments this value produces.
223    fn num_args(&self) -> usize;
224
225    /// Convert the value into an argument.
226    fn into_arg(self, args: &mut Args<'js>) -> Result<()>;
227}
228
229/// A trait for converting a tuple of values into a list arguments.
230pub trait IntoArgs<'js> {
231    /// The number of arguments this value produces.
232    fn num_args(&self) -> usize;
233
234    /// Convert the value into an argument.
235    fn into_args(self, args: &mut Args<'js>) -> Result<()>;
236
237    fn apply<R>(self, function: &Function<'js>) -> Result<R>
238    where
239        R: FromJs<'js>,
240        Self: Sized,
241    {
242        let mut args = Args::new(function.ctx().clone(), self.num_args());
243        self.into_args(&mut args)?;
244        args.apply(function)
245    }
246
247    fn defer<R>(self, function: Function<'js>) -> Result<()>
248    where
249        Self: Sized,
250    {
251        let mut args = Args::new(function.ctx().clone(), self.num_args());
252        self.into_args(&mut args)?;
253        args.defer(function)
254    }
255
256    fn construct<R>(self, function: &Constructor<'js>) -> Result<()>
257    where
258        Self: Sized,
259    {
260        let mut args = Args::new(function.ctx().clone(), self.num_args());
261        self.into_args(&mut args)?;
262        args.construct(function)
263    }
264}
265
266impl<'js, T: IntoJs<'js>> IntoArg<'js> for T {
267    fn num_args(&self) -> usize {
268        1
269    }
270
271    fn into_arg(self, args: &mut Args<'js>) -> Result<()> {
272        args.push_arg(self)
273    }
274}
275
276impl<'js, T: IntoJs<'js>> IntoArg<'js> for This<T> {
277    fn num_args(&self) -> usize {
278        0
279    }
280
281    fn into_arg(self, args: &mut Args<'js>) -> Result<()> {
282        args.this(self.0)
283    }
284}
285
286impl<'js, T: IntoJs<'js>> IntoArg<'js> for Opt<T> {
287    fn num_args(&self) -> usize {
288        self.0.is_some() as usize
289    }
290
291    fn into_arg(self, args: &mut Args<'js>) -> Result<()> {
292        if let Some(x) = self.0 {
293            args.push_arg(x)?
294        }
295        Ok(())
296    }
297}
298
299impl<'js, T: IntoJs<'js>> IntoArg<'js> for Rest<T> {
300    fn num_args(&self) -> usize {
301        self.0.len()
302    }
303
304    fn into_arg(self, args: &mut Args<'js>) -> Result<()> {
305        args.push_args(self.0)
306    }
307}
308
309impl<'js, T: IntoArgs<'js>> IntoArg<'js> for Flat<T> {
310    fn num_args(&self) -> usize {
311        self.0.num_args()
312    }
313
314    fn into_arg(self, args: &mut Args<'js>) -> Result<()> {
315        self.0.into_args(args)
316    }
317}
318
319macro_rules! impl_into_args {
320    ($($t:ident),*) => {
321        #[allow(non_snake_case)]
322        impl<'js $(,$t)*> IntoArgs<'js> for ($($t,)*)
323        where
324            $($t : IntoArg<'js>,)*
325        {
326            fn num_args(&self) -> usize{
327                let ($(ref $t,)*) = *self;
328                0 $(+ $t.num_args())*
329            }
330
331            fn into_args(self, _args: &mut Args<'js>) -> Result<()>{
332                let ($($t,)*) = self;
333                $($t.into_arg(_args)?;)*
334                Ok(())
335            }
336        }
337    };
338}
339
340impl_into_args!();
341impl_into_args!(A);
342impl_into_args!(A, B);
343impl_into_args!(A, B, C);
344impl_into_args!(A, B, C, D);
345impl_into_args!(A, B, C, D, E);
346impl_into_args!(A, B, C, D, E, F);
347impl_into_args!(A, B, C, D, E, F, G);