mica_language/value/
dicts.rs

1//! Implementation of the `Dict` type based on hashbrown.
2
3use std::cell::UnsafeCell;
4use std::collections::hash_map::RandomState;
5use std::hash::{BuildHasher, Hash, Hasher};
6use std::mem;
7
8use hashbrown::raw::RawTable;
9
10use super::{RawValue, ValueKind};
11
12type DictMap = RawTable<(RawValue, RawValue)>;
13type DictHashBuilder = RandomState;
14
15#[derive(Default, Clone)]
16struct DictInner {
17   table: DictMap,
18   state: DictHashBuilder,
19}
20
21/// A dict (dictionary) storing arbitrarily typed keys and values.
22///
23/// Note that this type has interior mutability. This is because dicts in Mica are shared by
24/// reference; creating a new dict requires using `clone`.
25#[derive(Default)]
26pub struct Dict {
27   inner: UnsafeCell<DictInner>,
28}
29
30impl Dict {
31   /// Creates a new dict.
32   pub fn new() -> Self {
33      Self::default()
34   }
35
36   /// Returns the number of elements stored in the dict.
37   pub fn len(&self) -> usize {
38      let inner = unsafe { &*self.inner.get() };
39      inner.table.len()
40   }
41
42   /// Returns whether the dict is empty.
43   pub fn is_empty(&self) -> bool {
44      self.len() == 0
45   }
46
47   /// Sets the value at the given key. Returns the old value, or `nil` if there was no value.
48   pub fn insert(&self, key: RawValue, value: RawValue) -> RawValue {
49      let inner = unsafe { &mut *self.inner.get() };
50      let hasher = make_hasher(&inner.state);
51      let key_hash = hasher(&(key, value));
52      if let Some((_, item)) = inner.table.get_mut(key_hash, equivalent_key(key)) {
53         mem::replace(item, value)
54      } else {
55         inner.table.insert(key_hash, (key, value), hasher);
56         RawValue::from(())
57      }
58   }
59
60   /// Removes the value at the given key and returns it (or `nil` if there was no value).
61   pub fn remove(&self, key: RawValue) -> RawValue {
62      let inner = unsafe { &mut *self.inner.get() };
63      match inner.table.remove_entry(
64         key.hash(&mut inner.state.build_hasher()),
65         equivalent_key(key),
66      ) {
67         Some((_, v)) => v,
68         None => RawValue::from(()),
69      }
70   }
71
72   /// Returns the value under the given key, or `None` if there is no such value.
73   pub fn get(&self, key: RawValue) -> Option<RawValue> {
74      let inner = unsafe { &*self.inner.get() };
75      if inner.table.is_empty() {
76         None
77      } else {
78         let hash = key.hash(&mut inner.state.build_hasher());
79         inner.table.get(hash, equivalent_key(key)).map(|&(_key, value)| value)
80      }
81   }
82
83   /// Returns whether the dict contains a value under the given key.
84   pub fn contains_key(&self, key: RawValue) -> bool {
85      self.get(key).is_some()
86   }
87
88   /// Returns an iterator over pairs stored in the dict.
89   ///
90   /// # Safety
91   /// The dict must not be modified while iterating over it. The iterator must not outlive the
92   /// dict.
93   pub(crate) unsafe fn iter(&self) -> impl Iterator<Item = (RawValue, RawValue)> + '_ {
94      let inner = &*self.inner.get();
95      let iterator = inner.table.iter();
96      iterator.map(|bucket| bucket.read())
97   }
98}
99
100impl Clone for Dict {
101   fn clone(&self) -> Self {
102      Self {
103         inner: UnsafeCell::new((unsafe { &*self.inner.get() }).clone()),
104      }
105   }
106}
107
108impl PartialEq for Dict {
109   fn eq(&self, other: &Self) -> bool {
110      if self.len() != other.len() {
111         return false;
112      }
113      unsafe { self.iter() }.all(|(key, value)| other.get(key).map_or(false, |v| value == v))
114   }
115}
116
117impl RawValue {
118   /// Hashes the value.
119   /// Note that this is not part of a `std::hash::Hash` implementation because this may get extra
120   /// arguments in the future to support overloading hashing from the language itself.
121   pub fn hash<H>(&self, state: &mut H) -> u64
122   where
123      H: Hasher,
124   {
125      self.kind().hash(state);
126      unsafe {
127         match self.kind() {
128            // Primitives are hashed by value.
129            ValueKind::Nil => (),
130            ValueKind::Boolean => self.get_boolean_unchecked().hash(state),
131            // Hashing floats isn't normally considered OK by Rust, but in our case it's the only
132            // option because we don't have an integer type (and probably never will).
133            ValueKind::Number => self.get_number_unchecked().to_bits().hash(state),
134            ValueKind::String => self.get_raw_string_unchecked().get().hash(state),
135            // Objects with interior mutability are hashed by reference.
136            ValueKind::Function => self.get_raw_function_unchecked().get_raw().hash(state),
137            ValueKind::Struct => self.get_raw_struct_unchecked().get_raw().hash(state),
138            ValueKind::Trait => self.get_raw_trait_unchecked().get_raw().hash(state),
139            ValueKind::List => {
140               let slice = self.get_raw_list_unchecked().get().as_slice();
141               slice.len().hash(state);
142               for value in slice.iter() {
143                  value.hash(state);
144               }
145            }
146            ValueKind::Dict => {
147               // This is another hash that Rust doesn't want you to have, but I see no reason to
148               // leave it out in Mica. Yes, it's stupid, and nobody in their right mind is gonna
149               // use it, but also, why not?
150               let dict = self.get_raw_dict_unchecked().get();
151               for (key, value) in dict.iter() {
152                  key.hash(state);
153                  value.hash(state);
154               }
155            }
156            ValueKind::UserData => self.get_raw_user_data_unchecked().get_raw().hash(state),
157         }
158      }
159      state.finish()
160   }
161}
162
163fn make_hasher(builder: &DictHashBuilder) -> impl Fn(&(RawValue, RawValue)) -> u64 + '_ {
164   move |&(key, _value)| key.hash(&mut builder.build_hasher())
165}
166
167fn equivalent_key(key: RawValue) -> impl Fn(&(RawValue, RawValue)) -> bool {
168   move |&(key2, _value)| key == key2
169}