use crate::{
Result,
dfa::{Dfa, DfaWithNumberOfCharacterClasses},
ids::{TerminalID, TerminalIDBase},
nfa::Nfa,
};
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
macro_rules! parse_ident {
($input:ident, $name:ident) => {
$input.parse().map_err(|e| {
syn::Error::new(
e.span(),
concat!("expected identifier `", stringify!($name), "`"),
)
})?
};
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AutomatonType {
Nfa(Nfa),
Dfa(Dfa),
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub enum Lookahead {
#[default]
None,
Positive(AutomatonType),
Negative(AutomatonType),
}
impl Lookahead {
pub fn positive(pattern: String) -> Result<Self> {
let nfa = Nfa::build(&Pattern::new(pattern, TerminalIDBase::MAX.into()))
.map_err(|e| format!("Failed to create NFA from regex pattern: {e}"))?;
Ok(Lookahead::Positive(AutomatonType::Nfa(nfa)))
}
pub fn negative(pattern: String) -> Result<Self> {
let nfa = Nfa::build(&Pattern::new(pattern, TerminalIDBase::MAX.into()))
.map_err(|e| format!("Failed to create NFA from regex pattern: {e}"))?;
Ok(Lookahead::Negative(AutomatonType::Nfa(nfa)))
}
}
impl syn::parse::Parse for Lookahead {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let followed_or_not: syn::Ident = parse_ident!(input, followed_or_not);
if followed_or_not != "followed" && followed_or_not != "not" {
return Err(input.error("expected 'followed' or 'not'"));
}
let mut is_positive = true;
if followed_or_not == "not" {
is_positive = false;
let followed: syn::Ident = parse_ident!(input, followed);
if followed != "followed" {
return Err(input.error("expected 'followed'"));
}
}
let by: syn::Ident = parse_ident!(input, by);
if by != "by" {
return Err(input.error("expected 'by'"));
}
let pattern: syn::LitStr = input.parse().map_err(|e| {
syn::Error::new(
e.span(),
"expected a string literal for the lookahead pattern",
)
})?;
let pattern = pattern.value();
Ok(if is_positive {
Lookahead::positive(pattern).map_err(|e| {
syn::Error::new(
input.span(),
format!("Failed to create positive lookahead: {e}"),
)
})?
} else {
Lookahead::negative(pattern).map_err(|e| {
syn::Error::new(
input.span(),
format!("Failed to create negative lookahead: {e}"),
)
})?
})
}
}
#[derive(Debug)]
pub(crate) struct LookaheadWithNumberOfCharacterClasses {
pub lookahead: Lookahead,
pub character_classes: usize,
}
impl LookaheadWithNumberOfCharacterClasses {
pub fn new(lookahead: Lookahead, character_classes: usize) -> Self {
Self {
lookahead,
character_classes,
}
}
}
impl ToTokens for LookaheadWithNumberOfCharacterClasses {
fn to_tokens(&self, tokens: &mut TokenStream) {
let lookahead_tokens = match &self.lookahead {
Lookahead::None => quote! { Lookahead::None },
Lookahead::Positive(AutomatonType::Dfa(dfa)) => {
let dfa_with_classes =
DfaWithNumberOfCharacterClasses::new(dfa, self.character_classes);
quote! { Lookahead::Positive(#dfa_with_classes) }
}
Lookahead::Negative(AutomatonType::Dfa(dfa)) => {
let dfa_with_classes =
DfaWithNumberOfCharacterClasses::new(dfa, self.character_classes);
quote! { Lookahead::Negative(#dfa_with_classes) }
}
_ => panic!("Unexpected lookahead type in Lookahead: {self:?}"),
};
tokens.extend(lookahead_tokens);
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Pattern {
pub pattern: String,
pub terminal_type: TerminalID,
pub priority: usize,
pub lookahead: Lookahead,
}
impl Pattern {
pub fn new(pattern: String, terminal_type: TerminalID) -> Self {
const DEFAULT_PRIORITY: usize = 0;
Self {
pattern,
terminal_type,
priority: DEFAULT_PRIORITY,
lookahead: Lookahead::None,
}
}
pub fn with_lookahead(mut self, lookahead: Lookahead) -> Self {
self.lookahead = lookahead;
self
}
pub fn with_priority(mut self, priority: usize) -> Self {
self.priority = priority;
self
}
}
impl syn::parse::Parse for Pattern {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let pattern: syn::LitStr = input.parse().map_err(|e| {
syn::Error::new(
e.span(),
format!("expected a string literal for the pattern: {input:?}"),
)
})?;
let pattern = pattern.value();
let mut lookahead: Option<Lookahead> = None;
if input.peek(syn::Ident) {
lookahead = Some(input.parse()?);
}
input.parse::<syn::Token![=>]>()?;
let token_type: syn::LitInt = input.parse()?;
let token_type: TerminalIDBase = token_type.base10_parse()?;
let mut pattern = Pattern::new(pattern, token_type.into());
if input.peek(syn::Token![;]) {
input.parse::<syn::Token![;]>()?;
} else {
return Err(input.error("expected ';'"));
}
let lookahead = lookahead.unwrap_or(Lookahead::None);
pattern = pattern.with_lookahead(lookahead);
Ok(pattern)
}
}
#[derive(Debug)]
pub(crate) struct PatternWithNumberOfCharacterClasses<'a> {
pub pattern: &'a Pattern,
pub character_classes: usize,
}
impl<'a> PatternWithNumberOfCharacterClasses<'a> {
pub fn new(pattern: &'a Pattern, character_classes: usize) -> Self {
Self {
pattern,
character_classes,
}
}
}
impl ToTokens for PatternWithNumberOfCharacterClasses<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let PatternWithNumberOfCharacterClasses {
pattern,
character_classes,
} = self;
let terminal_type = pattern.terminal_type.as_usize().to_token_stream();
let priority = pattern.priority.to_token_stream();
let lookahead_with_number_of_character_classes = LookaheadWithNumberOfCharacterClasses::new(
pattern.lookahead.clone(),
*character_classes,
);
tokens.extend(quote! {
AcceptData {
token_type: #terminal_type,
priority: #priority,
lookahead: #lookahead_with_number_of_character_classes,
}
});
}
}