stateful_macro_rules/
lib.rs1#![deny(missing_docs)]
2
3use 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#[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 [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 [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 [G('(', pat), P('='), P('>'), G('{', body)] => {
144 rule.append_rule(pat.stream(), None, body.stream())?;
145 }
146
147 [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 #[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 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(
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 (y += #d t:ident) => {
291 y.append(#d t);
292 };
293
294 (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 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}