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            let mut args = Vec::with_capacity(2usize);
66            args.push(extendr_api::metadata::Arg { name: "use_symbols", arg_type: "bool", default: None });
67            args.push(extendr_api::metadata::Arg { name: "package_name", arg_type: "&str", default: None });
68            let args = args;
69
70            // Add this function to the list, but set hidden: true.
71            functions.push(extendr_api::metadata::Func {
72                doc: "Wrapper generator.",
73                rust_name: #make_module_wrappers_name_string,
74                mod_name: #make_module_wrappers_name_string,
75                r_name: #make_module_wrappers_name_string,
76                args,
77                return_type: "String",
78                func_ptr: #wrap_make_module_wrappers as * const u8,
79                hidden: true,
80            });
81
82            extendr_api::metadata::Metadata {
83                name: #modname_string,
84                functions,
85                impls,
86            }
87        }
88
89        #[no_mangle]
90        #[allow(non_snake_case)]
91        pub extern "C" fn #wrap_module_metadata_name() -> extendr_api::SEXP {
92            use extendr_api::GetSexp;
93            unsafe { extendr_api::Robj::from(#module_metadata_name()).get() }
94        }
95
96        #[no_mangle]
97        #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
98        pub extern "C" fn #wrap_make_module_wrappers(
99            use_symbols_sexp: extendr_api::SEXP,
100            package_name_sexp: extendr_api::SEXP,
101        ) -> extendr_api::SEXP {
102            unsafe {
103                use extendr_api::robj::*;
104                use extendr_api::GetSexp;
105                let robj = Robj::from_sexp(use_symbols_sexp);
106                let use_symbols: bool = <bool>::try_from(&robj).unwrap();
107
108                let robj = Robj::from_sexp(package_name_sexp);
109                let package_name: &str = <&str>::try_from(&robj).unwrap();
110
111                extendr_api::Robj::from(
112                    #module_metadata_name()
113                        .make_r_wrappers(
114                            use_symbols,
115                            package_name,
116                        ).unwrap()
117                ).get()
118            }
119        }
120
121        #[no_mangle]
122        #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
123        pub extern "C" fn #module_init_name(info: * mut extendr_api::DllInfo) {
124            unsafe { extendr_api::register_call_methods(info, #module_metadata_name()) };
125        }
126    })
127}
128
129#[derive(Debug)]
130struct Module {
131    modname: Option<Ident>,
132    fnnames: Vec<Ident>,
133    implnames: Vec<Type>,
134    usenames: Vec<Ident>,
135}
136
137// Custom parser for the module.
138impl syn::parse::Parse for Module {
139    fn parse(input: ParseStream) -> syn::Result<Self> {
140        use syn::spanned::Spanned;
141        let mut res = Self {
142            modname: None,
143            fnnames: Vec::new(),
144            implnames: Vec::new(),
145            usenames: Vec::new(),
146        };
147        while !input.is_empty() {
148            if let Ok(kmod) = input.parse::<Token![mod]>() {
149                let name: Ident = input.parse()?;
150                if res.modname.is_some() {
151                    return Err(syn::Error::new(kmod.span(), "only one mod allowed"));
152                }
153                res.modname = Some(name);
154            } else if input.parse::<Token![fn]>().is_ok() {
155                res.fnnames.push(input.parse()?);
156            } else if input.parse::<Token![impl]>().is_ok() {
157                res.implnames.push(input.parse()?);
158            } else if input.parse::<Token![use]>().is_ok() {
159                res.usenames.push(input.parse()?);
160            } else {
161                return Err(syn::Error::new(input.span(), "expected mod, fn or impl"));
162            }
163
164            input.parse::<Token![;]>()?;
165        }
166        if res.modname.is_none() {
167            return Err(syn::Error::new(input.span(), "expected one 'mod name'"));
168        }
169        Ok(res)
170    }
171}