use std::num::NonZeroUsize;
use lasso::{Capacity, Rodeo, Spur};
pub type NameId = Spur;
#[derive(Debug, Default)]
pub struct NameInterner {
rodeo: Rodeo,
}
impl NameInterner {
#[must_use]
pub fn new() -> Self {
Self { rodeo: Rodeo::default() }
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
let bytes = NonZeroUsize::new(capacity.saturating_mul(32).max(1)).unwrap_or(NonZeroUsize::MIN);
Self { rodeo: Rodeo::with_capacity(Capacity::new(capacity, bytes)) }
}
#[inline]
pub fn intern(&mut self, name: &str) -> NameId {
debug_assert!(!name.is_empty(), "must not intern an empty string");
self.rodeo.get_or_intern(name)
}
#[inline]
#[must_use]
pub fn resolve(&self, id: NameId) -> &str {
self.rodeo.resolve(&id)
}
#[inline]
#[must_use]
pub fn get(&self, name: &str) -> Option<NameId> {
self.rodeo.get(name)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn intern_and_resolve() {
let mut interner = NameInterner::new();
let id = interner.intern("x1");
assert_eq!(interner.resolve(id), "x1");
}
#[test]
fn idempotent_interning() {
let mut interner = NameInterner::new();
let id1 = interner.intern("x1");
let id2 = interner.intern("x1");
assert_eq!(id1, id2);
}
#[test]
fn distinct_names_get_distinct_ids() {
let mut interner = NameInterner::new();
let id1 = interner.intern("x1");
let id2 = interner.intern("x2");
assert_ne!(id1, id2);
}
#[test]
fn get_returns_none_for_unknown() {
let interner = NameInterner::new();
assert!(interner.get("unknown").is_none());
}
#[test]
fn get_returns_some_for_known() {
let mut interner = NameInterner::new();
let id = interner.intern("x1");
assert_eq!(interner.get("x1"), Some(id));
}
#[test]
fn with_capacity_works() {
let mut interner = NameInterner::with_capacity(100);
let id = interner.intern("x1");
assert_eq!(interner.resolve(id), "x1");
}
#[test]
#[should_panic(expected = "must not intern an empty string")]
fn empty_string_panics() {
let mut interner = NameInterner::new();
interner.intern("");
}
}