fancy-regex 0.19.0

An implementation of regexes, supporting a relatively rich set of features, including backreferences and look-around. Aims to be compatible with Oniguruma syntax when the relevant flag is set.
Documentation
use crate::bytes::MatchBytes;
use crate::Match;
use alloc::string::String;
use core::ops::Range;

/// Returns the smallest possible index of the next valid UTF-8 sequence
/// starting after `i`.
///
/// Adapted from a function with the same name in the `regex` crate.
pub(crate) fn next_input_pos(text: &[u8], i: usize) -> usize {
    let b = match text.get(i) {
        None => return i + 1,
        Some(&b) => b,
    };
    i + crate::codepoint_len(b)
}

/// A search configuration for matching against a haystack.
///
/// This keeps the original haystack together with a starting search position
/// and a range that constrains where the overall match may occur. Unlike
/// slicing a haystack, anchors and lookaround can still inspect the full
/// original input, and reported offsets remain absolute.
#[derive(Debug)]
pub struct RegexInput<'h, S: Input + ?Sized> {
    haystack: &'h S,
    start: usize,
    range: Range<usize>,
    anchored: bool,
    start_text: Option<bool>,
    end_text: Option<bool>,
    continue_from_previous_match_end: Option<bool>,
}

impl<'h, S: Input + ?Sized> Clone for RegexInput<'h, S> {
    // Manual Clone avoids the extra `S: Clone` bound generated by `#[derive(Clone)]`,
    // so `RegexInput<'_, str>` and `RegexInput<'_, [u8]>` remain clonable.
    fn clone(&self) -> Self {
        Self {
            haystack: self.haystack,
            start: self.start,
            range: self.range.clone(),
            anchored: self.anchored,
            start_text: self.start_text,
            end_text: self.end_text,
            continue_from_previous_match_end: self.continue_from_previous_match_end,
        }
    }
}

impl<'h, S: Input + ?Sized> RegexInput<'h, S> {
    /// Create a new search input over the full haystack.
    pub fn new(haystack: &'h S) -> Self {
        Self {
            haystack,
            start: 0,
            range: 0..haystack.len(),
            anchored: false,
            start_text: None,
            end_text: None,
            continue_from_previous_match_end: None,
        }
    }

    /// Return the haystack being searched.
    pub fn haystack(&self) -> &'h S {
        self.haystack
    }

    /// Return the requested starting position for this search.
    pub fn start(&self) -> usize {
        self.start
    }

    /// Return the range constraining where match `0` may occur.
    pub fn get_range(&self) -> Range<usize> {
        self.range.clone()
    }

    /// Return a copy of this input with a different search start.
    pub fn from_pos(mut self, start: usize) -> Self {
        self.start = start;
        self
    }

    /// Return a copy of this input with a different search range.
    ///
    /// # Panics
    ///
    /// Panics if the range is not within the haystack bounds or if
    /// `range.start > range.end`.
    pub fn range(mut self, range: Range<usize>) -> Self {
        assert!(range.start <= range.end, "range start must be <= range end");
        assert!(
            range.end <= self.haystack.len(),
            "range end must be within haystack bounds"
        );
        self.range = range;
        self
    }

    /// Return a copy of this input with an override for whether `^`/`\A`
    /// should match.
    ///
    /// This override is suppression-only: `false` suppresses the assertion, while
    /// `true` clears the override and preserves default behavior.
    pub fn start_text(mut self, yes: bool) -> Self {
        self.start_text = if yes { None } else { Some(false) };
        self
    }

    /// Return a copy of this input with an override for whether `$`/`\z`
    /// should match.
    ///
    /// This override is suppression-only: `false` suppresses the assertion, while
    /// `true` clears the override and preserves default behavior.
    pub fn end_text(mut self, yes: bool) -> Self {
        self.end_text = if yes { None } else { Some(false) };
        self
    }

    /// Return a copy of this input with an override for whether `\G` should
    /// match.
    ///
    /// This override is suppression-only: `false` suppresses the assertion, while
    /// `true` clears the override and preserves default behavior.
    pub fn continue_from_previous_match_end(mut self, yes: bool) -> Self {
        self.continue_from_previous_match_end = if yes { None } else { Some(false) };
        self
    }

    /// Return a copy of this input with an override for whether matching should
    /// be anchored at the start position.
    ///
    /// When `true`, the regex will only match at the exact start position,
    /// without scanning forward through the haystack. This is more efficient
    /// when you already know where a potential match must occur.
    pub fn anchored(mut self, yes: bool) -> Self {
        self.anchored = yes;
        self
    }

    pub(crate) fn effective_start(&self) -> usize {
        self.start.max(self.range.start)
    }

    pub(crate) fn is_done(&self) -> bool {
        self.effective_start() > self.range.end
    }

    pub(crate) fn set_start(&mut self, start: usize) {
        self.start = start;
    }

    pub(crate) fn start_text_override(&self) -> Option<bool> {
        self.start_text
    }

    pub(crate) fn end_text_override(&self) -> Option<bool> {
        self.end_text
    }

    pub(crate) fn continue_from_previous_match_end_override(&self) -> Option<bool> {
        self.continue_from_previous_match_end
    }

    pub(crate) fn is_anchored(&self) -> bool {
        self.anchored
    }
}

impl<'h, S: Input + ?Sized> From<&'h S> for RegexInput<'h, S> {
    fn from(haystack: &'h S) -> Self {
        Self::new(haystack)
    }
}

