use std::collections::HashMap;
use std::sync::{Arc, OnceLock, RwLock};
pub type Id = u32;
pub const EMPTY: Id = 0;
struct Table {
names: Vec<Arc<str>>,
ids: HashMap<Arc<str>, Id>,
}
fn table() -> &'static RwLock<Table> {
static TABLE: OnceLock<RwLock<Table>> = OnceLock::new();
TABLE.get_or_init(|| {
let empty: Arc<str> = Arc::from("");
RwLock::new(Table {
names: vec![empty.clone()],
ids: HashMap::from([(empty, EMPTY)]),
})
})
}
pub fn intern(name: &str) -> Id {
let lock = table();
if let Some(&id) = lock.read().expect("symbol table").ids.get(name) {
return id;
}
let mut t = lock.write().expect("symbol table");
if let Some(&id) = t.ids.get(name) {
return id;
}
let id = Id::try_from(t.names.len()).expect("symbol table index");
let shared: Arc<str> = Arc::from(name);
t.names.push(shared.clone());
t.ids.insert(shared, id);
id
}
pub fn name(id: Id) -> Arc<str> {
table()
.read()
.expect("symbol table")
.names
.get(id as usize)
.cloned()
.unwrap_or_else(|| Arc::from(""))
}
pub fn names(ids: &[Id]) -> Vec<Arc<str>> {
let t = table().read().expect("symbol table");
ids.iter()
.map(|&id| t.names.get(id as usize).cloned().unwrap_or_else(|| Arc::from("")))
.collect()
}
pub fn interned() -> usize {
table().read().expect("symbol table").names.len()
}
pub fn cmp(a: Id, b: Id) -> std::cmp::Ordering {
if a == b {
return std::cmp::Ordering::Equal;
}
let t = table().read().expect("symbol table");
let of = |id: Id| t.names.get(id as usize).map(Arc::as_ref).unwrap_or("");
of(a).cmp(of(b))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_empty_name_is_id_zero() {
assert_eq!(intern(""), EMPTY);
assert_eq!(&*name(EMPTY), "");
}
#[test]
fn the_same_text_interns_to_the_same_id() {
let a = intern("libjay-test-alpha");
let b = intern("libjay-test-alpha");
assert_eq!(a, b);
assert_ne!(a, intern("libjay-test-beta"));
assert_eq!(&*name(a), "libjay-test-alpha");
}
#[test]
fn symbols_order_by_text_not_by_when_they_were_interned() {
let z = intern("libjay-test-zzz");
let a = intern("libjay-test-aaa");
assert!(z > a || a > z);
assert_eq!(cmp(z, a), std::cmp::Ordering::Greater);
}
}