extendr_macros/
R.rs

1use proc_macro2::TokenStream;
2use quote::quote;
3use syn::{parse_quote, punctuated::Punctuated, Expr, Token};
4
5#[allow(non_snake_case)]
6pub fn R(item: TokenStream, expand_params: bool) -> TokenStream {
7    // Check if the input is a string.
8    let lit = match syn::parse2::<syn::LitStr>(item.clone()) {
9        Ok(lit) => lit,
10        Err(_) => {
11            // If not a string, expand the tokens to make a string.
12            let src = format!("{}", item);
13            return quote!(extendr_api::functions::eval_string(#src));
14        }
15    };
16
17    let mut src = lit.value();
18
19    let mut expressions: Punctuated<Expr, Token!(,)> = Punctuated::new();
20    if expand_params {
21        // Replace rust expressions in {{..}} with _expr0, _expr1, ...
22        while let Some(start) = src.find("{{") {
23            if let Some(end) = src[start + 2..].find("}}") {
24                if let Ok(param) = syn::parse_str::<Expr>(&src[start + 2..start + 2 + end]) {
25                    src = format!(
26                        "{} param.{} {}",
27                        &src[0..start],
28                        expressions.len(),
29                        &src[start + 2 + end + 2..]
30                    );
31                    expressions.push(parse_quote!(&extendr_api::Robj::from(#param)));
32                } else {
33                    return quote!(compile_error!("Not a valid rust expression."));
34                }
35            } else {
36                return quote!(compile_error!("Unterminated {{ block."));
37            }
38        }
39    }
40
41    if expressions.is_empty() {
42        quote!(extendr_api::functions::eval_string(#src))
43    } else {
44        quote!(
45            {
46                let params = &[#expressions];
47                extendr_api::functions::eval_string_with_params(#src, params)
48            }
49        )
50    }
51}
52
53#[cfg(test)]
54mod test {
55    use super::*;
56
57    #[test]
58    fn test_r_macro() {
59        // Note: strip spaces to cover differences between compilers.
60
61        // Naked R!
62        assert_eq!(
63            format!("{}", R(quote!(data.frame), true)),
64            format!(
65                "{}",
66                quote!(extendr_api::functions::eval_string("data . frame"))
67            )
68        );
69
70        // Quoted R!
71        assert_eq!(
72            format!("{}", R(quote!("data.frame"), true)),
73            format!(
74                "{}",
75                quote!(extendr_api::functions::eval_string("data.frame"))
76            )
77        );
78
79        // Param R!
80        assert_eq!(
81            format!("{}", R(quote!("a <- {{1}}"), true)),
82            format!(
83                "{}",
84                quote!({
85                    let params = &[&extendr_api::Robj::from(1)];
86                    extendr_api::functions::eval_string_with_params("a <-  param.0 ", params)
87                })
88            )
89        );
90
91        // Unquoted R!
92        assert_eq!(
93            format!("{}", R(quote!(r#""hello""#), true)),
94            format!(
95                "{}",
96                quote!(extendr_api::functions::eval_string("\"hello\""))
97            )
98        );
99
100        // Rraw!
101        assert_eq!(
102            format!("{}", R(quote!("a <- {{1}}"), false)),
103            format!(
104                "{}",
105                quote!(extendr_api::functions::eval_string("a <- {{1}}"))
106            )
107        );
108    }
109}