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}