use std::cmp::Ordering;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
mod r#const;
pub mod k_merge;
mod literal;
mod matcher;
mod pattern;
mod prefilter;
mod smith_waterman;
mod sort;
use r#const::*;
pub use matcher::Matcher;
pub use pattern::Pattern;
pub use sort::radix_sort_matches;
pub mod iter {
pub use crate::matcher::{FuzzyMatch, FuzzyMatchExt, FuzzyMatchIndices};
}
#[derive(Debug, Clone, Copy, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Match {
pub score: u16,
pub index: u32,
pub exact: bool,
#[cfg(feature = "match_end_col")]
pub end_col: u16,
}
impl Match {
pub fn from_index(index: usize) -> Self {
Self {
score: 0,
index: index as u32,
exact: false,
#[cfg(feature = "match_end_col")]
end_col: 0,
}
}
}
impl PartialOrd for Match {
fn partial_cmp(&self, other: &Match) -> Option<Ordering> {
Some(std::cmp::Ord::cmp(self, other))
}
}
impl Ord for Match {
fn cmp(&self, other: &Self) -> Ordering {
(self.score as u64)
.cmp(&(other.score as u64))
.reverse()
.then_with(|| self.index.cmp(&other.index))
}
}
impl PartialEq for Match {
fn eq(&self, other: &Self) -> bool {
self.score == other.score && self.index == other.index
}
}
impl Eq for Match {}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct MatchIndices {
pub score: u16,
pub index: u32,
pub exact: bool,
pub indices: Vec<u32>,
}
impl MatchIndices {
pub fn from_index(index: usize) -> Self {
Self {
score: 0,
index: u32::try_from(index)
.expect("too many items in haystack, will overflow the u32 index"),
exact: false,
indices: vec![],
}
}
}
impl PartialOrd for MatchIndices {
fn partial_cmp(&self, other: &MatchIndices) -> Option<Ordering> {
Some(std::cmp::Ord::cmp(self, other))
}
}
impl Ord for MatchIndices {
fn cmp(&self, other: &Self) -> Ordering {
(self.score as u64)
.cmp(&(other.score as u64))
.reverse()
.then_with(|| self.index.cmp(&other.index))
}
}
impl PartialEq for MatchIndices {
fn eq(&self, other: &Self) -> bool {
self.score == other.score && self.index == other.index
}
}
impl Eq for MatchIndices {}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Config {
pub max_typos: Option<u16>,
#[cfg_attr(feature = "serde", serde(default))]
pub casing: CaseMatching,
#[cfg_attr(feature = "serde", serde(default))]
pub unicode: UnicodeMatching,
#[cfg_attr(feature = "serde", serde(default))]
pub matching: Matching,
#[cfg_attr(feature = "serde", serde(default))]
pub sort: SortStrategy,
pub scoring: Scoring,
}
impl Default for Config {
fn default() -> Self {
Config {
max_typos: Some(0),
casing: CaseMatching::Ignore,
unicode: UnicodeMatching::Smart,
matching: Matching::Fuzzy,
sort: SortStrategy::ScoreThenIndexAsc,
scoring: Scoring::default(),
}
}
}
impl Config {
pub fn matching(mut self, matching: Matching) -> Self {
self.matching = matching;
self
}
pub fn max_typos(mut self, max_typos: Option<u16>) -> Self {
self.max_typos = max_typos;
self
}
pub fn casing(mut self, casing: CaseMatching) -> Self {
self.casing = casing;
self
}
pub fn unicode(mut self, unicode: UnicodeMatching) -> Self {
self.unicode = unicode;
self
}
pub fn sort(mut self, sort: SortStrategy) -> Self {
self.sort = sort;
self
}
pub fn scoring(mut self, scoring: Scoring) -> Self {
self.scoring = scoring;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum SortStrategy {
#[default]
ScoreThenIndexAsc,
ScoreThenIndexDesc,
IndexAsc,
IndexDesc,
}
impl SortStrategy {
pub fn reverse(self) -> Self {
match self {
SortStrategy::ScoreThenIndexAsc => SortStrategy::ScoreThenIndexDesc,
SortStrategy::IndexAsc => SortStrategy::IndexDesc,
SortStrategy::ScoreThenIndexDesc => SortStrategy::ScoreThenIndexAsc,
SortStrategy::IndexDesc => SortStrategy::IndexAsc,
}
}
pub fn is_reversed(self) -> bool {
matches!(
self,
SortStrategy::IndexDesc | SortStrategy::ScoreThenIndexDesc
)
}
pub fn is_by_score(self) -> bool {
matches!(
self,
SortStrategy::ScoreThenIndexAsc | SortStrategy::ScoreThenIndexDesc
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CaseMatching {
#[default]
Ignore,
Smart,
Respect,
}
impl CaseMatching {
#[inline(always)]
pub(crate) fn respects_case_for(self, needle: &str) -> bool {
match self {
CaseMatching::Ignore => false,
CaseMatching::Smart => needle.chars().any(char::is_uppercase),
CaseMatching::Respect => true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum UnicodeMatching {
Ignore,
#[default]
Smart,
Always,
}
impl UnicodeMatching {
#[inline(always)]
pub(crate) fn respects_unicode_for(self, needle: &str) -> bool {
match self {
UnicodeMatching::Ignore => false,
UnicodeMatching::Smart => !needle.is_ascii(),
UnicodeMatching::Always => true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Matching {
#[default]
Fuzzy,
Exact,
Prefix,
Suffix,
Substring,
}
impl Matching {
#[inline(always)]
pub(crate) fn is_fuzzy(self) -> bool {
matches!(self, Matching::Fuzzy)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Scoring {
pub match_score: u16,
pub mismatch_penalty: u16,
pub gap_open_penalty: u16,
pub gap_extend_penalty: u16,
pub prefix_bonus: u16,
pub capitalization_bonus: u16,
pub matching_case_bonus: u16,
pub exact_match_bonus: u16,
pub delimiter_bonus: u16,
}
impl Default for Scoring {
fn default() -> Self {
Scoring {
match_score: MATCH_SCORE,
mismatch_penalty: MISMATCH_PENALTY,
gap_open_penalty: GAP_OPEN_PENALTY,
gap_extend_penalty: GAP_EXTEND_PENALTY,
prefix_bonus: PREFIX_BONUS,
capitalization_bonus: CAPITALIZATION_BONUS,
matching_case_bonus: MATCHING_CASE_BONUS,
exact_match_bonus: EXACT_MATCH_BONUS,
delimiter_bonus: DELIMITER_BONUS,
}
}
}
impl Scoring {
pub(crate) fn guard_against_score_overflow(&self, needle_len: usize, max_bonus_per_char: u16) {
let max_per_char = self.match_score + max_bonus_per_char;
let max_needle_len = (u16::MAX - self.prefix_bonus - self.exact_match_bonus) / max_per_char;
assert!(
needle_len <= max_needle_len as usize,
"needle too long and could overflow the u16 score: {needle_len} > {max_needle_len}"
);
}
}