use crate::ast::{Atom, Pattern};
use crate::engine::Span;
use crate::lexer::Significant;
use crate::token::{BracketKind, TokenKind};
#[must_use]
pub fn kind_sequence(pattern: &Pattern) -> Option<Vec<u32>> {
let atoms: &[Pattern] = match pattern {
Pattern::Concat(v) => v,
one => std::slice::from_ref(one),
};
let mut codes = Vec::with_capacity(atoms.len());
for atom in atoms {
let (kind, times) = match atom {
Pattern::Atom(Atom::Kind(kind)) => (kind, 1usize),
Pattern::Repeat(inner, lo, Some(hi), _) if lo == hi && *lo > 0 => {
let Pattern::Atom(Atom::Kind(kind)) = inner.as_ref() else {
return None;
};
(kind, *lo)
}
_ => return None,
};
if matches!(kind, TokenKind::Whitespace | TokenKind::Custom(_)) {
return None;
}
for _ in 0..times {
codes.push(kind.code());
}
}
(!codes.is_empty()).then_some(codes)
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct OpeningKinds(u64);
const CUSTOM: u64 = 1 << 63;
impl OpeningKinds {
#[must_use]
pub fn just(kind: TokenKind) -> Option<OpeningKinds> {
if matches!(kind, TokenKind::Custom(_)) {
return None;
}
(kind.code() < 64).then(|| OpeningKinds(1 << kind.code()))
}
#[must_use]
pub fn with(self, other: OpeningKinds) -> OpeningKinds {
OpeningKinds(self.0 | other.0)
}
#[must_use]
pub fn with_custom(self) -> OpeningKinds {
OpeningKinds(self.0 | CUSTOM)
}
#[must_use]
pub fn admits(self, kind: TokenKind) -> bool {
if matches!(kind, TokenKind::Custom(_)) {
return self.0 & CUSTOM != 0;
}
let code = kind.code();
code < 64 && self.0 & (1u64 << code) != 0
}
}
#[must_use]
pub fn first_kinds(pattern: &Pattern) -> Option<OpeningKinds> {
if pattern.takes_no_tokens() {
return None;
}
kinds_when_consuming(pattern)
}
fn kinds_when_consuming(pattern: &Pattern) -> Option<OpeningKinds> {
match pattern {
Pattern::Concat(v) => {
let mut set: Option<OpeningKinds> = None;
for p in v {
if p.max_tokens() == Some(0) {
continue;
}
let here = kinds_when_consuming(p)?;
set = Some(set.map_or(here, |s| s.with(here)));
if !p.takes_no_tokens() {
return set;
}
}
None
}
Pattern::Bind(_, _, inner) | Pattern::Atomic(inner) => kinds_when_consuming(inner),
Pattern::Repeat(inner, _, _, _)
| Pattern::Plus(inner, _)
| Pattern::Star(inner, _)
| Pattern::Opt(inner, _) => kinds_when_consuming(inner),
Pattern::Alt(branches, _) => {
let mut rest = branches.iter();
let first = kinds_when_consuming(rest.next()?)?;
rest.try_fold(first, |set, branch| Some(set.with(kinds_when_consuming(branch)?)))
}
Pattern::Balanced(which, _) => match which {
Some(bracket) => OpeningKinds::just(TokenKind::Open(*bracket)),
None => [BracketKind::Paren, BracketKind::Square, BracketKind::Brace]
.into_iter()
.try_fold(OpeningKinds(0), |set, bracket| {
Some(set.with(OpeningKinds::just(TokenKind::Open(bracket))?))
}),
},
Pattern::Atom(Atom::Kind(kind))
if !matches!(kind, TokenKind::Whitespace | TokenKind::Custom(_)) =>
{
OpeningKinds::just(*kind)
}
Pattern::Atom(Atom::Literal(lit, crate::orbit::OrbitGroup::Identity)) => {
literal_kind(lit).and_then(OpeningKinds::just).map(OpeningKinds::with_custom)
}
_ => None,
}
}
fn literal_kind(lit: &str) -> Option<TokenKind> {
let toks = crate::lexer::lex(lit.as_bytes());
let mut significant = toks.iter().filter(|t| t.is_significant());
let only = significant.next()?;
if significant.next().is_some() || only.start() != 0 || only.end() != lit.len() {
return None;
}
Some(only.kind)
}
#[must_use]
pub fn scan_kind_sequence(codes: &[u32], parts: &[Significant]) -> Vec<Span> {
let mut out = Vec::new();
if codes.is_empty() {
return out;
}
let (mut p, mut i) = (0usize, 0usize);
loop {
while p < parts.len() && i >= parts[p].kinds.len() {
p += 1;
i = 0;
}
if p >= parts.len() {
break;
}
let (mut q, mut j) = (p, i);
let mut matched = true;
for &code in codes {
while q < parts.len() && j >= parts[q].kinds.len() {
q += 1;
j = 0;
}
if q >= parts.len() || parts[q].kinds[j] != code {
matched = false;
break;
}
j += 1;
}
if matched {
out.push(Span { start: parts[p].spans[i].0, end: parts[q].spans[j - 1].1 });
p = q;
i = j;
} else {
i += 1;
}
}
out
}
#[must_use]
pub fn kind_run(pattern: &Pattern) -> Option<(u32, usize, usize)> {
let Pattern::Repeat(inner, lo, Some(hi), greed) = pattern else {
return None;
};
let (lo, hi) = (*lo, *hi);
if lo == 0 || lo >= hi {
return None;
}
let Pattern::Atom(Atom::Kind(kind)) = inner.as_ref() else {
return None;
};
if matches!(kind, TokenKind::Whitespace | TokenKind::Custom(_)) {
return None;
}
matches!(greed, crate::ast::Greed::Greedy).then(|| (kind.code(), lo, hi))
}
#[must_use]
pub fn scan_kind_run(code: u32, lo: usize, hi: usize, parts: &[Significant]) -> Vec<Span> {
let mut out = Vec::new();
let (mut p, mut i) = (0usize, 0usize);
loop {
while p < parts.len() && i >= parts[p].kinds.len() {
p += 1;
i = 0;
}
if p >= parts.len() {
break;
}
let (mut q, mut j, mut took) = (p, i, 0usize);
while took < hi {
while q < parts.len() && j >= parts[q].kinds.len() {
q += 1;
j = 0;
}
if q >= parts.len() || parts[q].kinds[j] != code {
break;
}
j += 1;
took += 1;
}
if took >= lo {
let (mut e, mut k) = (q, j);
while k == 0 {
e -= 1;
k = parts[e].kinds.len();
}
out.push(Span { start: parts[p].spans[i].0, end: parts[e].spans[k - 1].1 });
p = q;
i = j;
} else {
i += 1;
}
}
out
}
#[must_use]
pub fn bare_balanced(pattern: &Pattern) -> Option<Option<crate::token::BracketKind>> {
let Pattern::Balanced(kind, inner) = pattern else {
return None;
};
let Pattern::Star(body, _) = inner.as_ref() else {
return None;
};
matches!(body.as_ref(), Pattern::Atom(Atom::Any)).then_some(*kind)
}
#[must_use]
pub fn scan_balanced(
kind: Option<crate::token::BracketKind>,
toks: &[crate::token::Token],
) -> Vec<Span> {
let mut out: Vec<Span> = Vec::new();
let mut from = 0usize;
for t in toks {
let TokenKind::Open(k) = t.kind else { continue };
if kind.is_some_and(|want| want != k) {
continue;
}
let Some(m) = t.mate() else { continue };
let start = t.start();
if start < from {
continue;
}
let end = toks[m].end();
out.push(Span { start: start as u32, end: end as u32 });
from = end;
}
out
}
#[must_use]
pub fn scan_balanced_parts(
kind: Option<crate::token::BracketKind>,
parts: &[(crate::lexer::PairedSignificant, crate::lexer::Seams)],
) -> Vec<Span> {
use crate::token::BracketKind;
let mut codes = [0u32; 3];
let wanted = match kind {
Some(k) => {
codes = [TokenKind::Open(k).code(); 3];
&codes[..1]
}
None => {
for (slot, k) in codes
.iter_mut()
.zip([BracketKind::Paren, BracketKind::Square, BracketKind::Brace])
{
*slot = TokenKind::Open(k).code();
}
&codes[..]
}
};
let (lo, hi) = wanted.iter().fold((u32::MAX, 0), |(lo, hi), &c| (lo.min(c), hi.max(c)));
let mut bases: Vec<u32> = Vec::with_capacity(parts.len() + 1);
let mut acc = 0u32;
for (part, _) in parts {
bases.push(acc);
acc += u32::try_from(part.mates.len()).expect("a token index within the stored width");
}
bases.push(acc);
let end_of = |at: u32| -> u32 {
let ci = bases.partition_point(|&b| b <= at) - 1;
parts[ci].0.parts.spans[(at - bases[ci]) as usize].1
};
let _walking = crate::trace::phase("the balanced pass over the parts");
let mut out: Vec<Span> = Vec::new();
let mut from = 0u32;
for (ci, (part, _)) in parts.iter().enumerate() {
let base = bases[ci];
let past = bases[ci + 1];
for (i, &code) in part.parts.kinds.iter().enumerate() {
if code < lo || code > hi || !wanted.contains(&code) {
continue;
}
let mate = part.mates[i];
if mate == crate::lexer::NO_MATE {
continue;
}
let start = part.parts.spans[i].0;
if start < from {
continue;
}
let end = if mate >= base && mate < past {
part.parts.spans[(mate - base) as usize].1
} else {
end_of(mate)
};
out.push(Span { start, end });
from = end;
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn engine(src: &str, input: &[u8]) -> Vec<Span> {
let p = crate::parse(src).expect("pattern parses");
crate::nfa::scan_nfa(&p, input).expect("the single-pass engine takes these patterns")
}
fn routed(src: &str, input: &[u8]) -> Vec<Span> {
let p = crate::parse(src).expect("pattern parses");
let codes = kind_sequence(&p).expect("a kind sequence");
crate::parallel_lex::lex_significant_parts_held(input, |parts| scan_kind_sequence(&codes, parts))
}
fn opens(src: &str) -> Option<OpeningKinds> {
first_kinds(&crate::parse(src).expect("pattern parses"))
}
fn just(kind: TokenKind) -> Option<OpeningKinds> {
OpeningKinds::just(kind)
}
#[test]
fn an_opening_kind_is_read_only_where_the_pattern_forces_one() {
assert_eq!(opens("\\W \\B"), just(TokenKind::Word), "a kind leads it");
assert_eq!(opens("\\N \"=\""), just(TokenKind::Number), "and so here");
assert_eq!(opens("\\W"), just(TokenKind::Word), "one atom is its own first");
assert_eq!(opens("\\W:a \\B"), just(TokenKind::Word), "a binding is transparent");
assert_eq!(opens("\\W{2} \\B"), just(TokenKind::Word), "a repeat that must take one");
assert_eq!(opens("\\W+ \\B"), just(TokenKind::Word), "a plus always takes one");
assert_eq!(opens("\\W*"), None, "a star alone matches nothing anywhere");
assert_eq!(opens("\\W{0,3}"), None, "and so does a repeat from zero");
assert_eq!(opens("(?>\\W*)"), None, "a group around one is the same case");
}
#[test]
fn a_literal_opens_on_the_kind_its_bytes_lex_to() {
let word = opens("\"let\" \\W \"=\"").expect("a literal leads it");
assert!(word.admits(TokenKind::Word), "`let` lexes to a word");
assert!(!word.admits(TokenKind::Number), "and to no other built-in kind");
assert!(word.admits(TokenKind::Custom(0)), "a declared shape can carry those bytes");
assert!(word.admits(TokenKind::Custom(200)), "whatever id it was given");
let number = opens("\"200\" \\W").expect("a number literal leads it");
assert!(number.admits(TokenKind::Number), "`200` lexes to a number");
assert!(!number.admits(TokenKind::Word), "and not to a word");
assert_eq!(opens("\"x=\" \\W"), None, "bytes that lex to two tokens name no kind");
assert_eq!(opens("(?orbit:case \"Cat\") \\W"), None, "another orbit does not fix a kind");
let kind_led = opens("\\W \\B").expect("a kind leads it");
assert!(!kind_led.admits(TokenKind::Custom(0)), "a kind atom tests the kind itself");
}
#[test]
fn the_route_admits_every_kind_that_opens_a_match() {
let inputs: &[&[u8]] = &[
b"let x = 1",
b"a let = 2 let = 3",
b"200 = x",
b"aGVsbG8gd29ybGQhIQ== = x",
b"deadbeefdeadbeefdeadbeefdeadbeef = x",
b"tail aGVsbG8gd29ybGQhIQ== = x head",
];
let sources = [
"\"let\" \\W \"=\"",
"\"200\" \"=\"",
"\"aGVsbG8gd29ybGQhIQ==\" \"=\"",
"\"deadbeefdeadbeefdeadbeefdeadbeef\" \"=\"",
];
for src in sources {
let pattern = crate::parse(src).expect("pattern parses");
match first_kinds(&pattern) {
None => {}
Some(set) => {
for input in inputs {
let toks = crate::lexer::lex(input);
for span in crate::scan(&pattern, input) {
let opener = toks
.iter()
.find(|t| t.start() == span.start())
.expect("a match begins at a token");
assert!(
set.admits(opener.kind),
"{src} opens on {} over {:?}, and the route refuses it",
opener.kind.name(),
String::from_utf8_lossy(input)
);
}
}
}
}
}
}
#[test]
fn a_declared_shape_carrying_a_literals_bytes_still_matches() {
use crate::custom::{Precedence, ShapeSet};
let pattern = crate::parse("\"let\" \\W \"=\"").expect("pattern parses");
let input = b"let x = 1";
let plain = crate::engine::scan_with_shapes(&pattern, input, &ShapeSet::new());
assert_eq!(plain.len(), 1, "the default lex reads `let` as a word and matches");
let mut shapes = ShapeSet::new();
shapes.declare("keyword = `let`", Precedence::Before).expect("declares");
let claimed = crate::engine::scan_with_shapes(&pattern, input, &shapes);
assert_eq!(claimed, plain, "a shape claiming the bytes does not lose the match");
}
#[test]
fn an_alternation_opens_on_every_branch_or_on_none() {
for src in ["(\\N | \\W) \"=\"", "(\\N |> \\W) \"=\""] {
let set = opens(src).unwrap_or_else(|| panic!("both branches force a kind: {src}"));
assert!(set.admits(TokenKind::Number), "the number branch: {src}");
assert!(set.admits(TokenKind::Word), "the word branch: {src}");
assert!(!set.admits(TokenKind::Punct), "and nothing else: {src}");
}
let optional = opens("(\\N | \\W*) \"=\"").expect("the run must take a token");
assert!(optional.admits(TokenKind::Number), "the number branch");
assert!(optional.admits(TokenKind::Word), "the star branch when it takes one");
assert!(optional.admits(TokenKind::Punct), "and the `=` when it takes none");
let mixed = opens("(\\N | \"let\") \"=\"").expect("a literal branch forces a kind too");
assert!(mixed.admits(TokenKind::Number), "the kind branch");
assert!(mixed.admits(TokenKind::Word), "the literal branch, which lexes to a word");
assert!(mixed.admits(TokenKind::Custom(0)), "and the literal's declared kinds");
assert!(!mixed.admits(TokenKind::Punct), "and nothing else");
}
#[test]
fn a_leading_node_that_takes_no_token_is_read_through() {
assert_eq!(opens("~\"lit\" \\W"), just(TokenKind::Word), "a guard is zero width");
assert_eq!(opens("~(\\W) \\N"), just(TokenKind::Number), "so is an assertion");
assert_eq!(opens("!~(\\W) \\N"), just(TokenKind::Number), "and its negation");
assert_eq!(opens("@seam \\W"), just(TokenKind::Word), "and an axis anchor");
assert_eq!(opens("@nested>0 \\N"), just(TokenKind::Number), "and a stress anchor");
}
#[test]
fn a_leading_node_that_may_take_nothing_joins_what_follows() {
for (src, second) in [
("\\W{0,3} \"=\"", TokenKind::Punct),
("(?>\\W*) \"=\"", TokenKind::Punct),
("\\W* \\N", TokenKind::Number),
("\\W? \\N", TokenKind::Number),
] {
let set = opens(src).unwrap_or_else(|| panic!("the run must take a token: {src}"));
assert!(set.admits(TokenKind::Word), "the node itself can open it: {src}");
assert!(set.admits(second), "and so can what follows, when it takes none: {src}");
assert!(!set.admits(TokenKind::Quoted), "and nothing else does: {src}");
}
assert_eq!(opens("\\W* \\N*"), None, "every element may take nothing");
assert_eq!(opens("\\W*"), None, "and a star alone is that case");
}
#[test]
fn a_balanced_group_opens_on_the_brackets_it_admits() {
let set = opens("\\B").expect("a bare balanced group forces a bracket");
for bracket in [BracketKind::Paren, BracketKind::Square, BracketKind::Brace] {
assert!(set.admits(TokenKind::Open(bracket)), "{bracket:?} opens a bare group");
}
assert!(!set.admits(TokenKind::Word), "a word does not");
assert!(!set.admits(TokenKind::Close(BracketKind::Paren)), "nor a close");
}
#[test]
fn a_sequence_of_kinds_is_routable_and_nothing_else_is() {
for src in ["\\W", "\\N", "\\W \\N", "\\N \\N \\W", "\\Q"] {
let p = crate::parse(src).expect("pattern parses");
assert!(kind_sequence(&p).is_some(), "{src} is a kind sequence");
}
for (src, want) in [("\\W{2}", 2usize), ("\\N{3}", 3), ("\\W{2} \\N", 3), ("\\W{1}", 1)] {
let p = crate::parse(src).expect("pattern parses");
let codes = kind_sequence(&p).unwrap_or_else(|| panic!("{src} is a kind sequence"));
assert_eq!(codes.len(), want, "{src} spans {want} tokens");
}
for src in
["\"alpha\"", "\\W \"=\"", "\\W*", "(\\W | \\N)", "\\W:n", "\\W{2,}", "\\W{2,4}"]
{
let p = crate::parse(src).expect("pattern parses");
assert_eq!(kind_sequence(&p), None, "{src} must not route");
}
}
#[test]
fn the_kind_route_answers_what_the_engine_answers() {
let inputs: &[&[u8]] = &[
b"tag 1 tag 2 tag 3",
b"1 2 3 tag tag 4 5 tag",
b"alpha (beta) 42 \"q\" 7 8 9 x",
b"",
b" ",
b"tag\n1\ntag\n2",
b"x",
];
for src in [
"\\W", "\\N", "\\W \\N", "\\N \\W", "\\N \\N", "\\W \\W \\N", "\\Q", "\\W{2}",
"\\N{2}", "\\W{3}", "\\W{2} \\N",
] {
for input in inputs {
assert_eq!(routed(src, input), engine(src, input), "{src} on {input:?}");
}
}
for (long, short) in [("\\N \\N", "\\N{2}"), ("\\W \\W \\N", "\\W{2} \\N")] {
for input in inputs {
assert_eq!(routed(long, input), routed(short, input), "{long} against {short}");
}
}
}
#[test]
fn a_kind_run_reports_what_the_engine_reports() {
let mut corpus = String::new();
for i in 0..300u32 {
corpus.push_str(&format!("a{i} {i}\n"));
corpus.push_str(&format!("b{i} c{i} {i}\n"));
corpus.push_str(&format!("d{i} e{i} f{i} {i}\n"));
corpus.push_str(&format!("g{i} h{i} i{i} j{i} {i}\n"));
corpus.push_str(&format!("k{i} l{i} m{i} n{i} o{i} p{i} {i}\n"));
}
let input = corpus.as_bytes();
for src in ["\\W{2,4}", "\\W{1,2}", "\\W{3,6}", "\\W{2,3}", "\\N{1,2}"] {
let p = crate::parse(src).expect("pattern parses");
let (code, lo, hi) = kind_run(&p).expect("this shape is a kind run");
let got = crate::parallel_lex::lex_significant_parts_held(input, |parts| {
scan_kind_run(code, lo, hi, parts)
});
assert_eq!(got, engine(src, input), "{src}");
}
for src in ["\\W{2}", "\\W{2,}", "\\W{2,4}?"] {
let p = crate::parse(src).expect("pattern parses");
assert!(kind_run(&p).is_none(), "{src} is not a greedy bounded run");
}
}
#[test]
fn the_balanced_route_reports_what_the_set_engine_reports() {
let mut corpus = String::new();
for i in 0..300u32 {
corpus.push_str(&format!("if (cond_{i}) {{ do_{i}(x) ; }}\n"));
corpus.push_str(&format!("call_{i}(alpha, [beta, {i}], gamma) ;\n"));
corpus.push_str(&format!("bare_{i} = \"a ( b ) c\" ;\n"));
}
corpus.push_str("unclosed ( a b\nstray ) c\nmixed ( a ] b\n");
let input = corpus.as_bytes();
let toks = crate::lexer::lex(input);
for src in ["\\B", "\\B(.*)", "\\B[.*]", "\\B{.*}"] {
let p = crate::parse(src).expect("pattern parses");
let kind = bare_balanced(&p).expect("this shape is a bare balanced group");
let got = scan_balanced(kind, &toks);
let want = crate::engine::scan_set_reachability(&p, input);
assert_eq!(got, want, "{src}");
}
for src in ["\\B(\\W)", "\\B(\\N .*)", "\\B[\\W]"] {
let p = crate::parse(src).expect("pattern parses");
assert!(bare_balanced(&p).is_none(), "{src} constrains its interior");
}
}
#[test]
fn a_window_crossing_a_chunk_boundary_is_read_across_it() {
let mut input = String::new();
for i in 0..200_000u32 {
input.push_str("tag ");
input.push_str(&(i % 1000).to_string());
input.push('\n');
}
let bytes = input.as_bytes();
assert!(bytes.len() > crate::parallel_lex::active_parallel_threshold());
for src in ["\\N \\W", "\\W \\N", "\\N", "\\W \\N \\W"] {
assert_eq!(routed(src, bytes), engine(src, bytes), "{src}");
}
}
#[test]
fn the_balanced_route_over_parts_answers_what_it_answers_over_tokens() {
use crate::token::BracketKind;
let mut src = String::from("( {\n");
for i in 0..20_000 {
src.push_str(&format!("f_{i} [ a_{i} ] b_{i} ( c_{i} )\n"));
}
src.push_str("} )\n");
let input = src.as_bytes();
let toks = crate::parallel_lex::lex_parallel(input);
for kind in [
None,
Some(BracketKind::Paren),
Some(BracketKind::Square),
Some(BracketKind::Brace),
] {
let want = scan_balanced(kind, &toks);
let got = crate::parallel_lex::lex_paired_parts_held(input, |parts| {
scan_balanced_parts(kind, parts)
});
assert_eq!(got, want, "bracket kind {kind:?}");
}
for short in [&b""[..], b"()", b"a (b [c] d) e", b"( ]", b"a ) b ( c"] {
let toks = crate::parallel_lex::lex_parallel(short);
let want = scan_balanced(None, &toks);
let got = crate::parallel_lex::lex_paired_parts_held(short, |parts| {
scan_balanced_parts(None, parts)
});
assert_eq!(got, want, "{:?}", String::from_utf8_lossy(short));
}
}
}