qjs 0.1.2

Rust binding for the QuickJS Javascript Engine
Documentation
use failure::Error;
use foreign_types::ForeignTypeRef;

use crate::{ffi, value::FALSE, ContextRef, Local, NewAtom, NewValue, Value};

pub trait Args {
    type Values: AsRef<[ffi::JSValue]>;

    fn into_values(self, ctxt: &ContextRef) -> Self::Values;
}

impl<T> Args for T
where
    T: NewValue + Sized,
{
    type Values = [ffi::JSValue; 1];

    fn into_values(self, ctxt: &ContextRef) -> Self::Values {
        [self.new_value(ctxt)]
    }
}

impl<T> Args for &[T]
where
    T: NewValue + Clone,
{
    type Values = Vec<ffi::JSValue>;

    fn into_values(self, ctxt: &ContextRef) -> Self::Values {
        self.iter().map(|v| v.clone().new_value(ctxt)).collect()
    }
}

macro_rules! array_args {
    ($($N:expr)+) => {
        $(
            impl<T> Args for [T; $N]
            where
                T: NewValue,
            {
                type Values = Vec<ffi::JSValue>;

                fn into_values(self, ctxt: &ContextRef) -> Self::Values {
                    let len = self.len();
                    let mut data = std::mem::ManuallyDrop::new(self);

                    (0..len).map(|idx| unsafe {
                        std::ptr::read(data.get_unchecked_mut(idx)).new_value(ctxt)
                    }).collect()
                }
            }
        )*
    };
}

array_args! {
    0  1  2  3  4  5  6  7  8  9
    10 11 12 13 14 15 16 17 18 19
    20 21 22 23 24 25 26 27 28 29
    30 31 32
}

macro_rules! tuple_args {
    () => {
        impl Args for () {
            type Values = [ffi::JSValue; 0];

            fn into_values(self, _ctxt: &ContextRef) -> Self::Values {
                []
            }
        }
    };

    ($($name:ident)+) => {
        impl<$( $name ),*> Args for ($( $name, )*)
        where
            $( $name: NewValue, )*
        {
            type Values = [ffi::JSValue; count!($( $name )*)];

            #[allow(non_snake_case)]
            fn into_values(self, ctxt: &ContextRef) -> Self::Values {
                let ( $($name,)* ) = self;

                [ $( $name.new_value(ctxt), )* ]
            }
        }
    }
}

macro_rules! count {
    () => (0usize);
    ( $x:tt $($xs:tt)* ) => (1usize + count!($($xs)*));
}

tuple_args! {}
tuple_args! { A }
tuple_args! { A B }
tuple_args! { A B C }
tuple_args! { A B C D }
tuple_args! { A B C D E }
tuple_args! { A B C D E F }
tuple_args! { A B C D E F G }
tuple_args! { A B C D E F G H }
tuple_args! { A B C D E F G H I }
tuple_args! { A B C D E F G H I J }
tuple_args! { A B C D E F G H I J K }
tuple_args! { A B C D E F G H I J K L }
tuple_args! { A B C D E F G H I J K L M }
tuple_args! { A B C D E F G H I J K L M N }
tuple_args! { A B C D E F G H I J K L M N O }
tuple_args! { A B C D E F G H I J K L M N O P }
tuple_args! { A B C D E F G H I J K L M N O P Q }
tuple_args! { A B C D E F G H I J K L M N O P Q R }
tuple_args! { A B C D E F G H I J K L M N O P Q R S }
tuple_args! { A B C D E F G H I J K L M N O P Q R S T }

impl<'a> Local<'a, Value> {
    pub fn call<T: Args>(&self, this: Option<&Value>, args: T) -> Result<Local<Value>, Error> {
        self.ctxt.call(self, this, args)
    }

    pub fn invoke<N: NewAtom, T: Args>(&self, atom: N, args: T) -> Result<Local<Value>, Error> {
        self.ctxt.invoke(self, atom, args)
    }

    pub fn call_constructor<T: Args>(&self, args: T) -> Result<Local<Value>, Error> {
        self.ctxt.call_constructor(self, args)
    }

