logicaffeine_base/
intern.rs1use std::collections::HashMap;
33
34#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
39pub struct Symbol(u32);
40
41impl Symbol {
42 pub const EMPTY: Symbol = Symbol(0);
44
45 pub fn index(self) -> usize {
49 self.0 as usize
50 }
51
52 pub fn from_index(i: usize) -> Symbol {
57 Symbol(i as u32)
58 }
59}
60
61impl Default for Symbol {
62 fn default() -> Self {
63 Self::EMPTY
64 }
65}
66
67#[derive(Clone)]
72pub struct Interner {
73 map: HashMap<String, Symbol>,
74 vec: Vec<String>,
75}
76
77impl Interner {
78 pub fn new() -> Self {
80 let mut interner = Interner {
81 map: HashMap::new(),
82 vec: Vec::new(),
83 };
84 interner.vec.push(String::new());
85 interner
86 }
87
88 pub fn intern(&mut self, s: &str) -> Symbol {
92 if let Some(&sym) = self.map.get(s) {
93 return sym;
94 }
95 let sym = Symbol(self.vec.len() as u32);
96 self.vec.push(s.to_string());
97 self.map.insert(s.to_string(), sym);
98 sym
99 }
100
101 pub fn resolve(&self, sym: Symbol) -> &str {
107 &self.vec[sym.0 as usize]
108 }
109
110 pub fn lookup(&self, s: &str) -> Option<Symbol> {
114 self.map.get(s).copied()
115 }
116
117 pub fn len(&self) -> usize {
119 self.vec.len()
120 }
121
122 pub fn is_empty(&self) -> bool {
124 self.vec.len() <= 1
125 }
126}
127
128impl Default for Interner {
129 fn default() -> Self {
130 Self::new()
131 }
132}
133
134pub trait SymbolEq {
138 fn is(&self, interner: &Interner, s: &str) -> bool;
140}
141
142impl SymbolEq for Symbol {
143 #[inline]
144 fn is(&self, interner: &Interner, s: &str) -> bool {
145 interner.resolve(*self) == s
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn intern_returns_same_symbol_for_same_string() {
155 let mut interner = Interner::new();
156 let s1 = interner.intern("hello");
157 let s2 = interner.intern("hello");
158 assert_eq!(s1, s2);
159 }
160
161 #[test]
162 fn intern_returns_different_symbols_for_different_strings() {
163 let mut interner = Interner::new();
164 let s1 = interner.intern("hello");
165 let s2 = interner.intern("world");
166 assert_ne!(s1, s2);
167 }
168
169 #[test]
170 fn resolve_returns_original_string() {
171 let mut interner = Interner::new();
172 let sym = interner.intern("test");
173 assert_eq!(interner.resolve(sym), "test");
174 }
175
176 #[test]
177 fn empty_symbol_resolves_to_empty_string() {
178 let interner = Interner::new();
179 assert_eq!(interner.resolve(Symbol::EMPTY), "");
180 }
181
182 #[test]
183 fn symbols_are_copy() {
184 let mut interner = Interner::new();
185 let s1 = interner.intern("copy_test");
186 let s2 = s1;
187 assert_eq!(s1, s2);
188 assert_eq!(interner.resolve(s1), interner.resolve(s2));
189 }
190
191 #[test]
192 fn symbol_equality_is_fast() {
193 let mut interner = Interner::new();
194 let s1 = interner.intern("a_very_long_string_that_would_be_slow_to_compare");
195 let s2 = interner.intern("a_very_long_string_that_would_be_slow_to_compare");
196 assert_eq!(s1, s2);
197 }
198
199 #[test]
200 fn len_tracks_interned_count() {
201 let mut interner = Interner::new();
202 assert_eq!(interner.len(), 1);
203 interner.intern("first");
204 assert_eq!(interner.len(), 2);
205 interner.intern("second");
206 assert_eq!(interner.len(), 3);
207 interner.intern("first");
208 assert_eq!(interner.len(), 3);
209 }
210
211 #[test]
212 fn is_empty_after_new() {
213 let interner = Interner::new();
214 assert!(interner.is_empty());
215 }
216
217 #[test]
218 fn not_empty_after_intern() {
219 let mut interner = Interner::new();
220 interner.intern("something");
221 assert!(!interner.is_empty());
222 }
223
224 #[test]
225 fn symbol_index_matches_position() {
226 let mut interner = Interner::new();
227 let s1 = interner.intern("first");
228 let s2 = interner.intern("second");
229 assert_eq!(s1.index(), 1);
230 assert_eq!(s2.index(), 2);
231 }
232
233 #[test]
234 fn symbol_is_matches_interned_string() {
235 let mut interner = Interner::new();
236 let sym = interner.intern("test");
237 assert!(sym.is(&interner, "test"));
238 }
239
240 #[test]
241 fn symbol_is_rejects_different_string() {
242 let mut interner = Interner::new();
243 let sym = interner.intern("hello");
244 assert!(!sym.is(&interner, "world"));
245 }
246
247 #[test]
248 fn symbol_is_case_sensitive() {
249 let mut interner = Interner::new();
250 let sym = interner.intern("Test");
251 assert!(!sym.is(&interner, "test"));
252 assert!(sym.is(&interner, "Test"));
253 }
254
255 #[test]
256 fn symbol_empty_is_empty_string() {
257 let interner = Interner::new();
258 assert!(Symbol::EMPTY.is(&interner, ""));
259 }
260}