rmin_macros/
lib.rs

1use core::{ops::DerefMut, str::FromStr};
2use proc_macro::{TokenTree::*, *};
3use std::sync::Mutex;
4static FUNCS: Mutex<Vec<Meta>> = Mutex::new(Vec::new());
5#[cfg(feature = "write-r-func-to-out-dir")]
6const R_SCRIPT_NAME: &str = "aaa.rmin.Rust.Functions.R";
7
8#[derive(Default)]
9struct Flag(
10    // TODO.
11);
12#[derive(Clone)]
13struct Meta {
14    params: Vec<[String; 2]>,
15    #[cfg(feature = "write-r-func-to-out-dir")]
16    public: bool
17}
18impl Meta {
19    fn fname(&self) -> &str {
20        &self.params[0][0]
21    }
22    #[cfg(feature = "write-r-func-to-out-dir")]
23    fn doc(&self) -> &str {
24        &self.params[0][1]
25    }
26    fn name(&self) -> &str {
27        self.fname().strip_prefix("r#").unwrap_or(self.fname())
28    }
29    fn safe_name(&self) -> String {
30        #[cfg(feature = "camel-ass")]
31        format! {"_rUST_{}_wRAPPER_" , self.name()}
32        #[cfg(not(feature = "camel-ass"))]
33        format!("_rust_{}_wrapper_", self.name())
34    }
35    fn unsafe_name(&self) -> String {
36        #[cfg(feature = "camel-ass")]
37        format! {"_rUST_{}_wRAPPER_uNSAFE_" , self.name()}
38        #[cfg(not(feature = "camel-ass"))]
39        format!("_rust_{}_wrapper_unsafe_", self.name())
40    }
41    fn param(&self) -> String {
42        self.params
43            .iter()
44            .skip(1)
45            .map(|x| x[0].clone())
46            .collect::<Vec<_>>()
47            .join(", ")
48    }
49    fn param_check(&self, delim: &str) -> String {
50        self.params
51            .iter()
52            .skip(1)
53            .filter(|x|!x[1].starts_with("Option"))
54            .map(|x| format!("{}.missing()", x[0]))
55            .collect::<Vec<_>>()
56            .join(delim)
57    }
58    fn param_check_report(&self) -> String {
59        self.params
60            .iter()
61            .skip(1)
62            .filter(|x|!x[1].starts_with("Option"))
63            .map(|x| format!("  missing {}: {{}}", x[0]))
64            .collect::<Vec<_>>()
65            .join("\n")
66    }
67    fn len(&self) -> usize {
68        self.params.len().wrapping_sub(1)
69    }
70}
71
72impl Flag {
73    fn new() -> Self {
74        Default::default()
75    }
76    fn flag(&mut self, flag: String) {
77        todo!("{flag} is not a legal switch")
78    }
79}
80fn add<T: Into<TokenTree> + Clone>(a: &mut TokenStream, b: &T) {
81    a.extend(<TokenTree as Into<TokenStream>>::into((b.clone()).into()).into_iter())
82}
83
84mod get_name;
85use get_name::get_meta;
86mod get_sig;
87use get_sig::get_sig;
88
89#[proc_macro_attribute]
90pub fn export(attr: TokenStream, item: TokenStream) -> TokenStream {
91    let attr = attr.into_iter().collect::<Vec<_>>();
92    let mut flags = Flag::new();
93    let mut keep = Vec::new();
94    if attr.len() > 0 {
95        attr.split(|x| {
96            if let Punct(y) = x {
97                y.as_char() == ','
98            } else {
99                false
100            }
101        })
102        .for_each(|x| {
103            if x.len() == 0 {
104                println!("warning: ignore empty attribute.")
105            } else if x.len() == 1 {
106                flags.flag(x[0].to_string())
107            } else {
108                let key = x[0].to_string();
109                match key.as_str() {
110                    "keep" => keep.extend(x[2..].iter().map(|x| x.to_string())),
111                    _ => println!("unknown key {}", x[0]),
112                }
113            }
114        });
115
116        // TODO: parse macro attrs
117        // if parsed.len() > 0 {
118        //     println!("warning: unknown option keys: [{}]", parsed.into_keys().collect::<Vec<_>>().as_slice().join(", "))
119        // }
120        // keep.sort_unstable();
121        println!("attr is {keep:?}");
122    }
123
124    let mut ret =
125        TokenStream::from_str("#[inline(always)]").expect("FATAL, quote system does not function.");
126    let mut iter = item.into_iter();
127
128    // before: @ ..... fn name (params) -> out {...}
129    let mut meta = get_meta(&mut ret, &mut iter);
130    // after : ..... fn name @ (params) -> out {...} // finding the first fn and read name
131    // before:      ... name @ (params) -> out {...}
132    let (sig, params) = get_sig(&mut ret, &mut iter);
133    // after :      ... name (params) -> out @ {...} // finding the first {...}
134
135    #[cfg(feature = "verbose-output")]
136    println!(
137        "got fn_name = {fname}, sig = `{sig}`, params = {params:?}",
138        fname = meta.fname()
139    );
140
141    meta.params.extend(params);
142
143    if let Some(wtf) = iter.next() {
144        add(&mut ret, &wtf);
145        ret.extend(iter);
146        println!("warning, function have the form `fn(..)... {{...}} (unexpected more things)`, keep {wtf:?} as-is.");
147    }
148
149    let n = {
150        let mut vec = FUNCS.lock()
151            .expect("fatal error: internal errors while writting static variable FUNCS, compile again might help, file an issue might also help.");
152        let len = vec.deref_mut().len();
153        vec.deref_mut().push(meta.clone());
154        len
155    };
156
157    let fname = meta.fname();
158    let safe = meta.safe_name();
159    let usafe = meta.unsafe_name();
160    let param = meta.param();
161    let check = meta.param_check(" || ");
162    let check_vals = meta.param_check(", ");
163    let report = meta.param_check_report();
164    let safe_variant = if cfg!(not(feature = "force-symbol")) && check.len() > 0 {
165        // with routine registered, missing parameter is not allowed. thus there is no need to check parameters here again.
166        // currently safe and unsafe do the same things, but things could be changed in the future if some extra check should be done.
167        format!(
168            r#"
169            if {check} {{
170                rmin::handle_panic(||panic!("Parameter missing detected\n{report}", {check_vals}))
171            }} else {{
172                {usafe}_{n}({param})
173            }}"#
174        )
175    } else {
176        format!(
177            r#"
178            {usafe}_{n}({param})"#
179        )
180    };
181
182    let expanded = format!(
183        r#"
184    mod {fname} {{
185        use super::*;
186        #[no_mangle]
187        extern fn {safe}_{n} {sig} {{{safe_variant}
188        }}
189        #[no_mangle]
190        extern fn {usafe}_{n} {sig} {{
191            rmin::handle_panic(||{fname}({param}))
192        }}
193    }}"#
194    );
195    #[cfg(feature = "verbose-output")]
196    println!("#[export] writting additional mod: {expanded}");
197    let res = TokenStream::from_str(&expanded).unwrap_or_else(|err|panic!("macro auto expand to {expanded}, there should be an unexpected error: {err:?}. File an issue please."));
198    ret.extend(res);
199    ret
200}
201#[proc_macro]
202pub fn done(input: TokenStream) -> TokenStream {
203    let crate_name = if let Some(Ident(x)) = input.into_iter().next() {
204        x.to_string()
205    } else if let Ok(s) = std::env::var("CARGO_PKG_NAME") {
206        s.to_string()
207    } else {
208        println!("Warning: CARGO_PKG_NAME is not set. should provide a crate name.");
209        return Default::default();
210    };
211
212    finalize(crate_name)
213}
214fn finalize(crate_name: String) -> TokenStream {
215    let data = core::mem::take(FUNCS.lock().expect("fatal error: internal errors while reading static variable FUNCS, compile again might help, file an issue might also help.").deref_mut());
216    if data.len() == 0 {
217        println!("warning: no fn could be done, abort processing.");
218        return Default::default();
219    }
220    // data.sort_unstable_by(|a,b|a.name.cmp(&b.name));
221    let iter = data
222        .iter()
223        .enumerate()
224        .map(|(n, x)| (n as isize, x.safe_name(), x.len()))
225        .chain(
226            data.iter()
227                .enumerate()
228                .map(|(n, x)| (!(n as isize), x.unsafe_name(), x.len())),
229        );
230    let dlls=iter.clone().map(|(n,name, cntr)|format!(r#"        R_CallMethodDef {{name:c".{prefix}{cname}".as_ptr(), fun:{name}_{n} as *const _, numArgs: {cntr}}},
231"#,cname = if n<0{!n} else {n}, prefix = if n <0 {"u"} else {"c"}, n=if n<0 {!n} else {n})).collect::<String>();
232    let fns = iter
233        .clone()
234        .map(|(n, name, cntr)| {
235            format!(
236                r#"        fn {name}_{n}({parameters})->Owned<()>;
237"#,
238                parameters = (0..cntr)
239                    .map(|x| format!("arg{x}: Sexp<()>"))
240                    .collect::<Vec<_>>()
241                    .as_slice()
242                    .join(", "),
243                n = if n < 0 { !n } else { n }
244            )
245        })
246        .collect::<String>();
247    let s = format!(
248        r#"mod {mod_name} {{{camel}
249    use ::rmin::{{Sexp, Owned, reg::*}};
250    use ::core::ptr::null;
251    extern "C" {{
252{funcs}    }}
253    const R_CALL_METHOD:&[R_CallMethodDef]=&[
254{saves}        R_CallMethodDef {{name: null(), fun: null(), numArgs: 0}}
255    ];
256    // in case `lib{name}.so` is used.
257    #[no_mangle]
258    extern fn R_init_lib{name}(info:*mut DllInfo){{
259        R_init_{name}(info)
260    }}
261    #[no_mangle]
262    extern fn R_init_{name}(info:*mut DllInfo){{
263        unsafe {{
264            R_registerRoutines(
265                info,
266                null(),
267                R_CALL_METHOD.as_ptr(),
268                null(),
269                null()
270            );
271            R_useDynamicSymbols(info, 0);
272            R_forceSymbols(info, {forceSymbols}); // change this to 1 will make most of the functions unsearchable, which is sad for people who want to compile in Rust and load in R directly.
273        }}
274    }}
275}}"#,
276        name = crate_name,
277        saves = dlls,
278        funcs = fns,
279        forceSymbols = if cfg!(feature = "force-symbol") {1} else {0},
280        mod_name = "_please_do_not_use_rmin_export_interface_as_your_mod_name_",
281        camel = if cfg!(feature = "camel-ass") {
282            "\n"
283        } else {
284            ""
285        }
286    );
287
288    #[cfg(feature = "verbose-output")]
289    println!("finalizer generates:\n{s}");
290
291    #[cfg(feature = "write-r-func-to-out-dir")]
292    if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") {
293        #[cfg(feature = "verbose-output")]
294        println!("Writting R wrappers to {dir}");
295        use std::fs;
296        use std::path::Path;
297        let path = Path::new(&dir).join("R");
298        if path.is_dir() {
299            fs::write(
300                path.join(R_SCRIPT_NAME),
301                format!(
302                    r#"# nolint start
303#' @name {crate_name}
304#' @docType package
305#' @usage NULL
306#' @useDynLib {crate_name}, .registration = TRUE
307"_PACKAGE"
308
309{all_fns}
310
311# nolint end
312"#,
313                    all_fns = data
314                        .iter()
315                        .enumerate()
316                        .map(|(n, meta)| format!(
317                            r#"{docs}{export}
318{name} <- function({param}).Call(.c{n}{sep}{param})"#,
319                            docs = meta.doc(),
320                            name = if meta.name().starts_with("_") {format!("`{}`", meta.name())} else {meta.name().to_string()},
321                            param = meta.param(),
322                            sep = if meta.len() == 0 { "" } else { ", " },
323                            export = if meta.public { "\n#' @export" } else { "" }
324                        ))
325                        .collect::<Vec<_>>()
326                        .join("\n\n")
327                ),
328            )
329            .unwrap_or_else(|_| {
330                println!("warning: failed to write R wrapper file `{R_SCRIPT_NAME}`")
331            });
332        } else {
333            println!("Warning: environment variable $(R_SCRIPT_DIR) is set but the path `{dir}/R` is not a dir!")
334        }
335    } else {
336        println!(
337            "warning: $(CARGO_MANIFEST_DIR) does not have a value, thus abort writting R wrappers."
338        )
339    }
340    TokenStream::from_str(&s).expect("fatal error: internal errors with macro `done`, please disable the `done` macro, and file an issue about that.")
341}
342#[cfg(feature = "write-r-func-to-out-dir")]
343#[allow(non_snake_case)]
344fn print_env() {
345    let CARGO_MANIFEST_DIR = std::env::var("CARGO_MANIFEST_DIR");
346    let CARGO_PKG_VERSION = std::env::var("CARGO_PKG_VERSION");
347    let CARGO_PKG_VERSION_MAJOR = std::env::var("CARGO_PKG_VERSION_MAJOR");
348    let CARGO_PKG_VERSION_MINOR = std::env::var("CARGO_PKG_VERSION_MINOR");
349    let CARGO_PKG_VERSION_PATCH = std::env::var("CARGO_PKG_VERSION_PATCH");
350    let CARGO_PKG_VERSION_PRE = std::env::var("CARGO_PKG_VERSION_PRE");
351    let CARGO_PKG_AUTHORS = std::env::var("CARGO_PKG_AUTHORS");
352    let CARGO_PKG_NAME = std::env::var("CARGO_PKG_NAME");
353    let CARGO_PKG_DESCRIPTION = std::env::var("CARGO_PKG_DESCRIPTION");
354    let CARGO_PKG_HOMEPAGE = std::env::var("CARGO_PKG_HOMEPAGE");
355    let CARGO_PKG_REPOSITORY = std::env::var("CARGO_PKG_REPOSITORY");
356    let OUT_DIR = std::env::var("OUT_DIR");
357
358    println!(
359        r#"vars:
360    CARGO_MANIFEST_DIR = {CARGO_MANIFEST_DIR:?}
361    CARGO_PKG_VERSION = {CARGO_PKG_VERSION:?}
362    CARGO_PKG_VERSION_MAJOR = {CARGO_PKG_VERSION_MAJOR:?}
363    CARGO_PKG_VERSION_MINOR = {CARGO_PKG_VERSION_MINOR:?}
364    CARGO_PKG_VERSION_PATCH = {CARGO_PKG_VERSION_PATCH:?}
365    CARGO_PKG_VERSION_PRE = {CARGO_PKG_VERSION_PRE:?}
366    CARGO_PKG_AUTHORS = {CARGO_PKG_AUTHORS:?}
367    CARGO_PKG_NAME = {CARGO_PKG_NAME:?}
368    CARGO_PKG_DESCRIPTION = {CARGO_PKG_DESCRIPTION:?}
369    CARGO_PKG_HOMEPAGE = {CARGO_PKG_HOMEPAGE:?}
370    CARGO_PKG_REPOSITORY = {CARGO_PKG_REPOSITORY:?}
371    OUT_DIR = {OUT_DIR:?}"#
372    )
373}