litcheck_filecheck/pattern/search/
substring_set.rs

1use aho_corasick::{AhoCorasick, AhoCorasickBuilder, MatchKind, StartKind};
2
3use crate::common::*;
4
5use super::AhoCorasickSearcher;
6
7pub struct SubstringSetSearcher<'a, 'patterns, 'input> {
8    buffer: &'input [u8],
9    crlf: bool,
10    /// The set of raw input patterns from which
11    /// this matcher was constructed
12    patterns: Cow<'patterns, Vec<Span<Cow<'a, str>>>>,
13    /// The compiled regex which will be used to search the input buffer
14    pattern: AhoCorasick,
15    /// The searcher used to maintain the search state in the buffer
16    searcher: AhoCorasickSearcher<'input>,
17}
18impl<'a, 'patterns, 'input> SubstringSetSearcher<'a, 'patterns, 'input> {
19    pub fn new(
20        input: Input<'input>,
21        patterns: Cow<'patterns, Vec<Span<Cow<'a, str>>>>,
22    ) -> DiagResult<Self> {
23        let buffer = input.buffer();
24        let crlf = input.is_crlf();
25        let mut builder = AhoCorasickBuilder::new();
26        builder
27            .ascii_case_insensitive(false)
28            .match_kind(MatchKind::LeftmostLongest)
29            .start_kind(StartKind::Unanchored);
30        let pattern = builder
31            .build(patterns.iter().map(|p| p.as_bytes()))
32            .map_err(|err| {
33                let labels = patterns
34                    .iter()
35                    .map(|s| Label::new(s.span(), err.to_string()).into());
36                let diag = Diag::new("failed to build substring set searcher")
37                    .and_labels(labels)
38                    .with_help(format!(
39                        "search configuration: {:?}, {:?}",
40                        MatchKind::LeftmostLongest,
41                        StartKind::Unanchored
42                    ));
43                Report::from(diag)
44            })?;
45
46        let searcher = AhoCorasickSearcher::new(input.into());
47
48        Ok(Self {
49            buffer,
50            crlf,
51            patterns,
52            pattern,
53            searcher,
54        })
55    }
56
57    pub fn input(&self) -> Input<'input> {
58        let input = self.searcher.input();
59        Input::new(self.buffer, self.crlf)
60            .span(input.get_range())
61            .anchored(input.get_anchored().is_anchored())
62    }
63
64    pub fn num_patterns(&self) -> usize {
65        self.patterns.len()
66    }
67
68    pub fn pattern_span(&self, index: usize) -> SourceSpan {
69        self.patterns[index].span()
70    }
71}
72impl<'a, 'patterns, 'input> fmt::Debug for SubstringSetSearcher<'a, 'patterns, 'input> {
73    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
74        f.debug_struct("SubstringSetSearcher")
75            .field("patterns", &self.patterns)
76            .field("kind", &self.pattern.kind())
77            .field("start_kind", &self.pattern.start_kind())
78            .field("match_kind", &self.pattern.match_kind())
79            .finish()
80    }
81}
82
83impl<'a, 'patterns, 'input> Spanned for SubstringSetSearcher<'a, 'patterns, 'input> {
84    fn span(&self) -> SourceSpan {
85        let start = self.patterns.iter().map(|p| p.start()).min().unwrap();
86        let end = self.patterns.iter().map(|p| p.end()).max().unwrap();
87        SourceSpan::from(start..end)
88    }
89}
90impl<'a, 'patterns, 'input> PatternSearcher<'input>
91    for SubstringSetSearcher<'a, 'patterns, 'input>
92{
93    type Input = aho_corasick::Input<'input>;
94    type PatternID = aho_corasick::PatternID;
95
96    fn input(&self) -> &Self::Input {
97        self.searcher.input()
98    }
99    fn last_match_end(&self) -> Option<usize> {
100        self.searcher.last_match_end()
101    }
102    fn set_last_match_end(&mut self, end: usize) {
103        self.searcher.set_last_match_end(end);
104    }
105    fn patterns_len(&self) -> usize {
106        self.num_patterns()
107    }
108    fn pattern_span(&self, id: Self::PatternID) -> SourceSpan {
109        SubstringSetSearcher::pattern_span(self, id.as_usize())
110    }
111    fn try_match_next<'context, C>(&mut self, context: &mut C) -> DiagResult<MatchResult<'input>>
112    where
113        C: Context<'input, 'context> + ?Sized,
114    {
115        use super::Input as SearchInput;
116        let result = self
117            .searcher
118            .advance(|input| self.pattern.try_find(input.as_input()));
119        if let Some(matched) = result {
120            let pattern_id = matched.pattern().as_usize();
121            let pattern_span = self.patterns[pattern_id].span();
122            Ok(MatchResult::ok(MatchInfo::new_with_pattern(
123                matched.range(),
124                pattern_span,
125                pattern_id,
126            )))
127        } else {
128            Ok(MatchResult::failed(
129                CheckFailedError::MatchNoneButExpected {
130                    span: self.span(),
131                    match_file: context.match_file(),
132                    note: None,
133                },
134            ))
135        }
136    }
137}