use crate::ast::Pattern;
use crate::engine::Span;
enum Source {
Routed { spans: Vec<Span>, at: usize },
Walk(Box<crate::nfa::SerialWalk<crate::nfa::OwnedStream>>),
}
pub struct Cursor<'h> {
input: &'h [u8],
source: Source,
starts: Option<Vec<usize>>,
}
impl<'h> Cursor<'h> {
#[must_use]
pub fn new(pattern: &Pattern, input: &'h [u8]) -> Self {
if let Some(shapes) = crate::library::shapes_for(pattern) {
return Cursor::over_library_kinds(pattern, input, &shapes);
}
if let Some(spans) = crate::engine::routed_spans(pattern, input) {
return Cursor { input, source: Source::Routed { spans, at: 0 }, starts: None };
}
Cursor::past_the_routes(pattern, input)
}
fn over_library_kinds(pattern: &Pattern, input: &'h [u8], shapes: &crate::custom::ShapeSet) -> Self {
let blobs = crate::lexer::blob_runs(input);
let toks = crate::lexer::lex_with_shapes(input, &blobs, shapes, 0);
let spans = match crate::nfa::scan_nfa_over(pattern, input, &toks) {
Some(spans) => spans,
None => crate::engine::scan_tokens_from(pattern, input, &toks, 0),
};
let starts =
toks.iter().filter(|t| t.is_significant()).map(crate::token::Token::start).collect();
Cursor { input, source: Source::Routed { spans, at: 0 }, starts: Some(starts) }
}
fn over_every_match(pattern: &Pattern, input: &'h [u8]) -> Self {
let spans = crate::engine::scan(pattern, input);
Cursor { input, source: Source::Routed { spans, at: 0 }, starts: None }
}
fn past_the_routes(pattern: &Pattern, input: &'h [u8]) -> Self {
if let Some(walk) = crate::nfa::SerialWalk::over(pattern, input) {
return Cursor { input, source: Source::Walk(Box::new(walk)), starts: None };
}
let spans = crate::engine::scan_set_reachability(pattern, input);
Cursor { input, source: Source::Routed { spans, at: 0 }, starts: None }
}
pub fn next_extent(&mut self) -> Option<(Span, usize)> {
if let Source::Walk(w) = &mut self.source {
return w.next_span_and_extent(self.input);
}
let s = self.next()?;
let input = self.input;
let starts = self.starts.get_or_insert_with(|| {
crate::parallel_lex::lex_parallel(input)
.iter()
.filter(|t| t.is_significant())
.map(crate::token::Token::start)
.collect()
});
let first = starts.partition_point(|&b| b < s.start());
let past = starts.partition_point(|&b| b < s.end());
Some((s, past - first))
}
}
impl Iterator for Cursor<'_> {
type Item = Span;
fn next(&mut self) -> Option<Span> {
match &mut self.source {
Source::Routed { spans, at } => {
let s = spans.get(*at).copied();
if s.is_some() {
*at += 1;
}
s
}
Source::Walk(w) => w.next_span(self.input),
}
}
}
#[must_use]
pub fn find(pattern: &Pattern, input: &[u8]) -> Option<Span> {
if crate::library::shapes_for(pattern).is_some() {
crate::trace::rung("find", "a lex under the library's shapes", input.len());
return Cursor::new(pattern, input).next();
}
if let Some(first) = crate::engine::routed_first(pattern, input) {
crate::trace::rung("find", "a route, not the cursor", input.len());
return first;
}
if let Some(answer) = crate::prefilter::find_by_growing_prefix(pattern, input) {
crate::trace::rung("find", "a widening prefix", input.len());
return answer;
}
crate::trace::rung("find", "the cursor over a whole lex", input.len());
Cursor::past_the_routes(pattern, input).next()
}
#[doc(hidden)]
#[must_use]
pub fn find_by_full_lex(pattern: &Pattern, input: &[u8]) -> Option<Span> {
Cursor::new(pattern, input).next()
}
pub fn find_iter<'h>(pattern: &Pattern, input: &'h [u8]) -> Cursor<'h> {
Cursor::new(pattern, input)
}
#[must_use]
pub fn find_at(pattern: &Pattern, input: &[u8], at: usize) -> Option<Span> {
if let Some(shapes) = crate::library::shapes_for(pattern) {
return crate::engine::scan_with_shapes_from(pattern, input, &shapes, at).into_iter().next();
}
if let Some(first) = crate::engine::routed_first_at(pattern, input, at) {
return first;
}
if let Some(answer) = crate::prefilter::find_at_by_growing_prefix(pattern, input, at) {
crate::trace::rung("find_at", "a widening prefix from the offset", input.len());
return answer;
}
if let Some(mut w) = crate::nfa::SerialWalk::over(pattern, input) {
crate::trace::rung("find_at", "the held walk over a whole fused lex", input.len());
w.seek(at);
return w.next_span(input);
}
let toks = crate::parallel_lex::lex_parallel(input);
let start = toks.partition_point(|t| t.start() < at);
crate::engine::scan_tokens_from(pattern, input, &toks, start).into_iter().next()
}
#[must_use]
pub fn is_match_at(pattern: &Pattern, input: &[u8], at: usize) -> bool {
find_at(pattern, input, at).is_some()
}
enum MatchSource<'h> {
Resolved(std::vec::IntoIter<crate::engine::Match>),
Flat {
ms: std::vec::IntoIter<crate::nfa::FlatMatch>,
names: std::sync::Arc<[String]>,
},
Plain(Box<Cursor<'h>>),
Walk(Box<crate::nfa::SerialWalk<crate::nfa::OwnedStream>>),
}
pub struct MatchCursor<'h> {
input: &'h [u8],
source: MatchSource<'h>,
held: Option<Held>,
}
enum Held {
Flat(crate::nfa::FlatMatch),
Owned(crate::engine::Match),
Span(Span),
}
pub struct MatchRef<'c> {
pub start: usize,
pub end: usize,
regs: &'c [Span],
names: &'c [String],
}
impl<'c> MatchRef<'c> {
#[must_use]
pub fn span(&self) -> Span {
Span { start: self.start as u32, end: self.end as u32 }
}
#[must_use]
pub fn captures(&self) -> &[Span] {
self.regs
}
#[must_use]
pub fn names(&self) -> &[String] {
self.names
}
#[must_use]
pub fn group<'h>(&self, name: &str, input: &'h [u8]) -> Option<&'h [u8]> {
let k = self.names.iter().position(|n| n == name)?;
self.regs.get(k).map(|s| &input[s.range()])
}
#[must_use]
pub fn to_match(&self) -> crate::engine::Match {
if self.names.is_empty() {
return crate::engine::Match::plain(self.start, self.end);
}
crate::engine::Match::bound(
self.start,
self.end,
crate::engine::Regs::from_slice(self.regs),
std::sync::Arc::from(self.names.to_vec()),
)
}
}
impl MatchCursor<'_> {
pub fn next_ref(&mut self) -> Option<MatchRef<'_>> {
let input = self.input;
self.held = match &mut self.source {
MatchSource::Flat { ms, .. } => ms.next().map(Held::Flat),
MatchSource::Resolved(ms) => ms.next().map(Held::Owned),
MatchSource::Plain(c) => c.next().map(Held::Span),
MatchSource::Walk(w) => w.next_match(input).map(Held::Owned),
};
let flat_names: &[String] = match &self.source {
MatchSource::Flat { names, .. } => names,
_ => &[],
};
match self.held.as_ref()? {
Held::Flat(m) => Some(MatchRef {
start: m.span.start(),
end: m.span.end(),
regs: &m.regs[..flat_names.len()],
names: flat_names,
}),
Held::Owned(m) => {
Some(MatchRef { start: m.start, end: m.end, regs: m.captures(), names: m.names() })
}
Held::Span(s) => {
Some(MatchRef { start: s.start(), end: s.end(), regs: &[], names: &[] })
}
}
}
}
impl Iterator for MatchCursor<'_> {
type Item = crate::engine::Match;
fn next(&mut self) -> Option<crate::engine::Match> {
match &mut self.source {
MatchSource::Resolved(ms) => ms.next(),
MatchSource::Flat { ms, names } => ms.next().map(|m| {
crate::engine::Match::bound(
m.span.start(),
m.span.end(),
crate::engine::Regs::from_slice(&m.regs[..names.len()]),
names.clone(),
)
}),
MatchSource::Plain(c) => c.next().map(crate::engine::Match::from),
MatchSource::Walk(w) => w.next_match(self.input),
}
}
}
pub fn captures_iter<'h>(pattern: &Pattern, input: &'h [u8]) -> MatchCursor<'h> {
if !pattern.binds() {
let c = Cursor::new(pattern, input);
return MatchCursor { input, source: MatchSource::Plain(Box::new(c)), held: None };
}
if crate::library::shapes_for(pattern).is_some() {
let spans = crate::engine::scan(pattern, input);
let ms = crate::engine::captures(pattern, input, &spans);
return MatchCursor { input, source: MatchSource::Resolved(ms.into_iter()), held: None };
}
if let Some((ms, names)) = crate::prefilter::scan_flat_by_literal_windows(pattern, input) {
crate::trace::rung("captures_iter", "windows, registers inline", input.len());
return MatchCursor { input, source: MatchSource::Flat { ms: ms.into_iter(), names }, held: None };
}
if let Some(ms) = crate::prefilter::scan_captures_by_literal_windows(pattern, input) {
crate::trace::rung("captures_iter", "windows, matched and resolved at once", input.len());
return MatchCursor { input, source: MatchSource::Resolved(ms.into_iter()), held: None };
}
if let Some(spans) = crate::engine::routed_spans(pattern, input) {
if let Some((ms, names)) =
crate::prefilter::flat_captures_by_byte_bounds(pattern, input, &spans)
{
crate::trace::rung("captures_iter", "a byte route, registers off the bytes", input.len());
return MatchCursor { input, source: MatchSource::Flat { ms: ms.into_iter(), names }, held: None };
}
if let Some((ms, names)) = crate::prefilter::flat_captures_by_windows(pattern, input, &spans) {
crate::trace::rung("captures_iter", "a byte route, registers inline", input.len());
return MatchCursor { input, source: MatchSource::Flat { ms: ms.into_iter(), names }, held: None };
}
let ms = crate::engine::captures(pattern, input, &spans);
crate::trace::rung("captures_iter", "a byte route, resolved together", input.len());
return MatchCursor { input, source: MatchSource::Resolved(ms.into_iter()), held: None };
}
if let Some(walk) = crate::nfa::SerialWalk::over(pattern, input) {
crate::trace::rung("captures_iter", "the held walk over a whole fused lex", input.len());
return MatchCursor { input, source: MatchSource::Walk(Box::new(walk)), held: None };
}
let spans = crate::engine::scan_set_reachability(pattern, input);
let ms = crate::engine::captures(pattern, input, &spans);
MatchCursor { input, source: MatchSource::Resolved(ms.into_iter()), held: None }
}
#[must_use]
pub fn captures_first(pattern: &Pattern, input: &[u8]) -> Option<crate::engine::Match> {
if crate::library::shapes_for(pattern).is_some() {
let first = find(pattern, input)?;
return crate::engine::captures(pattern, input, &[first]).into_iter().next();
}
if let Some(first) = crate::engine::routed_first(pattern, input) {
let first = first?;
return crate::engine::captures(pattern, input, &[first]).into_iter().next();
}
let settled = crate::prefilter::settle_first_from_a_prefix(pattern, input, |s, toks| {
crate::nfa::captures_over(pattern, input, toks, &[s]).and_then(|v| v.into_iter().next())
});
match settled {
Some(Some(Some(m))) => Some(m),
Some(None) => None,
Some(Some(None)) | None => captures_iter(pattern, input).next(),
}
}
#[must_use]
pub fn captures_at(pattern: &Pattern, input: &[u8], at: usize) -> Option<crate::engine::Match> {
if !pattern.binds() {
return find_at(pattern, input, at).map(crate::engine::Match::from);
}
if crate::library::shapes_for(pattern).is_some() {
let first = find_at(pattern, input, at)?;
return crate::engine::captures(pattern, input, &[first]).into_iter().next();
}
if let Some(first) = crate::engine::routed_first_at(pattern, input, at) {
let first = first?;
return crate::engine::captures(pattern, input, &[first]).into_iter().next();
}
if let Some(first) = crate::prefilter::find_at_by_growing_prefix(pattern, input, at) {
crate::trace::rung("captures_at", "a widening prefix from the offset", input.len());
let first = first?;
return crate::engine::captures(pattern, input, &[first]).into_iter().next();
}
if let Some(mut w) = crate::nfa::SerialWalk::over(pattern, input) {
crate::trace::rung("captures_at", "the held walk over a whole fused lex", input.len());
w.seek(at);
return w.next_match(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()?;
crate::engine::captures_over(pattern, input, &toks, &[span]).into_iter().next()
}
#[must_use]
pub fn shortest_match(pattern: &Pattern, input: &[u8]) -> Option<usize> {
if crate::library::shapes_for(pattern).is_some() {
return find(pattern, input).map(|s| s.end());
}
if let Some(first) = crate::engine::routed_first_positional(pattern, input) {
crate::trace::rung("shortest_match", "a positional byte route, no lex", input.len());
return first.map(|s| s.end());
}
match crate::prefilter::shortest_end_in_windows(pattern, input) {
Ok(end) => {
crate::trace::rung("shortest_match", "the windows a literal opens", input.len());
return end;
}
Err(why) => {
crate::trace::rung("shortest_match", &format!("the windows refuse: {why}"), input.len());
}
}
if let Some(end) = crate::prefilter::shortest_end_by_growing_prefix(pattern, input) {
crate::trace::rung("shortest_match", "a widening prefix", input.len());
return end;
}
if let Some(end) = crate::nfa::shortest_end_from_byte(pattern, input, 0) {
crate::trace::rung("shortest_match", "the engine over a whole fused lex", input.len());
return Some(end);
}
if crate::nfa::compile_pattern(pattern).is_some() {
crate::trace::rung("shortest_match", "the engine, which found nothing", input.len());
return None;
}
crate::trace::rung("shortest_match", "the set engine over a stitched lex", input.len());
let toks = crate::parallel_lex::lex_parallel(input);
crate::engine::scan_tokens_from(pattern, input, &toks, 0).first().map(Span::end)
}
#[must_use]
pub fn shortest_match_at(pattern: &Pattern, input: &[u8], at: usize) -> Option<usize> {
if crate::library::shapes_for(pattern).is_some() {
return find_at(pattern, input, at).map(|s| s.end());
}
if let Some(first) = crate::engine::routed_first_at(pattern, input, at) {
return first.map(|s| s.end());
}
if let Some(end) = crate::prefilter::shortest_end_by_growing_prefix_from(pattern, input, at) {
crate::trace::rung("shortest_match_at", "a widening prefix from the offset", input.len());
return end;
}
if let Some(end) = crate::nfa::shortest_end_from_byte(pattern, input, at) {
crate::trace::rung("shortest_match_at", "the engine over a whole fused lex", input.len());
return Some(end);
}
if crate::nfa::compile_pattern(pattern).is_some() {
crate::trace::rung("shortest_match_at", "the engine, which found nothing", input.len());
return None;
}
crate::trace::rung("shortest_match_at", "the set engine over a stitched lex", input.len());
let toks = crate::parallel_lex::lex_parallel(input);
let start = toks.partition_point(|t| t.start() < at);
crate::engine::scan_tokens_from(pattern, input, &toks, start).first().map(Span::end)
}
enum Separators<'h> {
Cursor(Box<Cursor<'h>>),
Anchored { pattern: Pattern, reader: Box<Option<crate::prefilter::ByteReader<'h>>>, at: usize },
Prefix {
pattern: Pattern,
settled: std::vec::IntoIter<Span>,
covered: usize,
handed: usize,
},
Walk(Box<crate::nfa::SerialWalk<crate::nfa::OwnedStream>>),
}
impl<'h> Separators<'h> {
fn next(&mut self, input: &'h [u8]) -> Option<Span> {
let (pattern, from) = match self {
Separators::Cursor(c) => return c.next(),
Separators::Walk(w) => return w.next_span(input),
Separators::Prefix { pattern, settled, covered, handed } => loop {
if let Some(s) = settled.next() {
*handed = s.end().max(s.start() + 1);
return Some(s);
}
if *covered >= input.len() {
return None;
}
*covered = (*covered * 2).min(input.len());
let Some((found, _)) =
crate::prefilter::settled_prefix_matches(pattern, input, *covered)
else {
break (pattern.clone(), *handed);
};
let fresh: Vec<Span> =
found.into_iter().filter(|s| s.start() >= *handed).collect();
*settled = fresh.into_iter();
},
Separators::Anchored { pattern, reader, at } => {
let answered = crate::engine::routed_first_at_reading(pattern, input, *at, reader)
.or_else(|| {
crate::prefilter::first_by_literal_windows_at(pattern, input, *at)
});
match answered {
Some(found) => {
let Some(s) = found else {
*at = input.len();
return None;
};
*at = s.end().max(s.start() + 1);
return Some(s);
}
None => (pattern.clone(), *at),
}
}
};
let mut walk = crate::nfa::SerialWalk::over(&pattern, input)
.expect("a pattern an anchored byte route answered compiles for this engine");
walk.seek(from);
let next = walk.next_span(input);
*self = Separators::Walk(Box::new(walk));
next
}
}
pub struct Split<'h> {
separators: Separators<'h>,
input: &'h [u8],
last: usize,
left: usize,
done: bool,
depth: Option<(crate::stress::StressField, u16)>,
}
impl<'h> Iterator for Split<'h> {
type Item = &'h [u8];
fn next(&mut self) -> Option<&'h [u8]> {
if self.done || self.left == 0 {
return None;
}
if self.left == 1 {
self.done = true;
return Some(&self.input[self.last..]);
}
loop {
match self.separators.next(self.input) {
Some(s) => {
if let Some((field, want)) = &self.depth
&& field.depth_at(s.start()) != *want
{
continue;
}
let piece = &self.input[self.last..s.start()];
self.last = s.end();
self.left = self.left.saturating_sub(1);
return Some(piece);
}
None => {
self.done = true;
return Some(&self.input[self.last..]);
}
}
}
}
}
pub fn split<'h>(pattern: &Pattern, input: &'h [u8]) -> Split<'h> {
Split {
separators: Separators::Cursor(Box::new(Cursor::over_every_match(pattern, input))),
input,
last: 0,
left: usize::MAX,
done: false,
depth: None,
}
}
const PREFIX_FIRST_BYTES: usize = 64 * 1024;
pub fn splitn<'h>(pattern: &Pattern, input: &'h [u8], limit: usize) -> Split<'h> {
let mut reader = None;
let bare = pattern.without_bindings();
let sought = bare.as_ref().unwrap_or(pattern);
let separators = if crate::library::shapes_for(pattern).is_some() {
crate::trace::rung("splitn", "a cursor over a lex under the library's shapes", input.len());
Separators::Cursor(Box::new(Cursor::new(pattern, input)))
} else if crate::prefilter::requires_absent(pattern, input) {
crate::trace::rung("splitn", "a cursor over every match", input.len());
Separators::Cursor(Box::new(Cursor::new(pattern, input)))
} else if crate::engine::routed_first_at_reading(sought, input, 0, &mut reader).is_some()
|| crate::prefilter::first_by_literal_windows_at(sought, input, 0).is_some()
{
crate::trace::rung("splitn", "one separator at a time", input.len());
Separators::Anchored { pattern: sought.clone(), reader: Box::new(reader), at: 0 }
} else if let Some((settled, _)) =
crate::prefilter::settled_prefix_matches(sought, input, PREFIX_FIRST_BYTES)
{
crate::trace::rung("splitn", "a prefix widened only when it runs out", input.len());
Separators::Prefix {
pattern: sought.clone(),
settled: settled.into_iter(),
covered: PREFIX_FIRST_BYTES.min(input.len()),
handed: 0,
}
} else {
crate::trace::rung("splitn", "a cursor over every match", input.len());
Separators::Cursor(Box::new(Cursor::new(pattern, input)))
};
Split { separators, input, last: 0, left: limit, done: false, depth: None }
}
pub fn split_at_depth<'h>(pattern: &Pattern, input: &'h [u8], depth: u16) -> Split<'h> {
Split {
separators: Separators::Cursor(Box::new(Cursor::over_every_match(pattern, input))),
input,
last: 0,
left: usize::MAX,
done: false,
depth: Some((crate::stress::analyze_bytes(input), depth)),
}
}
#[must_use]
pub fn escape(text: &str) -> String {
let bytes = text.as_bytes();
let mut out = String::with_capacity(text.len() + 2);
for t in crate::lexer::lex(bytes).iter().filter(|t| t.is_significant()) {
if !out.is_empty() {
out.push(' ');
}
out.push('"');
for c in String::from_utf8_lossy(&bytes[t.start()..t.end()]).chars() {
if c == '\\' || c == '"' {
out.push('\\');
}
out.push(c);
}
out.push('"');
}
out
}
#[must_use]
pub fn capture_names(pattern: &Pattern) -> Vec<String> {
pattern.capture_names()
}
#[must_use]
pub fn captures_len(pattern: &Pattern) -> usize {
capture_names(pattern).len()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_borrowed_view_reports_what_the_owning_iterator_reports() {
let mut text = String::new();
for i in 0..400u32 {
text.push_str(&format!("let value_{i} = {i} ; call_{i}(alpha, beta) ;\n"));
}
let input = text.as_bytes();
for src in ["\"let\" \\W:v \"=\"", "\\W:w", "\"alpha\"", "\\W \"=\"", "\\N:n \";\""] {
let p = crate::parse(src).expect("the test's own patterns parse");
let owned: Vec<crate::engine::Match> = captures_iter(&p, input).collect();
let mut cur = captures_iter(&p, input);
let mut seen = 0usize;
while let Some(m) = cur.next_ref() {
let want = &owned[seen];
assert_eq!((m.start, m.end), (want.start, want.end), "{src} span at {seen}");
assert_eq!(m.captures(), want.captures(), "{src} registers at {seen}");
assert_eq!(m.names(), want.names(), "{src} names at {seen}");
assert_eq!(&m.to_match(), want, "{src} owned copy at {seen}");
seen += 1;
}
assert_eq!(seen, owned.len(), "{src} reported a different number of matches");
}
}
#[test]
fn a_binding_separator_splits_where_its_bare_twin_splits() {
let mut text = String::new();
for i in 0..400u32 {
text.push_str(&format!("let value_{i} = {i} ; call_{i}(alpha, beta) ;\n"));
}
let input = text.as_bytes();
for (bound, bare) in [
("\\W:name \"=\"", "\\W \"=\""),
("\"let\" \\W:v \"=\"", "\"let\" \\W \"=\""),
("\\W:w", "\\W"),
] {
let b = crate::parser::parse(bound).expect(bound);
let u = crate::parser::parse(bare).expect(bare);
for limit in [1usize, 2, 4, 50, usize::MAX] {
let got: Vec<&[u8]> = splitn(&b, input, limit).collect();
let want: Vec<&[u8]> = splitn(&u, input, limit).collect();
assert_eq!(got, want, "{bound} against {bare}, limit {limit}");
}
let got: Vec<&[u8]> = split(&b, input).collect();
let want: Vec<&[u8]> = split(&u, input).collect();
assert_eq!(got, want, "{bound} against {bare}, unlimited");
}
}
#[test]
fn split_and_a_limitless_splitn_reach_the_same_pieces_by_different_routes() {
let mut text = String::new();
for i in 0..3_000u32 {
text.push_str(&format!("let value_{i} = {} ;\n", i * 7));
text.push_str(&format!("call_{i}(alpha, beta, {i}) ;\n"));
text.push_str(&format!("if (cond_{i}) {{ do_{i}(x) ; }}\n"));
}
for input in [text.as_bytes(), b"", b"nothing matches in here"] {
for src in PATTERNS {
let p = match crate::parse(src) {
Ok(p) => p,
Err(e) => panic!("{src} does not parse: {e:?}"),
};
let eager: Vec<&[u8]> = split(&p, input).collect();
let walked: Vec<&[u8]> = splitn(&p, input, usize::MAX).collect();
assert_eq!(eager, walked, "{src}");
assert!(
eager.iter().map(|p| p.len()).sum::<usize>() <= input.len(),
"{src}: the pieces outgrew the input"
);
}
}
}
const PATTERNS: &[&str] = &[
"\"alpha\"",
"\"zzzqqq\"",
"\\W",
"\\N",
"\\W \"=\"",
"`cond_[0-9]+`",
"(\"alpha\" | \"beta\")",
"\"let\" \\W \"=\"",
"\\W{2}",
"^ \"let\"",
"\\W:name \"=\"",
"\\W:x \"=\" =x",
"\\B(\\W)",
"\\W \"=\" ~(\\N)",
];
fn corpus() -> Vec<u8> {
let mut s = String::new();
for i in 0..400 {
match i % 4 {
0 => s.push_str(&format!("let value_{i} = {} ;\n", i * 37)),
1 => s.push_str(&format!("call_{i}(alpha, beta, {i}) ;\n")),
2 => s.push_str(&format!("key_{i}: item_{i}, item_{} ;\n", i + 1)),
_ => s.push_str(&format!("if (cond_{i}) {{ do_{i}(x) ; }}\n")),
}
}
s.into_bytes()
}
#[test]
fn a_cursor_collects_to_what_the_scan_returns() {
let input = corpus();
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
let want = crate::scan(&p, &input);
let got: Vec<Span> = find_iter(&p, &input).collect();
assert_eq!(got, want, "{src}");
}
}
fn quoted_corpus() -> Vec<u8> {
let mut s = String::new();
for i in 0..400 {
match i % 5 {
0 => s.push_str(&format!("let value_{i} = \"text {i}\" ;\n")),
1 => s.push_str(&format!("call_{i}(alpha, \"beta {i}\", {i}) ;\n")),
2 => s.push_str(&format!("key_{i}: \"a \\\"quoted\\\" {i}\" ;\n")),
3 => s.push_str(&format!("note_{i} = 'c' ; other_{i} = {} ;\n", i * 37)),
_ => s.push_str(&format!("if (cond_{i}) {{ do_{i}(\"x\") ; }}\n")),
}
}
s.into_bytes()
}
#[test]
fn a_cursor_collects_to_what_the_scan_returns_over_quoted_text() {
let input = quoted_corpus();
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
let want = crate::scan(&p, &input);
let got: Vec<Span> = find_iter(&p, &input).collect();
assert_eq!(got, want, "{src}");
}
}
#[test]
fn find_is_the_first_match_the_scan_reports() {
let input = corpus();
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
assert_eq!(find(&p, &input), crate::scan(&p, &input).first().copied(), "{src}");
}
}
#[test]
fn the_early_exit_reports_the_leftmost_match_not_the_first_literal_tried() {
let hay = b"gamma beta alpha gamma";
let p = crate::parse("(\"alpha\" | \"beta\")").expect("pattern parses");
assert_eq!(find(&p, hay).map(|s| s.start()), Some(6), "beta is leftmost");
assert_eq!(find(&p, hay), crate::scan(&p, hay).first().copied());
let q = crate::parse("(\"beta\" | \"alpha\")").expect("pattern parses");
assert_eq!(find(&q, hay), find(&p, hay));
}
#[test]
fn the_widening_prefix_answers_what_the_whole_lex_answers() {
let mut input = corpus();
while input.len() < 400_000 {
let more = corpus();
input.extend_from_slice(&more);
}
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
assert_eq!(find(&p, &input), find_by_full_lex(&p, &input), "{src}");
assert_eq!(find(&p, &input), crate::scan(&p, &input).first().copied(), "{src}");
}
}
#[test]
fn a_match_only_at_the_end_is_still_found() {
let mut input = corpus();
while input.len() < 400_000 {
let more = corpus();
input.extend_from_slice(&more);
}
input.extend_from_slice(b"\nsentinel_token = 99 ;\n");
let p = crate::parse("\"sentinel_token\" \"=\" \\N").expect("pattern parses");
let want = crate::scan(&p, &input).first().copied();
assert!(want.is_some(), "the sentinel must be there to be found");
assert_eq!(find(&p, &input), want);
assert_eq!(find_by_full_lex(&p, &input), want);
}
#[test]
fn absence_over_a_widening_prefix_is_absence_over_the_input() {
let mut input = corpus();
while input.len() < 400_000 {
let more = corpus();
input.extend_from_slice(&more);
}
for src in ["\"zzzqqq\"", "\"zzzqqq\" \"=\" \\N", "\\W \"@@\""] {
let p = crate::parse(src).expect("pattern parses");
assert_eq!(find(&p, &input), None, "{src}");
assert_eq!(crate::scan(&p, &input).first().copied(), None, "{src}");
}
}
#[test]
fn find_agrees_with_is_match_on_whether_there_is_one() {
let input = corpus();
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
assert_eq!(find(&p, &input).is_some(), crate::is_match(&p, &input), "{src}");
}
}
#[test]
fn seeking_past_a_match_finds_the_next_one() {
let input = corpus();
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
let want = crate::scan(&p, &input);
let mut at = 0usize;
for expected in &want {
let got = find_at(&p, &input, at).expect("a match the scan found");
assert_eq!(got, *expected, "{src} from {at}");
at = got.end();
}
assert_eq!(find_at(&p, &input, at), None, "{src}: nothing past the last match");
}
}
#[test]
fn seeking_from_zero_is_finding_from_the_start() {
let input = corpus();
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
assert_eq!(find_at(&p, &input, 0), find(&p, &input), "{src}");
assert_eq!(is_match_at(&p, &input, 0), crate::is_match(&p, &input), "{src}");
}
}
#[test]
fn the_pieces_and_the_matches_rebuild_the_input() {
let input = corpus();
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
let spans = crate::scan(&p, &input);
let pieces: Vec<&[u8]> = split(&p, &input).collect();
assert_eq!(pieces.len(), spans.len() + 1, "{src}: one more piece than matches");
let mut rebuilt: Vec<u8> = Vec::with_capacity(input.len());
for (i, piece) in pieces.iter().enumerate() {
rebuilt.extend_from_slice(piece);
if let Some(s) = spans.get(i) {
rebuilt.extend_from_slice(&input[s.range()]);
}
}
assert_eq!(rebuilt, input, "{src}");
}
}
#[test]
fn splitting_at_a_depth_ignores_the_separators_nested_deeper() {
let input = b"f(a, g(b, c), d)" as &[u8];
let comma = crate::parse("\",\"").expect("pattern parses");
let flat: Vec<&[u8]> = split(&comma, input).collect();
assert_eq!(flat.len(), 4, "every comma separates: {flat:?}");
let args: Vec<&[u8]> = split_at_depth(&comma, input, 1).collect();
assert_eq!(args.len(), 3, "only the top-level commas separate: {args:?}");
assert_eq!(args[0], b"f(a");
assert_eq!(args[1], b" g(b, c)", "the nested comma stayed inside its piece");
assert_eq!(args[2], b" d)");
for d in 0..3u16 {
let pieces: Vec<&[u8]> = split_at_depth(&comma, input, d).collect();
let joined = pieces.join(b"," as &[u8]);
assert_eq!(joined.len(), input.len(), "depth {d}: {pieces:?}");
}
}
#[test]
fn a_limited_split_keeps_the_rest_whole() {
let input = corpus();
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
let all: Vec<&[u8]> = split(&p, &input).collect();
assert_eq!(splitn(&p, &input, 0).count(), 0, "{src}: a limit of zero yields nothing");
for k in 1..=3.min(all.len()) {
let some: Vec<&[u8]> = splitn(&p, &input, k).collect();
assert_eq!(some.len(), k, "{src}: {k} pieces");
assert_eq!(some[..k - 1], all[..k - 1], "{src}: the pieces before the last agree");
let tail = some[k - 1];
assert_eq!(tail.as_ptr_range().end, input.as_ptr_range().end, "{src}");
}
}
}
#[test]
fn rewriting_n_matches_rewrites_the_first_n() {
let input = corpus();
let names: Vec<String> = Vec::new();
let tmpl = crate::Template::parse("X", &names).expect("the template parses");
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
let spans = crate::scan(&p, &input);
assert_eq!(
crate::rewrite_n(&p, &tmpl, &input, spans.len()),
crate::rewrite(&p, &tmpl, &input),
"{src}"
);
assert_eq!(crate::rewrite_n(&p, &tmpl, &input, 0), input, "{src}: none is untouched");
if let Some(first) = spans.first() {
let once = crate::rewrite_first(&p, &tmpl, &input);
assert_eq!(&once[..first.start()], &input[..first.start()], "{src}: before");
assert_eq!(&once[first.start()..first.start() + 1], b"X", "{src}: the replacement");
assert_eq!(&once[first.start() + 1..], &input[first.end()..], "{src}: after");
}
}
}
#[test]
fn escaped_text_matches_exactly_itself() {
for text in ["alpha", "a+b", "x(y)", "let x = 1", "a\\b", "one|two", "p.q", "\\W"] {
let pat = escape(text);
let p = crate::parse(&pat).unwrap_or_else(|e| panic!("`{pat}` should parse: {e:?}"));
let got = crate::find(&p, text.as_bytes());
assert_eq!(
got.map(|s| &text.as_bytes()[s.range()]),
Some(text.as_bytes()),
"`{pat}` over `{text}`"
);
}
}
#[test]
fn escaped_text_parses_even_where_it_cannot_match() {
for text in ["\"", "\\", "", " ", "'", "`"] {
let pat = escape(text);
crate::parse(&pat).unwrap_or_else(|e| panic!("`{pat}` from `{text}`: {e:?}"));
}
}
#[test]
fn escaping_text_that_is_not_ascii_does_not_panic() {
let texts =
["caf\u{e9}", "\u{3b8} = 1", "\u{4f60}\u{597d} world", "a \u{1f600} b", "na\u{ef}ve(x)"];
for text in texts {
let pat = escape(text);
crate::parse(&pat).unwrap_or_else(|e| panic!("`{pat}` from `{text}`: {e:?}"));
}
}
#[test]
fn the_first_match_captures_the_same_over_a_prefix_as_over_the_whole_input() {
let mut input = corpus();
while input.len() < 400_000 {
let more = corpus();
input.extend_from_slice(&more);
}
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
assert_eq!(captures_first(&p, &input), captures_iter(&p, &input).next(), "{src}");
}
}
#[test]
fn taking_captures_one_at_a_time_gives_what_taking_them_together_gives() {
let input = corpus();
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
let spans = crate::scan(&p, &input);
let want = crate::captures(&p, &input, &spans);
let got: Vec<crate::engine::Match> = captures_iter(&p, &input).collect();
assert_eq!(got, want, "{src}");
assert_eq!(captures_first(&p, &input), want.first().cloned(), "{src}");
}
}
#[test]
fn the_names_a_pattern_binds_are_the_names_its_matches_carry() {
let input = corpus();
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
let names = capture_names(&p);
assert_eq!(captures_len(&p), names.len(), "{src}");
let spans = crate::scan(&p, &input);
for m in crate::captures(&p, &input, &spans) {
let mut bound: Vec<&String> = m.names().iter().collect();
bound.sort_unstable();
let mut want: Vec<&String> = names.iter().collect();
want.sort_unstable();
assert_eq!(bound, want, "{src}: the match carries what the pattern binds");
}
}
}
#[test]
fn a_matchs_token_extent_counts_the_tokens_it_spans() {
let input = corpus();
let all = crate::lexer::lex(&input);
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
let mut c = Cursor::new(&p, &input);
let mut seen = 0usize;
while let Some((s, extent)) = c.next_extent() {
let want = all
.iter()
.filter(|t| t.is_significant() && t.start() >= s.start() && t.end() <= s.end())
.count();
assert_eq!(extent, want, "{src} at {}..{}", s.start(), s.end());
assert!(extent > 0, "{src}: a match spans at least one token");
seen += 1;
}
assert_eq!(seen, crate::scan(&p, &input).len(), "{src}: every match was handed out");
}
}
#[test]
fn a_token_extent_is_not_a_byte_length() {
let mut input = String::from("prefix = ");
input.push_str(&"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".repeat(8));
input.push_str(" ;\n");
let bytes = input.as_bytes();
let p = crate::parse("\"prefix\" \"=\" .").expect("pattern parses");
let mut c = Cursor::new(&p, bytes);
let (s, extent) = c.next_extent().expect("the pattern matches this input");
assert_eq!(extent, 3, "three tokens: the word, the equals and the run");
assert!(
s.end() - s.start() > 400,
"three tokens spanning {} bytes",
s.end() - s.start()
);
}
#[test]
fn the_shortest_end_never_reaches_past_the_first_match() {
let input = corpus();
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
let first = find(&p, &input);
let end = shortest_match(&p, &input);
assert_eq!(end.is_some(), first.is_some(), "{src}: they agree on whether");
if let (Some(e), Some(f)) = (end, first) {
assert!(e <= f.end(), "{src}: shortest end {e} past the first match's {}", f.end());
assert!(e >= f.start(), "{src}: shortest end {e} before the match begins");
}
}
}
#[test]
fn a_shorter_alternative_ends_sooner_than_the_preferred_match() {
let input = b"alpha beta gamma delta ;" as &[u8];
let p = crate::parse("\\W+").expect("pattern parses");
let first = find(&p, input).expect("it matches");
let end = shortest_match(&p, input).expect("it matches");
assert_eq!(&input[first.range()], b"alpha beta gamma delta", "greedy takes all four");
assert!(end < first.end(), "the soonest end is earlier: {end} against {}", first.end());
assert_eq!(&input[..end], b"alpha", "and it is the end of the first word");
}
#[test]
fn the_anchored_shortest_end_is_bounded_by_the_anchored_first_match() {
let input = corpus();
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
for at in [0, 1, 37, input.len() / 3, input.len() / 2, input.len()] {
let first = find_at(&p, &input, at);
let end = shortest_match_at(&p, &input, at);
assert_eq!(end.is_some(), first.is_some(), "{src} at {at}: agree on whether");
if let (Some(e), Some(f)) = (end, first) {
assert!(e <= f.end(), "{src} at {at}: end {e} past the match's {}", f.end());
assert!(e >= f.start(), "{src} at {at}: end {e} before the match begins");
}
}
}
}
#[test]
fn a_limited_split_gives_what_an_unlimited_one_gives() {
for src in [
"alpha beta alpha gamma alpha",
"let a = 1 ; let b = 2 ; let c = 3 ;",
"x alpha y alpha z",
"no separator here at all",
"alpha 'http://x/1' alpha beta alpha",
"alpha \"q\" alpha 'http://y/2' alpha end",
] {
let input = src.as_bytes();
for pat in ["\"alpha\"", "^ \"let\"", "\\W \"=\"", "\\N"] {
let p = crate::parse(pat).expect("pattern parses");
let whole: Vec<&[u8]> = split(&p, input).collect();
let unlimited: Vec<&[u8]> = splitn(&p, input, usize::MAX).collect();
assert_eq!(unlimited, whole, "{pat} over {src:?}");
for n in 1..=whole.len() + 1 {
let got: Vec<&[u8]> = splitn(&p, input, n).collect();
assert_eq!(got.len(), n.min(whole.len()), "{pat} over {src:?} at {n}");
for (i, piece) in got.iter().enumerate().take(got.len().saturating_sub(1)) {
assert_eq!(*piece, whole[i], "{pat} over {src:?} at {n}, piece {i}");
}
}
}
}
}
#[test]
fn the_line_anchored_literal_stops_at_the_first_occurrence_that_leads_a_line() {
let p = crate::parse("^ \"let\"").expect("pattern parses");
for src in [
"let a = 1 ;\nlet b = 2 ;\n",
"x let a = 1 ;\n let b = 2 ;\n",
"x let a ;\ny let b ;\nz let c ;\n",
"\n\n\tlet deep = 1 ;\n",
"nothing here at all\n",
] {
let input = src.as_bytes();
let want = crate::scan(&p, input).first().copied();
assert_eq!(find(&p, input), want, "{src:?}");
assert_eq!(crate::is_match(&p, input), want.is_some(), "{src:?} is_match");
for at in 0..input.len() {
let want_at = crate::scan(&p, input).into_iter().find(|s| s.start() >= at);
assert_eq!(find_at(&p, input, at), want_at, "{src:?} from {at}");
}
}
}
#[test]
fn the_quoted_route_names_the_token_the_lexer_makes() {
let p = crate::parse("\\Q").expect("pattern parses");
for src in [
"let s = \"hello world\" ;",
"&'static T and don't stop",
"c = '\"' ; d = 'x'",
"q = 'foo''bar' ;",
"the '90s and 5'10 tall",
"u = 'http://x/1' ; v = \"w\"",
"plain words and 12 numbers",
"'n dag 'n beer met 'n karakter",
"x = \"a\\\"b\" ; y = 2",
"s = \"unterminated",
"t = 'also unterminated",
] {
let input = src.as_bytes();
let want = crate::lexer::lex(input)
.iter()
.find(|t| t.kind == crate::token::TokenKind::Quoted)
.map(|t| (t.start(), t.end()));
assert_eq!(find(&p, input).map(|s| (s.start(), s.end())), want, "{src}");
for at in 0..input.len() {
let want_at = crate::lexer::lex(input)
.iter()
.find(|t| t.kind == crate::token::TokenKind::Quoted && t.start() >= at)
.map(|t| (t.start(), t.end()));
let got = find_at(&p, input, at).map(|s| (s.start(), s.end()));
assert_eq!(got, want_at, "{src} from {at}");
}
}
}
#[test]
fn the_anchored_byte_pattern_route_agrees_with_the_walk() {
let input = corpus();
let p = crate::parse("`cond_[0-9]+`").expect("pattern parses");
let mut compared = 0;
for at in [0, 1, 37, input.len() / 3, input.len() / 2, input.len()] {
let Some(routed) = crate::engine::routed_first_at(&p, &input, at) else {
continue;
};
let Some(mut w) = crate::nfa::SerialWalk::over(&p, &input) else {
continue;
};
w.seek(at);
assert_eq!(routed, w.next_span(&input), "the byte pattern from {at}");
compared += 1;
}
assert!(compared > 0, "the route never applied, so nothing was compared");
let small = b"x cond_1 y cond_22 z cond_333" as &[u8];
for at in [0, 1, 2, 9, 10, 19, 29] {
let routed =
crate::engine::routed_first_at(&p, small, at).expect("the route takes it");
let mut w = crate::nfa::SerialWalk::over(&p, small).expect("the engine takes it");
w.seek(at);
assert_eq!(routed, w.next_span(small), "the byte pattern from {at} of the small input");
}
assert!(
crate::engine::routed_first_at(&p, small, 0).expect("the route takes it").is_some(),
"the small input must hold a match"
);
}
#[test]
fn the_anchored_word_then_punct_route_skips_a_word_that_began_before_the_offset() {
let input = b"alpha = 1 ; beta = 2 ; gamma = 3" as &[u8];
let p = crate::parse("\\W \"=\"").expect("pattern parses");
for (at, want) in [(0, (0, 7)), (1, (12, 18)), (12, (12, 18)), (13, (23, 30))] {
let mut w = crate::nfa::SerialWalk::over(&p, input).expect("the engine takes it");
w.seek(at);
let walked = w.next_span(input);
assert_eq!(
walked.map(|s| (s.start(), s.end())),
Some(want),
"the walk from {at}"
);
assert_eq!(find_at(&p, input, at), walked, "the ladder from {at}");
}
}
#[test]
fn the_anchored_prefix_reruns_the_selection_rather_than_filtering_it() {
let input = b"a b c d e f g h" as &[u8];
let p = crate::parse("\\W{2}").expect("pattern parses");
let at = 2;
let mut w = crate::nfa::SerialWalk::over(&p, input).expect("the engine takes it");
w.seek(at);
let by_walk = w.next_span(input);
assert_eq!(
by_walk.map(|s| (s.start(), s.end())),
Some((2, 5)),
"the walk re-runs the selection from the offset"
);
assert_eq!(find_at(&p, input, at), by_walk, "and the ladder gives the same");
}
#[test]
fn the_anchored_prefix_settles_what_the_whole_input_settles() {
let input = corpus();
let mut compared = 0;
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
for at in [0, 1, 37, input.len() / 3, input.len() / 2, input.len()] {
let Some(from_prefix) = crate::prefilter::find_at_by_growing_prefix(&p, &input, at)
else {
continue;
};
let Some(mut w) = crate::nfa::SerialWalk::over(&p, &input) else {
continue;
};
w.seek(at);
assert_eq!(from_prefix, w.next_span(&input), "{src} at {at}: prefix against walk");
compared += 1;
}
}
assert!(compared > 0, "no pattern reached the prefix, so nothing was compared");
}
#[test]
fn the_prefix_settles_the_same_shortest_end_the_whole_input_does() {
let input = corpus();
let mut compared = 0;
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
for at in [0, 1, 37, input.len() / 3, input.len() / 2, input.len()] {
let Some(from_prefix) =
crate::prefilter::shortest_end_by_growing_prefix_from(&p, &input, at)
else {
continue;
};
let whole = crate::nfa::shortest_end_from_byte(&p, &input, at);
assert_eq!(from_prefix, whole, "{src} at {at}: prefix and whole input differ");
compared += 1;
}
}
assert!(compared > 0, "no pattern reached the prefix, so nothing was compared");
}
#[test]
fn an_anchor_holds_against_the_input_and_not_against_the_offset() {
let input = b"alpha beta gamma delta ;" as &[u8];
let p = crate::parse("\\A \\W").expect("pattern parses");
assert_eq!(shortest_match_at(&p, input, 0), Some(5), "at the start it matches");
assert_eq!(find_at(&p, input, 6), None, "find_at reads the anchor this way");
assert_eq!(shortest_match_at(&p, input, 6), None, "and so does this");
}
#[test]
fn an_empty_input_has_no_match_to_take() {
for src in PATTERNS {
let p = crate::parse(src).expect("pattern parses");
assert_eq!(find(&p, b""), None, "{src}");
assert_eq!(find_iter(&p, b"").count(), 0, "{src}");
assert_eq!(find_at(&p, b"", 0), None, "{src}");
}
}
}