use core::fmt;
pub const DUP_MAX: u32 = 255;
const PROG_MAX: usize = 100_000;
const DEPTH_MAX: u32 = 64;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Error {
BadPattern,
Collate,
CharClass,
TrailingBackslash,
BackRef,
Unsupported,
MissingBracket,
MissingParen,
MissingBrace,
BadBrace,
BadRange,
Space,
BadRepeat,
BadMax,
}
impl Error {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Error::BadPattern => "Invalid regexp",
Error::Collate => "Unknown collating element",
Error::CharClass => "Unknown character class name",
Error::TrailingBackslash => "Trailing backslash",
Error::BackRef => "Invalid back reference",
Error::Unsupported => "regular expression backreferences are not supported",
Error::MissingBracket => "Missing ']'",
Error::MissingParen => "Missing ')'",
Error::MissingBrace => "Missing '}'",
Error::BadBrace => "Invalid contents of {}",
Error::BadRange => "Invalid character range",
Error::Space => "Out of memory",
Error::BadRepeat => "Invalid use of repetition operators",
Error::BadMax => "Maximum repetition in {} larger than 255",
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
struct Class([u64; 4]);
impl Class {
const fn empty() -> Class {
Class([0; 4])
}
const fn all() -> Class {
Class([u64::MAX; 4])
}
fn set(&mut self, b: u8) {
self.0[(b >> 6) as usize] |= 1 << (b & 63);
}
fn clear(&mut self, b: u8) {
self.0[(b >> 6) as usize] &= !(1 << (b & 63));
}
fn set_range(&mut self, lo: u8, hi: u8) {
for b in lo..=hi {
self.set(b);
}
}
const fn has(self, b: u8) -> bool {
self.0[(b >> 6) as usize] >> (b & 63) & 1 == 1
}
fn negate(&mut self) {
for w in &mut self.0 {
*w = !*w;
}
}
fn fold_case(&mut self) {
for b in b'a'..=b'z' {
if self.has(b) {
self.set(b - 32);
}
}
for b in b'A'..=b'Z' {
if self.has(b) {
self.set(b + 32);
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Assert {
Start,
End,
Word(bool),
StartOfWord,
EndOfWord,
}
fn raw(b: u8) -> Class {
let mut c = Class::empty();
c.set(b);
c
}
const fn is_word(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'_'
}
impl Assert {
fn holds(self, hay: &[u8], at: usize) -> bool {
let before = at > 0 && is_word(hay[at - 1]);
let after = at < hay.len() && is_word(hay[at]);
match self {
Assert::Start => at == 0,
Assert::End => at == hay.len(),
Assert::Word(want) => {
let edge = at == 0 || at == hay.len() || hay[at] == 0;
(edge || before != after) == want
}
Assert::StartOfWord => !before && after,
Assert::EndOfWord => before && !after,
}
}
}
#[derive(Clone, Copy, Debug)]
enum Inst {
Class(u32),
Split(u32, u32),
Jump(u32),
Assert(Assert),
Match,
}
enum Ast {
Empty,
Class(Class),
Assert(Assert),
Concat(Vec<Ast>),
Alt(Vec<Ast>),
Repeat(Box<Ast>, u32, Option<u32>),
}
#[derive(Debug)]
pub struct Regex {
prog: Vec<Inst>,
classes: Vec<Class>,
}
impl Regex {
pub fn new(pattern: &[u8], nocase: bool) -> Result<Regex, Error> {
let mut p = Parser {
pat: pattern,
at: 0,
nocase,
newline: false,
depth: 0,
groups: 0,
backref: None,
};
let ast = p.alternation()?;
if p.at != pattern.len() {
return Err(Error::BadPattern);
}
if let Some(n) = p.backref {
if n > p.groups {
return Err(Error::BackRef);
}
return Err(Error::Unsupported);
}
let mut c = Compiler {
prog: Vec::new(),
classes: Vec::new(),
};
c.node(&ast)?;
c.push(Inst::Match)?;
Ok(Regex {
prog: c.prog,
classes: c.classes,
})
}
#[must_use]
fn len(&self) -> usize {
self.prog.len()
}
}
#[derive(Default, Debug)]
pub struct Matcher {
now: Vec<u32>,
next: Vec<u32>,
seen: Vec<u64>,
work: Vec<u32>,
stamp: u64,
}
impl Matcher {
#[must_use]
pub fn new() -> Matcher {
Matcher::default()
}
pub fn reserve(&mut self, re: &Regex) {
let n = re.len();
if self.seen.len() < n {
self.seen.resize(n, 0);
}
for v in [&mut self.now, &mut self.next, &mut self.work] {
v.reserve_exact(n.saturating_sub(v.capacity()));
}
}
pub fn is_match(&mut self, re: &Regex, hay: &[u8]) -> bool {
let n = re.len();
if self.seen.len() < n {
self.seen.resize(n, 0);
}
self.now.clear();
self.next.clear();
self.stamp += 1;
let mut stamp = self.stamp;
for at in 0..=hay.len() {
if add(
&mut self.now,
&mut self.seen,
&mut self.work,
stamp,
re,
hay,
at,
0,
) {
return true;
}
if at == hay.len() {
break;
}
let byte = hay[at];
self.stamp += 1;
stamp = self.stamp;
self.next.clear();
for i in 0..self.now.len() {
let pc = self.now[i];
if let Inst::Class(c) = re.prog[pc as usize]
&& re.classes[c as usize].has(byte)
&& add(
&mut self.next,
&mut self.seen,
&mut self.work,
stamp,
re,
hay,
at + 1,
pc + 1,
)
{
return true;
}
}
core::mem::swap(&mut self.now, &mut self.next);
}
false
}
}
#[allow(clippy::too_many_arguments)]
fn add(
list: &mut Vec<u32>,
seen: &mut [u64],
work: &mut Vec<u32>,
stamp: u64,
re: &Regex,
hay: &[u8],
at: usize,
pc: u32,
) -> bool {
work.clear();
work.push(pc);
while let Some(pc) = work.pop() {
let i = pc as usize;
if seen[i] == stamp {
continue;
}
seen[i] = stamp;
match re.prog[i] {
Inst::Class(_) => list.push(pc),
Inst::Split(a, b) => {
work.push(b);
work.push(a);
}
Inst::Jump(a) => work.push(a),
Inst::Assert(a) => {
if a.holds(hay, at) {
work.push(pc + 1);
}
}
Inst::Match => return true,
}
}
false
}
struct Parser<'a> {
pat: &'a [u8],
at: usize,
nocase: bool,
newline: bool,
depth: u32,
groups: u32,
backref: Option<u32>,
}
impl Parser<'_> {
fn peek(&self) -> Option<u8> {
self.pat.get(self.at).copied()
}
fn eat(&mut self, b: u8) -> bool {
if self.peek() == Some(b) {
self.at += 1;
return true;
}
false
}
fn alternation(&mut self) -> Result<Ast, Error> {
let mut arms = vec![self.branch()?];
while self.eat(b'|') {
arms.push(self.branch()?);
}
if arms.len() == 1 {
return Ok(arms.pop().expect("one arm"));
}
Ok(Ast::Alt(arms))
}
fn branch(&mut self) -> Result<Ast, Error> {
let mut parts: Vec<Ast> = Vec::new();
loop {
match self.peek() {
None | Some(b'|') => break,
Some(b')') if self.depth > 0 => break,
_ => {}
}
let atom = match self.peek() {
Some(b'*' | b'+' | b'?' | b'{') => Ast::Empty,
_ => self.atom()?,
};
parts.push(self.repeats(atom)?);
}
match parts.len() {
0 => Ok(Ast::Empty),
1 => Ok(parts.pop().expect("one part")),
_ => Ok(Ast::Concat(parts)),
}
}
fn repeats(&mut self, mut node: Ast) -> Result<Ast, Error> {
loop {
let (min, max) = match self.peek() {
Some(b'*') => {
self.at += 1;
(0, None)
}
Some(b'+') => {
self.at += 1;
(1, None)
}
Some(b'?') => {
self.at += 1;
(0, Some(1))
}
Some(b'{') => {
self.at += 1;
self.bound()?
}
_ => return Ok(node),
};
match self.peek() {
Some(b'?') => self.at += 1,
Some(b'*' | b'+') => return Err(Error::BadRepeat),
_ => {}
}
node = Ast::Repeat(Box::new(node), min, max);
}
}
fn bound(&mut self) -> Result<(u32, Option<u32>), Error> {
let start = self.at;
let mut min: i64 = self.number().map_or(-1, i64::from);
let mut max = min;
if self.eat(b',') {
if min < 0 {
min = 0;
}
max = self.number().map_or(-1, i64::from);
}
if max >= 0 && min > max {
return Err(Error::BadBrace);
}
if min > i64::from(DUP_MAX) || max > i64::from(DUP_MAX) {
return Err(Error::BadMax);
}
while matches!(self.peek(), Some(b' ' | b',')) {
self.at += 1;
}
if self.at >= self.pat.len() {
return Err(Error::MissingBrace);
}
if self.at == start {
return Err(Error::BadBrace);
}
if !self.eat(b'}') {
return Err(Error::BadBrace);
}
if min < 0 {
min = 1;
max = 1;
}
Ok((min as u32, if max < 0 { None } else { Some(max as u32) }))
}
fn number(&mut self) -> Option<u32> {
let start = self.at;
let mut n: u32 = 0;
while let Some(b) = self.peek() {
if !b.is_ascii_digit() {
break;
}
n = n.saturating_mul(10).saturating_add(u32::from(b - b'0'));
self.at += 1;
}
if self.at == start { None } else { Some(n) }
}
fn atom(&mut self) -> Result<Ast, Error> {
let b = self.peek().ok_or(Error::BadPattern)?;
match b {
b'(' => {
self.at += 1;
if self.eat(b'?') {
return self.extension();
}
self.groups += 1;
self.group()
}
b'.' => {
self.at += 1;
let mut c = Class::all();
if self.newline {
c.clear(b'\n');
}
Ok(Ast::Class(c))
}
b'^' => {
self.at += 1;
Ok(Ast::Assert(Assert::Start))
}
b'$' => {
self.at += 1;
Ok(Ast::Assert(Assert::End))
}
b'[' => {
self.at += 1;
self.bracket()
}
b'\\' => {
self.at += 1;
self.escape()
}
_ => {
self.at += 1;
Ok(Ast::Class(self.literal(b)))
}
}
}
fn group(&mut self) -> Result<Ast, Error> {
self.depth += 1;
if self.depth > DEPTH_MAX {
return Err(Error::Space);
}
let inner = self.alternation()?;
self.depth -= 1;
if !self.eat(b')') {
return Err(Error::MissingParen);
}
Ok(inner)
}
fn extension(&mut self) -> Result<Ast, Error> {
let (nocase, newline) = (self.nocase, self.newline);
let mut on = true;
let opens = loop {
match self.peek().ok_or(Error::BadPattern)? {
b'i' => self.nocase = on,
b'n' => self.newline = on,
b'r' | b'U' => {}
b'-' => on = false,
b':' => {
self.at += 1;
break true;
}
b'#' => {
while self.peek().is_some_and(|b| b != b')') {
self.at += 1;
}
if !self.eat(b')') {
return Err(Error::BadPattern);
}
break false;
}
b')' => {
self.at += 1;
break false;
}
_ => return Err(Error::BadPattern),
}
self.at += 1;
};
let inner = if opens {
self.group()?
} else {
self.alternation()?
};
self.nocase = nocase;
self.newline = newline;
Ok(inner)
}
fn literal(&self, b: u8) -> Class {
let mut c = raw(b);
if self.nocase {
c.fold_case();
}
c
}
fn escape(&mut self) -> Result<Ast, Error> {
let b = self.peek().ok_or(Error::TrailingBackslash)?;
if let Some(node) = self.macro_for(b) {
self.at += 1;
return Ok(node);
}
self.at += 1;
match b {
b'b' => Ok(Ast::Assert(Assert::Word(true))),
b'B' => Ok(Ast::Assert(Assert::Word(false))),
b'<' => Ok(Ast::Assert(Assert::StartOfWord)),
b'>' => Ok(Ast::Assert(Assert::EndOfWord)),
b'x' => self.hex(),
b'0'..=b'9' => {
let n = u32::from(b - b'0');
self.backref = Some(self.backref.map_or(n, |m| m.max(n)));
Ok(Ast::Empty)
}
_ => Ok(Ast::Class(raw(b))),
}
}
fn macro_for(&self, b: u8) -> Option<Ast> {
let byte = |v: u8| Some(Ast::Class(raw(v)));
let class = |keep: fn(u8) -> bool, negate: bool| {
let mut c = Class::empty();
for x in 0..=255u8 {
if keep(x) {
c.set(x);
}
}
if negate {
c.negate();
}
Some(Ast::Class(c))
};
let space = |x: u8| x.is_ascii_whitespace() || x == 0x0b;
let word = |x: u8| x.is_ascii_alphanumeric() || x == b'_';
match b {
b't' => byte(b'\t'),
b'n' => byte(b'\n'),
b'r' => byte(b'\r'),
b'f' => byte(0x0c),
b'a' => byte(0x07),
b'e' => byte(0x1b),
b'w' => class(word, false),
b'W' => class(word, true),
b's' => class(space, false),
b'S' => class(space, true),
b'd' => class(|x| x.is_ascii_digit(), false),
b'D' => class(|x| x.is_ascii_digit(), true),
_ => None,
}
}
fn hex(&mut self) -> Result<Ast, Error> {
let one = |v: u32| {
let mut c = Class::empty();
if v <= 255 {
c.set(v as u8);
}
c
};
if !self.eat(b'{') {
let mut v: u32 = 0;
for _ in 0..2 {
match self.peek().and_then(|b| (b as char).to_digit(16)) {
Some(d) => {
v = v * 16 + d;
self.at += 1;
}
None => break,
}
}
return Ok(Ast::Class(one(v)));
}
let mut v: u32 = 0;
let mut digits = 0;
loop {
match self.peek() {
Some(b'}') => {
self.at += 1;
return Ok(Ast::Class(one(v)));
}
Some(b) => match (b as char).to_digit(16) {
Some(d) if digits < 8 => {
v = v * 16 + d;
digits += 1;
self.at += 1;
}
Some(_) => self.at += 1,
None => return Err(Error::MissingBrace),
},
None => return Err(Error::MissingBrace),
}
}
}
fn bracket(&mut self) -> Result<Ast, Error> {
let mut class = Class::empty();
let negate = self.eat(b'^');
let first = self.at;
loop {
let b = self.peek().ok_or(Error::MissingBracket)?;
if b == b']' && self.at > first {
self.at += 1;
break;
}
let dash = self.pat.get(self.at + 1) == Some(&b'-');
let hi = self.pat.get(self.at + 2).copied();
if dash && hi.is_some_and(|h| h != b']') {
let hi = hi.expect("checked");
if b > hi {
return Err(Error::BadRange);
}
class.set_range(b, hi);
self.at += 3;
continue;
}
if b == b'[' {
match self.pat.get(self.at + 1) {
Some(b'.') | Some(b'=') => return Err(Error::Collate),
Some(b':') => {
self.named_class(&mut class)?;
continue;
}
_ => {}
}
}
if b == b'-'
&& self.at != first
&& self.pat.get(self.at + 1).is_some_and(|&n| n != b']')
{
return Err(Error::BadRange);
}
class.set(b);
self.at += 1;
}
if self.nocase {
class.fold_case();
}
if negate {
class.negate();
}
Ok(Ast::Class(class))
}
fn named_class(&mut self, class: &mut Class) -> Result<(), Error> {
let start = self.at + 2;
let mut end = start;
while end < self.pat.len() && self.pat[end] != b':' {
end += 1;
}
if end + 1 >= self.pat.len() || self.pat[end + 1] != b']' {
return Err(Error::CharClass);
}
let name = &self.pat[start..end];
let keep: fn(u8) -> bool = match name {
b"alnum" => |b| b.is_ascii_alphanumeric(),
b"alpha" => |b| b.is_ascii_alphabetic(),
b"cntrl" => |b| b.is_ascii_control(),
b"digit" => |b| b.is_ascii_digit(),
b"graph" => |b| b.is_ascii_graphic(),
b"lower" => |b| b.is_ascii_lowercase(),
b"print" => |b| b.is_ascii_graphic() || b == b' ',
b"punct" => |b| b.is_ascii_punctuation(),
b"space" => |b| b.is_ascii_whitespace() || b == 0x0b,
b"upper" => |b| b.is_ascii_uppercase(),
b"xdigit" => |b| b.is_ascii_hexdigit(),
_ => return Err(Error::CharClass),
};
for b in 0..=255u8 {
if keep(b) {
class.set(b);
}
}
self.at = end + 2;
Ok(())
}
}
struct Compiler {
prog: Vec<Inst>,
classes: Vec<Class>,
}
fn matches_empty(ast: &Ast) -> bool {
match ast {
Ast::Empty | Ast::Assert(_) => true,
Ast::Class(_) => false,
Ast::Concat(parts) => parts.iter().all(matches_empty),
Ast::Alt(arms) => arms.iter().any(matches_empty),
Ast::Repeat(inner, min, _) => *min == 0 || matches_empty(inner),
}
}
impl Compiler {
fn push(&mut self, i: Inst) -> Result<u32, Error> {
if self.prog.len() >= PROG_MAX {
return Err(Error::Space);
}
self.prog.push(i);
Ok(self.prog.len() as u32 - 1)
}
fn here(&self) -> u32 {
self.prog.len() as u32
}
fn class(&mut self, c: Class) -> Result<(), Error> {
let idx = match self.classes.iter().position(|&e| e == c) {
Some(i) => i as u32,
None => {
self.classes.push(c);
self.classes.len() as u32 - 1
}
};
self.push(Inst::Class(idx))?;
Ok(())
}
fn node(&mut self, ast: &Ast) -> Result<(), Error> {
match ast {
Ast::Empty => Ok(()),
Ast::Class(c) => self.class(*c),
Ast::Assert(a) => {
self.push(Inst::Assert(*a))?;
Ok(())
}
Ast::Concat(parts) => {
for p in parts {
self.node(p)?;
}
Ok(())
}
Ast::Alt(arms) => self.alt(arms),
Ast::Repeat(inner, min, max) => self.repeat(inner, *min, *max),
}
}
fn alt(&mut self, arms: &[Ast]) -> Result<(), Error> {
let mut ends = Vec::with_capacity(arms.len());
for (i, arm) in arms.iter().enumerate() {
if i + 1 == arms.len() {
self.node(arm)?;
break;
}
let split = self.push(Inst::Split(0, 0))?;
let first = self.here();
self.node(arm)?;
ends.push(self.push(Inst::Jump(0))?);
let second = self.here();
self.prog[split as usize] = Inst::Split(first, second);
}
let after = self.here();
for j in ends {
self.prog[j as usize] = Inst::Jump(after);
}
Ok(())
}
fn repeat(&mut self, inner: &Ast, min: u32, max: Option<u32>) -> Result<(), Error> {
let min = if min == 0 && max != Some(0) && matches_empty(inner) {
1
} else {
min
};
for _ in 0..min {
self.node(inner)?;
}
match max {
None => {
let split = self.push(Inst::Split(0, 0))?;
let body = self.here();
self.node(inner)?;
self.push(Inst::Jump(split))?;
let after = self.here();
self.prog[split as usize] = Inst::Split(body, after);
Ok(())
}
Some(max) => {
let mut splits = Vec::new();
for _ in min..max {
let split = self.push(Inst::Split(0, 0))?;
let body = self.here();
self.prog[split as usize] = Inst::Split(body, 0);
splits.push(split);
self.node(inner)?;
}
let after = self.here();
for s in splits {
let Inst::Split(body, _) = self.prog[s as usize] else {
unreachable!("only splits were recorded")
};
self.prog[s as usize] = Inst::Split(body, after);
}
Ok(())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hits(pattern: &str, subject: &str) -> bool {
let re = Regex::new(pattern.as_bytes(), false).expect("compiles");
Matcher::new().is_match(&re, subject.as_bytes())
}
fn hits_nocase(pattern: &str, subject: &str) -> bool {
let re = Regex::new(pattern.as_bytes(), true).expect("compiles");
Matcher::new().is_match(&re, subject.as_bytes())
}
fn refuses(pattern: &str) -> Error {
Regex::new(pattern.as_bytes(), false).expect_err("refused")
}
#[test]
fn a_literal_matches_anywhere_in_the_subject() {
assert!(hits("abc", "abc"));
assert!(hits("abc", "xxabcxx"));
assert!(!hits("abc", "ab"));
assert!(hits("", ""));
assert!(hits("", "anything"));
}
#[test]
fn the_anchors_are_the_ends_of_the_subject_and_not_of_a_line() {
assert!(hits("^abc", "abcdef"));
assert!(!hits("^abc", "xabcdef"));
assert!(hits("abc$", "xxabc"));
assert!(!hits("abc$", "abcx"));
assert!(hits("^abc$", "abc"));
assert!(!hits("^abc$", "abc\n"));
assert!(!hits("^b", "a\nb"));
assert!(hits(".", "\n"));
}
#[test]
fn the_three_unbounded_repetitions_do_what_they_say() {
assert!(hits("^ab*c$", "ac"));
assert!(hits("^ab*c$", "abbbbc"));
assert!(!hits("^ab+c$", "ac"));
assert!(hits("^ab+c$", "abc"));
assert!(hits("^ab?c$", "ac"));
assert!(hits("^ab?c$", "abc"));
assert!(!hits("^ab?c$", "abbc"));
assert!(hits("^(a*)*$", ""));
assert!(hits("^(a*)*$", "aaa"));
}
#[test]
fn a_bound_counts_and_refuses_what_it_cannot_count() {
assert!(hits("^a{3}$", "aaa"));
assert!(!hits("^a{3}$", "aa"));
assert!(!hits("^a{3}$", "aaaa"));
assert!(hits("^a{2,}$", "aaaaa"));
assert!(!hits("^a{2,}$", "a"));
assert!(hits("^a{2,4}$", "aa"));
assert!(hits("^a{2,4}$", "aaaa"));
assert!(!hits("^a{2,4}$", "aaaaa"));
assert!(hits("^a{0,2}$", ""));
assert_eq!(refuses("a{3,2}"), Error::BadBrace);
assert_eq!(refuses("a{256}"), Error::BadMax);
assert_eq!(refuses("a{2"), Error::MissingBrace);
assert_eq!(refuses("a{x}"), Error::BadBrace);
assert!(hits("^[{]a$", "{a"));
}
#[test]
fn alternation_tries_every_arm_and_an_empty_arm_is_an_arm() {
assert!(hits("^(cat|dog|bird)$", "dog"));
assert!(!hits("^(cat|dog|bird)$", "cow"));
assert!(hits("^(ab|a)b$", "ab"));
assert!(hits("^(a|)$", ""));
assert!(hits("^()$", ""));
assert!(hits("^a(b|c)*d$", "abcbcd"));
}
#[test]
fn a_bracket_expression_follows_position_rather_than_escaping() {
assert!(hits("^[abc]$", "b"));
assert!(!hits("^[abc]$", "d"));
assert!(hits("^[^abc]$", "d"));
assert!(!hits("^[^abc]$", "a"));
assert!(hits("^[a-z]+$", "hello"));
assert!(!hits("^[a-z]+$", "Hello"));
assert!(hits("^[]a]$", "]"));
assert!(hits("^[-a]$", "-"));
assert!(hits("^[a-]$", "-"));
assert!(hits("^[^]]$", "x"));
assert!(hits("^[\\]$", "\\"));
assert_eq!(refuses("[abc"), Error::MissingBracket);
assert_eq!(refuses("[z-a]"), Error::BadRange);
assert_eq!(refuses("[[.a.]]"), Error::Collate);
assert_eq!(refuses("[[=a=]]"), Error::Collate);
}
#[test]
fn the_named_classes_are_the_posix_ones() {
assert!(hits("^[[:digit:]]+$", "12345"));
assert!(!hits("^[[:digit:]]+$", "12a45"));
assert!(hits("^[[:alpha:][:digit:]]+$", "ab12"));
assert!(hits("^[[:space:]]$", "\t"));
assert!(hits("^[^[:alpha:]]$", "1"));
assert!(hits("^[[:xdigit:]]+$", "deadBEEF01"));
assert_eq!(refuses("[[:nosuch:]]"), Error::CharClass);
}
#[test]
fn the_escapes_are_tres_and_a_macro_beats_the_switch() {
assert!(hits("^a\\.c$", "a.c"));
assert!(!hits("^a\\.c$", "abc"));
assert!(hits("^a\\*$", "a*"));
assert!(hits("^\\x41$", "A"));
assert!(hits("^\\x{41}$", "A"));
assert!(!hits("\\x{100}", "\u{100}"));
assert!(!hits("\\x{100}", "\0"));
assert!(hits("^\\n$", "\n"));
assert!(!hits("^\\n$", "n"));
assert!(hits("^\\t\\r\\f\\a\\e$", "\t\r\x0c\x07\x1b"));
assert!(hits("^\\d+$", "42"));
assert!(!hits("^\\d+$", "4a"));
assert!(hits("^\\D$", "a"));
assert!(hits("^\\w+$", "a_1"));
assert!(!hits("^\\w+$", "a-1"));
assert!(hits("^\\W$", "-"));
assert!(hits("^\\s+$", " \t\n"));
assert!(hits("^\\S$", "x"));
assert!(hits("^\\d{3}$", "123"));
assert_eq!(refuses("a\\"), Error::TrailingBackslash);
}
#[test]
fn a_backreference_is_refused_with_one_of_two_sentences() {
assert_eq!(refuses("(a)\\1"), Error::Unsupported);
assert_eq!(refuses("(a)(b)\\2"), Error::Unsupported);
assert_eq!(refuses("\\0"), Error::Unsupported);
assert_eq!(refuses("\\1"), Error::BackRef);
assert_eq!(refuses("(a)\\2"), Error::BackRef);
assert_eq!(refuses("\\1(a)"), Error::Unsupported);
assert_eq!(refuses("((a)\\9"), Error::MissingParen);
assert_eq!(refuses("\\1a{256}"), Error::BadMax);
assert_eq!(
Error::Unsupported.as_str(),
"regular expression backreferences are not supported"
);
}
#[test]
fn a_backslash_in_a_bracket_expression_is_not_an_escape() {
assert!(hits("^[\\d]+$", "\\d"));
assert!(!hits("^[\\d]+$", "42"));
assert!(hits("^[\\n]+$", "\\n"));
assert!(!hits("^[\\n]$", "\n"));
}
#[test]
fn the_word_boundaries_look_at_both_sides() {
assert!(hits("\\bcat\\b", "the cat sat"));
assert!(!hits("\\bcat\\b", "concatenate"));
assert!(hits("\\Bcat\\B", "concatenate"));
assert!(!hits("\\Bcat\\B", "the cat sat"));
assert!(hits("\\<cat", "a cat"));
assert!(!hits("\\<cat", "concat"));
assert!(hits("cat\\>", "concat"));
assert!(!hits("cat\\>", "cats"));
assert!(hits("\\b-", "-a"));
assert!(!hits("\\B-", "-a"));
assert!(hits("-\\b", "a-"));
assert!(!hits("-\\B", "a-"));
assert!(!hits("-\\b-", "---"));
assert!(hits("-\\B-", "---"));
assert!(hits("\0\\b\0", "\0\0\0"));
assert!(!hits("\0\\B\0", "\0\0\0"));
assert!(hits("\\>", "a"));
assert!(!hits("\\>", "-"));
assert!(hits("\\<", "a"));
}
#[test]
fn nocase_folds_the_class_and_not_the_subject() {
assert!(hits_nocase("^abc$", "ABC"));
assert!(hits_nocase("^[a-c]+$", "ABC"));
assert!(hits_nocase("^[A-C]+$", "abc"));
assert!(!hits_nocase("^[^a]$", "A"));
assert!(hits_nocase("^[^a]$", "b"));
assert!(!hits_nocase("^abc$", "abd"));
assert!(!hits_nocase("\\x41.", "ab"));
assert!(hits_nocase("\\x41.", "Ab"));
assert!(!hits_nocase("\\x{41}.", "ab"));
}
#[test]
fn the_inline_flags_are_read_and_scoped_the_way_tre_scopes_them() {
assert!(hits("(?i)abc", "ABC"));
assert!(hits("(?i)ABC", "abc"));
assert!(hits("(?i:a)b", "Ab"));
assert!(!hits("(?i:a)b", "AB"));
assert!(hits_nocase("(?-i)A", "A"));
assert!(!hits_nocase("(?-i)A", "a"));
assert!(hits("a(?i)b|c", "ac"));
assert!(!hits("a(?i)b|c", "c"));
assert!(!hits("(?n).", "\n"));
assert!(hits("(?n).", "a"));
assert!(!hits("(?n)^b", "a\nb"));
assert!(hits("(?#a comment)abc", "abc"));
assert!(hits("(?U)a", "a"));
assert!(hits("(?r)a", "a"));
assert_eq!(refuses("(?x)a"), Error::BadPattern);
assert_eq!(refuses("(?"), Error::BadPattern);
assert_eq!(refuses("(?ia)"), Error::BadPattern);
assert_eq!(refuses("(?#unterminated"), Error::BadPattern);
}
#[test]
fn an_operator_with_nothing_in_front_of_it_repeats_nothing() {
assert_eq!(refuses("(abc"), Error::MissingParen);
assert!(hits("^a)b$", "a)b"));
assert!(!hits("^a)b$", "ab"));
assert!(hits("^(a)b$", "ab"));
assert!(hits("^*a$", "a"));
assert!(!hits("^*a$", "*a"));
assert!(hits("^+a$", "a"));
assert!(hits("^?a$", "a"));
assert!(hits("^{2}a$", "a"));
assert!(!hits("^{2}a$", "aa"));
assert!(hits("^{,3}a$", "a"));
assert_eq!(refuses("^{$"), Error::BadBrace);
assert_eq!(refuses("{256}"), Error::BadMax);
assert_eq!(refuses("{3,2}"), Error::BadBrace);
}
#[test]
fn a_repetition_of_something_that_matches_nothing_still_runs_once() {
assert!(!hits("x\\b?y", "xy"));
assert!(!hits("x\\b{0,3}y", "xy"));
assert!(!hits("x(\\b)*y", "xy"));
assert!(!hits("x\\b?y", "x-y"));
assert!(!hits("(^|a)*b", "cb"));
assert!(hits("(^|a)*b", "ab"));
assert!(!hits("(\\b*)*x", "yx"));
assert!(hits("(\\b*)*x", "y x"));
assert!(hits("(a|)*b", "b"));
assert!(hits("(a*|^)*b", "cb"));
assert!(hits("(|^)*b", "cb"));
assert!(hits("(a?)*b", "cb"));
assert!(hits("(a\\b)*c", "c"));
assert!(hits("^{0}a$", "*a"));
assert!(hits("^{0}a", "*a"));
}
#[test]
fn a_second_repetition_operator_is_reserved_and_refused() {
assert_eq!(refuses("a**"), Error::BadRepeat);
assert_eq!(refuses("a*+"), Error::BadRepeat);
assert_eq!(refuses("a+*"), Error::BadRepeat);
assert_eq!(refuses("a?*"), Error::BadRepeat);
assert_eq!(refuses("a?+"), Error::BadRepeat);
assert_eq!(refuses("a{2}*"), Error::BadRepeat);
assert_eq!(refuses("a{2}+"), Error::BadRepeat);
assert!(hits("^a*?$", ""));
assert!(hits("^a??$", ""));
assert!(hits("^a{2}?$", "aa"));
assert!(hits("^a{2}{3}$", "aaaaaa"));
assert!(!hits("^a{2}{3}$", "aa"));
assert!(hits("^a*{2}$", "aa"));
assert!(hits("^a{1,2}{1,2}$", "aaaa"));
}
#[test]
fn nothing_a_pattern_can_do_makes_the_walk_more_than_linear() {
let re = Regex::new(b"^(a+)+b$", false).expect("compiles");
let mut m = Matcher::new();
let subject = vec![b'a'; 4096];
assert!(!m.is_match(&re, &subject));
assert!(m.is_match(&re, b"aaaab"));
}
#[test]
fn a_pattern_that_would_compile_to_too_much_is_refused_rather_than_built() {
assert_eq!(refuses("((a{255}){255}){255}"), Error::Space);
let deep = "(".repeat(200) + "a" + &")".repeat(200);
assert_eq!(refuses(&deep), Error::Space);
assert!(Regex::new(b"(a{200}){200}", false).is_ok());
}
#[test]
fn one_matcher_serves_every_pattern_it_is_given() {
let big = Regex::new(b"^(abc|def){2,8}$", false).expect("compiles");
let small = Regex::new(b"^x$", false).expect("compiles");
let mut m = Matcher::new();
for _ in 0..4 {
assert!(m.is_match(&big, b"abcdefabc"));
assert!(m.is_match(&small, b"x"));
assert!(!m.is_match(&small, b"y"));
assert!(!m.is_match(&big, b"abcdefa"));
}
}
#[test]
fn the_error_messages_are_the_ones_tre_would_have_printed() {
assert_eq!(Error::MissingBracket.as_str(), "Missing ']'");
assert_eq!(Error::MissingParen.as_str(), "Missing ')'");
assert_eq!(Error::MissingBrace.as_str(), "Missing '}'");
assert_eq!(Error::BadBrace.as_str(), "Invalid contents of {}");
assert_eq!(Error::BadRange.as_str(), "Invalid character range");
assert_eq!(Error::CharClass.as_str(), "Unknown character class name");
assert_eq!(Error::Collate.as_str(), "Unknown collating element");
assert_eq!(Error::TrailingBackslash.as_str(), "Trailing backslash");
assert_eq!(
Error::BadRepeat.as_str(),
"Invalid use of repetition operators"
);
assert_eq!(
Error::BadMax.as_str(),
"Maximum repetition in {} larger than 255"
);
assert_eq!(Error::Space.as_str(), "Out of memory");
assert_eq!(Error::BadPattern.as_str(), "Invalid regexp");
assert_eq!(Error::BackRef.as_str(), "Invalid back reference");
}
#[test]
fn a_subject_is_bytes_and_not_text() {
let re = Regex::new(b"^.{3}$", false).expect("compiles");
let mut m = Matcher::new();
assert!(m.is_match(&re, &[0x00, 0xff, 0x80]));
assert!(m.is_match(&re, "☃".as_bytes()));
assert!(!m.is_match(&re, "ab".as_bytes()));
let hi = Regex::new(&[b'^', 0xff, b'$'], false).expect("compiles");
assert!(m.is_match(&hi, &[0xff]));
assert!(!m.is_match(&hi, &[0xfe]));
}
}