1use proc_macro2::{Ident, Span, TokenStream};
2use proc_macro_error::{abort, proc_macro_error};
3use quote::{quote, ToTokens};
4use syn::Fields;
5
6#[proc_macro_attribute]
7#[proc_macro_error]
8pub fn rematch(
9 attr: proc_macro::TokenStream,
10 item: proc_macro::TokenStream,
11) -> proc_macro::TokenStream {
12 parse(attr.into(), item.into()).to_token_stream().into()
13}
14
15enum Parsed {
16 Enum {
17 item: syn::ItemEnum,
18 attrs_per_variant: Vec<Vec<Attribute>>,
19 },
20 Struct {
21 attr: Attribute,
22 item: syn::ItemStruct,
23 },
24}
25
26#[derive(Clone, Debug)]
27enum Attribute {
28 Regex(TokenStream),
29}
30
31fn generate_fields(caps_ident: &Ident, fields: &Fields) -> TokenStream {
32 match fields {
33 Fields::Named(named) => {
34 let fields = named
35 .named
36 .iter()
37 .enumerate()
38 .map(|(idx, field)| {
39 let ident = field
40 .ident
41 .as_ref()
42 .expect("named field should have a name");
43 let ident_str = ident.to_string();
44 let ty = &field.ty;
45 quote! {
46 #ident: #caps_ident.get(1 + #idx)
47 .ok_or_else(|| anyhow::anyhow!("Getting group {} failed", 1 + #idx))?
48 .as_str()
49 .parse::<#ty>()
50 .map_err(|e| anyhow::anyhow!("Field '{}' parsing error: {}", #ident_str, e))?,
51 }
52 })
53 .collect::<TokenStream>();
54 quote! {
55 {
56 #fields
57 }
58 }
59 }
60 Fields::Unnamed(unnamed) => {
61 let fields = unnamed
62 .unnamed
63 .iter()
64 .enumerate()
65 .map(|(idx, field)| {
66 let ty = &field.ty;
67 quote! {
68 #caps_ident.get(1 + #idx)
69 .ok_or_else(|| anyhow::anyhow!("Getting group {} failed", 1 + #idx))?
70 .as_str()
71 .parse::<#ty>()
72 .map_err(|e| anyhow::anyhow!("Field {} parsing error: {}", #idx, e))?,
73 }
74 })
75 .collect::<TokenStream>();
76 quote! {
77 (
78 #fields
79 )
80 }
81 }
82 Fields::Unit => {
83 quote!()
84 }
85 }
86}
87
88fn generate_fields_matching(
89 re: &TokenStream,
90 re_ident: &Ident,
91 self_ident: TokenStream,
92 fields: &Fields,
93) -> TokenStream {
94 let caps_ident = Ident::new("caps", Span::call_site());
95 let fields = generate_fields(&caps_ident, fields);
96
97 quote! {
98 lazy_static::lazy_static! {
99 static ref #re_ident: regex::Regex = regex::Regex::new(#re).unwrap();
100 }
101
102 if let Some(#caps_ident) = #re_ident.captures(s) {
103 return Ok(#self_ident #fields);
104 }
105 }
106}
107
108fn generate_enum_matching<'a>(
109 variants: impl IntoIterator<Item = &'a syn::Variant>,
110 attrs_per_variant: &'a [Vec<Attribute>],
111) -> TokenStream {
112 variants
113 .into_iter()
114 .zip(attrs_per_variant.iter())
115 .flat_map(|(variant, attrs)| {
116 attrs.iter().enumerate().map(|(idx, attr)| {
117 let Attribute::Regex(re) = attr;
118 let re_ident =
119 Ident::new(&format!("RE_{}_{}", variant.ident, idx), Span::call_site());
120 let variant_ident = &variant.ident;
121 let self_ident = quote! { Self::#variant_ident };
122 generate_fields_matching(re, &re_ident, self_ident, &variant.fields)
123 })
124 })
125 .collect()
126}
127
128impl ToTokens for Parsed {
129 fn to_tokens(&self, tokens: &mut TokenStream) {
130 let (name, matching) = match self {
131 Parsed::Enum {
132 item,
133 attrs_per_variant,
134 } => {
135 item.to_tokens(tokens);
136 (
137 &item.ident,
138 generate_enum_matching(&item.variants, attrs_per_variant),
139 )
140 }
141 Parsed::Struct {
142 attr: Attribute::Regex(re),
143 item,
144 } => {
145 item.to_tokens(tokens);
146 let re_ident = Ident::new("RE", Span::call_site());
147 (
148 &item.ident,
149 generate_fields_matching(re, &re_ident, quote! { Self }, &item.fields),
150 )
151 }
152 };
153 quote! {
154 impl std::str::FromStr for #name {
155 type Err = anyhow::Error;
156
157 fn from_str(s: &str) -> Result<Self, Self::Err> {
158 #matching
159 return Err(anyhow::anyhow!("Regex matching failed for: {:?}", s));
160 }
161 }
162 }
163 .to_tokens(tokens);
164 }
165}
166
167fn parse(attr: TokenStream, item: TokenStream) -> Parsed {
168 match syn::parse2::<syn::Item>(item) {
169 Ok(syn::Item::Enum(mut e)) => {
170 let mut attrs_per_variant = Vec::new();
171 for v in &mut e.variants {
172 let mut attrs = Vec::new();
173 v.attrs.retain(|attr| {
174 if attr.path.get_ident().map(proc_macro2::Ident::to_string)
175 == Some("rematch".to_owned())
176 {
177 attrs.push(parse_rematch_attrs(attr.tokens.clone()));
178 false
179 } else {
180 true
181 }
182 });
183 attrs_per_variant.push(attrs);
184 }
185 Parsed::Enum {
186 item: e,
187 attrs_per_variant,
188 }
189 }
190 Ok(syn::Item::Struct(s)) => Parsed::Struct {
191 attr: parse_rematch_attrs(attr),
192 item: s,
193 },
194 Ok(item) => abort!(item, "item is not an enum and not a struct"),
195 _ => panic!("rematch can't be used on this item"), }
197}
198
199fn parse_rematch_attrs(raw_attrs: TokenStream) -> Attribute {
200 match syn::parse2::<syn::Expr>(raw_attrs.clone()) {
201 Ok(syn::Expr::Paren(paren_expr)) => {
202 Attribute::Regex(paren_expr.expr.to_token_stream())
203 }
204 Ok(syn::Expr::Lit(lit_expr)) => {
205 Attribute::Regex(lit_expr.lit.to_token_stream())
206 }
207 Ok(expr) => {
208 abort!(expr, "Unknown attributes expression!");
209 }
210 _ => panic!("invalid attributes"), }
212}