regulus/macros.rs
1/// Declares a group of builtin functions and exports them.
2///
3/// # Example
4/// ```rust
5/// use regulus::prelude::*;
6/// functions! {
7/// /// Length of a string.
8/// "strlen"(1) => |state, args| {
9/// let len = args[0].eval(state)?.string()?.len();
10/// Ok(Atom::Int(len as i64))
11/// }
12/// /// Concatenate strings.
13/// "strconcat"(_) => |state, args| {
14/// let mut string = String::new();
15/// for arg in args {
16/// string.push_str(&arg.eval(state)?.string()?);
17/// }
18/// Ok(Atom::String(string))
19/// }
20/// /// Logical AND.
21/// "&&"(2) => |state, args| Ok(Atom::Bool(
22/// args[0].eval(state)?.bool()? &&
23/// args[1].eval(state)?.bool()?
24/// ))
25/// }
26/// ```
27///
28/// Here, the name before the parens is the function ident,
29/// the parens contain the argc (`_` if any number of args is allowed)
30/// and the right side is the closure body of the builtin function.
31///
32/// The macro invocation generates a `pub` function called `functions` that returns
33/// `Vec<(&'static str, Function)>`.
34#[macro_export]
35macro_rules! functions {
36 // note:
37 // in the past, `$name` was a `tt` and did not require to be quoted, but:
38 // this has problems when a name is multiple tokens wide (`&&`, `==` etc.).
39 // this is because `$name: tt` matches only one token and `$($name: tt)* would cause
40 // ambiguity errors when matching `(`
41 // also, `$name: tt` caused issues when trying to match `$(#[$doc: meta])`
42 ($(
43 $(#[doc = $doc: literal])* $name: literal ($argc: tt) => $callback: expr)
44 *) => {
45 pub fn functions() -> std::vec::Vec<(&'static str, $crate::prelude::Function)> {
46 $(
47 $crate::check_nonempty_doc! {
48 $(#[doc = $doc])* $name
49 }
50 )*
51 vec![
52 $((
53 $name,
54 $crate::prelude::Function::new(
55 [$($doc),*].map(|s| s.trim_start()).join("\n"),
56 $crate::make_argc!($argc),
57 $callback,
58 ),
59 )),*
60 ]
61 }
62 };
63}
64
65#[macro_export]
66#[doc(hidden)]
67macro_rules! check_nonempty_doc {
68 ($name: literal) => {
69 compile_error!(concat!(
70 "builtin function ",
71 stringify!($name),
72 " has no documentation"
73 ));
74 };
75 ($(#[doc = $doc: literal])* $name: literal) => {};
76}
77
78#[macro_export]
79#[doc(hidden)]
80macro_rules! make_argc {
81 (_) => {
82 None
83 };
84 ($num: literal) => {
85 Some($num)
86 };
87}