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()
}
}
#[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<'_> {}