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 );
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 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 let mut meta = get_meta(&mut ret, &mut iter);
130 let (sig, params) = get_sig(&mut ret, &mut iter);
133 #[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 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 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}