deno_proc_macro_rules/
lib.rs

1//! `macro_rules`-style syntax matching for procedural macros.
2//! 
3//! This crate is work-in-progress, incomplete, and probably buggy!
4//! 
5//! Example:
6//! 
7//! ```rust
8//! use proc_macro_rules::rules;
9//! use proc_macro2::TokenStream;
10//!
11//! fn main() {
12//!     # use quote::quote;
13//!     # test_rules(quote! { A (B C) #[inner...] ... });
14//!     # test_rules(quote! { foo 1 + 1 });
15//!     # test_rules(quote! { foo });
16//! # }
17//! #
18//! # fn test_rules(tokens: TokenStream) {
19//!     # const IGNORE: &str = stringify! {
20//!     let tokens: TokenStream = /* ... */;
21//!     # };
22//!
23//!     rules!(tokens => {
24//!         ($finish:ident ($($found:ident)*) # [ $($inner:tt)* ] $($rest:tt)*) => {
25//!             for f in found {
26//!                 do_something(&finish, f, &inner, &rest[0]);
27//!             }
28//!         }
29//!         (foo $($bar:expr)?) => {
30//!             match bar {
31//!                 Some(e) => foo_with_expr(e),
32//!                 None => foo_no_expr(),
33//!             }
34//!         }
35//!     });
36//! }
37//! #
38//! # use syn::{Expr, Ident};
39//! # use proc_macro2::TokenTree;
40//! #
41//! # fn do_something(
42//! #     finish: &Ident,
43//! #     f: Ident,
44//! #     inner: &[TokenTree],
45//! #     rest: &TokenTree,
46//! # ) {}
47//! # fn foo_with_expr(e: Expr) {}
48//! # fn foo_no_expr() {}
49//! ```
50//! 
51//! Import the `rules` macro with `use proc_macro_rules::rules`, then use with 
52//! `rules!(tokens => { branches });` where `tokens` is an expression which
53//! evaluates to a `TokenStream` (such as the argument in the definition of a
54//! procedural macro).
55//! 
56//! Each branch in `branches` should have the form `( pattern ) => { body }` where
57//! `pattern` is a macro-rules-style pattern (using all the same syntax for
58//! meta-variables, AST nodes, repetition, etc.) and `body` is rust code executed
59//! when the pattern is matched. Within `body`, any meta-variables in the pattern
60//! are bound to variables of an appropriate type from either the
61//! [proc_macro2](https://github.com/alexcrichton/proc-macro2) or
62//! [syn](https://github.com/dtolnay/syn) crates. Where a meta-variable is
63//! inside a repetition or option clause, it will be wrapped in a `Vec` or
64//! `Option`, respectively.
65//! 
66//! For example, in the first branch in the above example `ident` has type
67//! `syn::Ident` and `inner` has type `Vec<proc_macro2::TokenTree>`.
68
69#[doc(hidden)]
70pub extern crate syn;
71
72pub use crate::match_set::{Fork, MatchSet};
73pub use deno_proc_macro_rules_macros::rules;
74
75mod match_set;
76
77// Regression tests
78#[cfg(test)]
79mod tests {
80    use crate as proc_macro_rules;
81    use super::*;
82
83    #[test]
84    fn test_smoke() {
85        let tokens: proc_macro2::TokenStream = "hi (a b c) # [there] the - rest".parse().unwrap();
86        rules!(tokens => {
87            ($finish:ident ($($found:ident)+) # [ $($inner:tt)? ] $($rest:expr)*) => {
88                assert_eq!(finish.to_string(), "hi");
89                assert_eq!(found.len(), 3);
90                assert!(inner.is_some());
91                assert_eq!(rest.len(), 1);
92                return;
93            }
94        });
95        panic!();
96    }
97
98    #[test]
99    fn test_empty() {
100        let tokens: proc_macro2::TokenStream = "".parse().unwrap();
101        rules!(tokens => {
102            () => {
103                return;
104            }
105        });
106        panic!();
107    }
108
109    #[test]
110    #[should_panic]
111    fn test_no_match() {
112        let tokens: proc_macro2::TokenStream = "foo".parse().unwrap();
113        rules!(tokens => {
114            (bar) => {}
115        });
116    }
117
118    #[test]
119    fn test_branches() {
120        fn apply(tokens: proc_macro2::TokenStream, expected_branch: usize) {
121            rules!(tokens => {
122                (foo) => {
123                    if expected_branch == 0 {
124                        return;
125                    } else {
126                        panic!("branch: 0, expected: {}", expected_branch);
127                    }
128                }
129                ($x:ident) => {
130                    if expected_branch == 1 {
131                        assert_eq!(x.to_string(), "bar");
132                        return;
133                    } else {
134                        // TODO failing here!
135                        panic!("branch: 1, expected: {}", expected_branch);
136                    }
137                }
138                ($($x:ident)*) => {
139                    if expected_branch == 2 {
140                        assert_eq!(x.len(), 3);
141                        assert_eq!(x[0].to_string(), "a");
142                        assert_eq!(x[1].to_string(), "b");
143                        assert_eq!(x[2].to_string(), "c");
144                        return;
145                    } else {
146                        panic!("branch: 2, expected: {}", expected_branch);
147                    }
148                }
149            });
150            panic!("Hit no branches, expected: {}", expected_branch);
151        }
152
153        apply("foo".parse().unwrap(), 0);
154        apply("bar".parse().unwrap(), 1);
155        apply("a b c".parse().unwrap(), 2);
156    }
157
158    #[test]
159    fn test_opt() {
160        fn apply(tokens: proc_macro2::TokenStream) {
161            rules!(tokens => {
162                (foo $(bar),? baz) => {
163                    return;
164                }
165            });
166            panic!();
167        }
168
169        apply("foo baz".parse().unwrap());
170        apply("foo bar baz".parse().unwrap());
171        apply("foo bar, baz".parse().unwrap());
172    }
173
174    #[test]
175    fn test_plus() {
176        fn apply(tokens: proc_macro2::TokenStream) {
177            rules!(tokens => {
178                (foo $(bar),+ baz) => {
179                    return;
180                }
181            });
182            panic!();
183        }
184
185        apply("foo bar baz".parse().unwrap());
186        apply("foo bar, baz".parse().unwrap());
187        apply("foo bar, bar baz".parse().unwrap());
188        apply("foo bar, bar, baz".parse().unwrap());
189        apply("foo bar, bar, bar baz".parse().unwrap());
190        apply("foo bar, bar, bar, baz".parse().unwrap());
191    }
192
193    #[test]
194    fn test_star() {
195        fn apply(tokens: proc_macro2::TokenStream) {
196            rules!(tokens => {
197                (foo $(bar),* baz) => {
198                    return;
199                }
200            });
201            panic!();
202        }
203
204        apply("foo baz".parse().unwrap());
205        apply("foo bar baz".parse().unwrap());
206        apply("foo bar, baz".parse().unwrap());
207        apply("foo bar, bar baz".parse().unwrap());
208        apply("foo bar, bar, baz".parse().unwrap());
209        apply("foo bar, bar, bar baz".parse().unwrap());
210        apply("foo bar, bar, bar, baz".parse().unwrap());
211    }
212}