extendr_macros/
extendr_module.rs

1use crate::wrappers;
2use proc_macro::TokenStream;
3use quote::{format_ident, quote};
4use syn::{parse::ParseStream, parse_macro_input, Ident, Token, Type};
5
6pub fn extendr_module(item: TokenStream) -> TokenStream {
7    let module = parse_macro_input!(item as Module);
8    let Module {
9        modname,
10        fnnames,
11        implnames,
12        usenames,
13    } = module;
14    let modname = modname.expect("cannot include unnamed modules");
15    let modname_string = modname.to_string();
16    let module_init_name = format_ident!("R_init_{}_extendr", modname);
17
18    let module_metadata_name = format_ident!("get_{}_metadata", modname);
19    let module_metadata_name_string = module_metadata_name.to_string();
20    let wrap_module_metadata_name =
21        format_ident!("{}get_{}_metadata", wrappers::WRAP_PREFIX, modname);
22
23    let make_module_wrappers_name = format_ident!("make_{}_wrappers", modname);
24    let make_module_wrappers_name_string = make_module_wrappers_name.to_string();
25    let wrap_make_module_wrappers =
26        format_ident!("{}make_{}_wrappers", wrappers::WRAP_PREFIX, modname);
27
28    let fnmetanames = fnnames
29        .iter()
30        .map(|id| format_ident!("{}{}", wrappers::META_PREFIX, id));
31    let implmetanames = implnames
32        .iter()
33        .map(|id| format_ident!("{}{}", wrappers::META_PREFIX, wrappers::type_name(id)));
34    let usemetanames = usenames
35        .iter()
36        .map(|id| format_ident!("get_{}_metadata", id))
37        .collect::<Vec<Ident>>();
38
39    TokenStream::from(quote! {
40        #[no_mangle]
41        #[allow(non_snake_case)]
42        pub fn #module_metadata_name() -> extendr_api::metadata::Metadata {
43            let mut functions = Vec::new();
44            let mut impls = Vec::new();
45
46            // Pushes metadata (eg. extendr_api::metadata::Func) to functions and impl vectors.
47            #( #fnmetanames(&mut functions); )*
48            #( #implmetanames(&mut impls); )*
49
50            // Extends functions and impls with the submodules metadata
51            #( functions.extend(#usenames::#usemetanames().functions); )*
52            #( impls.extend(#usenames::#usemetanames().impls); )*
53
54            // Add this function to the list, but set hidden: true.
55            functions.push(extendr_api::metadata::Func {
56                doc: "Metadata access function.",
57                rust_name: #module_metadata_name_string,
58                mod_name: #module_metadata_name_string,
59                r_name: #module_metadata_name_string,
60                args: Vec::new(),
61                return_type: "Metadata",
62                func_ptr: #wrap_module_metadata_name as * const u8,
63                hidden: true,
64            });
65
66            // Add this function to the list, but set hidden: true.
67            functions.push(extendr_api::metadata::Func {
68                doc: "Wrapper generator.",
69                rust_name: #make_module_wrappers_name_string,
70                mod_name: #make_module_wrappers_name_string,
71                r_name: #make_module_wrappers_name_string,
72                args: vec![
73                    extendr_api::metadata::Arg { name: "use_symbols", arg_type: "bool", default: None },
74                    extendr_api::metadata::Arg { name: "package_name", arg_type: "&str", default: None },
75                    ],
76                return_type: "String",
77                func_ptr: #wrap_make_module_wrappers as * const u8,
78                hidden: true,
79            });
80
81            extendr_api::metadata::Metadata {
82                name: #modname_string,
83                functions,
84                impls,
85            }
86        }
87
88        #[no_mangle]
89        #[allow(non_snake_case)]
90        pub extern "C" fn #wrap_module_metadata_name() -> extendr_api::SEXP {
91            use extendr_api::GetSexp;
92            unsafe { extendr_api::Robj::from(#module_metadata_name()).get() }
93        }
94
95        #[no_mangle]
96        #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
97        pub extern "C" fn #wrap_make_module_wrappers(
98            use_symbols_sexp: extendr_api::SEXP,
99            package_name_sexp: extendr_api::SEXP,
100        ) -> extendr_api::SEXP {
101            unsafe {
102                use extendr_api::robj::*;
103                use extendr_api::GetSexp;
104                let robj = Robj::from_sexp(use_symbols_sexp);
105                let use_symbols: bool = <bool>::try_from(&robj).unwrap();
106
107                let robj = Robj::from_sexp(package_name_sexp);
108                let package_name: &str = <&str>::try_from(&robj).unwrap();
109
110                extendr_api::Robj::from(
111                    #module_metadata_name()
112                        .make_r_wrappers(
113                            use_symbols,
114                            package_name,
115                        ).unwrap()
116                ).get()
117            }
118        }
119
120        #[no_mangle]
121        #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
122        pub extern "C" fn #module_init_name(info: * mut extendr_api::DllInfo) {
123            unsafe { extendr_api::register_call_methods(info, #module_metadata_name()) };
124        }
125    })
126}
127
128#[derive(Debug)]
129struct Module {
130    modname: Option<Ident>,
131    fnnames: Vec<Ident>,
132    implnames: Vec<Type>,
133    usenames: Vec<Ident>,
134}
135
136// Custom parser for the module.
137impl syn::parse::Parse for Module {
138    fn parse(input: ParseStream) -> syn::Result<Self> {
139        use syn::spanned::Spanned;
140        let mut res = Self {
141            modname: None,
142            fnnames: Vec::new(),
143            implnames: Vec::new(),
144            usenames: Vec::new(),
145        };
146        while !input.is_empty() {
147            if let Ok(kmod) = input.parse::<Token![mod]>() {
148                let name: Ident = input.parse()?;
149                if res.modname.is_some() {
150                    return Err(syn::Error::new(kmod.span(), "only one mod allowed"));
151                }
152                res.modname = Some(name);
153            } else if input.parse::<Token![fn]>().is_ok() {
154                res.fnnames.push(input.parse()?);
155            } else if input.parse::<Token![impl]>().is_ok() {
156                res.implnames.push(input.parse()?);
157            } else if input.parse::<Token![use]>().is_ok() {
158                res.usenames.push(input.parse()?);
159            } else {
160                return Err(syn::Error::new(input.span(), "expected mod, fn or impl"));
161            }
162
163            input.parse::<Token![;]>()?;
164        }
165        if res.modname.is_none() {
166            return Err(syn::Error::new(input.span(), "expected one 'mod name'"));
167        }
168        Ok(res)
169    }
170}