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}