use crate::types::*;
pub(crate) fn suffix_gate_literals(src: &str) -> Vec<String> {
use regex_syntax::hir::literal::{ExtractKind, Extractor};
const MIN_LEN: usize = 6;
const MAX_LITS: usize = 4;
let Ok(hir) = regex_syntax::ParserBuilder::new().build().parse(src) else {
return Vec::new();
};
let mut ex = Extractor::new();
ex.kind(ExtractKind::Suffix);
let seq = ex.extract(&hir);
if !seq.is_finite() {
return Vec::new();
}
let Some(lits) = seq.literals() else {
return Vec::new();
};
if lits.is_empty() || lits.len() > MAX_LITS {
return Vec::new();
}
let mut out = Vec::with_capacity(lits.len());
for l in lits {
if l.len() < MIN_LEN {
return Vec::new();
}
let Ok(s) = std::str::from_utf8(l.as_bytes()) else {
return Vec::new();
};
out.push(s.to_ascii_lowercase());
}
out.sort_unstable();
out.dedup();
out
}
pub(crate) fn build_confirmed_suffix_gate(
ac_map: &[CompiledPattern],
) -> (Option<aho_corasick::AhoCorasick>, Vec<Vec<u32>>) {
use std::collections::HashMap;
let mut literals: Vec<String> = Vec::new();
let mut literal_id: HashMap<String, usize> = HashMap::new();
let mut per_pattern: Vec<Vec<u32>> = vec![Vec::new(); ac_map.len()];
let mut src_cache: HashMap<&str, Vec<String>> = HashMap::new();
for (i, p) in ac_map.iter().enumerate() {
let src = p.regex.as_str();
let lits = src_cache
.entry(src)
.or_insert_with(|| suffix_gate_literals(src));
for lit in lits.iter() {
let id = *literal_id.entry(lit.clone()).or_insert_with(|| {
literals.push(lit.clone());
literals.len() - 1
});
per_pattern[i].push(id as u32);
}
}
if literals.is_empty() {
return (None, per_pattern);
}
let ac = match aho_corasick::AhoCorasickBuilder::new()
.match_kind(aho_corasick::MatchKind::Standard)
.ascii_case_insensitive(true)
.build(&literals)
{
Ok(ac) => Some(ac),
Err(error) => {
tracing::warn!(
literals = literals.len(),
%error,
"confirmed-pass suffix-gate Aho-Corasick build failed; suffix-gate optimization disabled (recall preserved)"
);
None
}
};
(ac, per_pattern)
}