scnr2_generate/
pattern.rs1use crate::{
5 Result,
6 dfa::{Dfa, DfaWithNumberOfCharacterClasses},
7 ids::{TerminalID, TerminalIDBase},
8 nfa::Nfa,
9};
10use proc_macro2::TokenStream;
11use quote::{ToTokens, quote};
12
13macro_rules! parse_ident {
14 ($input:ident, $name:ident) => {
15 $input.parse().map_err(|e| {
16 syn::Error::new(
17 e.span(),
18 concat!("expected identifier `", stringify!($name), "`"),
19 )
20 })?
21 };
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
26pub enum AutomatonType {
27 Nfa(Nfa),
29 Dfa(Dfa),
31}
32
33#[derive(Debug, Default, Clone, PartialEq, Eq)]
37pub enum Lookahead {
38 #[default]
40 None,
41 Positive(AutomatonType),
44 Negative(AutomatonType),
47}
48
49impl Lookahead {
50 pub fn positive(pattern: String) -> Result<Self> {
55 let nfa = Nfa::build(&Pattern::new(pattern, TerminalIDBase::MAX.into()))
58 .map_err(|e| format!("Failed to create NFA from regex pattern: {e}"))?;
59 Ok(Lookahead::Positive(AutomatonType::Nfa(nfa)))
60 }
61
62 pub fn negative(pattern: String) -> Result<Self> {
67 let nfa = Nfa::build(&Pattern::new(pattern, TerminalIDBase::MAX.into()))
70 .map_err(|e| format!("Failed to create NFA from regex pattern: {e}"))?;
71 Ok(Lookahead::Negative(AutomatonType::Nfa(nfa)))
72 }
73}
74
75impl syn::parse::Parse for Lookahead {
87 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
88 let followed_or_not: syn::Ident = parse_ident!(input, followed_or_not);
89 if followed_or_not != "followed" && followed_or_not != "not" {
90 return Err(input.error("expected 'followed' or 'not'"));
91 }
92 let mut is_positive = true;
93 if followed_or_not == "not" {
94 is_positive = false;
95 let followed: syn::Ident = parse_ident!(input, followed);
96 if followed != "followed" {
97 return Err(input.error("expected 'followed'"));
98 }
99 }
100 let by: syn::Ident = parse_ident!(input, by);
103 if by != "by" {
104 return Err(input.error("expected 'by'"));
105 }
106 let pattern: syn::LitStr = input.parse().map_err(|e| {
108 syn::Error::new(
109 e.span(),
110 "expected a string literal for the lookahead pattern",
111 )
112 })?;
113 let pattern = pattern.value();
114 Ok(if is_positive {
115 Lookahead::positive(pattern).map_err(|e| {
116 syn::Error::new(
117 input.span(),
118 format!("Failed to create positive lookahead: {e}"),
119 )
120 })?
121 } else {
122 Lookahead::negative(pattern).map_err(|e| {
123 syn::Error::new(
124 input.span(),
125 format!("Failed to create negative lookahead: {e}"),
126 )
127 })?
128 })
129 }
130}
131
132#[derive(Debug)]
133pub(crate) struct LookaheadWithNumberOfCharacterClasses {
134 pub lookahead: Lookahead,
136 pub character_classes: usize,
138}
139
140impl LookaheadWithNumberOfCharacterClasses {
141 pub fn new(lookahead: Lookahead, character_classes: usize) -> Self {
144 Self {
145 lookahead,
146 character_classes,
147 }
148 }
149}
150
151impl ToTokens for LookaheadWithNumberOfCharacterClasses {
152 fn to_tokens(&self, tokens: &mut TokenStream) {
153 let lookahead_tokens = match &self.lookahead {
154 Lookahead::None => quote! { Lookahead::None },
155 Lookahead::Positive(AutomatonType::Dfa(dfa)) => {
156 let dfa_with_classes =
157 DfaWithNumberOfCharacterClasses::new(dfa, self.character_classes);
158 quote! { Lookahead::Positive(#dfa_with_classes) }
159 }
160 Lookahead::Negative(AutomatonType::Dfa(dfa)) => {
161 let dfa_with_classes =
162 DfaWithNumberOfCharacterClasses::new(dfa, self.character_classes);
163 quote! { Lookahead::Negative(#dfa_with_classes) }
164 }
165 _ => panic!("Unexpected lookahead type in Lookahead: {self:?}"),
166 };
167 tokens.extend(lookahead_tokens);
168 }
169}
170
171#[derive(Debug, Default, Clone, PartialEq, Eq)]
173pub struct Pattern {
174 pub pattern: String,
176 pub terminal_type: TerminalID,
178 pub priority: usize,
181 pub lookahead: Lookahead,
183}
184
185impl Pattern {
186 pub fn new(pattern: String, terminal_type: TerminalID) -> Self {
192 const DEFAULT_PRIORITY: usize = 0;
193 Self {
194 pattern,
195 terminal_type,
196 priority: DEFAULT_PRIORITY,
197 lookahead: Lookahead::None,
198 }
199 }
200
201 pub fn with_lookahead(mut self, lookahead: Lookahead) -> Self {
205 self.lookahead = lookahead;
206 self
207 }
208
209 pub fn with_priority(mut self, priority: usize) -> Self {
213 self.priority = priority;
214 self
215 }
216}
217
218impl syn::parse::Parse for Pattern {
238 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
239 let pattern: syn::LitStr = input.parse().map_err(|e| {
240 syn::Error::new(
241 e.span(),
242 format!("expected a string literal for the pattern: {input:?}"),
243 )
244 })?;
245 let pattern = pattern.value();
246 let mut lookahead: Option<Lookahead> = None;
247 if input.peek(syn::Ident) {
249 lookahead = Some(input.parse()?);
253 }
254 input.parse::<syn::Token![=>]>()?;
255 let token_type: syn::LitInt = input.parse()?;
256 let token_type: TerminalIDBase = token_type.base10_parse()?;
257 let mut pattern = Pattern::new(pattern, token_type.into());
258 if input.peek(syn::Token![;]) {
260 input.parse::<syn::Token![;]>()?;
261 } else {
262 return Err(input.error("expected ';'"));
263 }
264
265 let lookahead = lookahead.unwrap_or(Lookahead::None);
267 pattern = pattern.with_lookahead(lookahead);
268
269 Ok(pattern)
270 }
271}
272
273#[derive(Debug)]
274pub(crate) struct PatternWithNumberOfCharacterClasses<'a> {
275 pub pattern: &'a Pattern,
277 pub character_classes: usize,
279}
280
281impl<'a> PatternWithNumberOfCharacterClasses<'a> {
282 pub fn new(pattern: &'a Pattern, character_classes: usize) -> Self {
285 Self {
286 pattern,
287 character_classes,
288 }
289 }
290}
291
292impl ToTokens for PatternWithNumberOfCharacterClasses<'_> {
293 fn to_tokens(&self, tokens: &mut TokenStream) {
294 let PatternWithNumberOfCharacterClasses {
295 pattern,
296 character_classes,
297 } = self;
298 let terminal_type = pattern.terminal_type.as_usize().to_token_stream();
299 let priority = pattern.priority.to_token_stream();
300 let lookahead_with_number_of_character_classes = LookaheadWithNumberOfCharacterClasses::new(
301 pattern.lookahead.clone(),
302 *character_classes,
303 );
304 tokens.extend(quote! {
305 AcceptData {
306 token_type: #terminal_type,
307 priority: #priority,
308 lookahead: #lookahead_with_number_of_character_classes,
309 }
310 });
311 }
312}