1use std::collections::HashMap;
25use std::sync::{Arc, Mutex, OnceLock};
26
27use cljrs_gc::{StaticGcPtr, static_alloc};
28
29use crate::keyword::Keyword;
30use crate::symbol::Symbol;
31
32type KwKey = (Option<Arc<str>>, Arc<str>);
35static KEYWORD_TABLE: OnceLock<Mutex<HashMap<KwKey, StaticGcPtr<Keyword>>>> = OnceLock::new();
36
37fn kw_table() -> &'static Mutex<HashMap<KwKey, StaticGcPtr<Keyword>>> {
38 KEYWORD_TABLE.get_or_init(|| Mutex::new(HashMap::new()))
39}
40
41pub fn intern_keyword(namespace: Option<&str>, name: &str) -> StaticGcPtr<Keyword> {
48 let ns_arc: Option<Arc<str>> = namespace.map(Arc::from);
49 let name_arc: Arc<str> = Arc::from(name);
50 let key = (ns_arc.clone(), name_arc.clone());
51
52 let mut table = kw_table().lock().unwrap();
53 if let Some(existing) = table.get(&key) {
54 return existing.clone();
55 }
56 let kw = Keyword {
57 namespace: ns_arc,
58 name: name_arc,
59 };
60 let ptr = static_alloc(kw);
61 table.insert(key, ptr.clone());
62 ptr
63}
64
65type SymKey = (Option<Arc<str>>, Arc<str>, Option<Arc<str>>);
68static SYMBOL_TABLE: OnceLock<Mutex<HashMap<SymKey, StaticGcPtr<Symbol>>>> = OnceLock::new();
69
70fn sym_table() -> &'static Mutex<HashMap<SymKey, StaticGcPtr<Symbol>>> {
71 SYMBOL_TABLE.get_or_init(|| Mutex::new(HashMap::new()))
72}
73
74pub fn intern_symbol(
79 namespace: Option<&str>,
80 name: &str,
81 version: Option<&str>,
82) -> StaticGcPtr<Symbol> {
83 let ns_arc: Option<Arc<str>> = namespace.map(Arc::from);
84 let name_arc: Arc<str> = Arc::from(name);
85 let ver_arc: Option<Arc<str>> = version.map(Arc::from);
86 let key = (ns_arc.clone(), name_arc.clone(), ver_arc.clone());
87
88 let mut table = sym_table().lock().unwrap();
89 if let Some(existing) = table.get(&key) {
90 return existing.clone();
91 }
92 let sym = Symbol {
93 namespace: ns_arc,
94 name: name_arc,
95 version: ver_arc,
96 };
97 let ptr = static_alloc(sym);
98 table.insert(key, ptr.clone());
99 ptr
100}
101
102#[cfg(test)]
105mod tests {
106 use super::*;
107 use cljrs_gc::StaticGcPtr;
108
109 #[test]
110 fn keyword_intern_returns_same_ptr_for_same_name() {
111 let a = intern_keyword(None, "foo");
112 let b = intern_keyword(None, "foo");
113 assert!(
114 StaticGcPtr::ptr_eq(&a, &b),
115 "same name must return same pointer"
116 );
117 }
118
119 #[test]
120 fn keyword_intern_qualified() {
121 let a = intern_keyword(Some("clojure.core"), "map");
122 let b = intern_keyword(Some("clojure.core"), "map");
123 assert!(StaticGcPtr::ptr_eq(&a, &b));
124 assert_eq!(a.get().namespace.as_deref(), Some("clojure.core"));
125 assert_eq!(a.get().name.as_ref(), "map");
126 }
127
128 #[test]
129 fn keyword_intern_different_names_differ() {
130 let a = intern_keyword(None, "foo");
131 let b = intern_keyword(None, "bar");
132 assert!(!StaticGcPtr::ptr_eq(&a, &b));
133 }
134
135 #[test]
136 fn symbol_intern_returns_same_ptr() {
137 let a = intern_symbol(None, "foo", None);
138 let b = intern_symbol(None, "foo", None);
139 assert!(StaticGcPtr::ptr_eq(&a, &b));
140 }
141
142 #[test]
143 fn symbol_intern_versioned() {
144 let a = intern_symbol(Some("my.ns"), "myfn", Some("abc1234"));
145 let b = intern_symbol(Some("my.ns"), "myfn", Some("abc1234"));
146 assert!(StaticGcPtr::ptr_eq(&a, &b));
147 assert_eq!(a.get().version.as_deref(), Some("abc1234"));
148 }
149}