use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};
use cljrs_gc::{StaticGcPtr, static_alloc};
use crate::keyword::Keyword;
use crate::symbol::Symbol;
type KwKey = (Option<Arc<str>>, Arc<str>);
static KEYWORD_TABLE: OnceLock<Mutex<HashMap<KwKey, StaticGcPtr<Keyword>>>> = OnceLock::new();
fn kw_table() -> &'static Mutex<HashMap<KwKey, StaticGcPtr<Keyword>>> {
KEYWORD_TABLE.get_or_init(|| Mutex::new(HashMap::new()))
}
pub fn intern_keyword(namespace: Option<&str>, name: &str) -> StaticGcPtr<Keyword> {
let ns_arc: Option<Arc<str>> = namespace.map(Arc::from);
let name_arc: Arc<str> = Arc::from(name);
let key = (ns_arc.clone(), name_arc.clone());
let mut table = kw_table().lock().unwrap();
if let Some(existing) = table.get(&key) {
return existing.clone();
}
let kw = Keyword {
namespace: ns_arc,
name: name_arc,
};
let ptr = static_alloc(kw);
table.insert(key, ptr.clone());
ptr
}
type SymKey = (Option<Arc<str>>, Arc<str>, Option<Arc<str>>);
static SYMBOL_TABLE: OnceLock<Mutex<HashMap<SymKey, StaticGcPtr<Symbol>>>> = OnceLock::new();
fn sym_table() -> &'static Mutex<HashMap<SymKey, StaticGcPtr<Symbol>>> {
SYMBOL_TABLE.get_or_init(|| Mutex::new(HashMap::new()))
}
pub fn intern_symbol(
namespace: Option<&str>,
name: &str,
version: Option<&str>,
) -> StaticGcPtr<Symbol> {
let ns_arc: Option<Arc<str>> = namespace.map(Arc::from);
let name_arc: Arc<str> = Arc::from(name);
let ver_arc: Option<Arc<str>> = version.map(Arc::from);
let key = (ns_arc.clone(), name_arc.clone(), ver_arc.clone());
let mut table = sym_table().lock().unwrap();
if let Some(existing) = table.get(&key) {
return existing.clone();
}
let sym = Symbol {
namespace: ns_arc,
name: name_arc,
version: ver_arc,
};
let ptr = static_alloc(sym);
table.insert(key, ptr.clone());
ptr
}
#[cfg(test)]
mod tests {
use super::*;
use cljrs_gc::StaticGcPtr;
#[test]
fn keyword_intern_returns_same_ptr_for_same_name() {
let a = intern_keyword(None, "foo");
let b = intern_keyword(None, "foo");
assert!(
StaticGcPtr::ptr_eq(&a, &b),
"same name must return same pointer"
);
}
#[test]
fn keyword_intern_qualified() {
let a = intern_keyword(Some("clojure.core"), "map");
let b = intern_keyword(Some("clojure.core"), "map");
assert!(StaticGcPtr::ptr_eq(&a, &b));
assert_eq!(a.get().namespace.as_deref(), Some("clojure.core"));
assert_eq!(a.get().name.as_ref(), "map");
}
#[test]
fn keyword_intern_different_names_differ() {
let a = intern_keyword(None, "foo");
let b = intern_keyword(None, "bar");
assert!(!StaticGcPtr::ptr_eq(&a, &b));
}
#[test]
fn symbol_intern_returns_same_ptr() {
let a = intern_symbol(None, "foo", None);
let b = intern_symbol(None, "foo", None);
assert!(StaticGcPtr::ptr_eq(&a, &b));
}
#[test]
fn symbol_intern_versioned() {
let a = intern_symbol(Some("my.ns"), "myfn", Some("abc1234"));
let b = intern_symbol(Some("my.ns"), "myfn", Some("abc1234"));
assert!(StaticGcPtr::ptr_eq(&a, &b));
assert_eq!(a.get().version.as_deref(), Some("abc1234"));
}
}