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