rquickjs_core/value/function/
args.rs

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