1use std::cell::{Cell, RefCell};
2use std::collections::HashMap;
3use std::rc::{Rc, Weak};
4
5use crate::lang::protocol::{
6 HashType, IDisplay, IHash, IHashCached, IMetadata, INamespaced, IObjType, MetaType, ObjType,
7};
8
9thread_local! {
10 static INTERNED: RefCell<HashMap<String, Weak<Data>>> = RefCell::new(HashMap::new());
11}
12
13#[derive(Debug)]
14struct Data {
15 namespace: Option<String>,
16 name: String,
17 full: String,
18 hash: Cell<u64>,
19}
20
21#[derive(Debug, Clone)]
22pub struct Symbol {
23 data: Rc<Data>,
24 metadata: Option<Rc<crate::lang::data::Metadata>>,
25}
26
27impl Symbol {
28 pub fn create(namespace: Option<&str>, name: &str) -> Self {
29 let full = namespace
30 .map(|ns| format!("{ns}/{name}"))
31 .unwrap_or_else(|| name.into());
32 Self::intern(namespace, name, &full)
33 }
34
35 pub fn parse(full: &str) -> Self {
36 let slash = if full == "/" {
37 None
38 } else {
39 full.find(char::from(47))
40 };
41 Self::intern(
42 slash.map(|i| &full[..i]),
43 slash.map(|i| &full[i + 1..]).unwrap_or(full),
44 full,
45 )
46 }
47
48 fn intern(namespace: Option<&str>, name: &str, full: &str) -> Self {
49 INTERNED.with(|cache| {
50 if let Some(value) = cache.borrow().get(full).and_then(Weak::upgrade) {
51 return Self {
52 data: value,
53 metadata: None,
54 };
55 }
56 let data = Rc::new(Data {
57 namespace: namespace.map(str::to_owned),
58 name: name.into(),
59 full: full.into(),
60 hash: Cell::new(0),
61 });
62 cache.borrow_mut().insert(full.into(), Rc::downgrade(&data));
63 let symbol = Self {
64 data,
65 metadata: None,
66 };
67 symbol.hash_put(symbol.hash());
70 symbol
71 })
72 }
73
74 pub fn as_str(&self) -> &str {
75 &self.data.full
76 }
77 pub fn same_identity(&self, other: &Self) -> bool {
78 Rc::ptr_eq(&self.data, &other.data)
79 }
80}
81
82impl INamespaced for Symbol {
83 fn get_name(&self) -> &str {
84 &self.data.name
85 }
86 fn get_namespace(&self) -> Option<&str> {
87 self.data.namespace.as_deref()
88 }
89}
90impl IMetadata for Symbol {
91 type Metadata = Rc<crate::lang::data::Metadata>;
92
93 fn meta(&self) -> Option<&Self::Metadata> {
94 self.metadata.as_ref()
95 }
96
97 fn with_meta(&self, metadata: Option<Self::Metadata>) -> Self {
98 Self {
99 data: self.data.clone(),
100 metadata,
101 }
102 }
103
104 fn metatype(&self) -> MetaType {
105 MetaType::String
106 }
107}
108impl IDisplay for Symbol {
109 fn display(&self) -> String {
110 self.data.full.clone()
111 }
112}
113impl IObjType for Symbol {
114 fn obj_type(&self) -> ObjType {
115 ObjType::Symbol
116 }
117}
118impl IHash for Symbol {
119 fn hash_calc(&self, hash_type: HashType) -> u64 {
120 crate::lang::hash::hash_string_type(
124 hash_type,
125 &format!(
126 "{}|hara.lang.data.Symbol<{}>",
127 self.hash_seed(),
128 self.display()
129 ),
130 ) as u64
131 }
132 fn hash_get(&self) -> u64 {
133 self.hash_cached()
134 }
135 fn hash_get_as(&self, hash_type: HashType) -> u64 {
136 self.hash_cached_as(hash_type)
137 }
138}
139impl IHashCached for Symbol {
140 fn hash_current(&self) -> u64 {
141 self.data.hash.get()
142 }
143 fn hash_put(&self, hash: u64) {
144 self.data.hash.set(hash);
145 }
146}
147impl crate::lang::hash::JavaHash for Symbol {
148 fn java_hash(&self, hash_type: HashType) -> i64 {
149 self.hash_calc(hash_type) as i64
150 }
151}
152impl PartialEq for Symbol {
153 fn eq(&self, other: &Self) -> bool {
154 self.data.full == other.data.full
155 }
156}
157impl Eq for Symbol {}
158impl std::hash::Hash for Symbol {
159 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
160 self.data.full.hash(state);
161 }
162}
163
164impl From<&str> for Symbol {
165 fn from(value: &str) -> Self {
166 Self::parse(value)
167 }
168}
169impl From<String> for Symbol {
170 fn from(value: String) -> Self {
171 Self::parse(&value)
172 }
173}
174impl std::fmt::Display for Symbol {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 f.write_str(self.as_str())
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::Symbol;
183 use crate::lang::protocol::{
184 HashType, IDisplay, IHash, IMetadata, INamespaced, IObjType, MetaType, ObjType,
185 };
186
187 #[test]
188 fn matches_java_namespace_and_interning() {
189 let first = Symbol::parse("hara/name");
190 let second = Symbol::create(Some("hara"), "name");
191 assert!(first.same_identity(&second));
192 assert_eq!(first.get_namespace(), Some("hara"));
193 assert_eq!(first.get_name(), "name");
194 assert_eq!(first.display(), "hara/name");
195 assert_eq!(first.obj_type(), ObjType::Symbol);
196 assert_eq!(first.hash_seed(), "::SYMBOL");
197 assert_eq!(first.metatype(), MetaType::String);
198 assert_eq!(first.hash_get(), first.hash());
199 assert_eq!(first.hash_type(), HashType::Rapid);
200 assert_eq!(Symbol::parse("/").get_namespace(), None);
201 let nested = Symbol::parse("a/b/c");
202 assert_eq!(nested.get_namespace(), Some("a"));
203 assert_eq!(nested.get_name(), "b/c");
204 assert_eq!(nested.as_str(), "a/b/c");
205
206 let multipart = Symbol::create(Some("constructor/namespace"), "name");
207 assert_eq!(multipart.as_str(), "constructor/namespace/name");
208 assert_eq!(multipart.get_namespace(), Some("constructor/namespace"));
209 assert_eq!(multipart.get_name(), "name");
210 let interned = Symbol::parse("constructor/namespace/name");
211 assert!(multipart.same_identity(&interned));
212 assert_eq!(interned.get_namespace(), Some("constructor/namespace"));
213
214 let documented = first.with_meta(Some(crate::lang::data::Metadata::document("doc")));
215 assert_eq!(documented.meta().and_then(|value| value.doc()), Some("doc"));
216 assert_eq!(documented, first);
217 assert!(documented.same_identity(&first));
218 }
219
220 #[test]
221 fn intern_precomputes_the_hash() {
222 use crate::lang::protocol::IHashCached;
223 let symbol = Symbol::parse("precomputed/hash");
226 let current = symbol.hash_current();
227 assert_ne!(current, 0);
228 assert_eq!(current, symbol.hash_calc(HashType::Rapid));
229 assert_eq!(symbol.hash_get(), current);
230 assert_eq!(symbol.hash_get_as(HashType::Rapid), current);
231 assert_eq!(
232 symbol.hash_get_as(HashType::Murmur3),
233 symbol.hash_calc(HashType::Murmur3)
234 );
235 let documented = symbol.with_meta(Some(crate::lang::data::Metadata::document("doc")));
237 assert_eq!(documented.hash_current(), current);
238 assert_eq!(documented.hash_get(), current);
239 }
240}