use std::collections::HashMap;
use std::hash::BuildHasherDefault;
use std::hash::DefaultHasher;
use std::sync::{Arc, RwLock};
type FxHashMap<K, V> = HashMap<K, V, BuildHasherDefault<DefaultHasher>>;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SymbolId(u32);
impl SymbolId {
pub(crate) fn new(id: u32) -> Self {
Self(id)
}
pub fn raw(self) -> u32 {
self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Symbol {
id: SymbolId,
name: Arc<str>,
}
impl Symbol {
pub fn id(&self) -> SymbolId {
self.id
}
pub fn as_str(&self) -> &str {
&self.name
}
}
pub struct SymbolTable {
strings: RwLock<Vec<Arc<str>>>,
lookup: RwLock<FxHashMap<Arc<str>, SymbolId>>,
}
impl SymbolTable {
pub fn new() -> Self {
Self {
strings: RwLock::new(Vec::new()),
lookup: RwLock::new(FxHashMap::default()),
}
}
pub fn intern(&self, s: &str) -> SymbolId {
{
let lookup = self.lookup.read().unwrap();
if let Some(&id) = lookup.get(s) {
return id;
}
}
let mut lookup = self.lookup.write().unwrap();
let mut strings = self.strings.write().unwrap();
if let Some(&id) = lookup.get(s) {
return id;
}
let arc_str: Arc<str> = Arc::from(s);
let id = SymbolId::new(strings.len() as u32);
strings.push(arc_str.clone());
lookup.insert(arc_str, id);
id
}
pub fn get(&self, id: SymbolId) -> Option<Symbol> {
let strings = self.strings.read().unwrap();
strings.get(id.0 as usize).map(|name| Symbol {
id,
name: name.clone(),
})
}
pub fn resolve(&self, id: SymbolId) -> Option<Arc<str>> {
let strings = self.strings.read().unwrap();
strings.get(id.0 as usize).cloned()
}
pub fn len(&self) -> usize {
self.strings.read().unwrap().len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl Default for SymbolTable {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_symbol_interning() {
let table = SymbolTable::new();
let id1 = table.intern("hello");
let id2 = table.intern("world");
let id3 = table.intern("hello");
assert_eq!(id1, id3);
assert_ne!(id1, id2);
assert_eq!(table.len(), 2);
}
#[test]
fn test_symbol_resolution() {
let table = SymbolTable::new();
let id = table.intern("test");
let sym = table.get(id).unwrap();
assert_eq!(sym.as_str(), "test");
assert_eq!(sym.id(), id);
}
}