lazy_regex_proc_macros/
regex_code.rs

1use {
2    proc_macro::TokenStream,
3    proc_macro2::TokenStream as TokenStream2,
4    quote::quote,
5    syn::LitStr,
6};
7
8/// The lazy static regex building code, which is produced and
9/// inserted by all lazy-regex macros
10pub(crate) struct RegexCode {
11    pub build: TokenStream2,
12    pub regex: RegexInstance,
13}
14
15pub(crate) enum RegexInstance {
16    Regex(regex::Regex),
17    Bytes(regex::bytes::Regex),
18}
19
20impl RegexCode {
21    pub fn from_token_stream(token_stream: TokenStream, is_bytes: bool) -> Result<Self, syn::Error> {
22        Self::from_lit_str(syn::parse::<syn::LitStr>(token_stream)?, is_bytes)
23    }
24    pub fn from_lit_str(lit_str: LitStr, mut is_bytes: bool) -> Result<Self, syn::Error> {
25        let pattern = lit_str.value();
26        let mut case_insensitive = false;
27        let mut multi_line = false;
28        let mut dot_matches_new_line = false;
29        let mut ignore_whitespace = false;
30        let mut swap_greed = false;
31        for (i, ch) in lit_str.suffix().chars().enumerate() {
32            match ch {
33                'i' => case_insensitive = true,
34                'm' => multi_line = true,
35                's' => dot_matches_new_line = true,
36                'x' => ignore_whitespace = true,
37                'U' => swap_greed = true,
38                'B' => is_bytes = true, // non-standard!
39                _ => {
40                    let lit = lit_str.token();
41                    let pos = lit.to_string().len() - i;
42                    // subspan only works on nighlty
43                    return Err(syn::Error::new(
44                        lit.subspan(pos - 1..pos).unwrap_or_else(|| lit.span()),
45                        format!("unrecognized regex flag {:?}", ch),
46                    ));
47                }
48            };
49        }
50
51        let regex = if is_bytes {
52            regex::bytes::Regex::new(&pattern).map(RegexInstance::Bytes)
53        } else {
54            regex::Regex::new(&pattern).map(RegexInstance::Regex)
55        };
56        let regex = regex.map_err(|e| syn::Error::new(lit_str.span(), e.to_string()))?;
57
58        let builder_token = if is_bytes {
59            quote!(BytesRegexBuilder)
60        } else {
61            quote!(RegexBuilder)
62        };
63        let build = quote! {
64            lazy_regex::Lazy::new(|| {
65                //println!("compiling regex {:?}", #pattern);
66                lazy_regex:: #builder_token ::new(#pattern)
67                    .case_insensitive(#case_insensitive)
68                    .multi_line(#multi_line)
69                    .dot_matches_new_line(#dot_matches_new_line)
70                    .ignore_whitespace(#ignore_whitespace)
71                    .swap_greed(#swap_greed)
72                    .build()
73                    .unwrap()
74            })
75        };
76        Ok(Self { build, regex })
77    }
78}
79
80impl RegexCode {
81    pub fn statick(&self) -> TokenStream2 {
82        let build = &self.build;
83        let regex_token = match self.regex {
84            RegexInstance::Regex(..) => quote!(Regex),
85            RegexInstance::Bytes(..) => quote!(BytesRegex),
86        };
87        quote! {
88            static RE: lazy_regex::Lazy<lazy_regex:: #regex_token > = #build;
89        }
90    }
91
92    pub fn lazy_static(&self) -> TokenStream2 {
93        let statick = self.statick();
94        quote! {{
95            #statick;
96            &RE
97        }}
98    }
99
100    pub fn captures_len(&self) -> usize {
101        match &self.regex {
102            RegexInstance::Regex(regex) => regex.captures_len(),
103            RegexInstance::Bytes(regex) => regex.captures_len(),
104        }
105    }
106    pub fn named_groups(&self) -> Vec<(usize, &str)> {
107        match &self.regex {
108            RegexInstance::Regex(regex) => regex
109                .capture_names()
110                .enumerate()
111                .filter_map(|(i, n)| Some((i, n?)))
112                .collect(),
113            RegexInstance::Bytes(regex) => regex
114                .capture_names()
115                .enumerate()
116                .filter_map(|(i, n)| Some((i, n?)))
117                .collect(),
118        }
119    }
120}