use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use crate::symbol::Symbol;
const INITIAL_CAPACITY: usize = 16;
#[derive(Clone, Copy)]
struct Span {
start: usize,
len: usize,
}
#[derive(Clone, Copy)]
struct Slot {
hash: u32,
id: u32,
}
impl Slot {
const EMPTY: Slot = Slot { hash: 0, id: 0 };
#[inline]
fn is_empty(self) -> bool {
self.id == 0
}
}
pub struct Interner {
buf: String,
spans: Vec<Span>,
table: Vec<Slot>,
mask: usize,
}
impl Interner {
#[inline]
#[must_use]
pub fn new() -> Self {
Self {
buf: String::new(),
spans: Vec::new(),
table: Vec::new(),
mask: 0,
}
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
let mut interner = Self::new();
if capacity > 0 {
interner.spans.reserve(capacity);
let table_cap = table_capacity_for(capacity);
interner.resize_table(table_cap);
}
interner
}
pub fn intern(&mut self, s: &str) -> Symbol {
let hash = hash_bytes(s.as_bytes());
if let Some(symbol) = self.lookup(s, hash) {
return symbol;
}
self.insert_new(s, hash)
}
#[must_use]
pub fn get(&self, s: &str) -> Option<Symbol> {
self.lookup(s, hash_bytes(s.as_bytes()))
}
#[must_use]
pub fn resolve(&self, symbol: Symbol) -> Option<&str> {
let span = self.spans.get(symbol.index())?;
Some(&self.buf[span.start..span.start + span.len])
}
pub fn resolve_with<R, F>(&self, symbol: Symbol, f: F) -> Option<R>
where
F: FnOnce(&str) -> R,
{
self.resolve(symbol).map(f)
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.spans.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.spans.is_empty()
}
fn lookup(&self, s: &str, hash: u64) -> Option<Symbol> {
if self.table.is_empty() {
return None;
}
let fingerprint = hash as u32;
let mut idx = (hash as usize) & self.mask;
loop {
let slot = self.table[idx];
if slot.is_empty() {
return None;
}
if slot.hash == fingerprint && self.span_str(slot.id) == s {
return Some(Symbol::from_raw(slot.id));
}
idx = (idx + 1) & self.mask;
}
}
fn insert_new(&mut self, s: &str, hash: u64) -> Symbol {
self.reserve_one();
let span = Span {
start: self.buf.len(),
len: s.len(),
};
self.buf.push_str(s);
self.spans.push(span);
let id = id_for(self.spans.len());
self.insert_slot(Slot {
hash: hash as u32,
id,
});
Symbol::from_raw(id)
}
fn insert_slot(&mut self, slot: Slot) {
let mut idx = (slot.hash as usize) & self.mask;
while !self.table[idx].is_empty() {
idx = (idx + 1) & self.mask;
}
self.table[idx] = slot;
}
fn reserve_one(&mut self) {
let occupied_after = self.spans.len() + 1;
if self.table.is_empty() {
self.resize_table(INITIAL_CAPACITY);
} else if occupied_after * 4 > self.table.len() * 3 {
self.resize_table(self.table.len() * 2);
}
}
fn resize_table(&mut self, new_cap: usize) {
let mut table = Vec::new();
table.resize(new_cap, Slot::EMPTY);
let mask = new_cap - 1;
for (i, span) in self.spans.iter().enumerate() {
let s = &self.buf[span.start..span.start + span.len];
let hash = hash_bytes(s.as_bytes());
let id = id_for(i + 1);
let mut idx = (hash as usize) & mask;
while !table[idx].is_empty() {
idx = (idx + 1) & mask;
}
table[idx] = Slot {
hash: hash as u32,
id,
};
}
self.table = table;
self.mask = mask;
}
#[inline]
fn span_str(&self, id: u32) -> &str {
let span = self.spans[id as usize - 1];
&self.buf[span.start..span.start + span.len]
}
}
impl Default for Interner {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl crate::Lookup for Interner {
#[inline]
fn get(&self, s: &str) -> Option<Symbol> {
Interner::get(self, s)
}
#[inline]
fn resolve_with<R, F>(&self, symbol: Symbol, f: F) -> Option<R>
where
F: FnOnce(&str) -> R,
{
Interner::resolve_with(self, symbol, f)
}
#[inline]
fn len(&self) -> usize {
Interner::len(self)
}
#[inline]
fn is_empty(&self) -> bool {
Interner::is_empty(self)
}
}
impl fmt::Debug for Interner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Interner")
.field("strings", &self.spans.len())
.field("bytes", &self.buf.len())
.finish_non_exhaustive()
}
}
#[inline]
fn id_for(len: usize) -> u32 {
u32::try_from(len).unwrap_or(u32::MAX)
}
#[inline]
fn table_capacity_for(strings: usize) -> usize {
let target = strings.saturating_mul(4) / 3 + 1;
target.max(INITIAL_CAPACITY).next_power_of_two()
}
#[inline]
fn hash_bytes(bytes: &[u8]) -> u64 {
const K: u64 = 0x517c_c1b7_2722_0a95;
let mut hash = bytes.len() as u64;
let mut chunks = bytes.chunks_exact(8);
for chunk in chunks.by_ref() {
let word = u64::from_le_bytes(<[u8; 8]>::try_from(chunk).unwrap_or([0; 8]));
hash = (hash.rotate_left(5) ^ word).wrapping_mul(K);
}
let remainder = chunks.remainder();
if !remainder.is_empty() {
let mut tail = [0u8; 8];
tail[..remainder.len()].copy_from_slice(remainder);
let word = u64::from_le_bytes(tail);
hash = (hash.rotate_left(5) ^ word).wrapping_mul(K);
}
hash
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_intern_same_string_returns_same_symbol() {
let mut interner = Interner::new();
let a = interner.intern("name");
let b = interner.intern("name");
assert_eq!(a, b);
assert_eq!(interner.len(), 1);
}
#[test]
fn test_intern_distinct_strings_return_distinct_symbols() {
let mut interner = Interner::new();
let a = interner.intern("one");
let b = interner.intern("two");
assert_ne!(a, b);
assert_eq!(interner.len(), 2);
}
#[test]
fn test_resolve_roundtrips() {
let mut interner = Interner::new();
for s in ["", "a", "alpha", "a longer identifier with spaces"] {
let sym = interner.intern(s);
assert_eq!(interner.resolve(sym), Some(s));
}
}
#[test]
fn test_resolve_out_of_range_symbol_is_none() {
let mut issuer = Interner::new();
let _ = issuer.intern("a");
let high = issuer.intern("b");
let empty = Interner::new();
assert_eq!(empty.resolve(high), None);
}
#[test]
fn test_get_does_not_intern() {
let mut interner = Interner::new();
assert_eq!(interner.get("absent"), None);
assert_eq!(interner.len(), 0);
let sym = interner.intern("absent");
assert_eq!(interner.get("absent"), Some(sym));
}
#[test]
fn test_ids_are_sequential_from_one() {
let mut interner = Interner::new();
assert_eq!(interner.intern("a").as_u32(), 1);
assert_eq!(interner.intern("b").as_u32(), 2);
assert_eq!(interner.intern("a").as_u32(), 1);
assert_eq!(interner.intern("c").as_u32(), 3);
}
#[test]
fn test_growth_preserves_earlier_symbols() {
let mut interner = Interner::new();
let mut remembered = alloc::vec::Vec::new();
for i in 0..10_000 {
let s = alloc::format!("symbol_{i}");
remembered.push((interner.intern(&s), s));
}
for (sym, s) in &remembered {
assert_eq!(interner.resolve(*sym), Some(s.as_str()));
}
}
#[test]
fn test_empty_string_is_interned() {
let mut interner = Interner::new();
let empty = interner.intern("");
assert_eq!(interner.resolve(empty), Some(""));
assert_eq!(interner.intern(""), empty);
}
#[test]
fn test_unicode_roundtrips() {
let mut interner = Interner::new();
for s in ["café", "naïve", "日本語", "emoji 🦀", "Ωμέγα"] {
let sym = interner.intern(s);
assert_eq!(interner.resolve(sym), Some(s));
}
}
#[test]
fn test_with_capacity_behaves_like_new() {
let mut interner = Interner::with_capacity(64);
let sym = interner.intern("preallocated");
assert_eq!(interner.resolve(sym), Some("preallocated"));
assert_eq!(interner.len(), 1);
}
#[test]
fn test_strings_differing_only_in_trailing_byte_are_distinct() {
let mut interner = Interner::new();
let a = interner.intern("ab");
let b = interner.intern("ab\0");
assert_ne!(a, b);
assert_eq!(interner.resolve(a), Some("ab"));
assert_eq!(interner.resolve(b), Some("ab\0"));
}
#[test]
fn test_default_is_empty() {
let interner = Interner::default();
assert!(interner.is_empty());
}
#[test]
fn test_table_capacity_for_is_power_of_two_and_fits() {
for n in [0usize, 1, 12, 13, 100, 1000] {
let cap = table_capacity_for(n);
assert!(cap.is_power_of_two());
assert!(cap >= INITIAL_CAPACITY);
assert!(cap * 3 >= n.saturating_mul(4));
}
}
}