extendr_macros/
call.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse::ParseStream, punctuated::Punctuated, Token};
4use syn::{parse_macro_input, parse_quote, Expr, ExprAssign, ExprPath, LitStr};
5
6#[derive(Debug)]
7struct Call {
8    caller: LitStr,
9    pairs: Punctuated<Expr, Token![,]>,
10}
11
12// Custom parser for a call eg. call!("xyz", a=1, b, c)
13impl syn::parse::Parse for Call {
14    fn parse(input: ParseStream) -> syn::Result<Self> {
15        let mut res = Self {
16            caller: input.parse::<LitStr>()?,
17            pairs: Punctuated::new(),
18        };
19
20        while !input.is_empty() {
21            input.parse::<Token![,]>()?;
22            res.pairs.push(input.parse::<Expr>()?);
23        }
24        Ok(res)
25    }
26}
27
28pub fn call(item: TokenStream) -> TokenStream {
29    // Get a [Call] object from the input token stream.
30    // This consists of a literal string followed by named or unnamed arguments
31    // as in the pairlist macro.
32    let call = parse_macro_input!(item as Call);
33
34    // Convert the pairs into tuples of ("name", Robj::from(value))
35    let pairs = call
36        .pairs
37        .iter()
38        .map(|e| {
39            if let Expr::Assign(ExprAssign { left, right, .. }) = e {
40                if let Expr::Path(ExprPath { path, .. }) = &**left {
41                    if let Some(ident) = path.get_ident() {
42                        let s = ident.to_string();
43                        return parse_quote!( (#s, extendr_api::Robj::from(#right)) );
44                    }
45                }
46            }
47            parse_quote!( ("", extendr_api::Robj::from(#e)) )
48        })
49        .collect::<Vec<Expr>>();
50
51    // Use eval_string to convert the literal string into a callable object.
52    let caller = &call.caller;
53    let caller = quote!(extendr_api::functions::eval_string(#caller));
54
55    // Use the "call" method of Robj to call the function or primitive.
56    // This will error if the object is not callable.
57    let res = if pairs.is_empty() {
58        quote!(
59            (#caller).and_then(|caller| caller.call(extendr_api::wrapper::Pairlist::new()))
60        )
61    } else {
62        quote!(
63            (#caller).and_then(|caller| caller.call(extendr_api::wrapper::Pairlist::from_pairs(&[# ( #pairs ),*])))
64        )
65    };
66
67    TokenStream::from(res)
68}