mod compiled;
pub mod options;
pub use options::*;
use dyn_clone::DynClone;
use std::{
fmt,
iter::FromIterator,
iter::{FusedIterator, Peekable},
};
#[derive(Debug, Clone)]
pub struct Pattern {
pat: Box<dyn CompiledPat>,
}
trait CompiledPat: fmt::Debug + DynClone {
fn matches(&self, string: &str) -> bool;
}
dyn_clone::clone_trait_object!(CompiledPat);
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Chunk {
Str(String),
Wildcard,
UnknownChar,
}
impl Chunk {
fn from_char(chr: char) -> Option<Chunk> {
use Chunk::*;
match chr {
'*' => Some(Wildcard),
'?' => Some(UnknownChar),
_ => None,
}
}
pub fn str(s: &str) -> Chunk {
Chunk::Str(s.to_owned())
}
fn take_str(self) -> Option<String> {
match self {
Chunk::Str(s) => Some(s),
_ => None,
}
}
}
impl From<String> for Chunk {
fn from(s: String) -> Self {
Chunk::Str(s)
}
}
impl FromIterator<Chunk> for Pattern {
fn from_iter<T: IntoIterator<Item = Chunk>>(iter: T) -> Self {
use compiled::*;
dbg!("HI!");
let mut chunks = Vec::new();
for chunk in iter {
if !(chunk == Chunk::Wildcard && chunks.ends_with(&[Chunk::Wildcard])) {
chunks.push(chunk);
}
}
let chunks = chunks;
dbg!(&chunks);
if chunks.iter().all(|chunk| chunk == &Chunk::UnknownChar) {
Pattern::from_compiled(OptionalCharLen { len: chunks.len() })
} else if chunks.iter().all(|chunk| chunk == &Chunk::Wildcard) {
Pattern::from_compiled(MatchAny {})
} else if chunks.iter().all(|chunk| matches!(chunk, Chunk::Str(_))) {
Pattern::from_compiled(LiteralStr(
chunks.into_iter().map(|x| x.take_str().unwrap()).collect(),
))
} else {
let mut states = Vec::new();
for chunk in chunks {
match chunk {
Chunk::Wildcard => states.push(State::Wildcard),
Chunk::UnknownChar => states.push(State::UnknownChar),
Chunk::Str(string) => {
for chr in string.chars() {
states.push(State::Char(chr));
}
}
}
}
Pattern::from_compiled(General { states })
}
}
}
struct CompileIter<T: Iterator<Item = char>> {
iter: Peekable<T>,
opts: MatchOptions,
}
impl<T: Iterator<Item = char>> Iterator for CompileIter<T> {
type Item = Chunk;
fn next(&mut self) -> Option<Self::Item> {
use Chunk::*;
match self.iter.next() {
None => None,
Some(chr) => Some(match chr {
c if self.opts.contains(c.into()) => Chunk::from_char(c).unwrap(),
_ => {
let mut string = String::new();
string.push(chr);
loop {
dbg!("ok");
match self.iter.peek() {
Some(peeked) if !self.opts.contains(MatchOptions::from(*peeked)) => {
string.push(*peeked);
self.iter.next();
}
_ => break,
}
}
Str(string)
}
}),
}
}
}
impl<T: Iterator<Item = char>> FusedIterator for CompileIter<T> {}
impl Pattern {
pub fn compile(pat: &str, opts: MatchOptions) -> Pattern {
CompileIter {
iter: pat.chars().peekable(),
opts,
}
.collect()
}
pub fn compile_iter<T: IntoIterator<Item = char>>(pat: T, opts: MatchOptions) -> Pattern {
CompileIter {
iter: pat.into_iter().peekable(),
opts,
}
.collect()
}
pub fn matches(&self, string: &str) -> bool {
self.pat.matches(string)
}
fn from_compiled<T: CompiledPat + 'static>(pat: T) -> Pattern {
Pattern { pat: Box::new(pat) }
}
}
#[cfg(test)]
mod tests {
use super::{Chunk, MatchOptions, Pattern};
use Chunk::*;
fn check_match(patterns: Vec<Pattern>, strings: Vec<&str>, expected: bool) {
for pat in patterns {
for string in strings.iter() {
assert_eq!(
pat.matches(string),
expected,
"Pattern {:?} failed to match against {}",
pat,
string
);
}
}
}
fn matches(patterns: Vec<Pattern>, strings: Vec<&str>) {
check_match(patterns, strings, true)
}
fn matches_v(patterns: Vec<Vec<Chunk>>, strings: Vec<&str>) {
matches(
patterns
.into_iter()
.map(|v| v.into_iter().collect())
.collect(),
strings,
)
}
fn strings_to_pats(patterns: Vec<&str>) -> Vec<Pattern> {
patterns
.into_iter()
.map(|pat| Pattern::compile(pat, MatchOptions::ALL))
.collect()
}
fn matches_s(patterns: Vec<&str>, strings: Vec<&str>) {
matches(strings_to_pats(patterns), strings)
}
fn no_match_s(patterns: Vec<&str>, strings: Vec<&str>) {
check_match(strings_to_pats(patterns), strings, false)
}
macro_rules! chunk {
($i:ident) => {
$i
};
($s:tt) => {
Str($s.to_string())
};
}
macro_rules! chunks {
($($t:tt),*) => {
vec![ $(chunk!($t)),* ]
}
}
#[test]
fn cat() {
matches_v(vec![chunks!["c", UnknownChar, "t"]], vec!["cat"]);
matches_s(
vec![
"*", "c*", "*t", "*a*", "c*t", "ca*", "*at", "???", "?a?", "c??", "??t", "ca?",
"?at", "c?t", "cat", "ca*t", "c*at", "*cat", "cat*", "ca****t",
],
vec!["cat"],
);
no_match_s(
vec!["cat?", "?cat", "c?at", "ca?t", "?", "??", "????", "", " *"],
vec!["cat"],
);
}
#[test]
fn empty() {
matches_s(vec![""], vec![""]);
no_match_s(vec![""], vec!["a", "b", "c", " ", "\t", "\n"]);
}
}