use crate::ast::Pattern;
use crate::engine::Span;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CaptureSlots {
names: Vec<String>,
spans: Vec<Option<Span>>,
extents: Vec<Option<(usize, usize)>>,
matched: Option<Span>,
matched_extent: Option<(usize, usize)>,
}
impl CaptureSlots {
#[must_use]
pub fn of(pattern: &Pattern) -> Self {
let names = crate::cursor::capture_names(pattern);
let n = names.len();
Self {
names,
spans: vec![None; n],
extents: vec![None; n],
matched: None,
matched_extent: None,
}
}
#[must_use]
pub fn len(&self) -> usize {
self.names.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.names.is_empty()
}
#[must_use]
pub fn names(&self) -> &[String] {
&self.names
}
#[must_use]
pub fn name(&self, i: usize) -> Option<&str> {
self.names.get(i).map(String::as_str)
}
#[must_use]
pub fn index_of(&self, name: &str) -> Option<usize> {
self.names.iter().position(|n| n == name)
}
#[must_use]
pub fn get(&self, i: usize) -> Option<Span> {
*self.spans.get(i)?
}
#[must_use]
pub fn by_name(&self, name: &str) -> Option<Span> {
self.get(self.index_of(name)?)
}
#[must_use]
pub fn extent(&self, i: usize) -> Option<(usize, usize)> {
*self.extents.get(i)?
}
#[must_use]
pub fn tokens(&self, i: usize) -> Option<usize> {
self.extent(i).map(|(a, b)| b - a)
}
#[must_use]
pub fn matched(&self) -> Option<Span> {
self.matched
}
#[must_use]
pub fn matched_extent(&self) -> Option<(usize, usize)> {
self.matched_extent
}
#[must_use]
pub fn matched_tokens(&self) -> Option<usize> {
self.matched_extent.map(|(a, b)| b - a)
}
pub fn clear(&mut self) {
self.spans.fill(None);
self.extents.fill(None);
self.matched = None;
self.matched_extent = None;
}
fn take_match(&mut self, m: &crate::engine::Match, extent: Option<(usize, usize)>) {
self.clear();
self.matched = Some(m.span());
self.matched_extent = extent;
for (name, span) in m.names().iter().zip(m.captures()) {
if let Some(i) = self.index_of(name) {
self.spans[i] = Some(*span);
}
}
}
fn take_flat(&mut self, m: &crate::nfa::FlatMatch, names: &[String]) {
self.clear();
self.matched = Some(m.span);
for (i, name) in names.iter().enumerate() {
if let Some(k) = self.index_of(name) {
self.spans[k] = Some(m.regs[i]);
}
}
}
fn fill_from_walk(
&mut self,
w: &mut crate::nfa::SerialWalk<crate::nfa::OwnedStream>,
input: &[u8],
) -> Option<Span> {
let Self { names, spans, extents, matched, matched_extent } = self;
let (span, ks, ke) =
w.next_into(input, names.as_slice(), spans.as_mut_slice(), extents.as_mut_slice())?;
*matched = Some(span);
*matched_extent = Some((ks, ke));
Some(span)
}
}
pub fn captures_read(pattern: &Pattern, input: &[u8], slots: &mut CaptureSlots) -> Option<Span> {
captures_read_at(pattern, input, 0, slots)
}
pub fn captures_read_at(
pattern: &Pattern,
input: &[u8],
at: usize,
slots: &mut CaptureSlots,
) -> Option<Span> {
slots.clear();
if !pattern.binds() {
let span = crate::cursor::find_at(pattern, input, at)?;
slots.matched = Some(span);
return Some(span);
}
if let Some(first) = crate::engine::routed_first_at(pattern, input, at) {
let first = first?;
let m = crate::engine::captures(pattern, input, &[first]).into_iter().next()?;
slots.take_match(&m, None);
return Some(m.span());
}
if let Some(mut w) = crate::nfa::SerialWalk::over(pattern, input) {
w.seek(at);
return slots.fill_from_walk(&mut w, input);
}
let toks = crate::parallel_lex::lex_parallel(input);
let start = toks.partition_point(|t| t.start() < at);
let span = crate::engine::scan_tokens_from(pattern, input, &toks, start).into_iter().next()?;
let m = crate::engine::captures_over(pattern, input, &toks, &[span]).into_iter().next()?;
slots.take_match(&m, None);
Some(m.span())
}
pub struct SlotCursor<'h> {
input: &'h [u8],
source: SlotSource,
}
enum SlotSource {
Spans { spans: Vec<Span>, at: usize },
Resolved { ms: Vec<crate::engine::Match>, at: usize },
Flat { ms: Vec<crate::nfa::FlatMatch>, names: std::sync::Arc<[String]>, at: usize },
Walk(Box<crate::nfa::SerialWalk<crate::nfa::OwnedStream>>),
}
impl SlotCursor<'_> {
pub fn next_into(&mut self, slots: &mut CaptureSlots) -> Option<Span> {
match &mut self.source {
SlotSource::Spans { spans, at } => {
let s = *spans.get(*at)?;
*at += 1;
slots.clear();
slots.matched = Some(s);
Some(s)
}
SlotSource::Resolved { ms, at } => {
let m = ms.get(*at)?;
*at += 1;
slots.take_match(m, None);
Some(m.span())
}
SlotSource::Flat { ms, names, at } => {
let m = ms.get(*at)?;
*at += 1;
slots.take_flat(m, names);
Some(m.span)
}
SlotSource::Walk(w) => {
slots.clear();
slots.fill_from_walk(w, self.input)
}
}
}
}
#[must_use]
pub fn captures_read_iter<'h>(pattern: &Pattern, input: &'h [u8]) -> Option<SlotCursor<'h>> {
if !pattern.binds()
&& let Some(spans) = crate::engine::routed_spans(pattern, input)
{
return Some(SlotCursor { input, source: SlotSource::Spans { spans, at: 0 } });
}
if let Some((ms, names)) = crate::prefilter::scan_flat_by_literal_windows(pattern, input) {
return Some(SlotCursor { input, source: SlotSource::Flat { ms, names, at: 0 } });
}
if let Some(ms) = crate::prefilter::scan_captures_by_literal_windows(pattern, input) {
return Some(SlotCursor { input, source: SlotSource::Resolved { ms, at: 0 } });
}
if let Some(spans) = crate::engine::routed_spans(pattern, input) {
let source = if !pattern.binds() {
SlotSource::Spans { spans, at: 0 }
} else if let Some((ms, names)) =
crate::prefilter::flat_captures_by_byte_bounds(pattern, input, &spans)
{
SlotSource::Flat { ms, names, at: 0 }
} else if let Some((ms, names)) =
crate::prefilter::flat_captures_by_windows(pattern, input, &spans)
{
SlotSource::Flat { ms, names, at: 0 }
} else {
SlotSource::Resolved { ms: crate::engine::captures(pattern, input, &spans), at: 0 }
};
return Some(SlotCursor { input, source });
}
let walk = crate::nfa::SerialWalk::over(pattern, input)?;
Some(SlotCursor { input, source: SlotSource::Walk(Box::new(walk)) })
}
#[must_use]
pub fn static_captures_len(pattern: &Pattern) -> Option<usize> {
let all = crate::cursor::capture_names(pattern);
let mut certain = Vec::new();
certain_names(pattern, &mut certain);
(certain.len() == all.len()).then_some(all.len())
}
fn certain_names(pattern: &Pattern, out: &mut Vec<String>) {
match pattern {
Pattern::Bind(name, _, inner) => {
if !out.iter().any(|n| n == name) {
out.push(name.clone());
}
certain_names(inner, out);
}
Pattern::Plus(p, _)
| Pattern::Atomic(p)
| Pattern::Balanced(_, p)
| Pattern::Field(_, p) => certain_names(p, out),
Pattern::Repeat(p, lo, _, _) if *lo >= 1 => certain_names(p, out),
Pattern::Concat(v) => {
for p in v {
certain_names(p, out);
}
}
Pattern::Alt(v, _) => {
let Some((first, rest)) = v.split_first() else { return };
let mut common = Vec::new();
certain_names(first, &mut common);
for p in rest {
let mut theirs = Vec::new();
certain_names(p, &mut theirs);
common.retain(|n| theirs.contains(n));
}
for n in common {
if !out.contains(&n) {
out.push(n);
}
}
}
Pattern::Within(..)
| Pattern::Star(..)
| Pattern::Opt(..)
| Pattern::Repeat(..)
| Pattern::Assert(..)
| Pattern::Empty
| Pattern::Atom(_)
| Pattern::Guard(..)
| Pattern::Anchor(_) => {}
}
}
#[must_use]
pub fn static_token_extent(pattern: &Pattern) -> Option<usize> {
match pattern {
Pattern::Empty | Pattern::Guard(..) | Pattern::Anchor(_) | Pattern::Assert(..) => Some(0),
Pattern::Atom(_) => Some(1),
Pattern::Within(v, 0) => Some(v.len()),
Pattern::Within(..) => None,
Pattern::Bind(_, _, p) | Pattern::Atomic(p) | Pattern::Field(_, p) => {
static_token_extent(p)
}
Pattern::Balanced(_, p) => static_token_extent(p)?.checked_add(2),
Pattern::Concat(v) => {
let mut total = 0usize;
for p in v {
total = total.checked_add(static_token_extent(p)?)?;
}
Some(total)
}
Pattern::Alt(v, _) => {
let (first, rest) = v.split_first()?;
let width = static_token_extent(first)?;
for p in rest {
if static_token_extent(p)? != width {
return None;
}
}
Some(width)
}
Pattern::Repeat(p, lo, Some(hi), _) if lo == hi => {
static_token_extent(p)?.checked_mul(*lo)
}
Pattern::Star(..) | Pattern::Plus(..) | Pattern::Opt(..) | Pattern::Repeat(..) => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::parse;
#[test]
fn a_refilled_buffer_answers_what_a_match_would_have() {
let hay = b"a 1 b 2 c 3";
let p = parse("\\W:k \\N:v").expect("parses");
let want: Vec<_> = crate::captures_iter(&p, hay).collect();
assert_eq!(want.len(), 3, "three pairs: {want:?}");
let mut slots = CaptureSlots::of(&p);
let mut at = 0usize;
let mut seen = 0usize;
while let Some(span) = captures_read_at(&p, hay, at, &mut slots) {
assert_eq!(span, want[seen].span(), "match {seen}");
assert_eq!(
slots.by_name("k"),
want[seen].group_span("k"),
"register k of match {seen}"
);
assert_eq!(
slots.by_name("v"),
want[seen].group_span("v"),
"register v of match {seen}"
);
assert_eq!(slots.matched(), Some(span));
at = span.end();
seen += 1;
}
assert_eq!(seen, want.len(), "the loop took every match");
}
#[test]
fn the_inline_registers_are_the_ones_the_eager_resolve_reports() {
let mut text = String::new();
for i in 0..80u32 {
text.push_str(&format!("let value_{i} = {} ; call_{i}(alpha, beta) ;\n", i * 37));
}
let hay = text.as_bytes();
for src in ["\"let\" \\W:v \"=\"", "\\W:name \"=\"", "\"let\" \\W:z \"=\" \\N:a"] {
let p = parse(src).expect("parses");
let spans = crate::scan(&p, hay);
let want = crate::captures(&p, hay, &spans);
assert!(!want.is_empty(), "{src} matches the corpus");
let got: Vec<_> = crate::captures_iter(&p, hay).collect();
assert_eq!(got, want, "captures_iter {src}");
let mut slots = CaptureSlots::of(&p);
let mut c = captures_read_iter(&p, hay).expect("a route or the walk takes this");
let mut seen = 0usize;
while let Some(span) = c.next_into(&mut slots) {
assert_eq!(span, want[seen].span(), "{src} match {seen}");
for name in slots.names().to_vec() {
assert_eq!(
slots.by_name(&name),
want[seen].group_span(&name),
"{src} register {name} of match {seen}"
);
}
seen += 1;
}
assert_eq!(seen, want.len(), "{src} took every match");
}
}
#[test]
fn a_cursor_refilling_one_buffer_takes_the_same_matches() {
let hay = b"a 1 b 2 c 3 d 4";
let p = parse("\\W:k \\N:v").expect("parses");
let want: Vec<_> = crate::captures_iter(&p, hay).collect();
assert_eq!(want.len(), 4, "four pairs: {want:?}");
let mut slots = CaptureSlots::of(&p);
let mut c = captures_read_iter(&p, hay).expect("the walk takes this pattern");
let mut seen = 0usize;
while let Some(span) = c.next_into(&mut slots) {
assert_eq!(span, want[seen].span(), "match {seen}");
assert_eq!(slots.by_name("k"), want[seen].group_span("k"), "k of match {seen}");
assert_eq!(slots.by_name("v"), want[seen].group_span("v"), "v of match {seen}");
seen += 1;
}
assert_eq!(seen, want.len(), "the cursor took every match");
let routed = parse("\"a\"").expect("parses");
let mut rs = CaptureSlots::of(&routed);
let mut rc = captures_read_iter(&routed, hay).expect("a route answers this");
let mut got = Vec::new();
while let Some(s) = rc.next_into(&mut rs) {
got.push(s);
}
assert_eq!(got, crate::scan(&routed, hay), "the routed cursor selects what the scan does");
assert!(rs.is_empty(), "the pattern binds nothing, so there are no slots");
let balanced = parse("\\B(\\N:v)").expect("parses");
assert!(captures_read_iter(&balanced, b"(1) (2)").is_none());
assert_eq!(crate::captures_iter(&balanced, b"(1) (2)").count(), 2);
}
#[test]
fn binding_a_register_does_not_key_the_thread_list_but_reading_one_does() {
let bind_only = parse("\\W:k \"=\"").expect("parses");
assert!(bind_only.binds(), "it writes a register");
assert!(!bind_only.reads_registers(), "and never reads one back");
let reads = parse("\\W:x \"=\" =x").expect("parses");
assert!(reads.reads_registers(), "a back-reference reads one");
let hay = b"alpha = beta ; gamma = gamma ;";
let got: Vec<_> = crate::captures_iter(&bind_only, hay)
.map(|m| m.group_span("k").map(|s| (s.start(), s.end())))
.collect();
assert_eq!(got, vec![Some((0, 5)), Some((15, 20))], "both assignments, both keys");
let back: Vec<_> = crate::captures_iter(&reads, hay)
.map(|m| (m.span().start(), m.span().end()))
.collect();
assert_eq!(back.len(), 1, "only gamma = gamma repeats its token: {back:?}");
assert_eq!(&hay[back[0].0..back[0].1], &b"gamma = gamma"[..]);
}
#[test]
fn a_slot_carries_the_tokens_as_well_as_the_bytes() {
let hay = b"alpha 42 beta 7";
let p = parse("\\W:k \\N:v").expect("parses");
let mut slots = CaptureSlots::of(&p);
assert!(captures_read(&p, hay, &mut slots).is_some(), "alpha 42 matches");
let k = slots.index_of("k").expect("k is a register");
let v = slots.index_of("v").expect("v is a register");
assert_eq!(slots.tokens(k), Some(1), "a word is one token");
assert_eq!(slots.tokens(v), Some(1), "a number is one token");
assert_eq!(slots.matched_tokens(), Some(2), "the match spans both");
let (ks, ke) = slots.extent(k).expect("k bound");
let (vs, ve) = slots.extent(v).expect("v bound");
assert_eq!(ke, vs, "adjacent tokens: k ends at {ke}, v starts at {vs}");
assert_eq!(slots.matched_extent(), Some((ks, ve)));
}
#[test]
fn clearing_keeps_the_names_and_forgets_the_positions() {
let p = parse("\\W:k \\N:v").expect("parses");
let mut slots = CaptureSlots::of(&p);
assert!(captures_read(&p, b"alpha 42", &mut slots).is_some());
slots.clear();
assert_eq!(slots.len(), 2, "the shape is the pattern's, not the match's");
assert_eq!(slots.get(0), None);
assert_eq!(slots.matched(), None);
assert_eq!(slots.names().to_vec(), vec!["k".to_string(), "v".to_string()]);
}
#[test]
fn a_register_that_need_not_bind_has_no_static_count() {
assert_eq!(static_captures_len(&parse("\\W:k \\N:v").expect("parses")), Some(2));
assert_eq!(
static_captures_len(&parse("\\W:k (\\N:v)?").expect("parses")),
None,
"v is bound by some matches and not others"
);
assert_eq!(
static_captures_len(&parse("\\W:k | \\N:k").expect("parses")),
Some(1),
"every branch binds k"
);
assert_eq!(
static_captures_len(&parse("\\W:k | \\N:v").expect("parses")),
None,
"neither name is bound by both branches"
);
let asserted = parse("\\W ~(\\N:v)").expect("parses");
assert_eq!(crate::capture_names(&asserted), Vec::<String>::new(), "v cannot bind");
assert_eq!(static_captures_len(&asserted), Some(0), "every match binds none");
}
#[test]
fn a_fixed_token_width_is_a_property_no_byte_matcher_has() {
assert_eq!(static_token_extent(&parse("\\W \\N").expect("parses")), Some(2));
assert_eq!(static_token_extent(&parse("\\W{3}").expect("parses")), Some(3));
assert_eq!(
static_token_extent(&parse("\\B(\\N)").expect("parses")),
Some(3),
"the open and the close are tokens of the match"
);
assert_eq!(
static_token_extent(&parse("\\W ~(\\N)").expect("parses")),
Some(1),
"an assertion consumes nothing"
);
assert_eq!(static_token_extent(&parse("\\W | \\N").expect("parses")), Some(1));
assert_eq!(
static_token_extent(&parse("\\W | \\N \\N").expect("parses")),
None,
"the branches are one token and two"
);
assert_eq!(static_token_extent(&parse("\\W*").expect("parses")), None);
assert_eq!(static_token_extent(&parse("\\W{2,4}").expect("parses")), None);
}
#[test]
fn anchoring_selects_where_a_match_may_begin_and_never_what_the_input_is() {
let hay = b"a 1 b 2 c 3";
let p = parse("\\W:k \\N:v").expect("parses");
let all: Vec<_> = crate::captures_iter(&p, hay).collect();
assert_eq!(all.len(), 3);
let after_first = all[0].span().end();
let got = crate::captures_at(&p, hay, after_first).expect("a match follows the first");
assert_eq!(got.span(), all[1].span(), "the next match, not the first again");
assert_eq!(got.group_span("k"), all[1].group_span("k"));
assert_eq!(crate::shortest_match_at(&p, hay, 0), crate::shortest_match(&p, hay));
assert_eq!(
crate::shortest_match_at(&p, hay, after_first),
Some(all[1].span().end()),
"the soonest end at or after the position"
);
assert_eq!(crate::captures_at(&p, hay, hay.len()), None, "nothing begins past the end");
}
#[test]
fn a_set_reports_where_each_member_matched() {
let hay = b"alpha 42";
let set = crate::PatternSet::new(vec![
parse("\\N").expect("parses"),
parse("\"zzzqqq\"").expect("parses"),
parse("\\W").expect("parses"),
]);
let hits = set.matches_with_spans(hay);
assert_eq!(hits.iter().map(|&(i, _)| i).collect::<Vec<_>>(), vec![0, 2]);
assert_eq!(&hay[hits[0].1.start()..hits[0].1.end()], &b"42"[..]);
assert_eq!(&hay[hits[1].1.start()..hits[1].1.end()], &b"alpha"[..]);
let m = set.matched(hay);
assert!(m.matched(0) && !m.matched(1) && m.matched(2));
assert!(m.matched_any(), "two of three");
assert!(!m.matched_all(), "zzzqqq is nowhere in the input");
assert_eq!(m.iter().collect::<Vec<_>>(), vec![0, 2]);
assert_eq!(m.len(), 3, "every index carries a verdict, matched or not");
let at = set.matches_at(hay, 5);
assert!(at.matched(0), "42 begins at or after byte 5");
assert!(!at.matched(2), "alpha ends before it");
assert!(set.is_match_at(hay, 5));
assert!(!set.is_match_at(hay, hay.len()), "nothing begins past the end");
}
}