/// A trait abstracting over haystack types for regex matching.
///
/// This trait is implemented for `str`, `String`, `[u8]`, and `[u8; N]`,
/// allowing regex methods to work with both UTF-8 text and raw byte slices.
///
/// When the regex is compiled with [`BytesMode::Ascii`](crate::BytesMode),
/// patterns like `.` will match any byte in `[u8]` input. Without bytes mode,
/// `.` only matches valid UTF-8 codepoints even in byte slices.
///
/// The associated type [`Match<'t>`](Self::Match) is the concrete match type
/// returned for a given input: [`Match<'t>`](crate::Match) for string inputs,
/// [`MatchBytes<'t>`](crate::MatchBytes) for byte slice inputs.
pub trait Input {
    /// The match type produced for this input.
    type Match<'t>
    where
        Self: 't;

    /// Returns the length of the input in bytes.
    fn len(&self) -> usize;
    /// Returns the input as a raw byte slice.
    fn as_bytes(&self) -> &[u8];
    /// Returns `true` if `ix` is a valid position between UTF-8 codepoints.
    fn is_char_boundary(&self, ix: usize) -> bool;
    /// Returns `true` if the entire input is ASCII.
    fn is_ascii(&self) -> bool;
    /// Steps back one codepoint from position `i`, returning the start position.
    fn prev_codepoint_ix(&self, i: usize) -> usize;
    /// Returns `true` if the input is empty.
    fn is_empty(&self) -> bool {
        self.len() == 0
    }
    /// Construct a match from byte offsets.
    fn make_match<'t>(&'t self, start: usize, end: usize) -> Self::Match<'t>;
    /// Advance past the codepoint at position `i`.
    fn advance_position(&self, i: usize) -> usize;
}

impl<S: Input + ?Sized> Input for &S {
    type Match<'t>
        = S::Match<'t>
    where
        Self: 't;

    fn len(&self) -> usize {
        (**self).len()
    }
    fn as_bytes(&self) -> &[u8] {
        (**self).as_bytes()
    }
    fn is_char_boundary(&self, ix: usize) -> bool {
        (**self).is_char_boundary(ix)
    }
    fn is_ascii(&self) -> bool {
        (**self).is_ascii()
    }
    fn prev_codepoint_ix(&self, i: usize) -> usize {
        (**self).prev_codepoint_ix(i)
    }
    fn make_match<'t>(&'t self, start: usize, end: usize) -> Self::Match<'t> {
        (**self).make_match(start, end)
    }
    fn advance_position(&self, i: usize) -> usize {
        (**self).advance_position(i)
    }
}

impl Input for str {
    type Match<'t> = Match<'t>;

    fn len(&self) -> usize {
        str::len(self)
    }
    fn as_bytes(&self) -> &[u8] {
        str::as_bytes(self)
    }
    fn is_char_boundary(&self, ix: usize) -> bool {
        str::is_char_boundary(self, ix)
    }
    fn is_ascii(&self) -> bool {
        str::is_ascii(self)
    }
    fn prev_codepoint_ix(&self, i: usize) -> usize {
        crate::prev_codepoint_ix(self, i)
    }
    fn make_match<'t>(&'t self, start: usize, end: usize) -> Match<'t> {
        Match::new(self, start, end)
    }
    fn advance_position(&self, i: usize) -> usize {
        next_input_pos(self.as_bytes(), i)
    }
}

impl Input for String {
    type Match<'t> = Match<'t>;

    fn len(&self) -> usize {
        String::len(self)
    }
    fn as_bytes(&self) -> &[u8] {
        String::as_bytes(self)
    }
    fn is_char_boundary(&self, ix: usize) -> bool {
        str::is_char_boundary(self, ix)
    }
    fn is_ascii(&self) -> bool {
        str::is_ascii(self)
    }
    fn prev_codepoint_ix(&self, i: usize) -> usize {
        crate::prev_codepoint_ix(self, i)
    }
    fn make_match<'t>(&'t self, start: usize, end: usize) -> Match<'t> {
        Match::new(self, start, end)
    }
    fn advance_position(&self, i: usize) -> usize {
        next_input_pos(self.as_bytes(), i)
    }
}

impl Input for [u8] {
    type Match<'t> = MatchBytes<'t>;

    fn len(&self) -> usize {
        <[u8]>::len(self)
    }
    fn as_bytes(&self) -> &[u8] {
        self
    }
    fn is_char_boundary(&self, ix: usize) -> bool {
        ix == 0 || ix >= <[u8]>::len(self) || self[ix] & 0xC0 != 0x80
    }
    fn is_ascii(&self) -> bool {
        <[u8]>::is_ascii(self)
    }
    fn prev_codepoint_ix(&self, mut i: usize) -> usize {
        i -= 1;
        while i > 0 && self[i] & 0xC0 == 0x80 {
            i -= 1;
        }
        i
    }
    fn make_match<'t>(&'t self, start: usize, end: usize) -> MatchBytes<'t> {
        MatchBytes::new(self, start, end)
    }
    fn advance_position(&self, i: usize) -> usize {
        i + 1
    }
}

impl<const N: usize> Input for [u8; N] {
    type Match<'t> = MatchBytes<'t>;

    fn len(&self) -> usize {
        N
    }
    fn as_bytes(&self) -> &[u8] {
        self
    }
    fn is_char_boundary(&self, ix: usize) -> bool {
        ix == 0 || ix >= N || self[ix] & 0xC0 != 0x80
    }
    fn is_ascii(&self) -> bool {
        <[u8]>::is_ascii(self)
    }
    fn prev_codepoint_ix(&self, mut i: usize) -> usize {
        i -= 1;
        while i > 0 && self[i] & 0xC0 == 0x80 {
            i -= 1;
        }
        i
    }
    fn make_match<'t>(&'t self, start: usize, end: usize) -> MatchBytes<'t> {
        MatchBytes::new(self, start, end)
    }
    fn advance_position(&self, i: usize) -> usize {
        i + 1
    }
}