harper_core/patterns/
repeating_pattern.rs

1use super::Pattern;
2use crate::Token;
3
4/// A pattern that will match one or more repetitions of the same pattern.
5///
6/// Somewhat reminiscent of the `.*` operator in Regex.
7pub struct RepeatingPattern {
8    inner: Box<dyn Pattern>,
9    required_repetitions: usize,
10}
11
12impl RepeatingPattern {
13    pub fn new(pattern: Box<dyn Pattern>, required_repetitions: usize) -> Self {
14        Self {
15            inner: pattern,
16            required_repetitions,
17        }
18    }
19}
20
21impl Pattern for RepeatingPattern {
22    fn matches(&self, tokens: &[Token], source: &[char]) -> Option<usize> {
23        let mut tok_cursor = 0;
24        let mut repetition = 0;
25
26        loop {
27            let match_len = self.inner.matches(&tokens[tok_cursor..], source);
28
29            if let Some(match_len) = match_len {
30                if match_len == 0 {
31                    // If match_len == 0, we won't move forward ever again.
32                    // This means that we can get infinitely many repetitions,
33                    // so repetition >= self.required_repetitions is guaranteed.
34                    return Some(tok_cursor);
35                }
36
37                tok_cursor += match_len;
38                repetition += 1;
39            } else if repetition >= self.required_repetitions {
40                return Some(tok_cursor);
41            } else {
42                return None;
43            }
44        }
45    }
46}
47
48#[cfg(test)]
49mod tests {
50
51    use super::RepeatingPattern;
52    use crate::Document;
53    use crate::patterns::{AnyPattern, Pattern};
54
55    #[test]
56    fn matches_anything() {
57        let doc = Document::new_plain_english_curated(
58            "This matcher will match the entirety of any document!",
59        );
60        let pat = RepeatingPattern::new(Box::new(AnyPattern), 0);
61
62        assert_eq!(
63            pat.matches(doc.get_tokens(), doc.get_source()),
64            Some(doc.get_tokens().len())
65        )
66    }
67
68    #[test]
69    fn does_not_match_short() {
70        let doc = Document::new_plain_english_curated("No match");
71        let pat = RepeatingPattern::new(Box::new(AnyPattern), 4);
72
73        assert_eq!(pat.matches(doc.get_tokens(), doc.get_source()), None)
74    }
75}