match_string_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::parse::{Parse, ParseStream};
4use syn::{Expr, Ident, Token, parse_macro_input};
5
6struct MatchesInput {
7    reference: Expr,
8    _arrow: Token![=>],
9    pattern: PatternExpr,
10}
11
12struct PatternExpr {
13    kind: PatternKind,
14}
15
16enum PatternKind {
17    Lit(syn::Lit),
18    Ident(Ident),
19    Tuple(Vec<PatternExpr>),
20    Or(Vec<PatternExpr>),
21    Many(Box<PatternExpr>),
22    Some(Box<PatternExpr>),
23    Sep(Box<PatternExpr>, Box<PatternExpr>),
24    Sep1(Box<PatternExpr>, Box<PatternExpr>),
25    To(Ident, Box<PatternExpr>),
26}
27
28impl Parse for MatchesInput {
29    fn parse(input: ParseStream) -> syn::Result<Self> {
30        let reference: Expr = input.parse()?;
31        let _arrow: Token![=>] = input.parse()?;
32        let pattern = input.parse::<PatternExpr>()?;
33        Ok(MatchesInput {
34            reference,
35            _arrow,
36            pattern,
37        })
38    }
39}
40
41impl Parse for PatternExpr {
42    fn parse(input: ParseStream) -> syn::Result<Self> {
43        parse_seq_expr(input)
44    }
45}
46
47fn parse_seq_expr(input: ParseStream) -> syn::Result<PatternExpr> {
48    let mut terms = Vec::new();
49    loop {
50        terms.push(parse_or_expr(input)?);
51        if input.peek(Token![,]) {
52            input.parse::<Token![,]>()?;
53        } else {
54            break;
55        }
56    }
57    if terms.len() == 1 {
58        Ok(terms.into_iter().next().unwrap())
59    } else {
60        Ok(PatternExpr {
61            kind: PatternKind::Tuple(terms),
62        })
63    }
64}
65
66fn parse_or_expr(input: ParseStream) -> syn::Result<PatternExpr> {
67    let mut terms = Vec::new();
68    loop {
69        terms.push(parse_and_expr(input)?);
70        if input.peek(Token![/]) {
71            input.parse::<Token![/]>()?;
72        } else {
73            break;
74        }
75    }
76    if terms.len() == 1 {
77        Ok(terms.into_iter().next().unwrap())
78    } else {
79        Ok(PatternExpr {
80            kind: PatternKind::Or(terms),
81        })
82    }
83}
84
85fn parse_and_expr(input: ParseStream) -> syn::Result<PatternExpr> {
86    let expr = parse_term(input)?;
87    // support bracketed separator syntax: `elem[sep]+` => Sep(elem, sep)
88    if input.peek(syn::token::Bracket) {
89        let content;
90        syn::bracketed!(content in input);
91        let sep = parse_or_expr(&content)?;
92        if input.peek(Token![+]) {
93            input.parse::<Token![+]>()?;
94            return Ok(PatternExpr {
95                kind: PatternKind::Sep(Box::new(expr), Box::new(sep)),
96            });
97        } else if input.peek(Token![*]) {
98            input.parse::<Token![*]>()?;
99            return Ok(PatternExpr {
100                kind: PatternKind::Sep1(Box::new(expr), Box::new(sep)),
101            });
102        } else {
103            return Err(syn::Error::new(
104                input.span(),
105                "expected `+` or `*` after bracketed separator",
106            ));
107        }
108    }
109
110    if input.peek(Token![+]) {
111        input.parse::<Token![+]>()?;
112        Ok(PatternExpr {
113            kind: PatternKind::Many(Box::new(expr)),
114        })
115    } else if input.peek(Token![*]) {
116        input.parse::<Token![*]>()?;
117        Ok(PatternExpr {
118            kind: PatternKind::Some(Box::new(expr)),
119        })
120    } else {
121        Ok(expr)
122    }
123}
124
125fn parse_term(input: ParseStream) -> syn::Result<PatternExpr> {
126    if input.peek(syn::token::Paren) {
127        let content;
128        syn::parenthesized!(content in input);
129        // parentheses act as grouping/sequence; parse inner sequence expression
130        let inner = parse_seq_expr(&content)?;
131        return Ok(inner);
132    }
133
134    if input.peek(Ident) && input.peek2(Token![@]) {
135        let ident: Ident = input.parse()?;
136        input.parse::<Token![@]>()?;
137        let expr = parse_or_expr(input)?;
138        return Ok(PatternExpr {
139            kind: PatternKind::To(ident, Box::new(expr)),
140        });
141    }
142
143    if input.peek(syn::Lit) {
144        let lit: syn::Lit = input.parse()?;
145        return Ok(PatternExpr {
146            kind: PatternKind::Lit(lit),
147        });
148    }
149
150    if input.peek(Ident) {
151        let ident: Ident = input.parse()?;
152        return Ok(PatternExpr {
153            kind: PatternKind::Ident(ident),
154        });
155    }
156
157    Err(syn::Error::new(
158        input.span(),
159        "expected literal, identifier, grouped expression, or to",
160    ))
161}
162
163fn build_pattern_tokens(pattern: &PatternExpr) -> proc_macro2::TokenStream {
164    match &pattern.kind {
165        PatternKind::Lit(lit) => quote! { #lit },
166        PatternKind::Ident(ident) => quote! { #ident },
167        PatternKind::Or(exprs) => {
168            if exprs.is_empty() {
169                panic!("empty or");
170            } else if exprs.len() == 1 {
171                build_pattern_tokens(&exprs[0])
172            } else {
173                let mut tokens = build_pattern_tokens(&exprs[0]);
174                for expr in &exprs[1..] {
175                    let inner = build_pattern_tokens(expr);
176                    tokens = quote! { Or(#tokens, #inner) };
177                }
178                tokens
179            }
180        }
181        PatternKind::Tuple(exprs) => {
182            if exprs.is_empty() {
183                quote! { () }
184            } else if exprs.len() == 1 {
185                build_pattern_tokens(&exprs[0])
186            } else if exprs.len() == 2 {
187                let inner = exprs.iter().map(build_pattern_tokens);
188                quote! { (#(#inner),*) }
189            } else {
190                // Fold into nested pairs: (a,b,c,d) => (((a,b),c),d)
191                let mut tokens = build_pattern_tokens(&exprs[0]);
192                for expr in &exprs[1..] {
193                    let inner = build_pattern_tokens(expr);
194                    tokens = quote! { (#tokens, #inner) };
195                }
196                tokens
197            }
198        }
199        PatternKind::Many(expr) => {
200            let inner = build_pattern_tokens(expr);
201            quote! { RangeToInclusive { end: #inner } }
202        }
203        PatternKind::Some(expr) => {
204            let inner = build_pattern_tokens(expr);
205            quote! { RangeTo { end: #inner } }
206        }
207        PatternKind::Sep(elem, sep) => {
208            let e = build_pattern_tokens(elem);
209            let s = build_pattern_tokens(sep);
210            quote! { Sep(#s, #e) }
211        }
212        PatternKind::Sep1(elem, sep) => {
213            let e = build_pattern_tokens(elem);
214            let s = build_pattern_tokens(sep);
215            quote! { Sep1(#s, #e) }
216        }
217        PatternKind::To(ident, expr) => {
218            let inner = build_pattern_tokens(expr);
219            quote! { To(#inner, &#ident) }
220        }
221    }
222}
223
224#[proc_macro]
225pub fn matches(item: TokenStream) -> TokenStream {
226    let input = parse_macro_input!(item as MatchesInput);
227
228    let pattern_tokens = build_pattern_tokens(&input.pattern);
229
230    let reference = input.reference;
231
232    let output = quote!({
233        let __pattern = #pattern_tokens;
234        crate::__matches(&__pattern, & #reference)
235    });
236
237    output.into()
238}