Skip to main content

humblegen_rt/
regexset_map.rs

1//! `GEN,SERVER` - `RegexSetMap` maps a `s: &str` and some generic input `i: I` to an `Entry<I>`.
2//!
3//! An entry is a match candidate if
4//!
5//! - `.regex()` must match `s` and
6//! - `.matches_input(i(` must return true
7//!
8//! The `GetResult` contains a reference to the matching entry.
9
10use core::fmt;
11
12/// Refer to module-level docs.
13pub struct RegexSetMap<I, T: Entry<I>> {
14    set: regex::RegexSet,
15    entries: Vec<T>,
16    _marker: std::marker::PhantomData<I>,
17}
18
19/// Refer to module-level docs.
20pub trait Entry<I> {
21    fn regex(&self) -> &regex::Regex;
22    fn matches_input(&self, i: &I) -> bool;
23}
24
25impl<I, T: Entry<I>> Entry<I> for (regex::Regex, T) {
26    fn regex(&self) -> &regex::Regex {
27        &self.0
28    }
29    fn matches_input(&self, i: &I) -> bool {
30        self.1.matches_input(i)
31    }
32}
33
34impl<I, T: Entry<I>> fmt::Debug for RegexSetMap<I, T> {
35    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
36        formatter
37            .debug_struct("RegexSetMap")
38            .field("set.patterns", &self.set.patterns())
39            .field("entries", &"<NoTraitSpecialization>")
40            .finish()
41    }
42}
43
44/// Refer to module-level docs.
45#[derive(Debug)]
46pub enum GetResult<'a, T> {
47    None,
48    One(&'a T),
49    Ambiguous,
50}
51
52impl<I, T: Entry<I>> RegexSetMap<I, T> {
53    /// Refer to module-level docs.
54    pub fn new(entries: Vec<T>) -> Result<Self, regex::Error> {
55        let set = regex::RegexSet::new(entries.iter().map(|r| r.regex().as_str())).unwrap();
56        Ok(Self {
57            set,
58            entries,
59            _marker: std::marker::PhantomData::default(),
60        })
61    }
62
63    /// Refer to module-level docs.
64    pub fn get(&self, s: &str, input: &I) -> GetResult<'_, T> {
65        let mut matching_route_idxs = self
66            .set
67            .matches(s)
68            .into_iter()
69            .filter(|matching_idx| self.entries[*matching_idx].matches_input(input))
70            .peekable();
71
72        let matching_route_idx = matching_route_idxs.next();
73        let next_matching_route_idx = matching_route_idxs.peek();
74
75        let matching_idx = match (matching_route_idx, next_matching_route_idx) {
76            (Some(idx), None) => idx,
77            (None, s @ Some(_)) => {
78                unreachable!("peek after next() == None always returns None, got {:?}", s)
79            }
80            (None, None) => {
81                return GetResult::None;
82            }
83            (Some(_), Some(_)) => {
84                return GetResult::Ambiguous;
85            }
86        };
87
88        GetResult::One(&self.entries[matching_idx])
89    }
90}