use super::rank::rare_byte_offsets;
use crate::prefilter::algo::load_window;
use crate::prefilter::backend::{Backend, BitMaskOps};
use crate::prefilter::{UnicodeChar, case_needle, case_needle_unicode};
use crate::{Config, Match, MatchIndices, Matching, Scoring};
#[derive(Debug, Clone)]
pub(crate) struct LiteralImpl<B: Backend> {
mode: Matching,
scoring: Scoring,
needle_len: usize,
needle_ascii: Vec<(u8, u8)>,
needle_unicode: Vec<UnicodeChar>,
seed_a_off: usize,
seed_b_off: usize,
seed_a: (B::Chunk, B::Chunk),
seed_b: (B::Chunk, B::Chunk),
}
impl<B: Backend> LiteralImpl<B> {
#[inline(always)]
pub(crate) unsafe fn new(needle: &str, config: &Config) -> Self {
Self::guard_against_score_overflow(needle.len(), &config.scoring);
let case_sensitive = config.casing.respects_case_for(needle);
let unicode = config.unicode.respects_unicode_for(needle);
let needle_ascii = case_needle(needle.as_bytes(), case_sensitive);
let needle_unicode = case_needle_unicode(needle, case_sensitive);
let seed_bytes: Vec<(u8, u8)> = if unicode {
needle_unicode
.iter()
.flat_map(|c| (0..c.len).map(|i| (c.chars[i], c.flipped_chars[i])))
.collect()
} else {
needle_ascii.clone()
};
let (seed_a_off, seed_b_off) = if seed_bytes.len() >= 2 {
rare_byte_offsets(needle.as_bytes())
} else {
(0, 0)
};
let splat = |off: usize| {
let (orig, flipped) = seed_bytes.get(off).copied().unwrap_or_default();
unsafe { (B::splat(orig), B::splat(flipped)) }
};
let seed_a = splat(seed_a_off);
let seed_b = splat(seed_b_off);
Self {
mode: config.matching,
scoring: config.scoring.clone(),
needle_len: needle.len(),
needle_ascii,
needle_unicode,
seed_a_off,
seed_b_off,
seed_a,
seed_b,
}
}
pub(crate) fn is_available() -> bool {
B::is_available()
}
#[inline(always)]
pub(super) unsafe fn match_list_impl<const UNICODE: bool, H: AsRef<str>>(
&self,
haystacks: &[H],
haystack_index_offset: u32,
matches: &mut Vec<Match>,
) {
for (index, haystack) in (haystack_index_offset..).zip(haystacks.iter()) {
if let Some(m) = unsafe { self.match_one_impl::<UNICODE, &H>(haystack, index) } {
matches.push(m);
}
}
}
#[inline(always)]
pub(super) unsafe fn match_one_impl<const UNICODE: bool, H: AsRef<str>>(
&self,
haystack: H,
index: u32,
) -> Option<Match> {
let haystack = haystack.as_ref().as_bytes();
let (pos, score) = unsafe { self.find::<UNICODE>(haystack) }?;
let exact = pos == 0 && self.needle_len == haystack.len();
Some(Match {
index,
score,
exact,
#[cfg(feature = "match_end_col")]
end_col: (pos + self.needle_len)
.saturating_sub(1)
.min(u16::MAX as usize) as u16,
})
}
#[inline(always)]
pub(super) unsafe fn match_list_indices_impl<const UNICODE: bool, H: AsRef<str>>(
&self,
haystacks: &[H],
) -> Vec<MatchIndices> {
let mut matches = vec![];
for (index, haystack) in haystacks.iter().enumerate() {
if let Some(m) =
unsafe { self.match_one_indices_impl::<UNICODE, &H>(haystack, index as u32) }
{
matches.push(m);
}
}
matches
}
#[inline(always)]
pub(super) unsafe fn match_one_indices_impl<const UNICODE: bool, H: AsRef<str>>(
&self,
haystack: H,
index: u32,
) -> Option<MatchIndices> {
let haystack = haystack.as_ref().as_bytes();
let (pos, score) = unsafe { self.find::<UNICODE>(haystack) }?;
let exact = pos == 0 && self.needle_len == haystack.len();
let indices = (pos..pos + self.needle_len)
.rev()
.map(|i| i as u32)
.collect();
Some(MatchIndices {
index,
score,
exact,
indices,
})
}
#[inline(always)]
fn matches_at<const UNICODE: bool>(&self, haystack: &[u8], pos: usize) -> bool {
if UNICODE {
let mut k = pos;
for c in &self.needle_unicode {
let bytes = &haystack[k..k + c.len];
if bytes != &c.chars[..c.len] && bytes != &c.flipped_chars[..c.len] {
return false;
}
k += c.len;
}
} else {
for (k, &(orig, flipped)) in self.needle_ascii.iter().enumerate() {
let byte = haystack[pos + k];
if byte != orig && byte != flipped {
return false;
}
}
}
true
}
#[inline(always)]
fn score_scalar(&self, haystack: &[u8], start: usize, matched_exact_case: bool) -> u16 {
let s = &self.scoring;
let mut score = s.match_score;
if matched_exact_case {
score += s.matching_case_bonus;
}
if start == 0 {
score += s.prefix_bonus;
} else {
let byte = haystack[start];
let prev = haystack[start - 1];
if byte.is_ascii_uppercase() && prev.is_ascii_lowercase() {
score += s.capitalization_bonus;
}
if is_delimiter(prev) && !is_delimiter(byte) {
score += s.delimiter_bonus;
}
}
score
}
#[inline(always)]
fn score_at<const UNICODE: bool>(&self, haystack: &[u8], pos: usize) -> u16 {
let mut score = 0u16;
if UNICODE {
let mut start = pos;
for c in &self.needle_unicode {
let matched_exact_case = haystack[start..start + c.len] == c.chars[..c.len];
score += self.score_scalar(haystack, start, matched_exact_case);
start += c.len;
}
} else {
for (k, &(orig, _)) in self.needle_ascii.iter().enumerate() {
let start = pos + k;
score += self.score_scalar(haystack, start, haystack[start] == orig);
}
}
if pos == 0 && self.needle_len == haystack.len() {
score += self.scoring.exact_match_bonus;
}
score
}
#[inline(always)]
unsafe fn find<const UNICODE: bool>(&self, haystack: &[u8]) -> Option<(usize, u16)> {
let needle_len = self.needle_len;
if haystack.len() < needle_len {
return None;
}
match self.mode {
Matching::Fuzzy => unreachable!("fuzzy matching does not use the literal backend"),
Matching::Exact => (haystack.len() == needle_len
&& self.matches_at::<UNICODE>(haystack, 0))
.then(|| (0, self.score_at::<UNICODE>(haystack, 0))),
Matching::Prefix => self
.matches_at::<UNICODE>(haystack, 0)
.then(|| (0, self.score_at::<UNICODE>(haystack, 0))),
Matching::Suffix => {
let pos = haystack.len() - needle_len;
self.matches_at::<UNICODE>(haystack, pos)
.then(|| (pos, self.score_at::<UNICODE>(haystack, pos)))
}
Matching::Substring => unsafe { self.find_substring::<UNICODE>(haystack) },
}
}
#[inline(always)]
unsafe fn find_substring<const UNICODE: bool>(&self, haystack: &[u8]) -> Option<(usize, u16)> {
let len = haystack.len();
let needle_len = self.needle_len;
debug_assert!(needle_len > 0, "empty needles are handled by the caller");
let last_start = len - needle_len + 1;
let mut best: Option<(usize, u16)> = None;
let mut start = 0usize;
while start < last_start {
let usable = last_start - start;
let valid = if usable >= B::LANES {
B::Mask::all()
} else {
B::Mask::first_n(usable)
};
let (chunk_a, _) = unsafe { load_window::<B>(haystack, start + self.seed_a_off, len) };
let hits_a = unsafe { B::occ(chunk_a, self.seed_a) }.and(valid);
if hits_a.is_zero() {
start += B::LANES;
continue;
}
let mut hits = if self.seed_a_off != self.seed_b_off {
let (chunk_b, _) =
unsafe { load_window::<B>(haystack, start + self.seed_b_off, len) };
hits_a.and(unsafe { B::occ(chunk_b, self.seed_b) })
} else {
hits_a
};
while !hits.is_zero() {
let pos = start + unsafe { B::first_hit_pos(hits) };
hits = hits.clear_through_lowest(hits);
if (!UNICODE && needle_len <= 2) || self.matches_at::<UNICODE>(haystack, pos) {
let score = self.score_at::<UNICODE>(haystack, pos);
if best.is_none_or(|(_, best_score)| score > best_score) {
best = Some((pos, score));
}
}
}
start += B::LANES;
}
best
}
#[inline(always)]
fn guard_against_score_overflow(needle_len: usize, scoring: &Scoring) {
let max_bonus_per_char =
scoring.capitalization_bonus.max(scoring.delimiter_bonus) + scoring.matching_case_bonus;
scoring.guard_against_score_overflow(needle_len, max_bonus_per_char);
}
}
#[inline(always)]
fn is_delimiter(byte: u8) -> bool {
byte <= 127 && !byte.is_ascii_alphanumeric()
}