    pub fn call_constructor2<T: Args>(
        &self,
        new_target: Option<&Value>,
        args: T,
    ) -> Result<Local<Value>, Error> {
        self.ctxt.call_constructor2(self, new_target, args)
    }
}

impl ContextRef {
    pub fn is_function(&self, val: &Value) -> bool {
        unsafe { ffi::JS_IsFunction(self.as_ptr(), val.raw()) != FALSE }
    }

    pub fn is_constructor(&self, val: &Value) -> bool {
        unsafe { ffi::JS_IsConstructor(self.as_ptr(), val.raw()) != FALSE }
    }

    pub fn call<T: Args>(
        &self,
        func: &Value,
        this: Option<&Value>,
        args: T,
    ) -> Result<Local<Value>, Error> {
        let args = args.into_values(self);
        let args = args.as_ref();
        let ret = {
            unsafe {
                ffi::JS_Call(
                    self.as_ptr(),
                    func.raw(),
                    this.map_or_else(|| Value::undefined().raw(), |v| v.raw()),
                    args.len() as i32,
                    args.as_ptr() as *mut _,
                )
            }
        };

        for arg in args {
            self.free_value(*arg);
        }

        self.bind(ret).ok()
    }

    pub fn invoke<N: NewAtom, T: Args>(
        &self,
        this: &Value,
        atom: N,
        args: T,
    ) -> Result<Local<Value>, Error> {
        let atom = atom.new_atom(self);
        let args = args.into_values(self);
        let args = args.as_ref();

        let res = self.bind(unsafe {
            ffi::JS_Invoke(
                self.as_ptr(),
                this.raw(),
                atom,
                args.len() as i32,
                args.as_ptr() as *mut _,
            )
        });
        self.free_atom(atom);
        for arg in args {
            self.free_value(*arg);
        }

        res.ok()
    }

    pub fn call_constructor<T: Args>(&self, func: &Value, args: T) -> Result<Local<Value>, Error> {
        let args = args.into_values(self);
        let args = args.as_ref();
        let ret = unsafe {
            ffi::JS_CallConstructor(
                self.as_ptr(),
                func.raw(),
                args.len() as i32,
                args.as_ptr() as *mut _,
            )
        };

        for arg in args {
            self.free_value(*arg);
        }

        self.bind(ret).ok()
    }

    pub fn call_constructor2<T: Args>(
        &self,
        func: &Value,
        new_target: Option<&Value>,
        args: T,
    ) -> Result<Local<Value>, Error> {
        let args = args.into_values(self);
        let args = args.as_ref();
        let ret = unsafe {
            ffi::JS_CallConstructor2(
                self.as_ptr(),
                func.raw(),
                new_target.map_or_else(|| Value::undefined().raw(), |v| v.raw()),
                args.len() as i32,
                args.as_ptr() as *mut _,
            )
        };

        for arg in args {
            self.free_value(*arg);
        }

        self.bind(ret).ok()
    }
}

#[cfg(test)]
mod tests {
    use crate::{Context, Eval, Runtime};

    #[test]
    fn call() {
        let _ = pretty_env_logger::try_init();

        let rt = Runtime::new();
        let ctxt = Context::new(&rt);

        ctxt.eval::<_, ()>(
            r#"
function fib(n)
{
    if (n <= 0)
        return 0;
    else if (n == 1)
        return 1;
    else
        return fib(n - 1) + fib(n - 2);
}

function Product(name, price) {
    this.name = name;
    this.price = price;
}
        "#,
            Eval::GLOBAL,
        )
        .unwrap();

        let global = ctxt.global_object();

        let fib = global.get_property("fib").unwrap();

        assert!(fib.is_function());

        assert_eq!(fib.call(None, [10]).unwrap().as_int().unwrap(), 55);

        let product_ctor = global.get_property("Product").unwrap();

        assert!(product_ctor.is_function());
        assert!(product_ctor.is_constructor());

        let product = product_ctor.call_constructor(("foobar", 30)).unwrap();

        assert_eq!(product.get_property("name").unwrap().to_string(), "foobar");
        assert_eq!(product.get_property("price").unwrap().as_int().unwrap(), 30);
    }
}