use parking_lot::RwLock;
use rustc_hash::FxHashMap;
use smol_str::SmolStr;
use std::fmt;
#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Name(u32);
impl Name {
#[inline]
pub(crate) const fn from_raw(index: u32) -> Self {
Self(index)
}
#[inline]
pub const fn index(self) -> u32 {
self.0
}
}
impl fmt::Debug for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Name({})", self.0)
}
}
#[derive(Default)]
pub struct Interner {
inner: RwLock<InternerInner>,
}
#[derive(Default)]
struct InternerInner {
map: FxHashMap<SmolStr, u32>,
strings: Vec<SmolStr>,
}
impl Interner {
pub fn new() -> Self {
Self::default()
}
pub fn intern(&self, s: &str) -> Name {
{
let inner = self.inner.read();
if let Some(&index) = inner.map.get(s) {
return Name::from_raw(index);
}
}
let mut inner = self.inner.write();
if let Some(&index) = inner.map.get(s) {
return Name::from_raw(index);
}
let smol = SmolStr::new(s);
let index = inner.strings.len() as u32;
inner.strings.push(smol.clone());
inner.map.insert(smol, index);
Name::from_raw(index)
}
pub fn lookup(&self, name: Name) -> Option<SmolStr> {
let inner = self.inner.read();
inner.strings.get(name.0 as usize).cloned()
}
pub fn get(&self, name: Name) -> SmolStr {
self.lookup(name).expect("Name not found in interner")
}
pub fn len(&self) -> usize {
self.inner.read().strings.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl fmt::Debug for Interner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let inner = self.inner.read();
f.debug_struct("Interner")
.field("count", &inner.strings.len())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_intern_same_string() {
let interner = Interner::new();
let a = interner.intern("hello");
let b = interner.intern("hello");
assert_eq!(a, b);
assert_eq!(interner.len(), 1);
}
#[test]
fn test_intern_different_strings() {
let interner = Interner::new();
let a = interner.intern("hello");
let b = interner.intern("world");
assert_ne!(a, b);
assert_eq!(interner.len(), 2);
}
#[test]
fn test_lookup() {
let interner = Interner::new();
let name = interner.intern("test");
let s = interner.get(name);
assert_eq!(s.as_str(), "test");
}
#[test]
fn test_name_size() {
assert_eq!(std::mem::size_of::<Name>(), 4);
}
}