stateful_macro_rules/
lib.rs

1#![deny(missing_docs)]
2
3//! # stateful_macro_rules
4//!
5//! `stateful_macro_rules` makes it easier to write `macro_rules` with states.
6//! It is especially useful where the `macro_rule` needs to take a list of
7//! inputs with various patterns.
8//!
9//! Refer to [`stateful_macro_rules`](macro.stateful_macro_rules.html) for
10//! the documentation of the main macro.
11
12use proc_macro::TokenStream as StdTokenStream;
13use proc_macro2::TokenStream;
14
15mod error;
16mod state;
17mod util;
18
19use error::Error;
20use error::Result;
21use state::StatefulMacroRule;
22use util::describe_tokens;
23use util::split_meta;
24use util::split_tokens;
25use util::Describe::{G, I, P};
26
27/// Generate `macro_rules!` macros that have states.
28///
29/// ## Basic: Macro name and body
30///
31/// To specify the generated macro name and its final expanded content, use
32/// `name() { body }`.
33///
34/// For example, the code below generates a macro called `foo!()` and it
35/// expands to `"foo"`.
36///
37/// ```
38/// # use stateful_macro_rules::stateful_macro_rules;
39/// stateful_macro_rules! {
40///     foo() { "foo" };
41/// }
42/// ```
43///
44/// ## States
45///
46/// To define states, add them to the `()` after the macro name. A state can
47/// be defined as `state_name: (pattern) = (default_value))`. Multiple
48/// states are separated by `,`. States can be referred by their pattern
49/// name.
50///
51/// For example, the code below defines a `plus!()` macro with `x` and `y`
52/// states (but there is no real way to use this macro):
53///
54/// ```
55/// # use stateful_macro_rules::stateful_macro_rules;
56/// stateful_macro_rules! {
57///     pos(x: ($x:expr) = (0), y: ($y:expr) = (0)) { ($x, $y) };
58/// }
59/// ```
60///
61/// ## Rules
62///
63/// To make the macro useful, macro rules like `(pattern) => { body }` are
64/// needed. Unlike rules in classic `macro_rules!`, the `pattern` matches
65/// incomplete tokens (use `...` to mark the incomplete portion), and `body`
66/// can only contain changes to states like `state_name.set(tokens)`, or
67/// `state_name.append(tokens)`.
68///
69/// ```
70/// # #![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]
71/// # use stateful_macro_rules::stateful_macro_rules;
72/// stateful_macro_rules! {
73///     pos(x: ($x:expr) = (0), y: ($y:expr) = (0)) { ($x, $y) };
74///     (incx($i:expr) ...) => { x.set($x + $i); };
75///     (incx ...) => { x.set($x + 1); };
76///     (incy($i:expr) ...) => { y.set($y + $i); };
77///     (incy ...) => { y.set($y + 1); };
78///
79///     // `...` is implicitly at the end
80///     (reset) => { x.set(0); y.set(0); };  
81///
82///     // `...` can be in the middle
83///     (eval( ... )) => { };
84/// }
85/// assert_eq!(pos!(incx(3) reset incy incy(10) incx), (1, 11));
86/// assert_eq!(pos!(eval(incx(10) incy(20))), (10, 20));
87/// ```
88///
89/// # Conditional Rules
90///
91/// Rules can be disabled by states. This is done by the `when` block.
92///
93/// ```
94/// # #![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]
95/// # use stateful_macro_rules::stateful_macro_rules;
96/// stateful_macro_rules! {
97///     toggle(result: ($v:tt) = (0)) { $v };
98///     (T) when { result: (0) } => { result.set(1); };
99///     (T) when { result: (1) } => { result.set(0); };
100/// }
101/// assert_eq!(toggle![T T T], 1);
102/// ```
103///
104/// # Debugging
105///
106/// If the `debug` feature is on, `debug;` can be used to print the
107/// generated code to stderr. The code can be used as a drop-in macro
108/// replacement to help debugging.
109#[proc_macro]
110pub fn stateful_macro_rules(input: StdTokenStream) -> StdTokenStream {
111    match stateful_macro_rules_fallible(input.into()) {
112        Ok(t) => t.into(),
113        Err(e) => e.into(),
114    }
115}
116
117pub(crate) fn stateful_macro_rules_fallible(input: TokenStream) -> Result<TokenStream> {
118    let mut rule = StatefulMacroRule::default();
119    #[cfg(feature = "debug")]
120    let mut debug = false;
121    for tokens in split_tokens(input, ';') {
122        let (meta, tokens) = split_meta(&tokens);
123        match describe_tokens(tokens)[..] {
124            // #[doc = r"foo"]
125            // name ( k: ty = v, k: ty = v, ) { result }
126            [I(i), G('(', s), G('{', r)] => {
127                rule.set_attributes(meta)?;
128                rule.set_name(i.clone())?;
129                rule.set_state(s)?;
130                rule.set_return(None, r)?;
131            }
132
133            // #[doc = r"foo"]
134            // name ( k: ty = v, k: ty = v, ) { result }
135            [I(i), G('(', s), I(iw), G('{', w), G('{', r)] if iw.to_string() == "when" => {
136                rule.set_attributes(meta)?;
137                rule.set_name(i.clone())?;
138                rule.set_state(s)?;
139                rule.set_return(Some(w.stream()), r)?;
140            }
141
142            // (pat) => { ... }
143            [G('(', pat), P('='), P('>'), G('{', body)] => {
144                rule.append_rule(pat.stream(), None, body.stream())?;
145            }
146
147            // (pat) when { state_name: (pat), ... } => { ... }
148            [G('(', pat), I(w), G('{', state), P('='), P('>'), G('{', body)]
149                if w.to_string() == "when" =>
150            {
151                rule.append_rule(pat.stream(), Some(state.stream()), body.stream())?;
152            }
153
154            // fs_write_expanded(path). write expanded macro to a specific file for debugging
155            // purpose.
156            #[cfg(feature = "debug")]
157            [I(i)] if i.to_string() == "debug" => {
158                debug = true;
159            }
160
161            _ => {
162                return Err(Error::UnexpectedTokens(
163                    tokens.to_vec(),
164                    concat!(
165                        "expect 'macro_name(state_name: (ty) = (default), ...) { ... };',",
166                        " or '(...) => { ... };'",
167                        " or '(...) when { state: (pat), ... } => { ... };'",
168                        " in stateful_macro_rule!"
169                    ),
170                ))
171            }
172        }
173    }
174
175    let code = rule.generate_code()?;
176    #[cfg(feature = "debug")]
177    if debug {
178        eprintln!("{}", util::to_string(code.clone(), 100));
179    }
180    Ok(code)
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186    use crate::state::dollar;
187    use quote::quote;
188
189    fn to_string(t: TokenStream) -> String {
190        crate::util::to_string(t, 80)
191    }
192
193    #[test]
194    fn test_minimal_example() {
195        let q = stateful_macro_rules_fallible(quote! {
196            minimal() { "foo" }
197        })
198        .unwrap();
199
200        assert_eq!(
201            to_string(q),
202            r#"
203# [macro_export] macro_rules ! minimal
204{
205  { $ ($ tt : tt) * } => { $ crate :: __minimal_state ! ([$ ($ tt) *] { }) } ;
206}
207# [doc (hidden)] # [macro_export] macro_rules ! __minimal_state
208{
209  ([] { }) => { "foo" } ;
210}"#
211        );
212    }
213
214    #[test]
215    fn test_attributes() {
216        let q = stateful_macro_rules_fallible(quote! {
217            #[cfg(feature = "bar")]
218            /// Some comment.
219            /// Foo bar.
220            attriute_test() { 1 }
221        })
222        .unwrap();
223
224        assert_eq!(
225            to_string(q),
226            r#"
227# [macro_export] # [cfg (feature = "bar")] # [doc = r" Some comment."] #
228[
229  doc = r" Foo bar."
230]
231macro_rules ! attriute_test
232{
233  { $ ($ tt : tt) * } =>
234  {
235    $ crate :: __attriute_test_state ! ([$ ($ tt) *] { })
236  }
237  ;
238}
239# [doc (hidden)] # [macro_export] macro_rules ! __attriute_test_state
240{
241  ([] { }) => { 1 } ;
242}"#
243        );
244    }
245
246    #[test]
247    fn test_when_clause() {
248        let d = dollar();
249        let q = stateful_macro_rules_fallible(quote! {
250            w(b: (#d b:tt) = (false)) when { b: (true) } { "ok" };
251            (t) when { b: (false) } => { b.set(true) };
252        })
253        .unwrap();
254
255        assert_eq!(
256            to_string(q),
257            r#"
258# [macro_export] macro_rules ! w
259{
260  { $ ($ tt : tt) * } => { $ crate :: __w_state ! ([$ ($ tt) *] { b [false] }) } ;
261}
262# [doc (hidden)] # [macro_export] macro_rules ! __w_state
263{
264  ([] { b [true] }) => { "ok" } ; ([t $ ($ _ddd : tt) *] { b [false] }) =>
265  {
266    $ crate :: __w_state ! ([$ ($ _ddd) *] { b [true] })
267  }
268  ;
269}"#
270        );
271    }
272
273    #[test]
274    fn test_complex_example() {
275        let d = dollar();
276        let q = stateful_macro_rules_fallible(quote! {
277            #[allow(dead_code)]
278            /// Foo bar
279            foo(
280                x: (#d (#d i:expr)*) = (1 2),
281                y: (#d (#d j:ident)*),
282                z: (#d (#d t:tt)* ) = (x),
283            ) {{
284                let v1 = vec![#d (#d i),*];
285                let v2 = vec![#d (stringify!(#d j)),*];
286                format!("{:?} {:?}", v1, v2)
287            }};
288
289            // Implicit "..."
290            (y += #d t:ident) => {
291                y.append(#d t);
292            };
293
294            // Matching state (z).
295            (y = #d t:ident ...) when { z: (x) } => {
296                y.set(#d t);
297                z.append(y);
298            };
299
300            (e(#d d:ident, #d e:expr) ...) => {
301                x.append(#d e);
302                y.append(#d d);
303                // 4 dots: the entire input without "...".
304                z.append(....);
305            }
306        })
307        .unwrap();
308        assert_eq!(
309            to_string(q),
310            r#"
311# [macro_export] # [allow (dead_code)] # [doc = r" Foo bar"] macro_rules ! foo
312{
313  { $ ($ tt : tt) * } =>
314  {
315    $ crate :: __foo_state ! ([$ ($ tt) *] { x [1 2] y [] z [x] })
316  }
317  ;
318}
319# [doc (hidden)] # [macro_export] macro_rules ! __foo_state
320{
321  ([] { x [$ ($ i : expr) *] y [$ ($ j : ident) *] z [$ ($ t : tt) *] }) =>
322  {
323    {
324      let v1 = vec ! [$ ($ i) , *] ; let v2 = vec ! [$ (stringify ! ($ j)) , *] ; format !
325      (
326        "{:?} {:?}" , v1 , v2
327      )
328    }
329  }
330  ;
331  (
332    [y += $ t : ident $ ($ _ddd : tt) *]
333    {
334      x [$ ($ i : expr) *] y [$ ($ j : ident) *] z [$ ($ t : tt) *]
335    }
336  )
337  =>
338  {
339    $ crate :: __foo_state !
340    (
341      [$ ($ _ddd) *] { x [$ ($ i) *] y [$ ($ j) * $ t] z [$ ($ t) *] }
342    )
343  }
344  ;
345  (
346    [y = $ t : ident $ ($ _ddd : tt) *]
347    {
348      x [$ ($ i : expr) *] y [$ ($ j : ident) *] z [x]
349    }
350  )
351  =>
352  {
353    $ crate :: __foo_state ! ([$ ($ _ddd) *] { x [$ ($ i) *] y [$ t] z [x y] })
354  }
355  ;
356  (
357    [e ($ d : ident , $ e : expr) $ ($ _ddd : tt) *]
358    {
359      x [$ ($ i : expr) *] y [$ ($ j : ident) *] z [$ ($ t : tt) *]
360    }
361  )
362  =>
363  {
364    $ crate :: __foo_state !
365    (
366      [$ ($ _ddd) *]
367      {
368        x [$ ($ i) * $ e] y [$ ($ j) * $ d] z [$ ($ t) * e ($ d , $ e)]
369      }
370    )
371  }
372  ;
373}"#
374        );
375    }
376}