Skip to main content

pattern_emit/
search.rs

1use core::fmt;
2use regex_automata::dfa::{sparse::DFA, Automaton};
3use regex_automata::Input;
4use spin::Once;
5
6/// Why a pattern can't answer: its table failed the load check, or a search gave up.
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub struct Unusable(&'static str);
9
10impl fmt::Display for Unusable {
11    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
12        f.write_str(self.0)
13    }
14}
15
16impl core::error::Error for Unusable {}
17
18/// One pattern's table, checked once and kept.
19///
20/// `static P: Pattern = Pattern::new(&[..]);` with the bytes `build` returned.
21pub struct Pattern {
22    bytes: &'static [u8],
23    dfa: Once<Result<DFA<&'static [u8]>, Unusable>>,
24}
25
26impl Pattern {
27    pub const fn new(bytes: &'static [u8]) -> Self {
28        Pattern {
29            bytes,
30            dfa: Once::new(),
31        }
32    }
33
34    /// Checks the table and keeps it, so a search never does. A caller checks each table once,
35    /// before its first search.
36    pub fn load(&self) -> Result<(), Unusable> {
37        self.dfa().map(|_| ())
38    }
39
40    fn dfa(&self) -> Result<&DFA<&'static [u8]>, Unusable> {
41        self.dfa
42            .call_once(|| {
43                DFA::from_bytes(self.bytes)
44                    .map(|(dfa, _)| dfa)
45                    .map_err(|_| Unusable("pattern table failed its check"))
46            })
47            .as_ref()
48            .map_err(|e| *e)
49    }
50
51    /// Whether the pattern matches anywhere in `s`, as RE2's `MatchString` does.
52    ///
53    /// A corrupt table or a search the table can't finish is an `Err`, never a panic. A table
54    /// [`Pattern::load`] has not checked is checked now.
55    pub fn is_match(&self, s: &str) -> Result<bool, Unusable> {
56        self.dfa()?
57            .try_search_fwd(&Input::new(s).earliest(true))
58            .map(|m| m.is_some())
59            .map_err(|_| Unusable("pattern search gave up"))
60    }
61}