use {
proc_macro::TokenStream,
proc_macro2,
quote::quote,
syn::{
Expr,
ExprClosure,
LitStr,
Token,
parse::{
Parse,
ParseStream,
Result,
},
parse_macro_input,
},
};
struct RegexCode {
regex: regex::Regex,
build: proc_macro2::TokenStream,
}
impl From<LitStr> for RegexCode {
fn from(lit_str: LitStr) -> Self {
let regex_string = lit_str.value();
let mut case_insensitive = false;
let mut multi_line = false;
let mut dot_matches_new_line = false;
let mut ignore_whitespace = false;
let mut swap_greed = false;
for ch in lit_str.suffix().chars() {
match ch {
'i' => case_insensitive = true,
'm' => multi_line = true,
's' => dot_matches_new_line = true,
'x' => ignore_whitespace = true,
'U' => swap_greed = true,
_ => {
panic!("unrecognized regex flag {:?}", ch);
}
};
}
let regex = regex::Regex::new(®ex_string).unwrap();
let build = quote! {{
Lazy::new(|| {
let mut builder = regex::RegexBuilder::new(#regex_string);
builder.case_insensitive(#case_insensitive);
builder.multi_line(#multi_line);
builder.dot_matches_new_line(#dot_matches_new_line);
builder.ignore_whitespace(#ignore_whitespace);
builder.swap_greed(#swap_greed);
builder.build().unwrap()
})
}};
Self { regex, build }
}
}
#[proc_macro]
pub fn regex(input: TokenStream) -> TokenStream {
let lit_str = syn::parse::<syn::LitStr>(input).unwrap();
let regex_build = RegexCode::from(lit_str).build;
let q = quote! {{
use lazy_regex::Lazy;
static RE: Lazy<regex::Regex> = #regex_build;
&RE
}};
q.into()
}
#[proc_macro]
pub fn lazy_regex(input: TokenStream) -> TokenStream {
let lit_str = syn::parse::<syn::LitStr>(input).unwrap();
let regex_build = RegexCode::from(lit_str).build;
regex_build.into()
}
struct RegexAndExpr {
regex_str: LitStr,
value: Expr,
}
impl Parse for RegexAndExpr {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let regex_str = input.parse::<LitStr>()?;
input.parse::<Token![,]>()?;
let value = input.parse::<Expr>()?;
let _ = input.parse::<Token![,]>(); Ok(RegexAndExpr {
regex_str,
value,
})
}
}
struct RegexAnd2Exprs {
regex_str: LitStr,
value: Expr,
fun: ExprClosure,
}
impl Parse for RegexAnd2Exprs {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let regex_str = input.parse::<LitStr>()?;
input.parse::<Token![,]>()?;
let value = input.parse::<Expr>()?;
input.parse::<Token![,]>()?;
let fun = input.parse::<ExprClosure>()?;
let _ = input.parse::<Token![,]>(); Ok(RegexAnd2Exprs {
regex_str,
value,
fun,
})
}
}
#[proc_macro]
pub fn regex_is_match(input: TokenStream) -> TokenStream {
let regex_and_expr_args = parse_macro_input!(input as RegexAndExpr);
let regex_build = RegexCode::from(regex_and_expr_args.regex_str).build;
let value = regex_and_expr_args.value;
let q = quote! {{
use lazy_regex::Lazy;
static RE: Lazy<regex::Regex> = #regex_build;
RE.is_match(#value)
}};
q.into()
}
#[proc_macro]
pub fn regex_find(input: TokenStream) -> TokenStream {
let regex_and_expr_args = parse_macro_input!(input as RegexAndExpr);
let regex_code = RegexCode::from(regex_and_expr_args.regex_str);
let regex_build = regex_code.build;
let value = regex_and_expr_args.value;
let q = quote! {{
use lazy_regex::Lazy;
static RE: Lazy<regex::Regex> = #regex_build;
RE.find(#value).map(|mat| mat.as_str())
}};
q.into()
}
#[proc_macro]
pub fn regex_captures(input: TokenStream) -> TokenStream {
let regex_and_expr_args = parse_macro_input!(input as RegexAndExpr);
let regex_code = RegexCode::from(regex_and_expr_args.regex_str);
let regex_build = regex_code.build;
let value = regex_and_expr_args.value;
let n = regex_code.regex.captures_len();
let groups = (0..n).map(|i| quote! {
caps.get(#i).map_or("", |c| c.as_str())
});
let q = quote! {{
use lazy_regex::Lazy;
static RE: Lazy<regex::Regex> = #regex_build;
RE.captures(#value)
.map(|caps| (
#(#groups),*
))
}};
q.into()
}
#[proc_macro]
pub fn regex_replace_all(input: TokenStream) -> TokenStream {
let args = parse_macro_input!(input as RegexAnd2Exprs);
let regex_code = RegexCode::from(args.regex_str);
let regex_build = regex_code.build;
let value = args.value;
let fun = args.fun;
let n = regex_code.regex.captures_len();
let groups = (0..n).map(|i| quote! {
caps.get(#i).map_or("", |c| c.as_str())
});
let q = quote! {{
use lazy_regex::Lazy;
static RE: Lazy<regex::Regex> = #regex_build;
RE.replace_all(
#value,
|caps: ®ex::Captures<'_>| {
let fun = #fun;
fun(
#(#groups),*
)
})
}};
q.into()
}