Skip to main content

fancy_regex/
input.rs

1use crate::bytes::MatchBytes;
2use crate::Match;
3use alloc::string::String;
4use core::ops::Range;
5
6/// Returns the smallest possible index of the next valid UTF-8 sequence
7/// starting after `i`.
8///
9/// Adapted from a function with the same name in the `regex` crate.
10pub(crate) fn next_input_pos(text: &[u8], i: usize) -> usize {
11    let b = match text.get(i) {
12        None => return i + 1,
13        Some(&b) => b,
14    };
15    i + crate::codepoint_len(b)
16}
17
18/// A search configuration for matching against a haystack.
19///
20/// This keeps the original haystack together with a starting search position
21/// and a range that constrains where the overall match may occur. Unlike
22/// slicing a haystack, anchors and lookaround can still inspect the full
23/// original input, and reported offsets remain absolute.
24#[derive(Debug)]
25pub struct RegexInput<'h, S: Input + ?Sized> {
26    haystack: &'h S,
27    start: usize,
28    range: Range<usize>,
29    anchored: bool,
30    start_text: Option<bool>,
31    end_text: Option<bool>,
32    continue_from_previous_match_end: Option<bool>,
33}
34
35impl<'h, S: Input + ?Sized> Clone for RegexInput<'h, S> {
36    // Manual Clone avoids the extra `S: Clone` bound generated by `#[derive(Clone)]`,
37    // so `RegexInput<'_, str>` and `RegexInput<'_, [u8]>` remain clonable.
38    fn clone(&self) -> Self {
39        Self {
40            haystack: self.haystack,
41            start: self.start,
42            range: self.range.clone(),
43            anchored: self.anchored,
44            start_text: self.start_text,
45            end_text: self.end_text,
46            continue_from_previous_match_end: self.continue_from_previous_match_end,
47        }
48    }
49}
50
51impl<'h, S: Input + ?Sized> RegexInput<'h, S> {
52    /// Create a new search input over the full haystack.
53    pub fn new(haystack: &'h S) -> Self {
54        Self {
55            haystack,
56            start: 0,
57            range: 0..haystack.len(),
58            anchored: false,
59            start_text: None,
60            end_text: None,
61            continue_from_previous_match_end: None,
62        }
63    }
64
65    /// Return the haystack being searched.
66    pub fn haystack(&self) -> &'h S {
67        self.haystack
68    }
69
70    /// Return the requested starting position for this search.
71    pub fn start(&self) -> usize {
72        self.start
73    }
74
75    /// Return the range constraining where match `0` may occur.
76    pub fn get_range(&self) -> Range<usize> {
77        self.range.clone()
78    }
79
80    /// Return a copy of this input with a different search start.
81    pub fn from_pos(mut self, start: usize) -> Self {
82        self.start = start;
83        self
84    }
85
86    /// Return a copy of this input with a different search range.
87    ///
88    /// # Panics
89    ///
90    /// Panics if the range is not within the haystack bounds or if
91    /// `range.start > range.end`.
92    pub fn range(mut self, range: Range<usize>) -> Self {
93        assert!(range.start <= range.end, "range start must be <= range end");
94        assert!(
95            range.end <= self.haystack.len(),
96            "range end must be within haystack bounds"
97        );
98        self.range = range;
99        self
100    }
101
102    /// Return a copy of this input with an override for whether `^`/`\A`
103    /// should match.
104    ///
105    /// This override is suppression-only: `false` suppresses the assertion, while
106    /// `true` clears the override and preserves default behavior.
107    pub fn start_text(mut self, yes: bool) -> Self {
108        self.start_text = if yes { None } else { Some(false) };
109        self
110    }
111
112    /// Return a copy of this input with an override for whether `$`/`\z`
113    /// should match.
114    ///
115    /// This override is suppression-only: `false` suppresses the assertion, while
116    /// `true` clears the override and preserves default behavior.
117    pub fn end_text(mut self, yes: bool) -> Self {
118        self.end_text = if yes { None } else { Some(false) };
119        self
120    }
121
122    /// Return a copy of this input with an override for whether `\G` should
123    /// match.
124    ///
125    /// This override is suppression-only: `false` suppresses the assertion, while
126    /// `true` clears the override and preserves default behavior.
127    pub fn continue_from_previous_match_end(mut self, yes: bool) -> Self {
128        self.continue_from_previous_match_end = if yes { None } else { Some(false) };
129        self
130    }
131
132    /// Return a copy of this input with an override for whether matching should
133    /// be anchored at the start position.
134    ///
135    /// When `true`, the regex will only match at the exact start position,
136    /// without scanning forward through the haystack. This is more efficient
137    /// when you already know where a potential match must occur.
138    pub fn anchored(mut self, yes: bool) -> Self {
139        self.anchored = yes;
140        self
141    }
142
143    pub(crate) fn effective_start(&self) -> usize {
144        self.start.max(self.range.start)
145    }
146
147    pub(crate) fn is_done(&self) -> bool {
148        self.effective_start() > self.range.end
149    }
150
151    pub(crate) fn set_start(&mut self, start: usize) {
152        self.start = start;
153    }
154
155    pub(crate) fn start_text_override(&self) -> Option<bool> {
156        self.start_text
157    }
158
159    pub(crate) fn end_text_override(&self) -> Option<bool> {
160        self.end_text
161    }
162
163    pub(crate) fn continue_from_previous_match_end_override(&self) -> Option<bool> {
164        self.continue_from_previous_match_end
165    }
166
167    pub(crate) fn is_anchored(&self) -> bool {
168        self.anchored
169    }
170}
171
172impl<'h, S: Input + ?Sized> From<&'h S> for RegexInput<'h, S> {
173    fn from(haystack: &'h S) -> Self {
174        Self::new(haystack)
175    }
176}
177
178/// A trait abstracting over haystack types for regex matching.
179///
180/// This trait is implemented for `str`, `String`, `[u8]`, and `[u8; N]`,
181/// allowing regex methods to work with both UTF-8 text and raw byte slices.
182///
183/// When the regex is compiled with [`BytesMode::Ascii`](crate::BytesMode),
184/// patterns like `.` will match any byte in `[u8]` input. Without bytes mode,
185/// `.` only matches valid UTF-8 codepoints even in byte slices.
186///
187/// The associated type [`Match<'t>`](Self::Match) is the concrete match type
188/// returned for a given input: [`Match<'t>`](crate::Match) for string inputs,
189/// [`MatchBytes<'t>`](crate::MatchBytes) for byte slice inputs.
190pub trait Input {
191    /// The match type produced for this input.
192    type Match<'t>
193    where
194        Self: 't;
195
196    /// Returns the length of the input in bytes.
197    fn len(&self) -> usize;
198    /// Returns the input as a raw byte slice.
199    fn as_bytes(&self) -> &[u8];
200    /// Returns `true` if `ix` is a valid position between UTF-8 codepoints.
201    fn is_char_boundary(&self, ix: usize) -> bool;
202    /// Returns `true` if the entire input is ASCII.
203    fn is_ascii(&self) -> bool;
204    /// Steps back one codepoint from position `i`, returning the start position.
205    fn prev_codepoint_ix(&self, i: usize) -> usize;
206    /// Returns `true` if the input is empty.
207    fn is_empty(&self) -> bool {
208        self.len() == 0
209    }
210    /// Construct a match from byte offsets.
211    fn make_match<'t>(&'t self, start: usize, end: usize) -> Self::Match<'t>;
212    /// Advance past the codepoint at position `i`.
213    fn advance_position(&self, i: usize) -> usize;
214}
215
216impl<S: Input + ?Sized> Input for &S {
217    type Match<'t>
218        = S::Match<'t>
219    where
220        Self: 't;
221
222    fn len(&self) -> usize {
223        (**self).len()
224    }
225    fn as_bytes(&self) -> &[u8] {
226        (**self).as_bytes()
227    }
228    fn is_char_boundary(&self, ix: usize) -> bool {
229        (**self).is_char_boundary(ix)
230    }
231    fn is_ascii(&self) -> bool {
232        (**self).is_ascii()
233    }
234    fn prev_codepoint_ix(&self, i: usize) -> usize {
235        (**self).prev_codepoint_ix(i)
236    }
237    fn make_match<'t>(&'t self, start: usize, end: usize) -> Self::Match<'t> {
238        (**self).make_match(start, end)
239    }
240    fn advance_position(&self, i: usize) -> usize {
241        (**self).advance_position(i)
242    }
243}
244
245impl Input for str {
246    type Match<'t> = Match<'t>;
247
248    fn len(&self) -> usize {
249        str::len(self)
250    }
251    fn as_bytes(&self) -> &[u8] {
252        str::as_bytes(self)
253    }
254    fn is_char_boundary(&self, ix: usize) -> bool {
255        str::is_char_boundary(self, ix)
256    }
257    fn is_ascii(&self) -> bool {
258        str::is_ascii(self)
259    }
260    fn prev_codepoint_ix(&self, i: usize) -> usize {
261        crate::prev_codepoint_ix(self, i)
262    }
263    fn make_match<'t>(&'t self, start: usize, end: usize) -> Match<'t> {
264        Match::new(self, start, end)
265    }
266    fn advance_position(&self, i: usize) -> usize {
267        next_input_pos(self.as_bytes(), i)
268    }
269}
270
271impl Input for String {
272    type Match<'t> = Match<'t>;
273
274    fn len(&self) -> usize {
275        String::len(self)
276    }
277    fn as_bytes(&self) -> &[u8] {
278        String::as_bytes(self)
279    }
280    fn is_char_boundary(&self, ix: usize) -> bool {
281        str::is_char_boundary(self, ix)
282    }
283    fn is_ascii(&self) -> bool {
284        str::is_ascii(self)
285    }
286    fn prev_codepoint_ix(&self, i: usize) -> usize {
287        crate::prev_codepoint_ix(self, i)
288    }
289    fn make_match<'t>(&'t self, start: usize, end: usize) -> Match<'t> {
290        Match::new(self, start, end)
291    }
292    fn advance_position(&self, i: usize) -> usize {
293        next_input_pos(self.as_bytes(), i)
294    }
295}
296
297impl Input for [u8] {
298    type Match<'t> = MatchBytes<'t>;
299
300    fn len(&self) -> usize {
301        <[u8]>::len(self)
302    }
303    fn as_bytes(&self) -> &[u8] {
304        self
305    }
306    fn is_char_boundary(&self, ix: usize) -> bool {
307        ix == 0 || ix >= <[u8]>::len(self) || self[ix] & 0xC0 != 0x80
308    }
309    fn is_ascii(&self) -> bool {
310        <[u8]>::is_ascii(self)
311    }
312    fn prev_codepoint_ix(&self, mut i: usize) -> usize {
313        i -= 1;
314        while i > 0 && self[i] & 0xC0 == 0x80 {
315            i -= 1;
316        }
317        i
318    }
319    fn make_match<'t>(&'t self, start: usize, end: usize) -> MatchBytes<'t> {
320        MatchBytes::new(self, start, end)
321    }
322    fn advance_position(&self, i: usize) -> usize {
323        i + 1
324    }
325}
326
327impl<const N: usize> Input for [u8; N] {
328    type Match<'t> = MatchBytes<'t>;
329
330    fn len(&self) -> usize {
331        N
332    }
333    fn as_bytes(&self) -> &[u8] {
334        self
335    }
336    fn is_char_boundary(&self, ix: usize) -> bool {
337        ix == 0 || ix >= N || self[ix] & 0xC0 != 0x80
338    }
339    fn is_ascii(&self) -> bool {
340        <[u8]>::is_ascii(self)
341    }
342    fn prev_codepoint_ix(&self, mut i: usize) -> usize {
343        i -= 1;
344        while i > 0 && self[i] & 0xC0 == 0x80 {
345            i -= 1;
346        }
347        i
348    }
349    fn make_match<'t>(&'t self, start: usize, end: usize) -> MatchBytes<'t> {
350        MatchBytes::new(self, start, end)
351    }
352    fn advance_position(&self, i: usize) -> usize {
353        i + 1
354    }
355}