use std::{cell::RefCell, hash::BuildHasher, num::NonZeroUsize, ops::Index};
use hashbrown::{HashTable, hash_table::Entry};
use rustc_hash::FxBuildHasher;
use crate::{
arena::InternerArena,
istr::{Istr, IstrRepr},
};
#[derive(Clone, Copy)]
struct Metadata<I: IstrRepr> {
interned: Istr<I>,
hash: u64,
}
struct Lookup<I: IstrRepr> {
random_state: FxBuildHasher,
table: HashTable<Metadata<I>>,
}
pub struct Interner<I: IstrRepr = NonZeroUsize> {
lookup: RefCell<Lookup<I>>,
arena: InternerArena,
}
impl<I: IstrRepr> Default for Interner<I> {
fn default() -> Self {
Self {
lookup: RefCell::new(Lookup {
random_state: FxBuildHasher::default(),
table: HashTable::default(),
}),
arena: InternerArena::default(),
}
}
}
impl Interner {
#[inline]
pub fn new() -> Self {
Self::default()
}
}
impl<I: IstrRepr> Interner<I> {
#[inline]
pub fn with_istr_repr() -> Self {
Self::default()
}
#[inline]
pub fn intern(&self, key: &str) -> Istr<I> {
self.try_intern(key).expect("too many interned strings")
}
pub fn try_intern(&self, key: &str) -> Option<Istr<I>> {
let mut lookup = self.lookup.borrow_mut();
let hash = lookup.random_state.hash_one(key);
let entry = lookup.table.entry(
hash,
|metadata| self.arena.get(metadata.interned.repr.to_index()) == Some(key),
|metadata| metadata.hash,
);
let interned = match entry {
Entry::Occupied(entry) => entry.get().interned,
Entry::Vacant(entry) => {
let index = self.arena.push_str(key);
let interned = Istr {
repr: I::from_index(index)?,
};
entry.insert(Metadata { interned, hash });
interned
}
};
Some(interned)
}
pub fn get_interned(&self, key: &str) -> Option<Istr<I>> {
let lookup = self.lookup.borrow();
let hash = lookup.random_state.hash_one(key);
lookup
.table
.find(hash, |metadata| {
self.arena.get(metadata.interned.repr.to_index()) == Some(key)
})
.map(|metadata| metadata.interned)
}
#[inline]
pub fn get_str(&self, interned: Istr<I>) -> Option<&str> {
self.arena.get(interned.repr.to_index())
}
}
impl<I: IstrRepr> Index<Istr<I>> for Interner<I> {
type Output = str;
#[inline]
fn index(&self, interned: Istr<I>) -> &Self::Output {
self.get_str(interned).expect("string not in interner")
}
}
#[test]
fn test_interner() {
let interner = Interner::new();
for n in 0..100 {
let s = n.to_string();
let a = interner.intern(&s);
let b = interner.intern(&s);
assert_eq!(a, b);
assert_eq!(interner.get_str(a), Some(s.as_str()));
}
}