Skip to main content

wolfram_expr/
macros.rs

1/// Build a Wolfram Language [`Expr`][crate::Expr] with WL-like syntax.
2///
3/// # Syntax
4///
5/// | Pattern | Result |
6/// |---------|--------|
7/// | `expr!(System::Head[a, b])` | `Normal` with the symbol `` System`Head `` |
8/// | `expr!(A::B::C[a, b])` | `Normal` with `` A`B`C `` (each `::` → a context backtick) |
9/// | `expr!(var[a, b])` | `Normal` with the variable `var` as head (call over `var`) |
10/// | `expr!(A::B::C)` | the symbol `` A`B`C `` |
11/// | `expr!(::Name)` / `expr!(::Name[…])` | the **context-less** symbol `Name` (works nested in arg, splice, and association-value positions too) |
12/// | `expr!(::$Name)` / `expr!(::$Name[…])` | the context-less `$`-symbol `$Name` (e.g. `` $Context ``, `` $InputFileName ``) |
13/// | `expr!(k -> v)` | `Rule[k, v]` — usable inline inside `…[...]` |
14/// | `expr!({k -> v, ...})` | `Association` |
15/// | `expr!(true)` / `expr!(false)` | `True` / `False` symbols |
16/// | `expr!("str")`, `expr!(42)`, `expr!(3.14)` | string / integer / real |
17/// | `expr!(rust_var)`, `expr!((rust_expr))` | `Expr::from(…)` |
18/// | `expr!(f[..iter])` | splice a sequence of `Into<Expr>` items as args |
19///
20/// # Conventions
21///
22/// - **Symbols are always fully qualified**: a bare ident is *always* a Rust
23///   variable (in head *and* arg position) — there is no implicit `System``
24///   prefix. Write `System::Times[…]`, `Global::x`, etc. for symbols.
25/// - **Arg position**: string literals become WL strings; `k -> v` becomes
26///   `Rule[k, v]` inline; `{k -> v}` an Association; `(rust_expr)` an arbitrary
27///   Rust expression; `..iter` splices a sequence.
28/// - **Nesting**: `Head[a, b]` in arg position recurses to all depths.
29///
30/// # Examples
31///
32/// ```
33/// # use wolfram_expr::{Expr, Symbol, expr};
34/// let msg = "something went wrong";
35/// let e = expr!(System::Failure["RustPanic", {"MessageTemplate" -> msg}]);
36/// let list = expr!(System::List[1, 2, 3]);
37/// let table = expr!(Tabular::Arrow::ToTabular[list]);
38/// let head = Symbol::new("Global`f");
39/// let call = expr!(head[1, 2]);   // a variable head — a call over `head`
40/// ```
41#[macro_export]
42macro_rules! expr {
43    // Booleans (must be before $e:expr to avoid ambiguity)
44    (true)  => { $crate::Expr::from($crate::Symbol::new("System`True")) };
45    (false) => { $crate::Expr::from($crate::Symbol::new("System`False")) };
46
47    // Rule: k -> v
48    ($k:tt -> $v:tt) => {
49        $crate::Expr::normal(
50            $crate::Symbol::new("System`Rule"),
51            vec![$crate::expr!($k), $crate::expr!($v)],
52        )
53    };
54
55    // Context-less call: ::Name[args] applies the bare-name symbol `Name` (no
56    // context — the leading `::` means "no context prefix").
57    (:: $name:ident [ $($args:tt)* ]) => {
58        $crate::Expr::normal(
59            $crate::Symbol::new(stringify!($name)),
60            $crate::__expr_args![$($args)*],
61        )
62    };
63
64    // Context-less `$`-symbol call: ::$Name[args]. `$` is its own token (not an
65    // ident char), so it's captured as a `tt` and pasted back onto the name —
66    // letting WL system symbols like `::$Context[…]` be written directly.
67    (:: $d:tt $name:ident [ $($args:tt)* ]) => {
68        $crate::Expr::normal(
69            $crate::Symbol::new(concat!(stringify!($d), stringify!($name))),
70            $crate::__expr_args![$($args)*],
71        )
72    };
73
74    // Context-less symbol value: ::Name -> the bare-name symbol `Name`.
75    (:: $name:ident) => {
76        $crate::Expr::symbol($crate::Symbol::new(stringify!($name)))
77    };
78
79    // Context-less `$`-symbol value: ::$Name -> the bare-name symbol `$Name`.
80    (:: $d:tt $name:ident) => {
81        $crate::Expr::symbol(
82            $crate::Symbol::new(concat!(stringify!($d), stringify!($name)))
83        )
84    };
85
86    // Context-qualified call: A::B::C[args] applies the symbol `A`B`C`.
87    // Each `::` becomes a context backtick and the path is taken verbatim (no
88    // `System`` prefix) — the escape hatch for any non-`System`` context.
89    ($head:ident $(:: $seg:ident)+ [ $($args:tt)* ]) => {
90        $crate::Expr::normal(
91            $crate::Symbol::new(concat!(stringify!($head) $(, "`", stringify!($seg))+)),
92            $crate::__expr_args![$($args)*],
93        )
94    };
95
96    // Function call over a variable: var[args] applies the (`Into<Expr>`) value
97    // `var` to args. A bare ident is *always* a Rust variable — symbols must be
98    // `::`-qualified (`System::Times[…]`); there is no implicit `System`` prefix.
99    // (A non-ident head expression has no inline form — bind it to a variable.)
100    // Args are parsed by the __expr_args! tt-muncher, which handles:
101    //   - single-tt args (literals, variables, {..} associations)
102    //   - (rust_expr) parenthesized Rust expressions, ..iter splices
103    //   - k -> v Rule args (three tokens)
104    ($head:ident [ $($args:tt)* ]) => {
105        $crate::Expr::normal($head, $crate::__expr_args![$($args)*])
106    };
107
108    // Association: {key -> value, ...}
109    // Values are parsed by __expr_assoc! so they can be Head[...] expressions.
110    ({ $($assoc:tt)* }) => {
111        $crate::Expr::new($crate::ExprKind::Association(
112            $crate::__expr_assoc![$($assoc)*]
113        ))
114    };
115
116    // Context-qualified symbol value: A::B::C -> the bare symbol `A`B`C`.
117    ($head:ident $(:: $seg:ident)+) => {
118        $crate::Expr::symbol($crate::Symbol::new(
119            concat!(stringify!($head) $(, "`", stringify!($seg))+)
120        ))
121    };
122
123    // Fallthrough: numbers, string literals, Rust variables, Vec<Expr>, etc.
124    // A bare ident (no `::`) is a Rust *variable*; a `::`-path is a symbol (above).
125    ($e:expr) => { $crate::Expr::from($e) };
126}
127
128/// Internal tt-muncher that parses a comma-separated arg list where each arg
129/// is a single token tree, a `Head[...]` function call, or a `k -> v` Rule.
130#[doc(hidden)]
131#[macro_export]
132macro_rules! __expr_args {
133    // Base: empty
134    () => { vec![] };
135    // Trailing comma only
136    (,) => { vec![] };
137    // ::Name[...] context-less call arg, followed by more
138    (:: $name:ident [ $($inner:tt)* ], $($rest:tt)*) => {{
139        let mut __args = vec![$crate::expr!(:: $name [ $($inner)* ])];
140        __args.extend($crate::__expr_args![$($rest)*]);
141        __args
142    }};
143    // ::Name[...] context-less call arg, last
144    (:: $name:ident [ $($inner:tt)* ]) => {
145        vec![$crate::expr!(:: $name [ $($inner)* ])]
146    };
147    // ::Name context-less symbol arg, followed by more
148    (:: $name:ident, $($rest:tt)*) => {{
149        let mut __args = vec![$crate::expr!(:: $name)];
150        __args.extend($crate::__expr_args![$($rest)*]);
151        __args
152    }};
153    // ::Name context-less symbol arg, last
154    (:: $name:ident) => {
155        vec![$crate::expr!(:: $name)]
156    };
157    // ::$Name[...] context-less `$`-symbol call arg, followed by more
158    (:: $d:tt $name:ident [ $($inner:tt)* ], $($rest:tt)*) => {{
159        let mut __args = vec![$crate::expr!(:: $d $name [ $($inner)* ])];
160        __args.extend($crate::__expr_args![$($rest)*]);
161        __args
162    }};
163    // ::$Name[...] context-less `$`-symbol call arg, last
164    (:: $d:tt $name:ident [ $($inner:tt)* ]) => {
165        vec![$crate::expr!(:: $d $name [ $($inner)* ])]
166    };
167    // ::$Name context-less `$`-symbol arg, followed by more
168    (:: $d:tt $name:ident, $($rest:tt)*) => {{
169        let mut __args = vec![$crate::expr!(:: $d $name)];
170        __args.extend($crate::__expr_args![$($rest)*]);
171        __args
172    }};
173    // ::$Name context-less `$`-symbol arg, last
174    (:: $d:tt $name:ident) => {
175        vec![$crate::expr!(:: $d $name)]
176    };
177    // Rule arg: k -> v, rest...
178    ($k:tt -> $v:tt, $($rest:tt)*) => {{
179        let mut __args = vec![$crate::expr!($k -> $v)];
180        __args.extend($crate::__expr_args![$($rest)*]);
181        __args
182    }};
183    // Rule arg, last: k -> v
184    ($k:tt -> $v:tt) => { vec![$crate::expr!($k -> $v)] };
185    // A::B::C[...] context-qualified call arg, followed by more
186    ($head:ident $(:: $seg:ident)+ [ $($inner:tt)* ], $($rest:tt)*) => {{
187        let mut __args = vec![$crate::expr!($head $(:: $seg)+ [ $($inner)* ])];
188        __args.extend($crate::__expr_args![$($rest)*]);
189        __args
190    }};
191    // A::B::C[...] context-qualified call arg, last
192    ($head:ident $(:: $seg:ident)+ [ $($inner:tt)* ]) => {
193        vec![$crate::expr!($head $(:: $seg)+ [ $($inner)* ])]
194    };
195    // A::B::C symbol arg, followed by more
196    ($head:ident $(:: $seg:ident)+, $($rest:tt)*) => {{
197        let mut __args = vec![$crate::expr!($head $(:: $seg)+)];
198        __args.extend($crate::__expr_args![$($rest)*]);
199        __args
200    }};
201    // A::B::C symbol arg, last
202    ($head:ident $(:: $seg:ident)+) => {
203        vec![$crate::expr!($head $(:: $seg)+)]
204    };
205    // ..iter splice arg (each item `Into<Expr>`), followed by more
206    ( .. $e:expr, $($rest:tt)* ) => {{
207        let mut __args: ::std::vec::Vec<$crate::Expr> =
208            ::core::iter::IntoIterator::into_iter($e)
209                .map(|__x| $crate::Expr::from(__x))
210                .collect();
211        __args.extend($crate::__expr_args![$($rest)*]);
212        __args
213    }};
214    // ..iter splice arg, last
215    ( .. $e:expr ) => {
216        ::core::iter::IntoIterator::into_iter($e)
217            .map(|__x| $crate::Expr::from(__x))
218            .collect::<::std::vec::Vec<$crate::Expr>>()
219    };
220    // Head[...] arg, followed by more args
221    ($head:ident [ $($inner:tt)* ], $($rest:tt)*) => {{
222        let mut __args = vec![$crate::expr!($head [ $($inner)* ])];
223        __args.extend($crate::__expr_args![$($rest)*]);
224        __args
225    }};
226    // Head[...] arg, last
227    ($head:ident [ $($inner:tt)* ]) => {
228        vec![$crate::expr!($head [ $($inner)* ])]
229    };
230    // Single-tt arg followed by more args
231    ($arg:tt, $($rest:tt)*) => {{
232        let mut __args = vec![$crate::expr!($arg)];
233        __args.extend($crate::__expr_args![$($rest)*]);
234        __args
235    }};
236    // Single-tt arg, last
237    ($arg:tt) => { vec![$crate::expr!($arg)] };
238}
239
240/// Internal tt-muncher that parses Association entries `k -> v, ...` where
241/// values can be single token trees or `Head[...]` expressions.
242#[doc(hidden)]
243#[macro_export]
244macro_rules! __expr_assoc {
245    () => { vec![] };
246    (,) => { vec![] };
247    // k -> ::Name[...] context-less call value, rest
248    ($k:tt -> :: $vname:ident [ $($vinner:tt)* ], $($rest:tt)*) => {{
249        let mut __v = vec![$crate::RuleEntry::rule(
250            $crate::expr!($k),
251            $crate::expr!(:: $vname [ $($vinner)* ]),
252        )];
253        __v.extend($crate::__expr_assoc![$($rest)*]);
254        __v
255    }};
256    // k -> ::Name[...] context-less call value, last
257    ($k:tt -> :: $vname:ident [ $($vinner:tt)* ]) => {
258        vec![$crate::RuleEntry::rule(
259            $crate::expr!($k),
260            $crate::expr!(:: $vname [ $($vinner)* ]),
261        )]
262    };
263    // k -> ::Name context-less symbol value, rest
264    ($k:tt -> :: $vname:ident, $($rest:tt)*) => {{
265        let mut __v = vec![$crate::RuleEntry::rule(
266            $crate::expr!($k),
267            $crate::expr!(:: $vname),
268        )];
269        __v.extend($crate::__expr_assoc![$($rest)*]);
270        __v
271    }};
272    // k -> ::Name context-less symbol value, last
273    ($k:tt -> :: $vname:ident) => {
274        vec![$crate::RuleEntry::rule($crate::expr!($k), $crate::expr!(:: $vname))]
275    };
276    // k -> A::B::C[...] qualified-head value, rest
277    ($k:tt -> $vh:ident $(:: $vseg:ident)+ [ $($vinner:tt)* ], $($rest:tt)*) => {{
278        let mut __v = vec![$crate::RuleEntry::rule(
279            $crate::expr!($k),
280            $crate::expr!($vh $(:: $vseg)+ [ $($vinner)* ]),
281        )];
282        __v.extend($crate::__expr_assoc![$($rest)*]);
283        __v
284    }};
285    // k -> A::B::C[...] qualified-head value, last
286    ($k:tt -> $vh:ident $(:: $vseg:ident)+ [ $($vinner:tt)* ]) => {
287        vec![$crate::RuleEntry::rule(
288            $crate::expr!($k),
289            $crate::expr!($vh $(:: $vseg)+ [ $($vinner)* ]),
290        )]
291    };
292    // k -> A::B::C qualified-symbol value, rest
293    ($k:tt -> $vh:ident $(:: $vseg:ident)+, $($rest:tt)*) => {{
294        let mut __v = vec![$crate::RuleEntry::rule(
295            $crate::expr!($k),
296            $crate::expr!($vh $(:: $vseg)+),
297        )];
298        __v.extend($crate::__expr_assoc![$($rest)*]);
299        __v
300    }};
301    // k -> A::B::C qualified-symbol value, last
302    ($k:tt -> $vh:ident $(:: $vseg:ident)+) => {
303        vec![$crate::RuleEntry::rule($crate::expr!($k), $crate::expr!($vh $(:: $vseg)+))]
304    };
305    // k -> Head[...], rest
306    ($k:tt -> $vhead:ident [ $($vinner:tt)* ], $($rest:tt)*) => {{
307        let mut __v = vec![$crate::RuleEntry::rule(
308            $crate::expr!($k),
309            $crate::expr!($vhead [ $($vinner)* ]),
310        )];
311        __v.extend($crate::__expr_assoc![$($rest)*]);
312        __v
313    }};
314    // k -> Head[...], last
315    ($k:tt -> $vhead:ident [ $($vinner:tt)* ]) => {
316        vec![$crate::RuleEntry::rule(
317            $crate::expr!($k),
318            $crate::expr!($vhead [ $($vinner)* ]),
319        )]
320    };
321    // k -> v, rest (single-tt value)
322    ($k:tt -> $v:tt, $($rest:tt)*) => {{
323        let mut __v = vec![$crate::RuleEntry::rule($crate::expr!($k), $crate::expr!($v))];
324        __v.extend($crate::__expr_assoc![$($rest)*]);
325        __v
326    }};
327    // k -> v, last
328    ($k:tt -> $v:tt) => {
329        vec![$crate::RuleEntry::rule($crate::expr!($k), $crate::expr!($v))]
330    };
331}