use super::class::ClassSet;
use super::parser::Expr;
#[derive(Debug, Clone)]
pub struct LinearMatcher {
items: Vec<Item>,
}
#[derive(Debug, Clone)]
struct Item {
class: ClassSet,
min: u32,
max: Option<u32>,
}
impl LinearMatcher {
pub fn try_build(ast: &Expr) -> Option<Self> {
let mut items: Vec<Item> = Vec::new();
collect(ast, &mut items)?;
for (i, it) in items.iter().enumerate() {
if it.max.is_none() && i + 1 != items.len() {
return None;
}
}
Some(Self { items })
}
pub fn is_match(&self, s: &str) -> bool {
let mut iter = s.chars().peekable();
for item in &self.items {
let max = item.max.unwrap_or(u32::MAX);
let mut matched = 0u32;
while matched < max {
match iter.peek() {
Some(&c) if item.class.contains(c) => {
iter.next();
matched += 1;
}
_ => break,
}
}
if matched < item.min { return false; }
}
iter.next().is_none()
}
}
fn collect(expr: &Expr, items: &mut Vec<Item>) -> Option<()> {
match expr {
Expr::Empty => Some(()),
Expr::Class(c) => {
items.push(Item { class: c.clone(), min: 1, max: Some(1) });
Some(())
}
Expr::Quant(inner, min, max) => match inner.as_ref() {
Expr::Class(c) => {
items.push(Item { class: c.clone(), min: *min, max: *max });
Some(())
}
_ => None,
},
Expr::Concat(parts) => {
for p in parts {
collect(p, items)?;
}
Some(())
}
Expr::Alt(_) => None,
Expr::Anchor(_) => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::parser::parse;
fn try_linear(src: &str) -> Option<LinearMatcher> {
LinearMatcher::try_build(&parse(src).unwrap())
}
#[test]
fn accepts_literal_chain() {
assert!(try_linear("abc").is_some());
}
#[test]
fn accepts_class_with_bounded_quant() {
assert!(try_linear(r"E\d{6}").is_some());
assert!(try_linear(r"[A-Z]{3}-\d{4}-[a-f0-9]{8}").is_some());
}
#[test]
fn accepts_trailing_unbounded() {
assert!(try_linear(r"[a-z][a-z0-9_\-\.]*").is_some());
assert!(try_linear(r"\d+").is_some());
}
#[test]
fn rejects_unbounded_followed_by_atom() {
assert!(try_linear(r"\d+5").is_none());
}
#[test]
fn rejects_alternation() {
assert!(try_linear("a|b").is_none());
}
#[test]
fn rejects_grouped_subexpression() {
assert!(try_linear("(ab)+").is_none());
assert!(try_linear(r"\d{5}(-\d{4})?").is_none());
}
#[test]
fn matches_basic() {
let m = try_linear(r"E\d{6}").unwrap();
assert!(m.is_match("E000123"));
assert!(!m.is_match("E00012"));
assert!(!m.is_match("E0001234"));
assert!(!m.is_match("X000123"));
}
#[test]
fn matches_with_trailing_star() {
let m = try_linear(r"[a-z][a-z0-9_\-\.]*").unwrap();
assert!(m.is_match("org.example"));
assert!(m.is_match("a"));
assert!(!m.is_match("A"));
assert!(!m.is_match(""));
}
}