use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream};
use syn::token::Brace;
use syn::{Attribute, Expr, Receiver, Token, braced, parenthesized, parse_macro_input};
mod kw {
syn::custom_keyword!(mode);
syn::custom_keyword!(sparse);
syn::custom_keyword!(dense);
}
#[derive(Copy, Clone)]
enum DfaType {
Sparse,
Dense,
}
struct Pattern {
pattern_tok: syn::LitStr,
expr: syn::Expr,
}
struct Mode {
vis: syn::Visibility,
#[allow(dead_code)]
mode_kw: kw::mode,
name: syn::Ident,
embed: bool,
meta: Vec<Attribute>,
dfa_type: DfaType,
receiver: Receiver,
raw_ident: Option<syn::Ident>,
typ: syn::Type,
brace: Brace,
patterns: Vec<Pattern>,
}
struct Input {
context: syn::ItemStruct,
modes: Vec<Mode>,
}
impl Parse for DfaType {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(kw::dense) {
input.parse::<kw::dense>().map(|_| DfaType::Dense)
} else if input.peek(kw::sparse) {
input.parse::<kw::sparse>().map(|_| DfaType::Sparse)
} else {
Err(input.error("Invalid DFA type; must be dense or sparse"))
}
}
}
impl Parse for Mode {
fn parse(input: ParseStream) -> syn::Result<Self> {
let meta = input.call(Attribute::parse_outer)?;
let mut saved_meta = vec![];
let mut embed = true;
let mut dfa_type = DfaType::Sparse;
for attr in meta {
if !attr.path().is_ident("leqxer") {
saved_meta.push(attr);
continue;
}
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("embed") {
embed = meta.value()?.parse::<syn::LitBool>()?.value;
Ok(())
} else if meta.path.is_ident("dfa") {
dfa_type = meta.value()?.parse()?;
Ok(())
} else {
Err(meta.error("Invalid leqxer option. Valid options are dfa={sparse,dense}, embed={true,false}"))
}
})?;
}
let vis = input.parse()?;
let mode_kw = input.parse()?;
let name = input.parse()?;
let (receiver, raw_ident) = if input.peek(syn::token::Paren) {
let content;
parenthesized!(content in input);
let receiver = content.parse()?;
content.parse::<Token![,]>()?;
(receiver, Some(content.parse()?))
} else {
(syn::parse_quote!(&self), None)
};
input.parse::<Token![->]>()?;
let typ = input.parse()?;
let content;
let brace = braced!(content in input);
let mut patterns = vec![];
while !content.is_empty() {
patterns.push(content.parse()?)
}
Ok(Mode {
meta: saved_meta,
vis,
mode_kw,
name,
receiver,
raw_ident,
typ,
brace,
patterns,
dfa_type,
embed,
})
}
}
impl Parse for Pattern {
fn parse(input: ParseStream) -> syn::Result<Self> {
let pattern_tok = <syn::LitStr as Parse>::parse(input)?;
<Token![=>]>::parse(input)?;
let expr = syn::Expr::parse(input)?;
let requires_comma = matches!(
expr,
Expr::If(_)
| Expr::Match(_)
| Expr::Block(_)
| Expr::Unsafe(_)
| Expr::While(_)
| Expr::Loop(_)
| Expr::ForLoop(_)
| Expr::TryBlock(_)
| Expr::Const(_)
);
if (requires_comma && !input.is_empty()) || input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
Ok(Pattern { pattern_tok, expr })
}
}
impl Parse for Input {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut result = Input {
context: input.parse()?,
modes: vec![],
};
while !input.is_empty() {
result.modes.push(input.parse()?);
}
Ok(result)
}
}
fn leqxer_impl(input: Input) -> Result<TokenStream, syn::Error> {
let mut mode_fns = proc_macro2::TokenStream::new();
for mode in input.modes.iter() {
let mode_span = mode.brace.span.join();
let input_sym = syn::Ident::new("input", proc_macro2::Span::mixed_site());
let mut pats = proc_macro2::TokenStream::new();
let mut pat_strs = vec![];
let mut matches = proc_macro2::TokenStream::new();
let extract = if let Some(ref ident) = mode.raw_ident {
quote! {
let #ident = std::str::from_utf8(&#input_sym.haystack()[start..end]).unwrap();
}
} else {
quote!()
};
for (i, pat) in mode.patterns.iter().enumerate() {
let re = &pat.pattern_tok;
let expr = &pat.expr;
pats.extend(quote! {#re,});
pat_strs.push(pat.pattern_tok.value());
matches.extend(quote! {#i => #expr,});
}
let (dfa, dfa_ty) = match mode.dfa_type {
DfaType::Sparse => {
let dfa = {
use regex_automata::dfa::{StartKind, dense::DFA};
DFA::builder()
.configure(DFA::config().start_kind(StartKind::Anchored))
.syntax(::regex_automata::util::syntax::Config::new()
.dot_matches_new_line(true))
.build_many(pat_strs.as_slice())
.and_then(|dfa| dfa.to_sparse())
.map_err(|err| syn::Error::new(mode.brace.span.join(), err))?
};
eprintln!("mode ({mode_name}) sparse size (le={le}. be={be})",
mode_name = mode.name,
le = dfa.to_bytes_little_endian().len(),
be = dfa.to_bytes_big_endian().len(),
);
let construct = if mode.embed {
let bstr_le =
syn::LitByteStr::new(dfa.to_bytes_little_endian().as_slice(), mode_span);
let bstr_be =
syn::LitByteStr::new(dfa.to_bytes_big_endian().as_slice(), mode_span);
quote! {
unsafe{ ::regex_automata::dfa::sparse::DFA::from_bytes_unchecked(if cfg!(target_endian="big") { #bstr_be } else { #bstr_le }).unwrap().0 }
}
} else {
quote! {{
use regex_automata::dfa::{dense::DFA, StartKind};
DFA::builder()
.configure(DFA::config().start_kind(StartKind::Anchored))
.syntax(::regex_automata::util::syntax::Config::new()
.dot_matches_new_line(true))
.build_many(pat_strs.as_slice())
.and_then(|dfa| dfa.to_sparse())
.unwrap()
}}
};
(construct, quote!{ ::regex_automata::dfa::sparse::DFA<&[u8]> })
}
DfaType::Dense => {
let dfa = {
use regex_automata::dfa::{StartKind, dense::DFA};
DFA::builder()
.configure(DFA::config().start_kind(StartKind::Anchored))
.syntax(::regex_automata::util::syntax::Config::new()
.dot_matches_new_line(true))
.build_many(pat_strs.as_slice())
.map_err(|err| syn::Error::new(mode.brace.span.join(), err))
}?;
eprintln!("mode ({mode_name}) dense size (le={le}. be={be})",
mode_name = mode.name,
le = dfa.to_bytes_little_endian().0.len(),
be = dfa.to_bytes_big_endian().0.len(),
);
let construct = if mode.embed {
let bstr_le =
syn::LitByteStr::new(dfa.to_bytes_little_endian().0.as_slice(), mode_span);
let bstr_be =
syn::LitByteStr::new(dfa.to_bytes_big_endian().0.as_slice(), mode_span);
quote! {
unsafe{ ::regex_automata::dfa::dense::DFA::from_bytes_unchecked(if cfg!(target_endian="big") { #bstr_be } else { #bstr_le }) }.unwrap().0
}
} else {
quote! {{
use regex_automata::dfa::{dense::DFA, StartKind};
DFA::builder()
.configure(DFA::config().start_kind(StartKind::Anchored))
.syntax(::regex_automata::util::syntax::Config::new()
.dot_matches_new_line(true))
.build_many(pat_strs.as_slice())
.map_err(|err| syn::Error::new(mode.brace.span.join(), err))
.unwrap()
}}
};
(construct, quote!{ ::regex_automata::dfa::dense::DFA<&[u32]> })
}
};
let vis = &mode.vis;
let mode_name = &mode.name;
let mode_receiver = &mode.receiver;
let mode_ty = &mode.typ;
let mut mode_meta = proc_macro2::TokenStream::new();
for meta in &mode.meta {
meta.to_tokens(&mut mode_meta);
}
mode_fns.extend(quote! {
#mode_meta
#vis fn #mode_name(#mode_receiver, #input_sym: &mut ::leqx::__private::Input) -> Option<#mode_ty> {
use ::regex_automata::dfa::Automaton;
static RE_SET: ::leqx::__private::LazyLock<#dfa_ty> = ::leqx::__private::LazyLock::new(|| #dfa);
#input_sym.set_anchored(::regex_automata::Anchored::Yes);
let res = RE_SET.try_search_fwd(#input_sym).unwrap()?;
let start = #input_sym.start();
let end = res.offset();
#input_sym.set_start(end);
#extract
Some(match res.pattern().as_usize() {
#matches
_ => unreachable!(),
})
}
});
}
let struct_decl = &input.context;
let (struct_impl, struct_type_params, struct_where) = input.context.generics.split_for_impl();
let struct_name = &input.context.ident;
Ok(quote! {
#struct_decl
impl #struct_impl #struct_name #struct_type_params #struct_where {
#mode_fns
}
}
.into())
}
#[proc_macro]
pub fn leqxer(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as Input);
let result = leqxer_impl(input).unwrap_or_else(|err| err.into_compile_error().into());
result
}