#[must_use]
pub fn matches(pattern: &[u8], text: &[u8]) -> bool {
let (mut p, mut t) = (0usize, 0usize);
let mut star: Option<usize> = None;
let mut mark = 0usize;
while t < text.len() {
let mut step = false;
if p < pattern.len() {
match pattern[p] {
b'*' => {
star = Some(p);
mark = t;
p += 1;
continue;
}
b'?' => {
p += 1;
t += 1;
continue;
}
b'[' => {
let (next, hit) = class(pattern, p, text[t]);
if hit {
p = next;
step = true;
}
}
b'\\' if p + 1 < pattern.len() => {
if pattern[p + 1] == text[t] {
p += 2;
step = true;
}
}
c => {
if c == text[t] {
p += 1;
step = true;
}
}
}
}
if step {
t += 1;
continue;
}
match star {
Some(at) => {
p = at + 1;
mark += 1;
t = mark;
}
None => return false,
}
}
while p < pattern.len() && pattern[p] == b'*' {
p += 1;
}
p == pattern.len()
}
fn class(pattern: &[u8], p: usize, c: u8) -> (usize, bool) {
let mut i = p + 1;
let negate = i < pattern.len() && pattern[i] == b'^';
if negate {
i += 1;
}
let mut hit = false;
while i < pattern.len() && pattern[i] != b']' {
if pattern[i] == b'\\' && i + 1 < pattern.len() {
i += 1;
hit |= pattern[i] == c;
i += 1;
} else if i + 2 < pattern.len() && pattern[i + 1] == b'-' && pattern[i + 2] != b']' {
let (mut lo, mut hi) = (pattern[i], pattern[i + 2]);
if lo > hi {
core::mem::swap(&mut lo, &mut hi);
}
hit |= c >= lo && c <= hi;
i += 3;
} else {
hit |= pattern[i] == c;
i += 1;
}
}
let next = if i < pattern.len() { i + 1 } else { i };
(next, hit != negate)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_pattern_without_a_wildcard_is_an_equality_test() {
assert!(matches(b"maxmemory", b"maxmemory"));
assert!(!matches(b"maxmemory", b"maxmemory-policy"));
assert!(!matches(b"maxmemory-policy", b"maxmemory"));
assert!(matches(b"", b""));
assert!(!matches(b"", b"x"));
}
#[test]
fn stars_match_any_run_including_none() {
assert!(matches(b"*", b""));
assert!(matches(b"*", b"anything at all"));
assert!(matches(b"maxmemory*", b"maxmemory"));
assert!(matches(b"maxmemory*", b"maxmemory-policy"));
assert!(matches(b"*policy", b"maxmemory-policy"));
assert!(matches(b"max*policy", b"maxmemory-policy"));
assert!(!matches(b"max*policy", b"maxmemory-clients"));
assert!(matches(b"a**b", b"ab"));
}
#[test]
fn a_question_mark_is_exactly_one_byte() {
assert!(matches(b"h?llo", b"hello"));
assert!(!matches(b"h?llo", b"hllo"));
assert!(!matches(b"h?llo", b"heello"));
}
#[test]
fn classes_do_ranges_and_negation() {
assert!(matches(b"h[ae]llo", b"hello"));
assert!(matches(b"h[ae]llo", b"hallo"));
assert!(!matches(b"h[ae]llo", b"hillo"));
assert!(matches(b"h[^e]llo", b"hallo"));
assert!(!matches(b"h[^e]llo", b"hello"));
assert!(matches(b"key[0-9]", b"key7"));
assert!(!matches(b"key[0-9]", b"keyx"));
assert!(matches(b"key[9-0]", b"key7"));
}
#[test]
fn a_backslash_takes_the_special_out_of_the_next_byte() {
assert!(matches(br"a\*b", b"a*b"));
assert!(!matches(br"a\*b", b"axxb"));
assert!(matches(br"a\\b", br"a\b"));
}
#[test]
fn a_pattern_built_to_be_slow_still_answers() {
let pattern = b"*a*a*a*a*a*b";
let text = vec![b'a'; 64];
assert!(!matches(pattern, &text));
}
#[test]
fn a_class_that_is_never_closed_ends_at_the_end() {
assert!(matches(b"a[bc", b"ab"));
assert!(!matches(b"a[bc", b"ax"));
}
}