harper_core/expr/
repeating.rs

1use super::Expr;
2use crate::{Span, Token};
3
4/// An expression that will match one or more repetitions of the same expression.
5///
6/// Somewhat reminiscent of the `+*` operator in Regex.
7pub struct Repeating {
8    inner: Box<dyn Expr>,
9    required_repetitions: usize,
10}
11
12impl Repeating {
13    pub fn new(expr: Box<dyn Expr>, required_repetitions: usize) -> Self {
14        Self {
15            inner: expr,
16            required_repetitions,
17        }
18    }
19}
20
21impl Expr for Repeating {
22    fn run(&self, mut cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span<Token>> {
23        let mut window = Span::new_with_len(cursor, 0);
24        let mut repetition = 0;
25
26        loop {
27            let res = self.inner.run(cursor, tokens, source);
28
29            if let Some(res) = res {
30                window.expand_to_include(res.start);
31                window.expand_to_include(res.end - 1);
32
33                if res.start < cursor {
34                    cursor = res.start;
35                } else {
36                    cursor = res.end;
37                }
38
39                if res.is_empty() {
40                    return Some(window);
41                }
42
43                repetition += 1;
44            } else if repetition >= self.required_repetitions {
45                return Some(window);
46            } else {
47                return None;
48            }
49        }
50    }
51}
52
53#[cfg(test)]
54mod tests {
55
56    use super::Repeating;
57    use crate::expr::{ExprExt, SequenceExpr};
58    use crate::patterns::AnyPattern;
59    use crate::{Document, Span};
60
61    #[test]
62    fn matches_anything() {
63        let doc = Document::new_plain_english_curated(
64            "This matcher will match the entirety of any document!",
65        );
66        let pat = Repeating::new(Box::new(SequenceExpr::from(AnyPattern)), 0);
67
68        assert_eq!(
69            pat.iter_matches(doc.get_tokens(), doc.get_source()).next(),
70            Some(Span::new(0, doc.get_tokens().len()))
71        )
72    }
73
74    #[test]
75    fn does_not_match_short() {
76        let doc = Document::new_plain_english_curated("No match");
77        let pat = Repeating::new(Box::new(SequenceExpr::from(AnyPattern)), 4);
78
79        assert_eq!(
80            pat.iter_matches(doc.get_tokens(), doc.get_source()).next(),
81            None
82        )
83    }
84}