1use std::fmt;
17
18use super::arena::Arena;
19use super::hash::FxHashMap;
20use super::string::{StringRecord, StringRepr};
21use crate::value::{StringId, SymbolId, Value};
22
23const INDEX_TAG: u32 = 1 << 31;
24const SYMBOL_TAG: u32 = 1 << 30;
25pub const KEY_INDEX_LIMIT: u32 = SYMBOL_TAG;
27
28#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
29pub struct PropertyKey(u32);
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32pub enum KeyKind {
33 Index(u32),
34 String(StringId),
35 Symbol(SymbolId),
36}
37
38impl PropertyKey {
39 #[inline]
41 pub const fn index(index: u32) -> Option<PropertyKey> {
42 if index < INDEX_TAG {
43 Some(PropertyKey(index | INDEX_TAG))
44 } else {
45 None
46 }
47 }
48
49 #[inline]
52 pub fn atom(id: StringId) -> PropertyKey {
53 assert!(id.0 < KEY_INDEX_LIMIT, "atom index out of key range");
54 PropertyKey(id.0)
55 }
56
57 #[inline]
58 pub fn symbol(id: SymbolId) -> PropertyKey {
59 assert!(id.0 < KEY_INDEX_LIMIT, "symbol index out of key range");
60 PropertyKey(id.0 | SYMBOL_TAG)
61 }
62
63 #[inline]
64 pub const fn kind(self) -> KeyKind {
65 if self.0 & INDEX_TAG != 0 {
66 KeyKind::Index(self.0 & !INDEX_TAG)
67 } else if self.0 & SYMBOL_TAG != 0 {
68 KeyKind::Symbol(SymbolId(self.0 & !SYMBOL_TAG))
69 } else {
70 KeyKind::String(StringId(self.0))
71 }
72 }
73
74 #[inline]
75 pub const fn raw(self) -> u32 {
76 self.0
77 }
78
79 #[inline]
82 pub const fn from_raw(bits: u32) -> PropertyKey {
83 PropertyKey(bits)
84 }
85
86 #[inline]
88 pub const fn to_value(self) -> Value {
89 match self.kind() {
90 KeyKind::Index(index) => Value::int32(index as i32),
91 KeyKind::String(id) => Value::string(id),
92 KeyKind::Symbol(id) => Value::symbol(id),
93 }
94 }
95
96 #[inline]
98 pub const fn heap_value(self) -> Option<Value> {
99 match self.kind() {
100 KeyKind::Index(_) => None,
101 KeyKind::String(id) => Some(Value::string(id)),
102 KeyKind::Symbol(id) => Some(Value::symbol(id)),
103 }
104 }
105}
106
107impl fmt::Debug for PropertyKey {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 match self.kind() {
110 KeyKind::Index(index) => write!(f, "Key::Index({index})"),
111 KeyKind::String(id) => write!(f, "Key::Atom({})", id.0),
112 KeyKind::Symbol(id) => write!(f, "Key::Symbol({})", id.0),
113 }
114 }
115}
116
117const OVERFLOW: u32 = 1 << 31;
121
122#[derive(Default)]
126pub struct AtomTable {
127 buckets: FxHashMap<u32, u32>,
128 collisions: Vec<Vec<StringId>>,
129 free_collisions: Vec<u32>,
131 count: usize,
132}
133
134impl AtomTable {
135 pub fn find(
136 &self,
137 strings: &Arena<StringRecord>,
138 repr: &StringRepr,
139 hash: u32,
140 ) -> Option<StringId> {
141 self.find_by(hash, |id| strings.get(id.0).flat() == Some(repr))
142 }
143
144 pub fn find_one_byte(
146 &self,
147 strings: &Arena<StringRecord>,
148 bytes: &[u8],
149 hash: u32,
150 ) -> Option<StringId> {
151 self.find_by(hash, |id| {
152 matches!(strings.get(id.0).flat(), Some(StringRepr::OneByte(atom)) if **atom == *bytes)
153 })
154 }
155
156 fn find_by(&self, hash: u32, matches: impl Fn(&StringId) -> bool) -> Option<StringId> {
157 let bucket = *self.buckets.get(&hash)?;
158 if bucket & OVERFLOW == 0 {
159 let id = StringId(bucket);
160 matches(&id).then_some(id)
161 } else {
162 self.collisions[(bucket & !OVERFLOW) as usize]
163 .iter()
164 .copied()
165 .find(matches)
166 }
167 }
168
169 pub fn insert(&mut self, hash: u32, id: StringId) {
170 debug_assert!(id.0 < KEY_INDEX_LIMIT);
171 self.count += 1;
172 match self.buckets.get_mut(&hash) {
173 None => {
174 self.buckets.insert(hash, id.0);
175 }
176 Some(bucket) if *bucket & OVERFLOW == 0 => {
177 let ids = vec![StringId(*bucket), id];
178 let list = match self.free_collisions.pop() {
179 Some(list) => {
180 self.collisions[list as usize] = ids;
181 list
182 }
183 None => {
184 self.collisions.push(ids);
185 self.collisions.len() as u32 - 1
186 }
187 };
188 *bucket = list | OVERFLOW;
189 }
190 Some(bucket) => self.collisions[(*bucket & !OVERFLOW) as usize].push(id),
191 }
192 }
193
194 pub fn remove(&mut self, hash: u32, id: StringId) {
197 let Some(bucket) = self.buckets.get_mut(&hash) else {
198 debug_assert!(false, "an atom outside the table");
199 return;
200 };
201 if *bucket & OVERFLOW == 0 {
202 debug_assert_eq!(*bucket, id.0);
203 self.buckets.remove(&hash);
204 } else {
205 let list = *bucket & !OVERFLOW;
206 let ids = &mut self.collisions[list as usize];
207 let Some(position) = ids.iter().position(|&other| other == id) else {
208 debug_assert!(false, "an atom outside the table");
209 return;
210 };
211 ids.swap_remove(position);
212 if let [last] = ids[..] {
213 *bucket = last.0;
214 self.collisions[list as usize] = Vec::new();
215 self.free_collisions.push(list);
216 }
217 }
218 self.count -= 1;
219 }
220
221 pub fn len(&self) -> usize {
222 self.count
223 }
224
225 pub fn is_empty(&self) -> bool {
226 self.count == 0
227 }
228
229 pub fn ids(&self) -> impl Iterator<Item = StringId> + '_ {
230 self.buckets.values().flat_map(move |&bucket| {
231 let ids: &[StringId] = if bucket & OVERFLOW == 0 {
232 &[]
233 } else {
234 &self.collisions[(bucket & !OVERFLOW) as usize]
235 };
236 let single = (bucket & OVERFLOW == 0).then_some(StringId(bucket));
237 single.into_iter().chain(ids.iter().copied())
238 })
239 }
240
241 pub fn bytes(&self) -> usize {
243 self.buckets.capacity() * (size_of::<(u32, u32)>() + 1)
244 + self
245 .collisions
246 .iter()
247 .map(|ids| 24 + ids.capacity() * 4)
248 .sum::<usize>()
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn key_tags_round_trip() {
258 assert_eq!(PropertyKey::index(0).unwrap().kind(), KeyKind::Index(0));
259 let max = INDEX_TAG - 1;
260 assert_eq!(PropertyKey::index(max).unwrap().kind(), KeyKind::Index(max));
261 assert_eq!(PropertyKey::index(INDEX_TAG), None);
262 assert_eq!(
263 PropertyKey::atom(StringId(5)).kind(),
264 KeyKind::String(StringId(5))
265 );
266 assert_eq!(
267 PropertyKey::symbol(SymbolId(5)).kind(),
268 KeyKind::Symbol(SymbolId(5))
269 );
270 assert_ne!(
271 PropertyKey::atom(StringId(5)),
272 PropertyKey::symbol(SymbolId(5))
273 );
274 assert_eq!(PropertyKey::index(7).unwrap().to_value(), Value::int32(7));
275 assert_eq!(PropertyKey::index(7).unwrap().heap_value(), None);
276 }
277}