extendr_macros/
call.rs

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse::ParseStream, punctuated::Punctuated, Token};
use syn::{parse_macro_input, parse_quote, Expr, ExprAssign, ExprPath, LitStr};

#[derive(Debug)]
struct Call {
    caller: LitStr,
    pairs: Punctuated<Expr, Token![,]>,
}

// Custom parser for a call eg. call!("xyz", a=1, b, c)
impl syn::parse::Parse for Call {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut res = Self {
            caller: input.parse::<LitStr>()?,
            pairs: Punctuated::new(),
        };

        while !input.is_empty() {
            input.parse::<Token![,]>()?;
            res.pairs.push(input.parse::<Expr>()?);
        }
        Ok(res)
    }
}

pub fn call(item: TokenStream) -> TokenStream {
    // Get a [Call] object from the input token stream.
    // This consists of a literal string followed by named or unnamed arguments
    // as in the pairlist macro.
    let call = parse_macro_input!(item as Call);

    // Convert the pairs into tuples of ("name", Robj::from(value))
    let pairs = call
        .pairs
        .iter()
        .map(|e| {
            if let Expr::Assign(ExprAssign { left, right, .. }) = e {
                if let Expr::Path(ExprPath { path, .. }) = &**left {
                    if let Some(ident) = path.get_ident() {
                        let s = ident.to_string();
                        return parse_quote!( (#s, extendr_api::Robj::from(#right)) );
                    }
                }
            }
            parse_quote!( ("", extendr_api::Robj::from(#e)) )
        })
        .collect::<Vec<Expr>>();

    // Use eval_string to convert the literal string into a callable object.
    let caller = &call.caller;
    let caller = quote!(extendr_api::functions::eval_string(#caller));

    // Use the "call" method of Robj to call the function or primitive.
    // This will error if the object is not callable.
    let res = if pairs.is_empty() {
        quote!(
            (#caller).and_then(|caller| caller.call(extendr_api::wrapper::Pairlist::new()))
        )
    } else {
        quote!(
            (#caller).and_then(|caller| caller.call(extendr_api::wrapper::Pairlist::from_pairs(&[# ( #pairs ),*])))
        )
    };

    TokenStream::from(res)
}