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 {
Self {
rodeo: Rodeo::with_capacity(Capacity::new(
capacity,
NonZeroUsize::new(capacity.saturating_mul(32).max(1)).expect("capacity overflow"),
)),
}
}
#[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]
pub fn intern_static(&mut self, name: &'static str) -> NameId {
debug_assert!(!name.is_empty(), "must not intern an empty string");
self.rodeo.get_or_intern_static(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)
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.rodeo.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.rodeo.is_empty()
}
#[must_use]
pub fn into_resolver(self) -> NameResolver {
NameResolver { resolver: self.rodeo.into_resolver() }
}
}
#[derive(Debug)]
pub struct NameResolver {
resolver: lasso::RodeoResolver,
}
impl NameResolver {
#[inline]
#[must_use]
pub fn resolve(&self, id: NameId) -> &str {
self.resolver.resolve(&id)
}
}
#[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);
assert_eq!(interner.len(), 1);
}
#[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);
assert_eq!(interner.len(), 2);
}
#[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 into_resolver_works() {
let mut interner = NameInterner::new();
let id = interner.intern("x1");
let resolver = interner.into_resolver();
assert_eq!(resolver.resolve(id), "x1");
}
#[test]
fn with_capacity_works() {
let mut interner = NameInterner::with_capacity(100);
let id = interner.intern("x1");
assert_eq!(interner.resolve(id), "x1");
}
#[test]
fn intern_static_works() {
let mut interner = NameInterner::new();
let id = interner.intern_static("static_name");
assert_eq!(interner.resolve(id), "static_name");
}
#[test]
#[should_panic(expected = "must not intern an empty string")]
fn empty_string_panics() {
let mut interner = NameInterner::new();
interner.intern("");
}
}