1use alloc::collections::BTreeMap;
20use alloc::string::String;
21use alloc::vec::Vec;
22
23use crate::error::{Error, Result};
24
25#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
32pub struct Symbol(u32);
33
34impl Symbol {
35 #[inline]
41 pub const fn id(self) -> u32 {
42 self.0
43 }
44}
45
46pub struct Interner {
64 storage: Vec<String>,
65 lookup: BTreeMap<String, u32>,
66}
67
68impl Interner {
69 #[inline]
71 #[must_use]
72 pub const fn new() -> Self {
73 Self {
74 storage: Vec::new(),
75 lookup: BTreeMap::new(),
76 }
77 }
78
79 #[inline]
82 #[must_use]
83 pub fn with_capacity(capacity: usize) -> Self {
84 Self {
85 storage: Vec::with_capacity(capacity),
86 lookup: BTreeMap::new(),
87 }
88 }
89
90 #[inline]
92 #[must_use]
93 pub fn len(&self) -> usize {
94 self.storage.len()
95 }
96
97 #[inline]
99 #[must_use]
100 pub fn is_empty(&self) -> bool {
101 self.storage.is_empty()
102 }
103
104 pub fn intern(&mut self, s: &str) -> Symbol {
110 match self.try_intern(s) {
111 Ok(sym) => sym,
112 Err(_) => panic!("interner symbol counter overflow (u32::MAX symbols)"),
113 }
114 }
115
116 pub fn try_intern(&mut self, s: &str) -> Result<Symbol> {
120 if let Some(&id) = self.lookup.get(s) {
121 return Ok(Symbol(id));
122 }
123 let id_usize = self.storage.len();
124 if id_usize > u32::MAX as usize {
125 return Err(Error::CounterOverflow);
126 }
127 let id = id_usize as u32;
128 let owned = String::from(s);
129 self.storage.push(owned.clone());
130 let _ = self.lookup.insert(owned, id);
131 Ok(Symbol(id))
132 }
133
134 #[inline]
137 #[must_use]
138 pub fn resolve(&self, symbol: Symbol) -> Option<&str> {
139 self.storage.get(symbol.0 as usize).map(String::as_str)
140 }
141
142 #[inline]
146 #[must_use]
147 pub fn contains(&self, s: &str) -> bool {
148 self.lookup.contains_key(s)
149 }
150
151 #[inline]
154 #[must_use]
155 pub fn lookup(&self, s: &str) -> Option<Symbol> {
156 self.lookup.get(s).copied().map(Symbol)
157 }
158
159 pub fn iter(&self) -> Iter<'_> {
162 Iter {
163 inner: self.storage.iter().enumerate(),
164 }
165 }
166}
167
168impl Default for Interner {
169 #[inline]
170 fn default() -> Self {
171 Self::new()
172 }
173}
174
175impl core::fmt::Debug for Interner {
176 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
177 f.debug_struct("Interner")
178 .field("len", &self.storage.len())
179 .finish()
180 }
181}
182
183pub struct Iter<'a> {
185 inner: core::iter::Enumerate<core::slice::Iter<'a, String>>,
186}
187
188impl<'a> Iterator for Iter<'a> {
189 type Item = (Symbol, &'a str);
190
191 fn next(&mut self) -> Option<Self::Item> {
192 let (i, s) = self.inner.next()?;
193 Some((Symbol(i as u32), s.as_str()))
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn same_string_returns_same_symbol() {
203 let mut i = Interner::new();
204 let a = i.intern("hello");
205 let b = i.intern("hello");
206 assert_eq!(a, b);
207 assert_eq!(i.len(), 1);
208 }
209
210 #[test]
211 fn distinct_strings_return_distinct_symbols() {
212 let mut i = Interner::new();
213 let a = i.intern("alpha");
214 let b = i.intern("bravo");
215 assert_ne!(a, b);
216 }
217
218 #[test]
219 fn resolve_round_trips() {
220 let mut i = Interner::new();
221 let s = i.intern("round-trip");
222 assert_eq!(i.resolve(s), Some("round-trip"));
223 }
224
225 #[test]
226 fn lookup_does_not_insert() {
227 let mut i = Interner::new();
228 let _ = i.intern("first");
229 assert!(i.lookup("second").is_none());
230 assert_eq!(i.len(), 1);
231 }
232
233 #[test]
234 fn contains_reflects_state() {
235 let mut i = Interner::new();
236 assert!(!i.contains("x"));
237 let _ = i.intern("x");
238 assert!(i.contains("x"));
239 }
240
241 #[test]
242 fn iter_yields_insertion_order() {
243 let mut i = Interner::new();
244 let _ = i.intern("a");
245 let _ = i.intern("b");
246 let _ = i.intern("c");
247 let collected: Vec<&str> = i.iter().map(|(_, s)| s).collect();
248 assert_eq!(collected, vec!["a", "b", "c"]);
249 }
250}