use std::iter::FusedIterator;
use std::ops::{Index, Range};
use crate::pattern::{Regex, expect};
pub(crate) type GroupSpans = Box<[Option<(usize, usize)>]>;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Match<'t> {
text: &'t str,
start: usize,
end: usize,
}
impl<'t> Match<'t> {
pub(crate) fn new(text: &'t str, start: usize, end: usize) -> Self {
debug_assert!(text.is_char_boundary(start) && text.is_char_boundary(end));
Self { text, start, end }
}
#[must_use]
pub fn start(&self) -> usize {
self.start
}
#[must_use]
pub fn end(&self) -> usize {
self.end
}
#[must_use]
pub fn range(&self) -> Range<usize> {
self.start..self.end
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.start == self.end
}
#[must_use]
pub fn len(&self) -> usize {
self.end - self.start
}
#[must_use]
pub fn as_str(&self) -> &'t str {
&self.text[self.start..self.end]
}
#[must_use]
pub fn as_bytes(&self) -> &'t [u8] {
&self.text.as_bytes()[self.start..self.end]
}
}
impl<'t> From<Match<'t>> for &'t str {
fn from(found: Match<'t>) -> Self {
found.as_str()
}
}
impl From<Match<'_>> for Range<usize> {
fn from(found: Match<'_>) -> Self {
found.range()
}
}
pub(crate) fn crate_sequence(spans: Vec<(usize, usize)>, text: &str) -> Vec<(usize, usize)> {
if !spans.iter().any(|&(s, e)| s == e) {
return spans;
}
let mut out = Vec::with_capacity(spans.len());
let (mut prev_end, mut resume) = (usize::MAX, 0);
for (start, end) in spans {
if start < resume {
continue; }
resume = if start == end {
start + char_width(text, start)
} else {
end
};
if start != end || start != prev_end {
out.push((start, end));
}
prev_end = end;
}
out
}
fn char_width(text: &str, at: usize) -> usize {
text[at..].chars().next().map_or(1, char::len_utf8)
}
#[derive(Clone, Debug)]
pub struct Matches<'t> {
text: &'t str,
spans: std::vec::IntoIter<(usize, usize)>,
}
impl<'t> Matches<'t> {
pub(crate) fn new(text: &'t str, spans: Vec<(usize, usize)>) -> Self {
Self {
text,
spans: spans.into_iter(),
}
}
}
impl<'t> Iterator for Matches<'t> {
type Item = Match<'t>;
fn next(&mut self) -> Option<Self::Item> {
let (start, end) = self.spans.next()?;
Some(Match::new(self.text, start, end))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.spans.size_hint()
}
}
impl DoubleEndedIterator for Matches<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
let (start, end) = self.spans.next_back()?;
Some(Match::new(self.text, start, end))
}
}
impl ExactSizeIterator for Matches<'_> {}
impl FusedIterator for Matches<'_> {}
#[derive(Clone, Debug)]
pub struct Captures<'r, 't> {
re: &'r Regex,
text: &'t str,
spans: GroupSpans,
}
impl<'r, 't> Captures<'r, 't> {
pub(crate) fn new(re: &'r Regex, text: &'t str, spans: GroupSpans) -> Self {
Self { re, text, spans }
}
#[must_use]
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.spans.len()
}
#[must_use]
pub fn get(&self, index: usize) -> Option<Match<'t>> {
let (start, end) = (*self.spans.get(index)?)?;
Some(Match::new(self.text, start, end))
}
#[must_use]
pub fn name(&self, name: &str) -> Option<Match<'t>> {
self.get(self.re.group_index(name)?)
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = Option<Match<'t>>> {
let text = self.text;
self.spans
.iter()
.map(move |span| span.map(|(start, end)| Match::new(text, start, end)))
}
pub fn expand(&self, template: &str, dst: &mut String) {
crate::replace::expand(self, template, dst);
}
}
impl<'t> Index<usize> for Captures<'_, 't> {
type Output = str;
fn index(&self, index: usize) -> &str {
match self.get(index) {
Some(found) => found.as_str(),
None if index < self.spans.len() => {
panic!("group {index} did not participate in this match; use get({index})")
},
None => panic!(
"no group at index {index}: `{}` declares {}",
self.re,
self.spans.len() - 1
),
}
}
}
impl<'t> Index<&str> for Captures<'_, 't> {
type Output = str;
fn index(&self, name: &str) -> &str {
if let Some(found) = self.name(name) {
return found.as_str();
}
assert!(
self.re.group_index(name).is_none(),
"group `{name}` did not participate in this match; use name(\"{name}\")"
);
panic!(
"no group named `{name}`: `{}` declares none by that name",
self.re
)
}
}
#[derive(Clone, Debug)]
pub struct CaptureMatches<'r, 't> {
re: &'r Regex,
text: &'t str,
spans: std::vec::IntoIter<(usize, usize)>,
}
impl<'r, 't> CaptureMatches<'r, 't> {
pub(crate) fn new(re: &'r Regex, text: &'t str, spans: Vec<(usize, usize)>) -> Self {
Self {
re,
text,
spans: spans.into_iter(),
}
}
}
impl<'r, 't> Iterator for CaptureMatches<'r, 't> {
type Item = Captures<'r, 't>;
fn next(&mut self) -> Option<Self::Item> {
let (start, end) = self.spans.next()?;
let spans = expect(self.re.captures_at(self.text, start, end));
Some(Captures::new(self.re, self.text, spans))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.spans.size_hint()
}
}
impl ExactSizeIterator for CaptureMatches<'_, '_> {}
impl FusedIterator for CaptureMatches<'_, '_> {}
#[derive(Clone, Debug)]
pub struct Split<'t> {
text: &'t str,
matches: Matches<'t>,
cut: usize,
left: usize,
}
impl<'t> Split<'t> {
pub(crate) fn new(text: &'t str, matches: Matches<'t>, limit: usize) -> Self {
Self {
text,
matches,
cut: 0,
left: limit,
}
}
}
impl<'t> Iterator for Split<'t> {
type Item = &'t str;
fn next(&mut self) -> Option<Self::Item> {
if self.left == 0 {
return None;
}
self.left -= 1;
if self.left == 0 {
return Some(&self.text[self.cut..]);
}
match self.matches.next() {
Some(found) => {
let piece = &self.text[self.cut..found.start()];
self.cut = found.end();
Some(piece)
},
None => {
self.left = 0;
Some(&self.text[self.cut..])
},
}
}
}
impl FusedIterator for Split<'_> {}
#[cfg(test)]
mod thinning {
use super::crate_sequence;
fn widest(pattern: &str, text: &str) -> Vec<(usize, usize)> {
let re = regex::Regex::new(pattern).unwrap();
(0..=text.len())
.filter_map(|at| {
let found = re.find_at(text, at)?;
(found.start() == at).then_some((found.start(), found.end()))
})
.collect()
}
fn thinned(pattern: &str, text: &str) -> Vec<(usize, usize)> {
crate_sequence(widest(pattern, text), text)
}
fn crate_says(pattern: &str, text: &str) -> Vec<(usize, usize)> {
regex::Regex::new(pattern)
.unwrap()
.find_iter(text)
.map(|found| (found.start(), found.end()))
.collect()
}
#[test]
fn thinning_the_widest_sequence_reproduces_the_regex_crate() {
for pattern in [
"a*", "b*", "x*", "", "a?", "l*", "a*b*", "(?:ab)*", "a{0,2}",
] {
for text in [
"", "a", "abc", "abcb", "aaa", "bbb", "aXaXa", "bab", "héllo",
] {
assert_eq!(
thinned(pattern, text),
crate_says(pattern, text),
"pattern {pattern:?} over text {text:?}"
);
}
}
}
#[test]
fn thinning_is_a_subsequence_of_what_it_was_given() {
for pattern in ["a*", "", "l*", "a?"] {
for text in ["", "abc", "héllo", "bab"] {
let (before, after) = (widest(pattern, text), thinned(pattern, text));
let mut left = before.iter();
assert!(
after.iter().all(|span| left.any(|had| had == span)),
"{pattern:?} over {text:?}: {after:?} is not a subsequence of {before:?}"
);
}
}
}
#[test]
fn a_sequence_without_empty_matches_is_unchanged() {
let spans = vec![(0, 1), (1, 2), (4, 9)];
assert_eq!(crate_sequence(spans.clone(), "abcdefghij"), spans);
}
}