use super::Matcher;
use super::backend::MatcherBackend;
use crate::{Match, MatchIndices};
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone)]
pub(super) enum CompiledPatterns {
Empty,
Single(CompiledPattern),
Multi(Vec<CompiledPattern>),
}
impl CompiledPatterns {
pub(super) fn is_empty(&self) -> bool {
matches!(self, CompiledPatterns::Empty)
}
}
#[derive(Debug, Clone)]
pub(super) struct CompiledPattern {
pub(super) negated: bool,
pub(super) needs_unicode: bool,
pub(super) backend: MatcherBackend,
}
impl Matcher {
pub(super) fn match_one_multi<S: AsRef<str>>(
patterns: &mut [CompiledPattern],
max_typos: Option<u16>,
haystack: S,
index: u32,
) -> Option<Match> {
let haystack = haystack.as_ref();
let mut combined = Match::from_index(index as usize);
for pattern in patterns {
let result = Self::dispatch_pattern_one(pattern, max_typos, haystack, index);
if pattern.negated {
if result.is_some() {
return None;
}
} else {
let m = result?;
combined.score = combined.score.saturating_add(m.score);
combined.exact |= m.exact;
#[cfg(feature = "match_end_col")]
{
combined.end_col = combined.end_col.max(m.end_col);
}
}
}
Some(combined)
}
pub(super) fn match_one_indices_multi<S: AsRef<str>>(
patterns: &mut [CompiledPattern],
max_typos: Option<u16>,
haystack: S,
index: u32,
) -> Option<MatchIndices> {
let haystack = haystack.as_ref();
let mut combined = MatchIndices::from_index(index as usize);
for pattern in patterns {
if pattern.negated {
if Self::dispatch_pattern_one(pattern, max_typos, haystack, index).is_some() {
return None;
}
} else {
let m = Self::dispatch_pattern_one_indices(pattern, max_typos, haystack, index)?;
combined.score = combined.score.saturating_add(m.score);
combined.exact |= m.exact;
combined.indices.extend(m.indices);
}
}
combined.indices.sort_unstable_by(|a, b| b.cmp(a));
combined.indices.dedup();
Some(combined)
}
pub(super) fn match_list_multi_into<S: AsRef<str>>(
patterns: &mut [CompiledPattern],
max_typos: Option<u16>,
haystacks: &[S],
haystack_index_offset: u32,
matches: &mut Vec<Match>,
) {
let base_pattern_idx = patterns.iter().position(|p| !p.negated);
let mut candidates = Vec::new();
match base_pattern_idx {
Some(i) => Self::dispatch_pattern_into(
&mut patterns[i],
max_typos,
haystacks,
haystack_index_offset,
&mut candidates,
),
None => {
let indices = (0..haystacks.len()).map(|i| i + haystack_index_offset as usize);
candidates.extend(indices.map(Match::from_index));
}
}
for (pattern_idx, pattern) in patterns.iter_mut().enumerate() {
if Some(pattern_idx) == base_pattern_idx || candidates.is_empty() {
continue;
}
let gathered = candidates
.iter()
.map(|m| haystacks[(m.index - haystack_index_offset) as usize].as_ref())
.collect::<Vec<&str>>();
let mut hits = Vec::new();
Self::dispatch_pattern_into(pattern, max_typos, &gathered, 0, &mut hits);
if pattern.negated {
let mut hits = hits.iter().peekable();
let mut position = 0;
candidates.retain(|_| {
let matched = hits.next_if(|hit| hit.index as usize == position).is_some();
position += 1;
!matched
});
} else {
candidates = hits
.into_iter()
.map(|mut hit| {
let candidate = candidates[hit.index as usize];
hit.index = candidate.index;
hit.score = hit.score.saturating_add(candidate.score);
hit.exact |= candidate.exact;
#[cfg(feature = "match_end_col")]
{
hit.end_col = hit.end_col.max(candidate.end_col);
}
hit
})
.collect();
}
}
matches.extend(candidates);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{CaseMatching, Config, Matching, Pattern, SortStrategy};
fn multi(query: &str, config: &Config) -> Matcher {
Matcher::from_patterns(&Pattern::parse_query(query), config)
}
#[test]
fn multi_pattern_negation() {
let haystacks = ["foobar", "foo", "barfoo", "bar", "qux"];
let config = Config::default().sort(SortStrategy::IndexAsc);
let matches = multi("foo !bar", &config).match_list(&haystacks);
assert_eq!(matches.iter().map(|m| m.index).collect::<Vec<_>>(), vec![1]);
}
#[test]
fn multi_pattern_negated_matching_modes() {
let haystacks = ["foo/bar", "bar/foo", "foo", "foobar"];
let config = Config::default().sort(SortStrategy::IndexAsc);
let matches = multi("foo !^bar", &config).match_list(&haystacks);
assert_eq!(
matches.iter().map(|m| m.index).collect::<Vec<_>>(),
vec![0, 2, 3]
);
let matches = multi("foo !bar$", &config).match_list(&haystacks);
assert_eq!(
matches.iter().map(|m| m.index).collect::<Vec<_>>(),
vec![1, 2]
);
}
#[test]
fn multi_pattern_scores_sum() {
let haystacks = ["foo", "xfoox", "bar"];
let config = Config::default().sort(SortStrategy::IndexAsc);
let single = Matcher::new("foo", &config).match_list(&haystacks);
let combined = multi("foo foo", &config).match_list(&haystacks);
assert_eq!(combined.len(), single.len());
for (c, s) in combined.iter().zip(&single) {
assert_eq!(c.index, s.index);
assert_eq!(c.score, s.score * 2);
assert_eq!(c.exact, s.exact);
#[cfg(feature = "match_end_col")]
assert_eq!(c.end_col, s.end_col);
}
}
#[test]
fn multi_pattern_all_negated() {
let haystacks = ["foo", "bar", "xfoox", "qux"];
let config = Config::default().sort(SortStrategy::IndexAsc);
let matches = multi("!foo", &config).match_list(&haystacks);
assert_eq!(
matches.iter().map(|m| m.index).collect::<Vec<_>>(),
vec![1, 3]
);
assert!(matches.iter().all(|m| m.score == 0));
let matches = multi("!foo !qux", &config).match_list(&haystacks);
assert_eq!(matches.iter().map(|m| m.index).collect::<Vec<_>>(), vec![1]);
}
#[test]
fn multi_pattern_contradiction_is_empty() {
let haystacks = ["foo", "foobar"];
let matches = multi("foo !foo", &Config::default()).match_list(&haystacks);
assert!(matches.is_empty());
}
#[test]
fn multi_pattern_score_sorted() {
let haystacks = ["xfoobarx", "foobar", "zzz"];
let matches = multi("foo bar", &Config::default()).match_list(&haystacks);
assert_eq!(matches.len(), 2);
assert!(matches.is_sorted());
assert_eq!(matches[0].index, 1);
}
#[test]
fn multi_pattern_match_iter_matches_match_list() {
let haystacks = ["foobar", "foo", "barfoo", "bar", "qux", "FooBar"];
for query in ["foo !bar", "foo bar", "!foo", "^foo bar$", "foo !^bar"] {
let config = Config::default().sort(SortStrategy::IndexAsc);
let mut matcher = multi(query, &config);
let from_iter = matcher.match_iter(haystacks.iter()).collect::<Vec<_>>();
let from_list = matcher.match_list(&haystacks);
assert_eq!(from_iter, from_list, "query: {query:?}");
}
}
#[test]
fn multi_pattern_match_list_indices_matches_match_list() {
let haystacks = ["foobar", "foo", "barfoo", "bar", "qux", "FooBar"];
for query in ["foo !bar", "foo bar", "!foo", "foo fo"] {
let config = Config::default().sort(SortStrategy::IndexAsc);
let mut matcher = multi(query, &config);
let matches = matcher.match_list(&haystacks);
let indices = matcher.match_list_indices(&haystacks);
assert_eq!(matches.len(), indices.len(), "query: {query:?}");
for (m, i) in matches.iter().zip(&indices) {
assert_eq!(m.index, i.index, "query: {query:?}");
assert_eq!(m.score, i.score, "query: {query:?}");
assert_eq!(m.exact, i.exact, "query: {query:?}");
assert!(
i.indices.windows(2).all(|w| w[0] > w[1]),
"query: {query:?}, indices: {:?}",
i.indices
);
}
}
}
#[test]
fn multi_pattern_overlapping_indices_deduped() {
let mut matcher = multi("foo fo", &Config::default());
let indices = matcher.match_list_indices(&["foo"]);
assert_eq!(indices.len(), 1);
assert_eq!(indices[0].indices, vec![2, 1, 0]);
}
#[test]
fn pattern_matching_override_matches_config() {
let haystacks = ["fooX", "xfoo", "foo"];
let config = Config::default().sort(SortStrategy::IndexAsc);
let from_pattern = Matcher::from_patterns(
&[Pattern::new("foo", Some(Matching::Prefix), false)],
&config,
)
.match_list(&haystacks);
let from_config =
Matcher::new("foo", &config.clone().matching(Matching::Prefix)).match_list(&haystacks);
assert_eq!(from_pattern, from_config);
}
#[test]
fn set_config_preserves_pattern_matching_override() {
let haystacks = ["fooX", "xfoo"];
let config = Config::default().sort(SortStrategy::IndexAsc);
let mut matcher = multi("^foo", &config);
matcher.set_config(config.clone().max_typos(None));
let matches = matcher.match_list(&haystacks);
assert_eq!(matches.iter().map(|m| m.index).collect::<Vec<_>>(), vec![0]);
}
#[test]
fn set_pattern_reverts_to_literal_matching() {
let config = Config::default().sort(SortStrategy::IndexAsc);
let mut matcher = multi("^foo", &config);
assert_eq!(matcher.patterns(), &[Pattern::parse("^foo")]);
assert_eq!(matcher.match_list(&["foobar", "^foo"]).len(), 1);
matcher.set_pattern("^foo");
let matches = matcher.match_list(&["foobar", "^foo"]);
assert_eq!(matches.iter().map(|m| m.index).collect::<Vec<_>>(), vec![1]);
}
#[test]
fn set_patterns_skips_rebuild_when_unchanged() {
let config = Config::default().sort(SortStrategy::IndexAsc);
let mut matcher = Matcher::new("foo", &config);
matcher.set_patterns(&["foo".into()]);
matcher.set_pattern("foo");
assert_eq!(matcher.match_list(&["foobar"]).len(), 1);
}
#[test]
fn multi_pattern_smart_case_per_pattern() {
let haystacks = ["Foo BAR", "foo bar"];
let config = Config::default()
.casing(CaseMatching::Smart)
.sort(SortStrategy::IndexAsc);
let matches = multi("Foo bar", &config).match_list(&haystacks);
assert_eq!(matches.iter().map(|m| m.index).collect::<Vec<_>>(), vec![0]);
}
#[test]
fn multi_pattern_unicode_per_pattern() {
let haystacks = ["다나 foo", "dana foo", "다나"];
let config = Config::default().sort(SortStrategy::IndexAsc);
let matches = multi("다나 foo", &config).match_list(&haystacks);
assert_eq!(matches.iter().map(|m| m.index).collect::<Vec<_>>(), vec![0]);
}
#[test]
fn from_patterns_empty_patterns_match_everything() {
let haystacks = ["foo", "bar"];
let mut matcher = Matcher::from_patterns(&[], &Config::default());
assert_eq!(matcher.match_list(&haystacks).len(), 2);
let mut matcher = multi("! ^$", &Config::default());
assert_eq!(matcher.match_list(&haystacks).len(), 2);
}
}