use std::cmp::max;
use crate::chars::{Char, CharClass};
use crate::{Config, Matcher};
pub(crate) const SCORE_MATCH: u16 = 16;
pub(crate) const PENALTY_GAP_START: u16 = 3;
pub(crate) const PENALTY_GAP_EXTENSION: u16 = 1;
pub(crate) const PREFIX_BONUS_SCALE: u16 = 2;
pub(crate) const MAX_PREFIX_BONUS: u16 = BONUS_BOUNDARY;
pub(crate) const BONUS_BOUNDARY: u16 = SCORE_MATCH / 2;
pub(crate) const BONUS_CAMEL123: u16 = BONUS_BOUNDARY - PENALTY_GAP_START;
pub(crate) const BONUS_NON_WORD: u16 = BONUS_BOUNDARY;
pub(crate) const BONUS_CONSECUTIVE: u16 = PENALTY_GAP_START + PENALTY_GAP_EXTENSION;
pub(crate) const BONUS_FIRST_CHAR_MULTIPLIER: u16 = 2;
impl Config {
#[inline]
pub(crate) fn bonus_for(&self, prev_class: CharClass, class: CharClass) -> u16 {
if class > CharClass::Delimiter {
match prev_class {
CharClass::Whitespace => return self.bonus_boundary_white,
CharClass::Delimiter => return self.bonus_boundary_delimiter,
CharClass::NonWord => return BONUS_BOUNDARY,
_ => (),
}
}
if prev_class == CharClass::Lower && class == CharClass::Upper
|| prev_class != CharClass::Number && class == CharClass::Number
{
BONUS_CAMEL123
} else if class == CharClass::Whitespace {
self.bonus_boundary_white
} else if class == CharClass::NonWord {
return BONUS_NON_WORD;
} else {
0
}
}
}
impl Matcher {
#[inline(always)]
pub(crate) fn bonus_for(&self, prev_class: CharClass, class: CharClass) -> u16 {
self.config.bonus_for(prev_class, class)
}
pub(crate) fn calculate_score<const INDICES: bool, H: Char + PartialEq<N>, N: Char>(
&mut self,
haystack: &[H],
needle: &[N],
start: usize,
end: usize,
indices: &mut Vec<u32>,
) -> u16 {
if INDICES {
indices.reserve(needle.len());
}
let mut prev_class = start
.checked_sub(1)
.map(|i| haystack[i].char_class(&self.config))
.unwrap_or(self.config.initial_char_class);
let mut needle_iter = needle.iter();
let mut needle_char = *needle_iter.next().unwrap();
let mut in_gap = false;
let mut consecutive = 1;
if INDICES {
indices.push(start as u32)
}
let class = haystack[start].char_class(&self.config);
let mut first_bonus = self.bonus_for(prev_class, class);
let mut score = SCORE_MATCH + first_bonus * BONUS_FIRST_CHAR_MULTIPLIER;
prev_class = class;
needle_char = *needle_iter.next().unwrap_or(&needle_char);
for (i, c) in haystack[start + 1..end].iter().enumerate() {
let (c, class) = c.char_class_and_normalize(&self.config);
if c == needle_char {
if INDICES {
indices.push(i as u32 + start as u32 + 1)
}
let mut bonus = self.bonus_for(prev_class, class);
if consecutive != 0 {
if bonus >= BONUS_BOUNDARY && bonus > first_bonus {
first_bonus = bonus
}
bonus = max(max(bonus, first_bonus), BONUS_CONSECUTIVE);
} else {
first_bonus = bonus;
}
score += SCORE_MATCH + bonus;
in_gap = false;
consecutive += 1;
if let Some(&next) = needle_iter.next() {
needle_char = next;
}
} else {
let penalty = if in_gap {
PENALTY_GAP_EXTENSION
} else {
PENALTY_GAP_START
};
score = score.saturating_sub(penalty);
in_gap = true;
consecutive = 0;
}
prev_class = class;
}
if self.config.prefer_prefix {
if start != 0 {
let penalty = PENALTY_GAP_START
+ PENALTY_GAP_START * (start - 1).min(u16::MAX as usize) as u16;
score += MAX_PREFIX_BONUS.saturating_sub(penalty / PREFIX_BONUS_SCALE);
} else {
score += MAX_PREFIX_BONUS;
}
}
score
}
}