Skip to main content

regast_core/
matcher.rs

1use std::{fmt, sync::Arc};
2
3use regast_syntax::Pattern;
4
5use crate::{
6    Backend, Disambiguation, IrKind, IrPool, Lowering, Value,
7    antimirov::partial_derivative_at,
8    deriv::{deriv_at, nullable_at},
9    inj::inj_at,
10    lower::lower,
11    mkeps::mkeps_at,
12    simp::simp,
13    tagged_nfa::TaggedNfa,
14    tree::ParseTree,
15    value_parser::parse_ir_value,
16};
17
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct NoMatch {
20    pub char_index: usize,
21    pub at_end: bool,
22}
23
24impl fmt::Display for NoMatch {
25    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
26        if self.at_end {
27            write!(formatter, "input ended before the pattern could match")
28        } else {
29            write!(
30                formatter,
31                "pattern cannot match at character {}",
32                self.char_index
33            )
34        }
35    }
36}
37
38impl std::error::Error for NoMatch {}
39
40#[derive(Clone, Debug, PartialEq, Eq)]
41pub enum MatchError {
42    NoMatch(NoMatch),
43    SizeLimitExceeded { limit: usize, states: usize },
44}
45
46impl fmt::Display for MatchError {
47    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
48        match self {
49            Self::NoMatch(error) => error.fmt(formatter),
50            Self::SizeLimitExceeded { limit, states } => write!(
51                formatter,
52                "matching state limit exceeded: {states} states (limit {limit})"
53            ),
54        }
55    }
56}
57
58impl std::error::Error for MatchError {}
59
60impl From<NoMatch> for MatchError {
61    fn from(error: NoMatch) -> Self {
62        Self::NoMatch(error)
63    }
64}
65
66#[derive(Clone, Debug, PartialEq, Eq)]
67pub struct TraceStep {
68    pub char_index: usize,
69    pub c: char,
70    pub before: String,
71    pub after: String,
72}
73
74#[derive(Clone, Debug)]
75pub struct Matcher {
76    pattern: Arc<Pattern>,
77    pool: IrPool,
78    lowering: Lowering,
79    disambiguation: Disambiguation,
80    size_limit: usize,
81    backend: Backend,
82    tagged_nfa: Option<TaggedNfa>,
83}
84
85impl Matcher {
86    #[must_use]
87    pub fn new(pattern: Pattern, disambiguation: Disambiguation, size_limit: usize) -> Self {
88        Self::new_with_backend(pattern, disambiguation, size_limit, Backend::Derivative)
89    }
90
91    #[must_use]
92    pub fn new_with_backend(
93        pattern: Pattern,
94        disambiguation: Disambiguation,
95        size_limit: usize,
96        backend: Backend,
97    ) -> Self {
98        let mut pool = IrPool::new();
99        let lowering = lower(&pattern, &mut pool, disambiguation);
100        let tagged_nfa =
101            (backend == Backend::TaggedNfa).then(|| TaggedNfa::compile(&pool, lowering.root));
102        Self {
103            pattern: Arc::new(pattern),
104            pool,
105            lowering,
106            disambiguation,
107            size_limit,
108            backend,
109            tagged_nfa,
110        }
111    }
112
113    #[must_use]
114    pub fn pattern(&self) -> &Pattern {
115        &self.pattern
116    }
117
118    #[must_use]
119    pub const fn disambiguation(&self) -> Disambiguation {
120        self.disambiguation
121    }
122
123    #[must_use]
124    pub fn lowering(&self) -> &Lowering {
125        &self.lowering
126    }
127
128    #[must_use]
129    pub fn pool(&self) -> &IrPool {
130        &self.pool
131    }
132
133    /// Parse an entire input and reconstruct its typed IR value.
134    ///
135    /// # Errors
136    ///
137    /// Returns [`MatchError::NoMatch`] when the selected backend rejects the input, and
138    /// [`MatchError::SizeLimitExceeded`] on recognition or value-reconstruction exhaustion.
139    pub fn parse_value(&mut self, input: &str) -> Result<Value, MatchError> {
140        self.parse_value_at(input, 0, input.len())
141    }
142
143    fn parse_value_at(
144        &mut self,
145        input: &str,
146        base: usize,
147        full_len: usize,
148    ) -> Result<Value, MatchError> {
149        if self.backend == Backend::Derivative && self.disambiguation == Disambiguation::Posix {
150            return self.parse_value_derivative_at(input, base, full_len);
151        }
152        let matched = match self.backend {
153            Backend::Derivative => self.try_is_match_at(input, base, full_len)?,
154            Backend::Antimirov => self.try_is_match_antimirov(input, base, full_len)?,
155            Backend::TaggedNfa => self.try_is_match_tagged_nfa(input, base, full_len)?,
156        };
157        if !matched {
158            return Err(NoMatch {
159                char_index: input.chars().count(),
160                at_end: true,
161            }
162            .into());
163        }
164        self.parse_value_direct_at(input, base, full_len)
165    }
166
167    fn parse_value_derivative_at(
168        &mut self,
169        input: &str,
170        base: usize,
171        full_len: usize,
172    ) -> Result<Value, MatchError> {
173        let mut trace = Vec::with_capacity(input.chars().count());
174        let mut root = self.lowering.root;
175        for (index, (byte_offset, c)) in input.char_indices().enumerate() {
176            let position = base + byte_offset;
177            let derivative = deriv_at(&mut self.pool, root, c, position, full_len);
178            let (simple, rect) = simp(&mut self.pool, derivative);
179            trace.push((root, c, rect, position));
180            root = simple;
181            if root == self.pool.zero() {
182                return Err(NoMatch {
183                    char_index: index,
184                    at_end: false,
185                }
186                .into());
187            }
188            if self.pool.len() > self.size_limit {
189                return Err(MatchError::SizeLimitExceeded {
190                    limit: self.size_limit,
191                    states: self.pool.len(),
192                });
193            }
194        }
195        let final_position = base + input.len();
196        let (final_root, final_rect) = simp(&mut self.pool, root);
197        if !nullable_at(&mut self.pool, final_root, final_position, full_len) {
198            return Err(NoMatch {
199                char_index: input.chars().count(),
200                at_end: true,
201            }
202            .into());
203        }
204        let final_value = mkeps_at(&mut self.pool, final_root, final_position, full_len);
205        let mut value = self.pool.rect(final_rect).apply(&self.pool, final_value);
206        for (previous, c, rect, position) in trace.into_iter().rev() {
207            value = self.pool.rect(rect).apply(&self.pool, value);
208            value = inj_at(&mut self.pool, previous, c, value, position, full_len);
209        }
210        debug_assert!(value.typed_by(&self.pool, self.lowering.root));
211        debug_assert_eq!(value.flatten(), input);
212        Ok(value)
213    }
214
215    fn parse_value_direct_at(
216        &self,
217        input: &str,
218        base: usize,
219        full_len: usize,
220    ) -> Result<Value, MatchError> {
221        let value = parse_ir_value(
222            &self.pool,
223            self.lowering.root,
224            input,
225            base,
226            full_len,
227            self.disambiguation,
228            self.size_limit,
229        )
230        .map_err(|states| MatchError::SizeLimitExceeded {
231            limit: self.size_limit,
232            states,
233        })?
234        .ok_or_else(|| {
235            MatchError::from(NoMatch {
236                char_index: input.chars().count(),
237                at_end: true,
238            })
239        })?;
240        debug_assert!(
241            value.typed_by(&self.pool, self.lowering.root),
242            "direct value {value:?} is not typed by {:?}",
243            self.pool.kind(self.lowering.root)
244        );
245        debug_assert_eq!(value.flatten(), input);
246        Ok(value)
247    }
248
249    /// Parse an entire input and expose the derivation in AST vocabulary.
250    ///
251    /// # Errors
252    ///
253    /// Returns [`MatchError::NoMatch`] if the entire input is not in the pattern's language, and
254    /// [`MatchError::SizeLimitExceeded`] on resource exhaustion.
255    pub fn parse(&mut self, input: &str) -> Result<ParseTree, MatchError> {
256        let value = self.parse_value(input)?;
257        Ok(ParseTree::build(
258            Arc::clone(&self.pattern),
259            &self.lowering,
260            &self.pool,
261            value,
262            input,
263            self.disambiguation,
264        ))
265    }
266
267    /// Find and parse the leftmost-longest matching substring.
268    ///
269    /// # Errors
270    ///
271    /// Returns [`MatchError::SizeLimitExceeded`] if the matching-state limit is exceeded.
272    pub fn find_parse(&mut self, input: &str) -> Result<Option<ParseTree>, MatchError> {
273        let mut boundaries: Vec<_> = input.char_indices().map(|(index, _)| index).collect();
274        boundaries.push(input.len());
275        for &start in &boundaries {
276            for &end in boundaries.iter().rev() {
277                if end < start {
278                    break;
279                }
280                let matched = &input[start..end];
281                if !self.try_is_match_at(matched, start, input.len())? {
282                    continue;
283                }
284                let value = match (self.backend, self.disambiguation) {
285                    (Backend::Derivative, Disambiguation::Posix) => {
286                        self.parse_value_derivative_at(matched, start, input.len())?
287                    }
288                    (Backend::Derivative, Disambiguation::Greedy)
289                    | (Backend::Antimirov | Backend::TaggedNfa, _) => {
290                        self.parse_value_direct_at(matched, start, input.len())?
291                    }
292                };
293                return Ok(Some(ParseTree::build_at(
294                    Arc::clone(&self.pattern),
295                    &self.lowering,
296                    &self.pool,
297                    value,
298                    matched,
299                    input,
300                    start,
301                    self.disambiguation,
302                )));
303            }
304        }
305        Ok(None)
306    }
307
308    /// Check an entire input without discarding resource-limit failures.
309    ///
310    /// # Errors
311    ///
312    /// Returns [`MatchError::SizeLimitExceeded`] if the matching-state limit is exceeded.
313    pub fn try_is_match(&mut self, input: &str) -> Result<bool, MatchError> {
314        self.try_is_match_at(input, 0, input.len())
315    }
316
317    fn try_is_match_at(
318        &mut self,
319        input: &str,
320        base: usize,
321        full_len: usize,
322    ) -> Result<bool, MatchError> {
323        match self.backend {
324            Backend::Antimirov => return self.try_is_match_antimirov(input, base, full_len),
325            Backend::TaggedNfa => return self.try_is_match_tagged_nfa(input, base, full_len),
326            Backend::Derivative => {}
327        }
328        let mut root = self.lowering.root;
329        for (byte_offset, c) in input.char_indices() {
330            let derivative = deriv_at(&mut self.pool, root, c, base + byte_offset, full_len);
331            root = simp(&mut self.pool, derivative).0;
332            if matches!(self.pool.kind(root), IrKind::Zero) {
333                return Ok(false);
334            }
335            if self.pool.len() > self.size_limit {
336                return Err(MatchError::SizeLimitExceeded {
337                    limit: self.size_limit,
338                    states: self.pool.len(),
339                });
340            }
341        }
342        let final_root = simp(&mut self.pool, root).0;
343        Ok(nullable_at(
344            &mut self.pool,
345            final_root,
346            base + input.len(),
347            full_len,
348        ))
349    }
350
351    fn try_is_match_antimirov(
352        &mut self,
353        input: &str,
354        base: usize,
355        full_len: usize,
356    ) -> Result<bool, MatchError> {
357        let mut states = vec![self.lowering.root];
358        for (byte_offset, c) in input.char_indices() {
359            let mut next = Vec::new();
360            for state in states {
361                next.extend(partial_derivative_at(
362                    &mut self.pool,
363                    state,
364                    c,
365                    base + byte_offset,
366                    full_len,
367                ));
368            }
369            next.sort_unstable_by_key(|state| state.index());
370            next.dedup();
371            if next.is_empty() {
372                return Ok(false);
373            }
374            states = next;
375            self.check_size_limit()?;
376        }
377        let position = base + input.len();
378        Ok(states
379            .into_iter()
380            .any(|state| nullable_at(&mut self.pool, state, position, full_len)))
381    }
382
383    fn try_is_match_tagged_nfa(
384        &self,
385        input: &str,
386        base: usize,
387        full_len: usize,
388    ) -> Result<bool, MatchError> {
389        let nfa = self
390            .tagged_nfa
391            .as_ref()
392            .expect("tagged NFA is compiled for its backend");
393        let states = nfa.state_count();
394        if states > self.size_limit {
395            return Err(MatchError::SizeLimitExceeded {
396                limit: self.size_limit,
397                states,
398            });
399        }
400        Ok(nfa.is_match(&self.pool, input, base, full_len))
401    }
402
403    fn check_size_limit(&self) -> Result<(), MatchError> {
404        if self.pool.len() <= self.size_limit {
405            return Ok(());
406        }
407        Err(MatchError::SizeLimitExceeded {
408            limit: self.size_limit,
409            states: self.pool.len(),
410        })
411    }
412
413    pub fn is_match(&mut self, input: &str) -> bool {
414        self.try_is_match(input).unwrap_or(false)
415    }
416
417    pub fn trace(&mut self, input: &str) -> Vec<TraceStep> {
418        let mut result = Vec::new();
419        let mut root = self.lowering.root;
420        for (index, (byte_offset, c)) in input.char_indices().enumerate() {
421            let before = self.pool.display(root);
422            let derivative = deriv_at(&mut self.pool, root, c, byte_offset, input.len());
423            root = simp(&mut self.pool, derivative).0;
424            result.push(TraceStep {
425                char_index: index,
426                c,
427                before,
428                after: self.pool.display(root),
429            });
430        }
431        result
432    }
433}
434
435#[cfg(test)]
436#[path = "matcher_tests.rs"]
437mod tests;