extendr_macros/
extendr_module.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
use crate::wrappers;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse::ParseStream, parse_macro_input, Ident, Token, Type};

pub fn extendr_module(item: TokenStream) -> TokenStream {
    let module = parse_macro_input!(item as Module);
    let Module {
        modname,
        fnnames,
        implnames,
        usenames,
    } = module;
    let modname = modname.expect("cannot include unnamed modules");
    let modname_string = modname.to_string();
    let module_init_name = format_ident!("R_init_{}_extendr", modname);

    let module_metadata_name = format_ident!("get_{}_metadata", modname);
    let module_metadata_name_string = module_metadata_name.to_string();
    let wrap_module_metadata_name =
        format_ident!("{}get_{}_metadata", wrappers::WRAP_PREFIX, modname);

    let make_module_wrappers_name = format_ident!("make_{}_wrappers", modname);
    let make_module_wrappers_name_string = make_module_wrappers_name.to_string();
    let wrap_make_module_wrappers =
        format_ident!("{}make_{}_wrappers", wrappers::WRAP_PREFIX, modname);

    let fnmetanames = fnnames
        .iter()
        .map(|id| format_ident!("{}{}", wrappers::META_PREFIX, id));
    let implmetanames = implnames
        .iter()
        .map(|id| format_ident!("{}{}", wrappers::META_PREFIX, wrappers::type_name(id)));
    let usemetanames = usenames
        .iter()
        .map(|id| format_ident!("get_{}_metadata", id))
        .collect::<Vec<Ident>>();

    TokenStream::from(quote! {
        #[no_mangle]
        #[allow(non_snake_case)]
        pub fn #module_metadata_name() -> extendr_api::metadata::Metadata {
            let mut functions = Vec::new();
            let mut impls = Vec::new();

            // Pushes metadata (eg. extendr_api::metadata::Func) to functions and impl vectors.
            #( #fnmetanames(&mut functions); )*
            #( #implmetanames(&mut impls); )*

            // Extends functions and impls with the submodules metadata
            #( functions.extend(#usenames::#usemetanames().functions); )*
            #( impls.extend(#usenames::#usemetanames().impls); )*

            // Add this function to the list, but set hidden: true.
            functions.push(extendr_api::metadata::Func {
                doc: "Metadata access function.",
                rust_name: #module_metadata_name_string,
                mod_name: #module_metadata_name_string,
                r_name: #module_metadata_name_string,
                args: Vec::new(),
                return_type: "Metadata",
                func_ptr: #wrap_module_metadata_name as * const u8,
                hidden: true,
            });

            // Add this function to the list, but set hidden: true.
            functions.push(extendr_api::metadata::Func {
                doc: "Wrapper generator.",
                rust_name: #make_module_wrappers_name_string,
                mod_name: #make_module_wrappers_name_string,
                r_name: #make_module_wrappers_name_string,
                args: vec![
                    extendr_api::metadata::Arg { name: "use_symbols", arg_type: "bool", default: None },
                    extendr_api::metadata::Arg { name: "package_name", arg_type: "&str", default: None },
                    ],
                return_type: "String",
                func_ptr: #wrap_make_module_wrappers as * const u8,
                hidden: true,
            });

            extendr_api::metadata::Metadata {
                name: #modname_string,
                functions,
                impls,
            }
        }

        #[no_mangle]
        #[allow(non_snake_case)]
        pub extern "C" fn #wrap_module_metadata_name() -> extendr_api::SEXP {
            use extendr_api::GetSexp;
            unsafe { extendr_api::Robj::from(#module_metadata_name()).get() }
        }

        #[no_mangle]
        #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
        pub extern "C" fn #wrap_make_module_wrappers(
            use_symbols_sexp: extendr_api::SEXP,
            package_name_sexp: extendr_api::SEXP,
        ) -> extendr_api::SEXP {
            unsafe {
                use extendr_api::robj::*;
                use extendr_api::GetSexp;
                let robj = Robj::from_sexp(use_symbols_sexp);
                let use_symbols: bool = <bool>::try_from(&robj).unwrap();

                let robj = Robj::from_sexp(package_name_sexp);
                let package_name: &str = <&str>::try_from(&robj).unwrap();

                extendr_api::Robj::from(
                    #module_metadata_name()
                        .make_r_wrappers(
                            use_symbols,
                            package_name,
                        ).unwrap()
                ).get()
            }
        }

        #[no_mangle]
        #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
        pub extern "C" fn #module_init_name(info: * mut extendr_api::DllInfo) {
            unsafe { extendr_api::register_call_methods(info, #module_metadata_name()) };
        }
    })
}

#[derive(Debug)]
struct Module {
    modname: Option<Ident>,
    fnnames: Vec<Ident>,
    implnames: Vec<Type>,
    usenames: Vec<Ident>,
}

// Custom parser for the module.
impl syn::parse::Parse for Module {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        use syn::spanned::Spanned;
        let mut res = Self {
            modname: None,
            fnnames: Vec::new(),
            implnames: Vec::new(),
            usenames: Vec::new(),
        };
        while !input.is_empty() {
            if let Ok(kmod) = input.parse::<Token![mod]>() {
                let name: Ident = input.parse()?;
                if res.modname.is_some() {
                    return Err(syn::Error::new(kmod.span(), "only one mod allowed"));
                }
                res.modname = Some(name);
            } else if input.parse::<Token![fn]>().is_ok() {
                res.fnnames.push(input.parse()?);
            } else if input.parse::<Token![impl]>().is_ok() {
                res.implnames.push(input.parse()?);
            } else if input.parse::<Token![use]>().is_ok() {
                res.usenames.push(input.parse()?);
            } else {
                return Err(syn::Error::new(input.span(), "expected mod, fn or impl"));
            }

            input.parse::<Token![;]>()?;
        }
        if res.modname.is_none() {
            return Err(syn::Error::new(input.span(), "expected one 'mod name'"));
        }
        Ok(res)
    }
}