1use std::collections::HashMap;
21use std::sync::{Arc, OnceLock, RwLock};
22
23pub type Id = u32;
26
27pub const EMPTY: Id = 0;
29
30struct Table {
31 names: Vec<Arc<str>>,
32 ids: HashMap<Arc<str>, Id>,
33}
34
35fn table() -> &'static RwLock<Table> {
36 static TABLE: OnceLock<RwLock<Table>> = OnceLock::new();
37 TABLE.get_or_init(|| {
38 let empty: Arc<str> = Arc::from("");
39 RwLock::new(Table {
40 names: vec![empty.clone()],
41 ids: HashMap::from([(empty, EMPTY)]),
42 })
43 })
44}
45
46pub fn intern(name: &str) -> Id {
51 let lock = table();
52 if let Some(&id) = lock.read().expect("symbol table").ids.get(name) {
53 return id;
54 }
55 let mut t = lock.write().expect("symbol table");
56 if let Some(&id) = t.ids.get(name) {
58 return id;
59 }
60 let id = Id::try_from(t.names.len()).expect("symbol table index");
61 let shared: Arc<str> = Arc::from(name);
62 t.names.push(shared.clone());
63 t.ids.insert(shared, id);
64 id
65}
66
67pub fn name(id: Id) -> Arc<str> {
70 table()
71 .read()
72 .expect("symbol table")
73 .names
74 .get(id as usize)
75 .cloned()
76 .unwrap_or_else(|| Arc::from(""))
77}
78
79pub fn names(ids: &[Id]) -> Vec<Arc<str>> {
82 let t = table().read().expect("symbol table");
83 ids.iter()
84 .map(|&id| t.names.get(id as usize).cloned().unwrap_or_else(|| Arc::from("")))
85 .collect()
86}
87
88pub fn interned() -> usize {
91 table().read().expect("symbol table").names.len()
92}
93
94pub fn cmp(a: Id, b: Id) -> std::cmp::Ordering {
96 if a == b {
97 return std::cmp::Ordering::Equal;
98 }
99 let t = table().read().expect("symbol table");
100 let of = |id: Id| t.names.get(id as usize).map(Arc::as_ref).unwrap_or("");
101 of(a).cmp(of(b))
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn the_empty_name_is_id_zero() {
110 assert_eq!(intern(""), EMPTY);
111 assert_eq!(&*name(EMPTY), "");
112 }
113
114 #[test]
115 fn the_same_text_interns_to_the_same_id() {
116 let a = intern("libjay-test-alpha");
117 let b = intern("libjay-test-alpha");
118 assert_eq!(a, b);
119 assert_ne!(a, intern("libjay-test-beta"));
120 assert_eq!(&*name(a), "libjay-test-alpha");
121 }
122
123 #[test]
124 fn symbols_order_by_text_not_by_when_they_were_interned() {
125 let z = intern("libjay-test-zzz");
127 let a = intern("libjay-test-aaa");
128 assert!(z > a || a > z);
129 assert_eq!(cmp(z, a), std::cmp::Ordering::Greater);
130 }
131}