use crate::orbit::OrbitGroup;
use crate::token::{BracketKind, TokenKind};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ByteClass {
Digit,
Word,
Space,
Hex,
Alpha,
Upper,
Lower,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Cmp {
Lt,
Le,
Gt,
Ge,
Eq,
Ne,
}
impl Cmp {
#[must_use]
pub fn holds(self, o: std::cmp::Ordering) -> bool {
use std::cmp::Ordering;
match self {
Cmp::Lt => o == Ordering::Less,
Cmp::Le => o != Ordering::Greater,
Cmp::Gt => o == Ordering::Greater,
Cmp::Ge => o != Ordering::Less,
Cmp::Eq => o == Ordering::Equal,
Cmp::Ne => o != Ordering::Equal,
}
}
#[must_use]
pub fn glyph(self) -> &'static str {
match self {
Cmp::Lt => "<",
Cmp::Le => "<=",
Cmp::Gt => ">",
Cmp::Ge => ">=",
Cmp::Eq => "=",
Cmp::Ne => "!=",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum EchoPred {
Count(Cmp, u32),
Nth(Cmp, i32),
Period,
PeriodAt(Cmp, u32),
}
impl AnchorKind {
#[must_use]
pub fn reads_whole_input(&self) -> bool {
!matches!(
self,
AnchorKind::LineStart
| AnchorKind::LineEnd
| AnchorKind::Resume
| AnchorKind::ResetStart
| AnchorKind::Joined { .. }
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TimeOrder {
Asc,
Desc,
}
impl TimeOrder {
#[must_use]
pub fn parse(name: &str) -> Option<TimeOrder> {
match name {
"asc" => Some(TimeOrder::Asc),
"desc" => Some(TimeOrder::Desc),
_ => None,
}
}
#[must_use]
pub fn label(self) -> &'static str {
match self {
TimeOrder::Asc => "asc",
TimeOrder::Desc => "desc",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SpecTexture {
Prose,
Code,
Math,
Data,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SpectralPred {
EntropyGe(u8),
EntropyLe(u8),
PeriodEq(u16),
PeriodAny,
Texture(SpecTexture),
Onset,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MagPred {
Ge(i32),
Le(i32),
Above(Scope, Delta),
Below(Scope, Delta),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Scope {
Window,
Phase,
Regime,
Echo,
Enclosing,
Key(String),
}
impl Scope {
#[must_use]
pub fn parse(name: &str) -> Scope {
match name {
"window" => Scope::Window,
"phase" => Scope::Phase,
"regime" => Scope::Regime,
"echo" => Scope::Echo,
"enclosing" => Scope::Enclosing,
other => Scope::Key(other.to_string()),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ContextUses {
pub window: bool,
pub phase: bool,
pub regime: bool,
pub echo: bool,
pub enclosing: bool,
pub key: bool,
}
impl ContextUses {
#[must_use]
pub fn any_related(&self) -> bool {
self.phase || self.regime || self.echo || self.enclosing || self.key
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Delta {
Orders(i32),
Sigmas(i32),
}
impl MagPred {
#[must_use]
pub fn matches(&self, mag: f32, context: Option<&crate::profile::MagnitudeProfile>) -> bool {
match self {
MagPred::Ge(centi) => mag * 100.0 >= *centi as f32,
MagPred::Le(centi) => mag * 100.0 <= *centi as f32,
MagPred::Above(_, delta) | MagPred::Below(_, delta) => {
let Some(c) = context.filter(|c| c.count > 0) else { return false };
let offset = match delta {
Delta::Orders(centi) => *centi as f32 / 100.0,
Delta::Sigmas(centi) => {
let sigma = c.std_dev();
if c.count < 2 || sigma <= 0.0 {
return false;
}
*centi as f32 / 100.0 * sigma
}
};
let threshold = c.mean() + offset;
if matches!(self, MagPred::Above(..)) { mag >= threshold } else { mag <= threshold }
}
}
}
#[must_use]
pub fn scope(&self) -> Option<&Scope> {
match self {
MagPred::Ge(_) | MagPred::Le(_) => None,
MagPred::Above(s, _) | MagPred::Below(s, _) => Some(s),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum Grain {
#[default]
Byte,
Token,
Super,
}
impl Grain {
#[must_use]
pub fn parse(name: &str) -> Option<Grain> {
Some(match name {
"byte" => Grain::Byte,
"token" => Grain::Token,
"super" => Grain::Super,
_ => return None,
})
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct OtherInput {
pub name: String,
pub bytes: Vec<u8>,
pub tokens: Vec<crate::token::Token>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Real(u64);
impl Real {
#[must_use]
pub fn new(v: f64) -> Real {
Real(v.to_bits())
}
#[must_use]
pub fn get(self) -> f64 {
f64::from_bits(self.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Level {
Percentile(Real),
Bits(Real),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GravityReading {
Strain,
Bound,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AnchorKind {
Seam(Grain),
Gravity(GravityReading, Grain, Cmp, Level),
Kin(Grain, String),
Nested(u16),
Ambiguous(Grain),
Novel,
Echoed,
Echo(EchoPred, OrbitGroup),
Order(TimeOrder),
Rare(crate::templates::Rarity),
Joined { other: std::sync::Arc<OtherInput>, recurs: bool, group: OrbitGroup },
LineStart,
LineEnd,
SuperStart,
SuperRole(crate::supertoken::Role),
ResetStart,
Resume,
InputStart,
InputEnd,
Phase(u16),
PhaseIn(u16, PeriodRef),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PeriodRef {
Length(u16),
Rank(u16),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TokenClass {
pub any: Vec<Atom>,
pub all: Vec<Atom>,
pub none: Vec<Atom>,
pub negated: bool,
}
impl TokenClass {
pub fn members(&self) -> impl Iterator<Item = &Atom> {
self.any.iter().chain(&self.all).chain(&self.none)
}
pub fn members_mut(&mut self) -> impl Iterator<Item = &mut Atom> {
self.any.iter_mut().chain(&mut self.all).chain(&mut self.none)
}
}
impl Atom {
#[must_use]
pub fn literal(text: &str) -> Atom {
Atom::Literal(text.to_string(), OrbitGroup::Identity)
}
pub fn set_orbit(&mut self, group: OrbitGroup) {
match self {
Atom::Literal(_, g) | Atom::LiteralWithin(_, _, g) | Atom::RegisterWithin(_, _, g) => {
*g = group;
}
Atom::Class(c) => {
for m in c.members_mut() {
m.set_orbit(group);
}
}
_ => {}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Atom {
Kind(TokenKind),
Any,
Literal(String, OrbitGroup),
RegisterEq(String, OrbitGroup),
RegisterRelated(String, crate::typed::Relation),
LiteralWithin(String, u8, OrbitGroup),
RegisterWithin(String, u8, OrbitGroup),
RegisterKin(String, Grain),
Byte(ByteClass),
BytePattern(crate::bytepat::BytePat),
Spectral(SpectralPred),
Magnitude(MagPred),
Class(Box<TokenClass>),
KindMag(TokenKind, MagPred),
KindPred(TokenKind, crate::typed::TypedPred),
Since(Cmp, Signed, String),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Signed {
pub negative: bool,
pub nanos: crate::typed::Decimal,
}
impl Atom {
#[must_use]
pub fn magnitude_pred(&self) -> Option<&MagPred> {
match self {
Atom::Magnitude(p) | Atom::KindMag(_, p) => Some(p),
Atom::Class(c) => c.members().find_map(Atom::magnitude_pred),
_ => None,
}
}
#[must_use]
pub fn key_scope(&self) -> Option<&str> {
match self.magnitude_pred().and_then(MagPred::scope) {
Some(Scope::Key(name)) => Some(name),
_ => None,
}
}
pub(crate) fn rename_registers(&mut self, rename: &dyn Fn(&mut String)) {
match self {
Atom::RegisterEq(name, _)
| Atom::RegisterRelated(name, _)
| Atom::RegisterWithin(name, _, _)
| Atom::RegisterKin(name, _)
| Atom::Since(_, _, name) => rename(name),
Atom::Magnitude(p) | Atom::KindMag(_, p) => p.rename_key(rename),
Atom::Class(c) => {
for member in c.members_mut() {
member.rename_registers(rename);
}
}
_ => {}
}
}
}
impl MagPred {
fn rename_key(&mut self, rename: &dyn Fn(&mut String)) {
if let MagPred::Above(Scope::Key(name), _) | MagPred::Below(Scope::Key(name), _) = self {
rename(name);
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AltMode {
First,
Longest,
Committed,
}
impl AltMode {
#[must_use]
pub fn glyph(self) -> &'static str {
match self {
AltMode::First => "|",
AltMode::Longest => "||",
AltMode::Committed => "|>",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Look {
Ahead,
Behind,
Within { window: usize, at_least: usize, at_most: Option<usize> },
InGroup { at_least: usize, at_most: Option<usize> },
}
impl Look {
#[must_use]
pub fn satisfied_by_absence(self) -> bool {
matches!(self, Self::Within { at_least: 0, .. } | Self::InGroup { at_least: 0, .. })
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Greed {
Greedy,
Lazy,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum EmptyLoop {
#[default]
Thompson,
Perl,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Pattern {
Empty,
Atom(Atom),
Bind(String, bool, Box<Pattern>),
Balanced(Option<BracketKind>, Box<Pattern>),
Guard(String, bool),
Assert(Box<Pattern>, bool, Look),
Anchor(AnchorKind),
Field(usize, Box<Pattern>),
Concat(Vec<Pattern>),
Alt(Vec<Pattern>, AltMode),
Star(Box<Pattern>, Greed),
Plus(Box<Pattern>, Greed),
Opt(Box<Pattern>, Greed),
Repeat(Box<Pattern>, usize, Option<usize>, Greed),
Atomic(Box<Pattern>),
Within(Vec<EditAtom>, u8),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EditAtom {
pub atom: Atom,
pub bind: Option<(String, bool)>,
}
impl Pattern {
#[must_use]
pub fn boxed(self) -> Box<Pattern> {
Box::new(self)
}
#[must_use]
pub fn reads_a_register(&self) -> bool {
fn atom_reads(a: &Atom) -> bool {
match a {
Atom::RegisterEq(..)
| Atom::RegisterRelated(..)
| Atom::RegisterWithin(..)
| Atom::RegisterKin(..)
| Atom::Since(..) => true,
Atom::Class(c) => {
c.any.iter().chain(&c.all).chain(&c.none).any(atom_reads)
}
_ => false,
}
}
match self {
Pattern::Atom(a) => atom_reads(a),
Pattern::Within(v, _) => v.iter().any(|e| atom_reads(&e.atom)),
Pattern::Empty | Pattern::Guard(..) | Pattern::Anchor(_) => false,
Pattern::Bind(_, _, p)
| Pattern::Balanced(_, p)
| Pattern::Field(_, p)
| Pattern::Star(p, _)
| Pattern::Plus(p, _)
| Pattern::Opt(p, _)
| Pattern::Repeat(p, _, _, _)
| Pattern::Atomic(p)
| Pattern::Assert(p, _, _) => p.reads_a_register(),
Pattern::Concat(v) | Pattern::Alt(v, _) => v.iter().any(Pattern::reads_a_register),
}
}
#[must_use]
pub fn binds_anything(&self) -> bool {
match self {
Pattern::Bind(..) => true,
Pattern::Within(v, _) => v.iter().any(|e| e.bind.is_some()),
Pattern::Empty | Pattern::Atom(_) | Pattern::Guard(..) | Pattern::Anchor(_) => false,
Pattern::Balanced(_, p)
| Pattern::Field(_, p)
| Pattern::Star(p, _)
| Pattern::Plus(p, _)
| Pattern::Opt(p, _)
| Pattern::Repeat(p, _, _, _)
| Pattern::Atomic(p)
| Pattern::Assert(p, _, _) => p.binds_anything(),
Pattern::Concat(v) | Pattern::Alt(v, _) => v.iter().any(Pattern::binds_anything),
}
}
#[must_use]
pub fn without_bindings(&self) -> Option<Pattern> {
if !self.binds_anything() || self.reads_a_register() {
return None;
}
fn strip(p: &Pattern) -> Pattern {
match p {
Pattern::Bind(_, _, inner) => strip(inner),
Pattern::Balanced(k, p) => Pattern::Balanced(*k, strip(p).boxed()),
Pattern::Field(k, p) => Pattern::Field(*k, strip(p).boxed()),
Pattern::Star(p, g) => Pattern::Star(strip(p).boxed(), *g),
Pattern::Plus(p, g) => Pattern::Plus(strip(p).boxed(), *g),
Pattern::Opt(p, g) => Pattern::Opt(strip(p).boxed(), *g),
Pattern::Repeat(p, lo, hi, g) => Pattern::Repeat(strip(p).boxed(), *lo, *hi, *g),
Pattern::Atomic(p) => Pattern::Atomic(strip(p).boxed()),
Pattern::Assert(p, neg, look) => Pattern::Assert(strip(p).boxed(), *neg, *look),
Pattern::Concat(v) => Pattern::Concat(v.iter().map(strip).collect()),
Pattern::Alt(v, mode) => Pattern::Alt(v.iter().map(strip).collect(), *mode),
Pattern::Within(v, k) => Pattern::Within(
v.iter().map(|e| EditAtom { atom: e.atom.clone(), bind: None }).collect(),
*k,
),
other => other.clone(),
}
}
Some(strip(self))
}
#[must_use]
pub fn starts_with_resume(&self) -> bool {
match self {
Pattern::Anchor(AnchorKind::Resume) => true,
Pattern::Concat(v) => v.first().is_some_and(Pattern::starts_with_resume),
Pattern::Bind(_, _, p) => p.starts_with_resume(),
_ => false,
}
}
#[must_use]
pub fn resume_is_misplaced(&self) -> bool {
match self {
Pattern::Concat(v) => {
let head_ok = v.first().is_some_and(Pattern::starts_with_resume);
let skip = usize::from(head_ok);
v.iter().skip(skip).any(Pattern::mentions_resume)
|| v.first().is_some_and(|p| !head_ok && p.mentions_resume())
}
Pattern::Anchor(AnchorKind::Resume) => false,
other => other.mentions_resume(),
}
}
#[must_use]
pub fn mentions_stream_end_anchor(&self) -> bool {
match self {
Pattern::Anchor(AnchorKind::InputStart | AnchorKind::InputEnd) => true,
Pattern::Empty
| Pattern::Atom(_)
| Pattern::Within(..)
| Pattern::Anchor(_)
| Pattern::Guard(..) => false,
Pattern::Star(p, _)
| Pattern::Plus(p, _)
| Pattern::Opt(p, _)
| Pattern::Bind(_, _, p)
| Pattern::Balanced(_, p)
| Pattern::Field(_, p)
| Pattern::Repeat(p, _, _, _)
| Pattern::Atomic(p)
| Pattern::Assert(p, _, _) => p.mentions_stream_end_anchor(),
Pattern::Concat(v) | Pattern::Alt(v, _) => {
v.iter().any(Pattern::mentions_stream_end_anchor)
}
}
}
#[must_use]
pub fn mentions_reset_start(&self) -> bool {
match self {
Pattern::Anchor(AnchorKind::ResetStart) => true,
Pattern::Empty
| Pattern::Atom(_)
| Pattern::Within(..)
| Pattern::Anchor(_)
| Pattern::Guard(..) => false,
Pattern::Star(p, _)
| Pattern::Plus(p, _)
| Pattern::Opt(p, _)
| Pattern::Bind(_, _, p)
| Pattern::Balanced(_, p)
| Pattern::Field(_, p)
| Pattern::Repeat(p, _, _, _) | Pattern::Atomic(p)
| Pattern::Assert(p, _, _) => p.mentions_reset_start(),
Pattern::Concat(v) | Pattern::Alt(v, _) => {
v.iter().any(Pattern::mentions_reset_start)
}
}
}
fn mentions_resume(&self) -> bool {
match self {
Pattern::Anchor(AnchorKind::Resume) => true,
Pattern::Empty
| Pattern::Atom(_)
| Pattern::Within(..)
| Pattern::Anchor(_)
| Pattern::Guard(..) => false,
Pattern::Star(p, _)
| Pattern::Plus(p, _)
| Pattern::Opt(p, _)
| Pattern::Bind(_, _, p)
| Pattern::Balanced(_, p)
| Pattern::Field(_, p)
| Pattern::Repeat(p, _, _, _) | Pattern::Atomic(p)
| Pattern::Assert(p, _, _) => p.mentions_resume(),
Pattern::Concat(v) | Pattern::Alt(v, _) => v.iter().any(Pattern::mentions_resume),
}
}
#[must_use]
pub fn contains_guard(&self) -> bool {
match self {
Pattern::Guard(..) => true,
Pattern::Assert(p, _, _) => p.contains_guard(),
Pattern::Empty | Pattern::Atom(_) | Pattern::Within(..) | Pattern::Anchor(_) => false,
Pattern::Star(p, _) | Pattern::Plus(p, _) | Pattern::Opt(p, _) | Pattern::Bind(_, _, p) => {
p.contains_guard()
}
Pattern::Repeat(p, _, _, _) | Pattern::Atomic(p) | Pattern::Balanced(_, p) | Pattern::Field(_, p) => {
p.contains_guard()
}
Pattern::Concat(v) | Pattern::Alt(v, _) => v.iter().any(Pattern::contains_guard),
}
}
#[must_use]
pub fn contains_field(&self) -> bool {
match self {
Pattern::Field(..) | Pattern::Anchor(_) => true,
Pattern::Assert(p, _, _) => p.contains_field(),
Pattern::Empty | Pattern::Atom(_) | Pattern::Within(..) | Pattern::Guard(..) => false,
Pattern::Star(p, _) | Pattern::Plus(p, _) | Pattern::Opt(p, _) | Pattern::Bind(_, _, p) => {
p.contains_field()
}
Pattern::Repeat(p, _, _, _) | Pattern::Atomic(p) | Pattern::Balanced(_, p) => p.contains_field(),
Pattern::Concat(v) | Pattern::Alt(v, _) => v.iter().any(Pattern::contains_field),
}
}
#[must_use]
pub fn depends_on_whole_input(&self) -> bool {
self.contains_guard()
|| self.contains_field()
|| self.has_spectral()
|| self.has_assert()
|| self.reads_context_window()
|| self.reads_related_context()
|| self.any_node(&|p| matches!(p, Pattern::Anchor(k) if k.reads_whole_input()))
}
#[must_use]
pub fn depends_on_more_than_its_lines(&self) -> bool {
self.contains_guard()
|| self.any_node(&|p| match p {
Pattern::Field(..) => true,
Pattern::Anchor(k) => !matches!(k, AnchorKind::LineStart | AnchorKind::LineEnd),
Pattern::Assert(_, _, look) => !matches!(look, Look::Within { .. } | Look::Behind),
_ => false,
})
|| self.looks_behind_its_match()
|| self.has_spectral()
|| self.reads_context_window()
|| self.reads_related_context()
}
#[must_use]
pub fn looks_behind_its_match(&self) -> bool {
self.looks_behind_from(0)
}
fn looks_behind_from(&self, before: usize) -> bool {
match self {
Pattern::Assert(p, _, Look::Behind) => match p.max_tokens() {
Some(reach) => reach > before || p.looks_behind_from(0),
None => true,
},
Pattern::Assert(p, _, _) => p.looks_behind_from(0),
Pattern::Empty | Pattern::Atom(_) | Pattern::Guard(..) | Pattern::Anchor(_) | Pattern::Within(..) => false,
Pattern::Bind(_, _, p)
| Pattern::Atomic(p)
| Pattern::Opt(p, _)
| Pattern::Star(p, _)
| Pattern::Plus(p, _)
| Pattern::Repeat(p, ..)
| Pattern::Field(_, p)
| Pattern::Balanced(_, p) => p.looks_behind_from(before),
Pattern::Concat(v) => {
let mut taken = before;
for p in v {
if p.looks_behind_from(taken) {
return true;
}
taken = taken.saturating_add(p.min_tokens());
}
false
}
Pattern::Alt(v, _) => v.iter().any(|p| p.looks_behind_from(before)),
}
}
#[must_use]
pub fn min_tokens(&self) -> usize {
match self {
Pattern::Empty | Pattern::Guard(..) | Pattern::Anchor(_) | Pattern::Assert(..) => 0,
Pattern::Atom(_) => 1,
Pattern::Within(v, k) => v.len().saturating_sub(usize::from(*k)).max(1),
Pattern::Bind(_, _, p) | Pattern::Atomic(p) | Pattern::Field(_, p) | Pattern::Plus(p, _) => {
p.min_tokens()
}
Pattern::Opt(..) | Pattern::Star(..) | Pattern::Balanced(..) => 0,
Pattern::Repeat(p, least, _, _) => p.min_tokens().saturating_mul(*least),
Pattern::Concat(v) => v.iter().map(Pattern::min_tokens).fold(0, usize::saturating_add),
Pattern::Alt(v, _) if v.is_empty() => 0,
Pattern::Alt(v, _) => v.iter().map(Pattern::min_tokens).fold(usize::MAX, usize::min),
}
}
#[must_use]
pub fn reads_context_window(&self) -> bool {
self.context_uses().window
}
#[must_use]
pub fn reads_related_context(&self) -> bool {
self.context_uses().any_related()
}
#[must_use]
pub fn context_uses(&self) -> ContextUses {
let uses = std::cell::Cell::new(ContextUses::default());
self.any_atom(&|a| {
if let Some(scope) = a.magnitude_pred().and_then(MagPred::scope) {
let mut u = uses.get();
match scope {
Scope::Window => u.window = true,
Scope::Phase => u.phase = true,
Scope::Regime => u.regime = true,
Scope::Echo => u.echo = true,
Scope::Enclosing => u.enclosing = true,
Scope::Key(_) => u.key = true,
}
uses.set(u);
}
false
});
let mut u = uses.get();
if self.any_node(&|p| matches!(p, Pattern::Anchor(AnchorKind::Phase(_)))) {
u.phase = true;
}
u
}
#[must_use]
pub fn reads_registers(&self) -> bool {
self.any_atom(&|a| {
matches!(
a,
Atom::RegisterEq(..)
| Atom::RegisterRelated(..)
| Atom::RegisterWithin(..)
| Atom::RegisterKin(..)
| Atom::Since(..)
)
})
}
#[must_use]
pub fn has_order(&self) -> bool {
self.any_node(&|p| matches!(p, Pattern::Anchor(AnchorKind::Order(_))))
}
#[must_use]
pub fn reads_whitespace(&self) -> bool {
self.any_atom(&|a| {
matches!(a, Atom::Kind(TokenKind::Whitespace) | Atom::Byte(ByteClass::Space))
})
}
#[must_use]
pub fn max_tokens(&self) -> Option<usize> {
match self {
Pattern::Empty | Pattern::Guard(..) | Pattern::Anchor(_) | Pattern::Assert(..) => Some(0),
Pattern::Atom(_) => Some(1),
Pattern::Within(v, k) => v.len().checked_add(usize::from(*k)),
Pattern::Bind(_, _, p) | Pattern::Atomic(p) | Pattern::Opt(p, _) | Pattern::Field(_, p) => {
p.max_tokens()
}
Pattern::Balanced(..) | Pattern::Star(..) | Pattern::Plus(..) => None,
Pattern::Repeat(p, _, most, _) => {
most.and_then(|n| p.max_tokens().and_then(|w| w.checked_mul(n)))
}
Pattern::Concat(v) => {
v.iter().try_fold(0usize, |acc, p| p.max_tokens().and_then(|w| acc.checked_add(w)))
}
Pattern::Alt(v, _) => {
v.iter().try_fold(0usize, |acc, p| p.max_tokens().map(|w| acc.max(w)))
}
}
}
#[must_use]
pub fn takes_no_tokens(&self) -> bool {
match self {
Pattern::Empty | Pattern::Guard(..) | Pattern::Anchor(_) | Pattern::Assert(..) => true,
Pattern::Atom(_) => false,
Pattern::Within(v, k) => v.len() <= usize::from(*k),
Pattern::Bind(_, _, p) | Pattern::Atomic(p) | Pattern::Field(_, p) => p.takes_no_tokens(),
Pattern::Opt(..) | Pattern::Star(..) => true,
Pattern::Plus(p, _) => p.takes_no_tokens(),
Pattern::Balanced(..) => false,
Pattern::Repeat(p, least, _, _) => *least == 0 || p.takes_no_tokens(),
Pattern::Concat(v) => v.iter().all(Pattern::takes_no_tokens),
Pattern::Alt(v, _) => v.iter().any(Pattern::takes_no_tokens),
}
}
#[must_use]
pub fn has_rare(&self) -> bool {
self.any_node(&|p| matches!(p, Pattern::Anchor(AnchorKind::Rare(_))))
}
#[must_use]
pub fn joins(&self) -> Vec<(std::sync::Arc<OtherInput>, OrbitGroup)> {
use std::sync::Arc;
let out = std::cell::RefCell::new(Vec::<(Arc<OtherInput>, OrbitGroup)>::new());
self.any_node(&|p| {
if let Pattern::Anchor(AnchorKind::Joined { other, group, .. }) = p {
let mut seen = out.borrow_mut();
if !seen.iter().any(|(o, g)| Arc::ptr_eq(o, other) && g == group) {
seen.push((Arc::clone(other), *group));
}
}
false
});
out.into_inner()
}
fn any_atom(&self, pred: &impl Fn(&Atom) -> bool) -> bool {
fn atom_has(a: &Atom, pred: &impl Fn(&Atom) -> bool) -> bool {
pred(a)
|| match a {
Atom::Class(c) => c.members().any(|m| atom_has(m, pred)),
_ => false,
}
}
self.any_node(&|p| matches!(p, Pattern::Atom(a) if atom_has(a, pred)))
}
#[must_use]
pub fn widest_forward_window(&self) -> usize {
let widest = std::cell::Cell::new(0usize);
self.any_node(&|p| {
if let Pattern::Assert(inner, _, Look::Within { window, .. }) = p {
let inner_len = crate::nfa::bounded_max_len(inner).unwrap_or(0);
widest.set(widest.get().max(window.saturating_add(inner_len)));
}
false
});
widest.get()
}
#[must_use]
pub fn has_assert(&self) -> bool {
self.any_node(&|p| {
matches!(p, Pattern::Assert(_, _, look) if !matches!(look, Look::Within { .. }))
})
}
#[must_use]
pub fn reads_supertokens(&self) -> bool {
self.has_super()
}
#[must_use]
pub fn has_seam(&self) -> bool {
self.has_seam_at(Grain::Byte)
}
#[must_use]
pub fn has_seam_at(&self, grain: Grain) -> bool {
self.any_node(&|p| matches!(p, Pattern::Anchor(AnchorKind::Seam(g)) if *g == grain))
}
#[must_use]
pub fn has_phase_in(&self) -> bool {
self.any_node(&|p| matches!(p, Pattern::Anchor(AnchorKind::PhaseIn(..))))
}
#[must_use]
pub fn has_gravity_at(&self, grain: Grain) -> bool {
self.any_node(&|p| {
matches!(
p,
Pattern::Anchor(AnchorKind::Gravity(_, g, ..) | AnchorKind::Kin(g, _)) if *g == grain
)
}) || self.any_atom(&|a| matches!(a, Atom::RegisterKin(_, g) if *g == grain))
|| self.any_node(&|p| {
matches!(p, Pattern::Within(v, _)
if v.iter().any(|e| matches!(&e.atom, Atom::RegisterKin(_, g) if *g == grain)))
})
}
#[must_use]
pub fn kin_examples(&self) -> Vec<(Grain, String)> {
let found = std::cell::RefCell::new(Vec::new());
self.any_node(&|p| {
if let Pattern::Anchor(AnchorKind::Kin(g, x)) = p {
let mut found = found.borrow_mut();
if !found.iter().any(|(fg, fx)| fg == g && fx == x) {
found.push((*g, x.clone()));
}
}
false
});
found.into_inner()
}
#[must_use]
pub fn has_choice(&self) -> bool {
self.any_node(&|p| {
matches!(
p,
Pattern::Alt(_, AltMode::First)
| Pattern::Star(..)
| Pattern::Plus(..)
| Pattern::Opt(..)
| Pattern::Repeat(..)
)
})
}
#[must_use]
pub fn has_field_anchor(&self) -> bool {
self.any_node(&|p| matches!(p, Pattern::Field(..)))
}
#[must_use]
pub fn has_stress(&self) -> bool {
self.any_node(&|p| matches!(p, Pattern::Anchor(AnchorKind::Nested(_))))
}
#[must_use]
pub fn has_observation(&self) -> bool {
self.has_observation_at(Grain::Byte)
}
#[must_use]
pub fn has_observation_at(&self, grain: Grain) -> bool {
self.any_node(&|p| matches!(p, Pattern::Anchor(AnchorKind::Ambiguous(g)) if *g == grain))
}
#[must_use]
pub fn has_echo(&self) -> bool {
self.any_node(&|p| {
matches!(p, Pattern::Anchor(AnchorKind::Novel | AnchorKind::Echoed | AnchorKind::Echo(..)))
})
}
#[must_use]
pub fn echo_orbits(&self) -> Vec<OrbitGroup> {
let out = std::cell::RefCell::new(Vec::new());
self.any_node(&|p| {
let group = match p {
Pattern::Anchor(AnchorKind::Novel | AnchorKind::Echoed) => OrbitGroup::Identity,
Pattern::Anchor(AnchorKind::Echo(_, g)) => *g,
_ => return false,
};
let mut seen = out.borrow_mut();
if !seen.contains(&group) {
seen.push(group);
}
false
});
out.into_inner()
}
#[must_use]
pub fn has_super(&self) -> bool {
self.any_node(&|p| {
matches!(p, Pattern::Anchor(AnchorKind::SuperStart | AnchorKind::SuperRole(_)))
})
}
fn any_node(&self, pred: &impl Fn(&Pattern) -> bool) -> bool {
if pred(self) {
return true;
}
match self {
Pattern::Assert(p, _, _) => p.any_node(pred),
Pattern::Empty
| Pattern::Atom(_)
| Pattern::Within(..)
| Pattern::Guard(..)
| Pattern::Anchor(_) => false,
Pattern::Star(p, _) | Pattern::Plus(p, _) | Pattern::Opt(p, _) | Pattern::Bind(_, _, p) => {
p.any_node(pred)
}
Pattern::Repeat(p, _, _, _) | Pattern::Atomic(p) | Pattern::Balanced(_, p) | Pattern::Field(_, p) => {
p.any_node(pred)
}
Pattern::Concat(v) | Pattern::Alt(v, _) => v.iter().any(|c| c.any_node(pred)),
}
}
#[must_use]
pub fn capture_names(&self) -> Vec<String> {
let mut out = Vec::new();
self.collect_capture_names(&mut out);
out
}
pub fn nest_registers(&mut self, prefix: &str) {
let inner = self.capture_names();
let rename = |name: &mut String| {
if inner.iter().any(|n| n == name) {
*name = format!("{prefix}.{name}");
}
};
self.rename_registers(&rename);
}
fn rename_registers(&mut self, rename: &dyn Fn(&mut String)) {
match self {
Pattern::Bind(name, _, p) => {
rename(name);
p.rename_registers(rename);
}
Pattern::Atom(a) => a.rename_registers(rename),
Pattern::Within(v, _) => {
for e in v {
e.atom.rename_registers(rename);
if let Some((name, _)) = &mut e.bind {
rename(name);
}
}
}
Pattern::Empty | Pattern::Guard(..) | Pattern::Anchor(_) => {}
Pattern::Balanced(_, p)
| Pattern::Field(_, p)
| Pattern::Star(p, _)
| Pattern::Plus(p, _)
| Pattern::Opt(p, _)
| Pattern::Repeat(p, _, _, _)
| Pattern::Atomic(p)
| Pattern::Assert(p, _, _) => p.rename_registers(rename),
Pattern::Concat(v) | Pattern::Alt(v, _) => {
for p in v {
p.rename_registers(rename);
}
}
}
}
#[must_use]
pub fn list_registers(&self) -> Vec<String> {
let mut out = Vec::new();
self.collect_list_registers(false, &mut out);
out
}
#[must_use]
pub fn has_list_registers(&self) -> bool {
!self.list_registers().is_empty()
}
#[must_use]
pub fn repetition_registers(&self) -> Vec<Vec<String>> {
let mut out = Vec::new();
self.collect_repetition_registers(&mut out);
out
}
fn collect_repetition_registers(&self, out: &mut Vec<Vec<String>>) {
let repetition = |p: &Pattern, out: &mut Vec<Vec<String>>| {
out.push(p.capture_names());
p.collect_repetition_registers(out);
};
match self {
Pattern::Star(p, _) | Pattern::Plus(p, _) => repetition(p, out),
Pattern::Repeat(p, _, hi, _) if *hi != Some(1) => repetition(p, out),
Pattern::Bind(_, _, p)
| Pattern::Repeat(p, _, _, _)
| Pattern::Opt(p, _)
| Pattern::Balanced(_, p)
| Pattern::Field(_, p)
| Pattern::Atomic(p)
| Pattern::Assert(p, _, _) => p.collect_repetition_registers(out),
Pattern::Concat(v) | Pattern::Alt(v, _) => {
for p in v {
p.collect_repetition_registers(out);
}
}
Pattern::Within(..) | Pattern::Empty | Pattern::Atom(_) | Pattern::Guard(..) | Pattern::Anchor(_) => {}
}
}
fn collect_list_registers(&self, repeated: bool, out: &mut Vec<String>) {
match self {
Pattern::Bind(name, _, p) => {
if repeated && !out.contains(name) {
out.push(name.clone());
}
p.collect_list_registers(repeated, out);
}
Pattern::Star(p, _) | Pattern::Plus(p, _) => p.collect_list_registers(true, out),
Pattern::Repeat(p, _, hi, _) => p.collect_list_registers(repeated || *hi != Some(1), out),
Pattern::Opt(p, _)
| Pattern::Balanced(_, p)
| Pattern::Field(_, p)
| Pattern::Atomic(p)
| Pattern::Assert(p, _, _) => p.collect_list_registers(repeated, out),
Pattern::Concat(v) | Pattern::Alt(v, _) => {
for p in v {
p.collect_list_registers(repeated, out);
}
}
Pattern::Within(v, _) => {
for name in v.iter().filter_map(|e| e.bind.as_ref()).map(|(n, _)| n) {
if repeated && !out.contains(name) {
out.push(name.clone());
}
}
}
Pattern::Empty | Pattern::Atom(_) | Pattern::Guard(..) | Pattern::Anchor(_) => {}
}
}
fn collect_capture_names(&self, out: &mut Vec<String>) {
match self {
Pattern::Assert(..) => {}
Pattern::Bind(name, _, p) => {
if !out.contains(name) {
out.push(name.clone());
}
p.collect_capture_names(out);
}
Pattern::Within(v, _) => {
for name in v.iter().filter_map(|e| e.bind.as_ref()).map(|(n, _)| n) {
if !out.contains(name) {
out.push(name.clone());
}
}
}
Pattern::Empty | Pattern::Atom(_) | Pattern::Guard(..) | Pattern::Anchor(_) => {}
Pattern::Star(p, _) | Pattern::Plus(p, _) | Pattern::Opt(p, _) => {
p.collect_capture_names(out);
}
Pattern::Repeat(p, _, _, _) | Pattern::Atomic(p) | Pattern::Balanced(_, p) | Pattern::Field(_, p) => {
p.collect_capture_names(out);
}
Pattern::Concat(v) | Pattern::Alt(v, _) => {
for c in v {
c.collect_capture_names(out);
}
}
}
}
#[must_use]
pub fn capture_kinds(&self) -> Vec<(String, Option<TokenKind>)> {
let mut out = Vec::new();
self.collect_capture_kinds(&mut out);
out
}
fn collect_capture_kinds(&self, out: &mut Vec<(String, Option<TokenKind>)>) {
match self {
Pattern::Assert(..) => {}
Pattern::Bind(name, _, p) => {
if !out.iter().any(|(n, _)| n == name) {
out.push((name.clone(), p.one_kind()));
}
p.collect_capture_kinds(out);
}
Pattern::Within(v, _) => {
for e in v {
if let Some((name, _)) = &e.bind
&& !out.iter().any(|(n, _)| n == name)
{
out.push((name.clone(), Pattern::Atom(e.atom.clone()).one_kind()));
}
}
}
Pattern::Empty | Pattern::Atom(_) | Pattern::Guard(..) | Pattern::Anchor(_) => {}
Pattern::Star(p, _) | Pattern::Plus(p, _) | Pattern::Opt(p, _) => {
p.collect_capture_kinds(out);
}
Pattern::Repeat(p, _, _, _)
| Pattern::Atomic(p)
| Pattern::Balanced(_, p)
| Pattern::Field(_, p) => {
p.collect_capture_kinds(out);
}
Pattern::Concat(v) | Pattern::Alt(v, _) => {
for c in v {
c.collect_capture_kinds(out);
}
}
}
}
fn one_kind(&self) -> Option<TokenKind> {
match self {
Pattern::Atom(Atom::Kind(k) | Atom::KindMag(k, _) | Atom::KindPred(k, _)) => Some(*k),
Pattern::Atomic(p) => p.one_kind(),
Pattern::Concat(v) | Pattern::Alt(v, _) if v.len() == 1 => v[0].one_kind(),
_ => None,
}
}
#[must_use]
pub fn custom_kinds(&self) -> Vec<u8> {
fn atom_into(a: &Atom, out: &mut Vec<u8>) {
match a {
Atom::Kind(TokenKind::Custom(id))
| Atom::KindMag(TokenKind::Custom(id), _)
| Atom::KindPred(TokenKind::Custom(id), _) => {
if !out.contains(id) {
out.push(*id);
}
}
Atom::Class(c) => c.members().for_each(|m| atom_into(m, out)),
_ => {}
}
}
fn into(p: &Pattern, out: &mut Vec<u8>) {
match p {
Pattern::Atom(a) => atom_into(a, out),
Pattern::Within(v, _) => v.iter().for_each(|e| atom_into(&e.atom, out)),
Pattern::Empty | Pattern::Guard(..) | Pattern::Anchor(_) => {}
Pattern::Assert(p, _, _)
| Pattern::Star(p, _)
| Pattern::Plus(p, _)
| Pattern::Opt(p, _)
| Pattern::Bind(_, _, p)
| Pattern::Repeat(p, _, _, _)
| Pattern::Atomic(p)
| Pattern::Balanced(_, p)
| Pattern::Field(_, p) => into(p, out),
Pattern::Concat(v) | Pattern::Alt(v, _) => v.iter().for_each(|p| into(p, out)),
}
}
let mut out = Vec::new();
into(self, &mut out);
out
}
#[must_use]
pub fn library_kinds(&self) -> Vec<u8> {
self.custom_kinds().into_iter().filter(|&id| id >= crate::custom::LIBRARY_ID_BASE).collect()
}
#[must_use]
pub fn has_byte_constraint(&self) -> bool {
fn atom_has(a: &Atom) -> bool {
match a {
Atom::Byte(_) | Atom::BytePattern(_) => true,
Atom::Class(c) => c.members().any(atom_has),
_ => false,
}
}
match self {
Pattern::Atom(a) if atom_has(a) => true,
Pattern::Within(v, _) if v.iter().any(|e| atom_has(&e.atom)) => true,
Pattern::Guard(..) => true,
Pattern::Assert(p, _, _) => p.has_byte_constraint(),
Pattern::Empty | Pattern::Atom(_) | Pattern::Within(..) | Pattern::Anchor(_) => false,
Pattern::Star(p, _) | Pattern::Plus(p, _) | Pattern::Opt(p, _) | Pattern::Bind(_, _, p) => {
p.has_byte_constraint()
}
Pattern::Repeat(p, _, _, _) | Pattern::Atomic(p) | Pattern::Balanced(_, p) | Pattern::Field(_, p) => {
p.has_byte_constraint()
}
Pattern::Concat(v) | Pattern::Alt(v, _) => v.iter().any(Pattern::has_byte_constraint),
}
}
#[must_use]
pub fn has_spectral(&self) -> bool {
fn atom_has(a: &Atom) -> bool {
match a {
Atom::Spectral(_) => true,
Atom::Class(c) => c.members().any(atom_has),
_ => false,
}
}
match self {
Pattern::Atom(a) if atom_has(a) => true,
Pattern::Within(v, _) if v.iter().any(|e| atom_has(&e.atom)) => true,
Pattern::Assert(p, _, _) => p.has_spectral(),
Pattern::Empty
| Pattern::Atom(_)
| Pattern::Within(..)
| Pattern::Guard(..)
| Pattern::Anchor(_) => false,
Pattern::Star(p, _) | Pattern::Plus(p, _) | Pattern::Opt(p, _) | Pattern::Bind(_, _, p) => {
p.has_spectral()
}
Pattern::Repeat(p, _, _, _) | Pattern::Atomic(p) | Pattern::Balanced(_, p) | Pattern::Field(_, p) => {
p.has_spectral()
}
Pattern::Concat(v) | Pattern::Alt(v, _) => v.iter().any(Pattern::has_spectral),
}
}
#[must_use]
pub fn spectral_needs(&self) -> crate::spectral::Needs {
fn atom_into(a: &Atom, out: &mut crate::spectral::Needs) {
match a {
Atom::Spectral(p) => *out = out.and(crate::spectral::Needs::of(p)),
Atom::Class(c) => c.members().for_each(|m| atom_into(m, out)),
_ => {}
}
}
let out = std::cell::RefCell::new(crate::spectral::Needs::none());
self.any_node(&|p| {
match p {
Pattern::Atom(a) => atom_into(a, &mut out.borrow_mut()),
Pattern::Within(v, _) => {
for e in v {
atom_into(&e.atom, &mut out.borrow_mut());
}
}
_ => {}
}
false
});
out.into_inner()
}
#[must_use]
pub fn binds(&self) -> bool {
match self {
Pattern::Bind(..) => true,
Pattern::Within(v, _) => v.iter().any(|e| e.bind.is_some()),
Pattern::Empty | Pattern::Atom(_) | Pattern::Guard(..) | Pattern::Anchor(_) => false,
Pattern::Assert(p, _, _)
| Pattern::Star(p, _)
| Pattern::Plus(p, _)
| Pattern::Opt(p, _)
| Pattern::Repeat(p, _, _, _)
| Pattern::Atomic(p)
| Pattern::Balanced(_, p)
| Pattern::Field(_, p) => p.binds(),
Pattern::Concat(v) | Pattern::Alt(v, _) => v.iter().any(Pattern::binds),
}
}
}
#[must_use]
pub fn normalize(p: Pattern) -> Pattern {
match p {
Pattern::Star(inner, g) => collapse_outer_star(normalize(*inner), g),
Pattern::Plus(inner, g) => collapse_outer_plus(normalize(*inner), g),
Pattern::Opt(inner, g) => collapse_outer_opt(normalize(*inner), g),
Pattern::Concat(v) => Pattern::Concat(v.into_iter().map(normalize).collect()),
Pattern::Alt(v, m) => Pattern::Alt(v.into_iter().map(normalize).collect(), m),
Pattern::Bind(n, s, x) => Pattern::Bind(n, s, normalize(*x).boxed()),
Pattern::Balanced(k, x) => Pattern::Balanced(k, normalize(*x).boxed()),
Pattern::Repeat(x, lo, hi, g) => Pattern::Repeat(normalize(*x).boxed(), lo, hi, g),
Pattern::Field(k, x) => Pattern::Field(k, normalize(*x).boxed()),
Pattern::Assert(x, neg, look) => Pattern::Assert(normalize(*x).boxed(), neg, look),
Pattern::Atomic(x) => Pattern::Atomic(normalize(*x).boxed()),
leaf @ (Pattern::Empty
| Pattern::Atom(_)
| Pattern::Within(..)
| Pattern::Guard(..)
| Pattern::Anchor(_)) => leaf,
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Quant {
Star,
Plus,
Opt,
}
fn foldable(inner: Quant, i: Greed, outer: Quant, g: Greed, x: &Pattern) -> bool {
if i != g || has_capture(x) {
return false;
}
match g {
Greed::Greedy => true,
Greed::Lazy => !matches!(
(inner, outer),
(Quant::Opt, Quant::Star | Quant::Plus) | (Quant::Plus, Quant::Opt)
),
}
}
fn collapse_outer_star(inner: Pattern, g: Greed) -> Pattern {
match inner {
Pattern::Star(x, i) if foldable(Quant::Star, i, Quant::Star, g, &x) => Pattern::Star(x, g),
Pattern::Plus(x, i) if foldable(Quant::Plus, i, Quant::Star, g, &x) => Pattern::Star(x, g),
Pattern::Opt(x, i) if foldable(Quant::Opt, i, Quant::Star, g, &x) => Pattern::Star(x, g),
other => Pattern::Star(other.boxed(), g),
}
}
fn collapse_outer_plus(inner: Pattern, g: Greed) -> Pattern {
match inner {
Pattern::Star(x, i) if foldable(Quant::Star, i, Quant::Plus, g, &x) => Pattern::Star(x, g),
Pattern::Plus(x, i) if foldable(Quant::Plus, i, Quant::Plus, g, &x) => Pattern::Plus(x, g),
Pattern::Opt(x, i) if foldable(Quant::Opt, i, Quant::Plus, g, &x) => Pattern::Star(x, g),
other => Pattern::Plus(other.boxed(), g),
}
}
fn collapse_outer_opt(inner: Pattern, g: Greed) -> Pattern {
match inner {
Pattern::Star(x, i) if foldable(Quant::Star, i, Quant::Opt, g, &x) => Pattern::Star(x, g),
Pattern::Plus(x, i) if foldable(Quant::Plus, i, Quant::Opt, g, &x) => Pattern::Star(x, g),
Pattern::Opt(x, i) if foldable(Quant::Opt, i, Quant::Opt, g, &x) => Pattern::Opt(x, g),
other => Pattern::Opt(other.boxed(), g),
}
}
fn has_capture(p: &Pattern) -> bool {
match p {
Pattern::Bind(..) | Pattern::Guard(..) | Pattern::Assert(..) => true,
Pattern::Atom(
Atom::RegisterEq(..) | Atom::RegisterRelated(..) | Atom::RegisterWithin(..) | Atom::RegisterKin(..),
) => true,
Pattern::Within(v, _) => v.iter().any(|e| {
e.bind.is_some()
|| matches!(
e.atom,
Atom::RegisterEq(..) | Atom::RegisterRelated(..) | Atom::RegisterWithin(..) | Atom::RegisterKin(..)
)
}),
Pattern::Atom(_) | Pattern::Empty | Pattern::Anchor(_) => false,
Pattern::Star(x, _)
| Pattern::Plus(x, _)
| Pattern::Opt(x, _)
| Pattern::Balanced(_, x)
| Pattern::Repeat(x, _, _, _)
| Pattern::Atomic(x)
| Pattern::Field(_, x) => has_capture(x),
Pattern::Concat(v) | Pattern::Alt(v, _) => v.iter().any(has_capture),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_supertoken_pattern_is_whole_input_dependent() {
let p = crate::parser::parse("@super:assign \\N").expect("parses");
assert!(p.reads_supertokens());
assert!(
p.depends_on_whole_input(),
"the tower is a whole-stream reading and has to be declared as one"
);
let plain = crate::parser::parse("\\N").expect("parses");
assert!(!plain.reads_supertokens());
assert!(!plain.depends_on_whole_input());
}
#[test]
fn constructs_one_of_every_variant() {
let nodes = vec![
Pattern::Empty,
Pattern::Atom(Atom::Kind(TokenKind::Word)),
Pattern::Atom(Atom::Any),
Pattern::Atom(Atom::literal("<")),
Pattern::Atom(Atom::RegisterEq("t".to_string(), OrbitGroup::Identity)),
Pattern::Atom(Atom::Byte(ByteClass::Digit)),
Pattern::Bind("t".to_string(), false, Pattern::Atom(Atom::Kind(TokenKind::Word)).boxed()),
Pattern::Balanced(
Some(BracketKind::Paren),
Pattern::Star(Pattern::Atom(Atom::Any).boxed(), Greed::Greedy).boxed(),
),
Pattern::Balanced(None, Pattern::Empty.boxed()),
Pattern::Guard("ERROR".to_string(), false),
Pattern::Field(3, Pattern::Empty.boxed()),
Pattern::Concat(vec![Pattern::Empty, Pattern::Empty]),
Pattern::Alt(vec![Pattern::Empty, Pattern::Empty], AltMode::First),
Pattern::Star(Pattern::Empty.boxed(), Greed::Greedy),
Pattern::Plus(Pattern::Empty.boxed(), Greed::Greedy),
Pattern::Opt(Pattern::Empty.boxed(), Greed::Lazy),
Pattern::Repeat(Pattern::Empty.boxed(), 1, Some(3), Greed::Greedy),
];
for n in &nodes {
assert!(!format!("{n:?}").is_empty());
}
assert_eq!(nodes.len(), 17);
}
#[test]
fn nested_quantifiers_collapse() {
let g = Greed::Greedy;
let any = || Pattern::Atom(Atom::Any).boxed();
let star = Pattern::Star(any(), g);
assert_eq!(normalize(Pattern::Star(star.clone().boxed(), g)), star);
let plus = Pattern::Plus(any(), g);
assert_eq!(normalize(Pattern::Star(plus.boxed(), g)), star);
let opt = Pattern::Opt(any(), g);
assert_eq!(normalize(Pattern::Plus(opt.clone().boxed(), g)), star);
assert_eq!(normalize(Pattern::Opt(opt.clone().boxed(), g)), opt);
}
#[test]
fn quantifiers_leaning_opposite_ways_do_not_collapse() {
let lazy_star = Pattern::Star(Pattern::Atom(Atom::Any).boxed(), Greed::Lazy);
let nested = Pattern::Star(lazy_star.clone().boxed(), Greed::Greedy);
assert_eq!(normalize(nested.clone()), nested, "(.*?)* keeps its nesting");
let greedy_star = Pattern::Star(Pattern::Atom(Atom::Any).boxed(), Greed::Greedy);
let nested = Pattern::Star(greedy_star.boxed(), Greed::Lazy);
assert_eq!(normalize(nested.clone()), nested, "(.*)*? keeps its nesting");
let lazy = Pattern::Star(Pattern::Atom(Atom::Any).boxed(), Greed::Lazy);
assert_eq!(normalize(Pattern::Star(lazy.clone().boxed(), Greed::Lazy)), lazy);
}
#[test]
fn a_lazy_fold_that_changes_where_the_form_stands_is_not_made() {
let any = || Pattern::Atom(Atom::Any).boxed();
let lazy_opt = Pattern::Opt(any(), Greed::Lazy);
let lazy_plus = Pattern::Plus(any(), Greed::Lazy);
for nested in [
Pattern::Plus(lazy_opt.clone().boxed(), Greed::Lazy),
Pattern::Star(lazy_opt.clone().boxed(), Greed::Lazy),
Pattern::Opt(lazy_plus.clone().boxed(), Greed::Lazy),
] {
assert_eq!(normalize(nested.clone()), nested, "{nested:?} keeps its nesting");
}
let lazy_star = Pattern::Star(any(), Greed::Lazy);
assert_eq!(normalize(Pattern::Plus(lazy_star.clone().boxed(), Greed::Lazy)), lazy_star);
assert_eq!(normalize(Pattern::Star(lazy_plus.clone().boxed(), Greed::Lazy)), lazy_star);
assert_eq!(normalize(Pattern::Plus(lazy_plus.clone().boxed(), Greed::Lazy)), lazy_plus);
assert_eq!(normalize(Pattern::Opt(lazy_star.clone().boxed(), Greed::Lazy)), lazy_star);
assert_eq!(normalize(Pattern::Opt(lazy_opt.clone().boxed(), Greed::Lazy)), lazy_opt);
let greedy_opt = Pattern::Opt(any(), Greed::Greedy);
let greedy_plus = Pattern::Plus(any(), Greed::Greedy);
let star = Pattern::Star(any(), Greed::Greedy);
assert_eq!(normalize(Pattern::Plus(greedy_opt.boxed(), Greed::Greedy)), star);
assert_eq!(normalize(Pattern::Opt(greedy_plus.boxed(), Greed::Greedy)), star);
}
#[test]
fn nested_quantifier_with_capture_is_preserved() {
let bind =
Pattern::Bind("t".to_string(), false, Pattern::Atom(Atom::Kind(TokenKind::Word)).boxed());
let g = Greed::Greedy;
let inner_star = Pattern::Star(bind.boxed(), g);
let nested = Pattern::Star(inner_star.clone().boxed(), g);
assert_eq!(normalize(nested), Pattern::Star(inner_star.boxed(), g));
}
}