use crate::fold::{fold_ascii_lowercase, fold_byte};
#[derive(Debug, Clone, Copy)]
struct ScalarPattern {
len: usize,
word: u32,
mask: u32,
}
impl Default for ScalarPattern {
#[inline]
fn default() -> Self {
Self {
len: 0,
word: 0,
mask: 0,
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct ScalarFilter {
patterns: [ScalarPattern; 16],
pattern_count: usize,
case_insensitive: bool,
}
#[inline]
pub(crate) fn pack_word(bytes: [u8; 4], len: usize) -> u32 {
let mut word = 0u32;
if len >= 1 {
word |= u32::from(bytes[0]);
}
if len >= 2 {
word |= u32::from(bytes[1]) << 8;
}
if len >= 3 {
word |= u32::from(bytes[2]) << 16;
}
if len >= 4 {
word |= u32::from(bytes[3]) << 24;
}
word
}
#[inline]
pub(crate) const fn build_mask(len: usize) -> u32 {
match len {
0 => 0,
1 => 0x0000_00FF,
2 => 0x0000_FFFF,
3 => 0x00FF_FFFF,
_ => 0xFFFF_FFFF,
}
}
#[inline]
fn fold_case_u32(word: u32) -> u32 {
let bytes = word.to_le_bytes();
u32::from_le_bytes([
fold_byte(bytes[0]),
fold_byte(bytes[1]),
fold_byte(bytes[2]),
fold_byte(bytes[3]),
])
}
#[inline]
fn load_u32_safe(block: &[u8], offset: usize) -> u32 {
let mut bytes = [0u8; 4];
let remaining = block.len().saturating_sub(offset);
let to_copy = remaining.min(4);
bytes[..to_copy].copy_from_slice(&block[offset..offset + to_copy]);
u32::from_le_bytes(bytes)
}
impl ScalarFilter {
pub(crate) const MAX_PATTERNS: usize = 16;
#[must_use]
pub(crate) fn new(prefixes: &[&[u8]], case_insensitive: bool) -> Self {
let mut ptrns = [ScalarPattern::default(); 16];
let count = prefixes.len().min(Self::MAX_PATTERNS);
for (i, &slice) in prefixes.iter().take(Self::MAX_PATTERNS).enumerate() {
let eval_len = slice.len().min(4);
let mut arr = [0u8; 4];
for j in 0..eval_len {
arr[j] = if case_insensitive {
fold_ascii_lowercase(slice[j])
} else {
slice[j]
};
}
let word = pack_word(arr, eval_len);
let mask = build_mask(eval_len);
ptrns[i] = ScalarPattern {
len: eval_len,
word,
mask,
};
}
Self {
patterns: ptrns,
pattern_count: count,
case_insensitive,
}
}
#[must_use]
#[inline]
pub(crate) fn check_64byte_block(&self, block: &[u8]) -> u64 {
let mut mask: u64 = 0;
for p_idx in 0..self.pattern_count {
let p = &self.patterns[p_idx];
if block.len() < 64 + p.len.saturating_sub(1) {
continue;
}
let pat_word = p.word;
let pat_mask = p.mask;
let ci = self.case_insensitive;
let mut i = 0;
while i + 6 < block.len() && i < 64 {
let loaded0 = unsafe {
u32::from_le_bytes([
*block.get_unchecked(i),
*block.get_unchecked(i + 1),
*block.get_unchecked(i + 2),
*block.get_unchecked(i + 3),
])
};
let loaded1 = unsafe {
u32::from_le_bytes([
*block.get_unchecked(i + 1),
*block.get_unchecked(i + 2),
*block.get_unchecked(i + 3),
*block.get_unchecked(i + 4),
])
};
let loaded2 = unsafe {
u32::from_le_bytes([
*block.get_unchecked(i + 2),
*block.get_unchecked(i + 3),
*block.get_unchecked(i + 4),
*block.get_unchecked(i + 5),
])
};
let loaded3 = unsafe {
u32::from_le_bytes([
*block.get_unchecked(i + 3),
*block.get_unchecked(i + 4),
*block.get_unchecked(i + 5),
*block.get_unchecked(i + 6),
])
};
let folded0 = if ci { fold_case_u32(loaded0) } else { loaded0 };
let folded1 = if ci { fold_case_u32(loaded1) } else { loaded1 };
let folded2 = if ci { fold_case_u32(loaded2) } else { loaded2 };
let folded3 = if ci { fold_case_u32(loaded3) } else { loaded3 };
if (folded0 ^ pat_word) & pat_mask == 0 {
mask |= 1 << i;
}
if (folded1 ^ pat_word) & pat_mask == 0 {
mask |= 1 << (i + 1);
}
if (folded2 ^ pat_word) & pat_mask == 0 {
mask |= 1 << (i + 2);
}
if (folded3 ^ pat_word) & pat_mask == 0 {
mask |= 1 << (i + 3);
}
i += 4;
}
while i < 64 {
let loaded = load_u32_safe(block, i);
let folded = if ci { fold_case_u32(loaded) } else { loaded };
if (folded ^ pat_word) & pat_mask == 0 {
mask |= 1 << i;
}
i += 1;
}
}
mask
}
}