Skip to main content

fff_grep/
matcher.rs

1//! Matcher trait inspired by ripgrep's `Matcher` just simpler
2
3/// A byte range representing a match.
4#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
5pub struct Match {
6    start: usize,
7    end: usize,
8}
9
10impl Match {
11    /// Create a new match from start/end byte offsets.
12    #[inline]
13    pub fn new(start: usize, end: usize) -> Match {
14        debug_assert!(start <= end);
15        Match { start, end }
16    }
17
18    /// Create a zero-width match at `offset`.
19    #[inline]
20    pub fn zero(offset: usize) -> Match {
21        Match {
22            start: offset,
23            end: offset,
24        }
25    }
26
27    /// Start byte offset.
28    #[inline]
29    pub fn start(&self) -> usize {
30        self.start
31    }
32
33    /// End byte offset (exclusive).
34    #[inline]
35    pub fn end(&self) -> usize {
36        self.end
37    }
38
39    /// Return a copy with a different end offset.
40    #[inline]
41    pub fn with_end(&self, end: usize) -> Match {
42        debug_assert!(self.start <= end);
43        Match { end, ..*self }
44    }
45
46    /// Shift both offsets forward by `amount`.
47    #[inline]
48    pub fn offset(&self, amount: usize) -> Match {
49        Match {
50            start: self.start + amount,
51            end: self.end + amount,
52        }
53    }
54
55    /// Byte length of the match.
56    #[inline]
57    pub fn len(&self) -> usize {
58        self.end - self.start
59    }
60
61    /// True if this is a zero-width match.
62    #[inline]
63    pub fn is_empty(&self) -> bool {
64        self.len() == 0
65    }
66}
67
68impl std::ops::Index<Match> for [u8] {
69    type Output = [u8];
70
71    #[inline]
72    fn index(&self, index: Match) -> &[u8] {
73        &self[index.start..index.end]
74    }
75}
76
77impl std::ops::IndexMut<Match> for [u8] {
78    #[inline]
79    fn index_mut(&mut self, index: Match) -> &mut [u8] {
80        &mut self[index.start..index.end]
81    }
82}
83
84impl std::ops::Index<Match> for str {
85    type Output = str;
86
87    #[inline]
88    fn index(&self, index: Match) -> &str {
89        &self[index.start..index.end]
90    }
91}
92
93/// A line terminator (always a single byte for fff — no CRLF support needed).
94#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
95pub struct LineTerminator(u8);
96
97impl LineTerminator {
98    /// Create a line terminator from a single byte.
99    #[inline]
100    pub fn byte(byte: u8) -> LineTerminator {
101        LineTerminator(byte)
102    }
103
104    /// Return the terminator byte.
105    #[inline]
106    pub fn as_byte(&self) -> u8 {
107        self.0
108    }
109
110    /// Return the terminator as a single-element byte slice.
111    #[inline]
112    pub fn as_bytes(&self) -> &[u8] {
113        std::slice::from_ref(&self.0)
114    }
115}
116
117impl Default for LineTerminator {
118    #[inline]
119    fn default() -> LineTerminator {
120        LineTerminator(b'\n')
121    }
122}
123
124/// An error type for matchers that never produce errors.
125#[derive(Debug, Eq, PartialEq)]
126pub struct NoError(());
127
128impl std::error::Error for NoError {}
129
130impl std::fmt::Display for NoError {
131    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        unreachable!("NoError should never be instantiated")
133    }
134}
135
136/// A matcher finds byte-level matches in a haystack.
137pub trait Matcher {
138    /// The error type (use [`NoError`] for infallible matchers).
139    type Error: std::fmt::Display;
140
141    /// Find the first match at or after `at` in `haystack`.
142    fn find_at(&self, haystack: &[u8], at: usize) -> Result<Option<Match>, Self::Error>;
143
144    /// Find the first match in `haystack`.
145    #[inline]
146    fn find(&self, haystack: &[u8]) -> Result<Option<Match>, Self::Error> {
147        self.find_at(haystack, 0)
148    }
149
150    /// The line terminator this matcher guarantees will never appear in a match.
151    /// Return `None` if the matcher can match across lines.
152    #[inline]
153    fn line_terminator(&self) -> Option<LineTerminator> {
154        None
155    }
156}
157
158impl<M: Matcher> Matcher for &M {
159    type Error = M::Error;
160
161    #[inline]
162    fn find_at(&self, haystack: &[u8], at: usize) -> Result<Option<Match>, Self::Error> {
163        (*self).find_at(haystack, at)
164    }
165
166    #[inline]
167    fn find(&self, haystack: &[u8]) -> Result<Option<Match>, Self::Error> {
168        (*self).find(haystack)
169    }
170
171    #[inline]
172    fn line_terminator(&self) -> Option<LineTerminator> {
173        (*self).line_terminator()
174    }
175}