formualizer_eval/engine/arena/
string_interner.rs1use rustc_hash::FxHashMap;
4use std::{fmt, sync::Arc};
5
6#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
8pub struct StringId(u32);
9
10impl StringId {
11 pub const MAX: u32 = u32::MAX - 1;
13
14 pub const INVALID: StringId = StringId(u32::MAX);
16
17 pub fn as_u32(self) -> u32 {
18 self.0
19 }
20
21 pub fn from_raw(raw: u32) -> Self {
22 StringId(raw)
23 }
24
25 pub fn is_valid(self) -> bool {
26 self.0 != u32::MAX
27 }
28}
29
30impl fmt::Display for StringId {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 write!(f, "StringId({})", self.0)
33 }
34}
35
36#[derive(Debug, Default)]
39pub(crate) struct StringGarbage {
40 strings: Vec<Arc<str>>,
41 lookup: FxHashMap<Arc<str>, StringId>,
42}
43
44impl StringGarbage {
45 pub(crate) fn len(&self) -> usize {
47 self.strings.len()
48 }
49}
50
51#[derive(Debug)]
53pub struct StringInterner {
54 strings: Vec<Arc<str>>,
56 lookup: FxHashMap<Arc<str>, StringId>,
58}
59
60impl StringInterner {
61 pub fn new() -> Self {
62 Self {
63 strings: Vec::new(),
64 lookup: FxHashMap::default(),
65 }
66 }
67
68 pub fn with_capacity(cap: usize) -> Self {
69 Self {
70 strings: Vec::with_capacity(cap),
71 lookup: FxHashMap::with_capacity_and_hasher(cap, Default::default()),
72 }
73 }
74
75 pub fn intern(&mut self, s: &str) -> StringId {
78 if let Some(&id) = self.lookup.get(s) {
80 return id;
81 }
82
83 let index = self.strings.len() as u32;
85 if index > StringId::MAX {
86 panic!("String interner overflow: too many strings");
87 }
88
89 let id = StringId(index);
91 let boxed: Arc<str> = s.into();
92
93 self.lookup.insert(boxed.clone(), id);
96 self.strings.push(boxed);
97
98 id
99 }
100
101 #[inline]
103 pub fn get(&self, id: StringId) -> Option<&str> {
104 if id.is_valid() {
105 self.strings.get(id.0 as usize).map(|s| &**s)
106 } else {
107 None
108 }
109 }
110
111 #[inline]
113 pub fn resolve(&self, id: StringId) -> &str {
114 self.get(id)
115 .unwrap_or_else(|| panic!("Invalid string ID: {id:?}"))
116 }
117
118 pub(crate) fn free_dead(&mut self, live: &[bool]) -> StringGarbage {
128 let kept = self
129 .strings
130 .iter()
131 .enumerate()
132 .filter(|(i, _)| live.get(*i).copied().unwrap_or(true))
133 .count();
134 if kept == self.strings.len() {
135 return StringGarbage::default();
136 }
137 let empty: Arc<str> = Arc::from("");
138 let mut lookup = FxHashMap::with_capacity_and_hasher(kept, Default::default());
139 let mut dead = Vec::with_capacity(self.strings.len() - kept);
140 for (i, slot) in self.strings.iter_mut().enumerate() {
141 if live.get(i).copied().unwrap_or(true) {
142 lookup.entry(slot.clone()).or_insert(StringId(i as u32));
143 } else {
144 dead.push(std::mem::replace(slot, empty.clone()));
145 }
146 }
147 let old_lookup = std::mem::replace(&mut self.lookup, lookup);
148 StringGarbage {
149 strings: dead,
150 lookup: old_lookup,
151 }
152 }
153
154 pub fn contains(&self, s: &str) -> bool {
155 self.lookup.contains_key(s)
156 }
157
158 pub fn get_id(&self, s: &str) -> Option<StringId> {
160 self.lookup.get(s).copied()
161 }
162
163 pub fn len(&self) -> usize {
165 self.strings.len()
166 }
167
168 pub fn is_empty(&self) -> bool {
170 self.strings.is_empty()
171 }
172
173 pub fn memory_usage(&self) -> usize {
175 let vec_overhead = self.strings.capacity() * std::mem::size_of::<Box<str>>();
177
178 let string_data: usize = self
180 .strings
181 .iter()
182 .map(|s| s.len() + std::mem::size_of::<Box<str>>())
183 .sum();
184
185 let map_overhead = self.lookup.capacity()
187 * (std::mem::size_of::<Box<str>>() + std::mem::size_of::<StringId>());
188
189 vec_overhead + string_data + map_overhead
190 }
191
192 pub fn clear(&mut self) {
194 self.strings.clear();
195 self.lookup.clear();
196 }
197
198 pub fn iter(&self) -> impl Iterator<Item = (StringId, &str)> + '_ {
200 self.strings
201 .iter()
202 .enumerate()
203 .map(|(i, s)| (StringId(i as u32), &**s))
204 }
205
206 pub fn stats(&self) -> InternerStats {
208 let total_bytes: usize = self.strings.iter().map(|s| s.len()).sum();
209 let unique_count = self.strings.len();
210
211 InternerStats {
212 unique_count,
213 total_bytes,
214 average_length: if unique_count > 0 {
215 total_bytes as f64 / unique_count as f64
216 } else {
217 0.0
218 },
219 }
220 }
221}
222
223impl Default for StringInterner {
224 fn default() -> Self {
225 Self::new()
226 }
227}
228
229#[derive(Debug, Clone)]
231pub struct InternerStats {
232 pub unique_count: usize,
233 pub total_bytes: usize,
234 pub average_length: f64,
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn test_string_interning() {
243 let mut interner = StringInterner::new();
244
245 let s1 = interner.intern("Hello");
246 let s2 = interner.intern("Hello");
247 let s3 = interner.intern("World");
248
249 assert_eq!(s1, s2);
250 assert_ne!(s1, s3);
251 assert_eq!(interner.get(s1), Some("Hello"));
252 assert_eq!(interner.get(s3), Some("World"));
253 }
254
255 #[test]
256 fn test_string_memory_efficiency() {
257 let mut interner = StringInterner::new();
258
259 let refs: Vec<_> = (0..1000).map(|_| interner.intern("repeated")).collect();
261
262 assert_eq!(interner.len(), 1);
264
265 for r in &refs[1..] {
267 assert_eq!(*r, refs[0]);
268 }
269
270 assert!(interner.memory_usage() < 1000);
272 }
273
274 #[test]
275 fn test_string_interner_capacity() {
276 let mut interner = StringInterner::with_capacity(100);
277
278 for i in 0..1000 {
280 let s = format!("string_{i}");
281 let id = interner.intern(&s);
282 assert_eq!(interner.get(id), Some(s.as_str()));
283 }
284
285 assert_eq!(interner.len(), 1000);
286 }
287
288 #[test]
289 fn test_string_interner_contains() {
290 let mut interner = StringInterner::new();
291
292 interner.intern("exists");
293
294 assert!(interner.contains("exists"));
295 assert!(!interner.contains("not_exists"));
296 }
297
298 #[test]
299 fn test_string_interner_get_id() {
300 let mut interner = StringInterner::new();
301
302 let id = interner.intern("test");
303
304 assert_eq!(interner.get_id("test"), Some(id));
305 assert_eq!(interner.get_id("not_interned"), None);
306 }
307
308 #[test]
309 fn test_string_interner_clear() {
310 let mut interner = StringInterner::new();
311
312 interner.intern("one");
313 interner.intern("two");
314 interner.intern("three");
315
316 assert_eq!(interner.len(), 3);
317
318 interner.clear();
319
320 assert_eq!(interner.len(), 0);
321 assert!(interner.is_empty());
322 }
323
324 #[test]
325 fn test_string_interner_iter() {
326 let mut interner = StringInterner::new();
327
328 let id1 = interner.intern("first");
329 let id2 = interner.intern("second");
330 let id3 = interner.intern("third");
331
332 let items: Vec<_> = interner.iter().collect();
333
334 assert_eq!(items.len(), 3);
335 assert_eq!(items[0], (id1, "first"));
336 assert_eq!(items[1], (id2, "second"));
337 assert_eq!(items[2], (id3, "third"));
338 }
339
340 #[test]
341 fn test_string_interner_stats() {
342 let mut interner = StringInterner::new();
343
344 interner.intern("short");
345 interner.intern("medium_length");
346 interner.intern("this_is_a_longer_string");
347
348 let stats = interner.stats();
349
350 assert_eq!(stats.unique_count, 3);
351 assert_eq!(stats.total_bytes, 5 + 13 + 23);
352 assert!((stats.average_length - 13.67).abs() < 0.01);
353 }
354
355 #[test]
356 fn test_invalid_string_id() {
357 let interner = StringInterner::new();
358
359 let invalid = StringId::INVALID;
360 assert_eq!(invalid.0, u32::MAX);
361 assert!(!StringId::INVALID.is_valid());
362 assert_eq!(interner.get(StringId::INVALID), None);
363 }
364
365 #[test]
366 #[should_panic(expected = "Invalid string ID")]
367 fn test_resolve_invalid_id() {
368 let interner = StringInterner::new();
369 interner.resolve(StringId::INVALID);
370 }
371
372 #[test]
373 fn test_string_id_ordering() {
374 let mut interner = StringInterner::new();
375
376 let id1 = interner.intern("a");
377 let id2 = interner.intern("b");
378 let id3 = interner.intern("c");
379
380 assert!(id1 < id2);
381 assert!(id2 < id3);
382 assert!(id1 < id3);
383 }
384
385 #[test]
386 fn test_empty_string() {
387 let mut interner = StringInterner::new();
388
389 let id = interner.intern("");
390 assert_eq!(interner.get(id), Some(""));
391
392 let id2 = interner.intern("");
394 assert_eq!(id, id2);
395 }
396
397 #[test]
398 fn test_unicode_strings() {
399 let mut interner = StringInterner::new();
400
401 let id1 = interner.intern("Hello δΈη");
402 let id2 = interner.intern("π¦ Rust");
403 let id3 = interner.intern("Hello δΈη");
404
405 assert_eq!(id1, id3);
406 assert_ne!(id1, id2);
407
408 assert_eq!(interner.get(id1), Some("Hello δΈη"));
409 assert_eq!(interner.get(id2), Some("π¦ Rust"));
410 }
411}