use std::cell::RefCell;
use std::collections::HashMap;
use std::marker::PhantomData;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Symbol<'s>(u32, PhantomData<&'s SymbolStore>);
impl Symbol<'_> {
#[inline]
pub fn index(self) -> u32 {
self.0
}
}
impl std::fmt::Debug for Symbol<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Symbol({})", self.0)
}
}
#[derive(Default)]
pub struct SymbolStore {
inner: RefCell<StoreInner>,
}
#[derive(Default)]
struct StoreInner {
texts: Vec<Box<str>>,
index: HashMap<Box<str>, u32>,
}
impl SymbolStore {
pub fn new() -> SymbolStore {
SymbolStore::default()
}
pub fn intern<'s>(&'s self, text: &str) -> Symbol<'s> {
let mut inner = self.inner.borrow_mut();
if let Some(&i) = inner.index.get(text) {
return Symbol(i, PhantomData);
}
let i = u32::try_from(inner.texts.len())
.expect("SymbolStore overflow: more than u32::MAX distinct identifiers");
let boxed: Box<str> = text.into();
inner.index.insert(boxed.clone(), i);
inner.texts.push(boxed);
Symbol(i, PhantomData)
}
pub fn resolve<'s>(&'s self, sym: Symbol<'s>) -> &'s str {
let inner = self.inner.borrow();
let s: &str = inner
.texts
.get(sym.0 as usize)
.unwrap_or_else(|| panic!("Symbol({}) does not belong to this SymbolStore", sym.0));
unsafe { &*(s as *const str) }
}
pub fn len(&self) -> usize {
self.inner.borrow().texts.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl std::fmt::Debug for SymbolStore {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SymbolStore({} symbols)", self.len())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn intern_dedups_and_resolves() {
let store = SymbolStore::new();
let a = store.intern("foo");
let b = store.intern("bar");
let a2 = store.intern("foo");
assert_eq!(a, a2);
assert_ne!(a, b);
assert_eq!(store.resolve(a), "foo");
assert_eq!(store.resolve(b), "bar");
assert_eq!(store.len(), 2);
}
#[test]
fn indices_are_dense_and_insertion_ordered() {
let store = SymbolStore::new();
assert!(store.is_empty());
let syms: Vec<_> = ["z", "y", "x"].iter().map(|s| store.intern(s)).collect();
assert_eq!(
syms.iter().map(|s| s.index()).collect::<Vec<_>>(),
vec![0, 1, 2]
);
let mut sorted = syms.clone();
sorted.sort();
assert_eq!(sorted, syms);
let mut by_text: Vec<&str> = syms.iter().map(|&s| store.resolve(s)).collect();
by_text.sort_unstable();
assert_eq!(by_text, vec!["x", "y", "z"]);
}
#[test]
fn resolved_borrows_survive_further_interning() {
let store = SymbolStore::new();
let first = store.resolve(store.intern("first"));
for i in 0..10_000 {
store.intern(&format!("derived%{i}"));
}
assert_eq!(first, "first");
assert_eq!(store.len(), 10_001);
}
#[test]
fn mangled_keys_round_trip_verbatim() {
let store = SymbolStore::new();
for key in ["M.x", "\\cmd", "+p", "M.\\cmd", "$M.atan2", "%context"] {
assert_eq!(store.resolve(store.intern(key)), key);
}
assert_eq!(store.len(), 6);
}
#[test]
fn symbols_are_hashable_keys() {
let store = SymbolStore::new();
let set: HashSet<Symbol<'_>> = ["a", "b", "a", "c"]
.iter()
.map(|s| store.intern(s))
.collect();
assert_eq!(set.len(), 3);
assert_eq!(std::mem::size_of::<Symbol<'_>>(), 4);
}
}