use std::collections::HashMap;
use std::hash::{BuildHasherDefault, Hasher};
pub(crate) const LINEAR_STRINGS: usize = 16;
const RECENT: usize = 64;
pub(crate) struct Interner {
spans: Vec<(usize, usize)>,
index: Option<Box<Index>>,
}
struct Index {
by_hash: HashMap<u64, u32, BuildHasherDefault<Fx>>,
recent: [u32; RECENT],
}
impl Interner {
pub(crate) fn new() -> Self {
Interner {
spans: Vec::with_capacity(LINEAR_STRINGS),
index: None,
}
}
pub(crate) fn clear(&mut self) {
self.spans.clear();
if let Some(index) = &mut self.index {
index.by_hash.clear();
index.recent = [0; RECENT];
}
}
pub(crate) fn find(&mut self, buf: &[u8], s: &str) -> Option<usize> {
let is = |&(at, len): &(usize, usize)| len == s.len() && &buf[at..at + len] == s.as_bytes();
let Some(index) = &mut self.index else {
return self.spans.iter().position(is);
};
let slot = recent_slot(s);
let recent = index.recent[slot] as usize;
if recent > 0 && is(&self.spans[recent - 1]) {
return Some(recent - 1);
}
let found = index
.by_hash
.get(&hash(s.as_bytes()))
.map(|&i| i as usize)
.filter(|&i| is(&self.spans[i]))?;
index.recent[slot] = found as u32 + 1;
Some(found)
}
pub(crate) fn add(&mut self, buf: &[u8], s: &str, at: usize) -> usize {
let i = self.spans.len();
self.spans.push((at, s.len()));
match &mut self.index {
Some(index) => {
index.by_hash.entry(hash(s.as_bytes())).or_insert(i as u32);
index.recent[recent_slot(s)] = i as u32 + 1;
}
None if self.spans.len() > LINEAR_STRINGS => {
let mut by_hash = HashMap::default();
for (n, &(at, len)) in self.spans.iter().enumerate() {
by_hash.entry(hash(&buf[at..at + len])).or_insert(n as u32);
}
self.index = Some(Box::new(Index {
by_hash,
recent: [0; RECENT],
}));
}
None => {}
}
i
}
}
fn recent_slot(s: &str) -> usize {
const ADDRESS_SHIFT: u32 = 3;
((s.as_ptr() as usize >> ADDRESS_SHIFT) ^ s.len()) & (RECENT - 1)
}
fn hash(b: &[u8]) -> u64 {
let mut h = Fx::default();
h.write(b);
h.finish()
}
#[derive(Default)]
struct Fx(u64);
const FX_SEED: u64 = 0x51_7c_c1_b7_27_22_0a_95;
const FX_ROTATE: u32 = 5;
const WORD: usize = 8;
impl Fx {
fn add(&mut self, word: u64) {
self.0 = (self.0.rotate_left(FX_ROTATE) ^ word).wrapping_mul(FX_SEED);
}
}
impl Hasher for Fx {
fn write(&mut self, bytes: &[u8]) {
let (words, rest) = bytes.as_chunks::<WORD>();
for w in words {
self.add(u64::from_le_bytes(*w));
}
if !rest.is_empty() {
let mut b = [0u8; WORD];
b[..rest.len()].copy_from_slice(rest);
self.add(u64::from_le_bytes(b));
}
}
fn write_u64(&mut self, n: u64) {
self.add(n);
}
fn finish(&self) -> u64 {
self.0
}
}