boa/builtins/map/
ordered_map.rs

1use crate::{
2    gc::{custom_trace, Finalize, Trace},
3    object::JsObject,
4    JsValue,
5};
6use indexmap::{Equivalent, IndexMap};
7use std::{
8    collections::hash_map::RandomState,
9    fmt::Debug,
10    hash::{BuildHasher, Hash, Hasher},
11};
12
13#[derive(PartialEq, Eq, Clone, Debug)]
14enum MapKey {
15    Key(JsValue),
16    Empty(usize), // Necessary to ensure empty keys are still unique.
17}
18
19// This ensures that a MapKey::Key(value) hashes to the same as value. The derived PartialEq implementation still holds.
20#[allow(clippy::derive_hash_xor_eq)]
21impl Hash for MapKey {
22    fn hash<H: Hasher>(&self, state: &mut H) {
23        match self {
24            MapKey::Key(v) => v.hash(state),
25            MapKey::Empty(e) => e.hash(state),
26        }
27    }
28}
29
30impl Equivalent<MapKey> for JsValue {
31    fn equivalent(&self, key: &MapKey) -> bool {
32        match key {
33            MapKey::Key(v) => v == self,
34            _ => false,
35        }
36    }
37}
38
39/// A newtype wrapping indexmap::IndexMap
40#[derive(Clone)]
41pub struct OrderedMap<V, S = RandomState> {
42    map: IndexMap<MapKey, Option<V>, S>,
43    lock: u32,
44    empty_count: usize,
45}
46
47impl<V: Trace, S: BuildHasher> Finalize for OrderedMap<V, S> {}
48unsafe impl<V: Trace, S: BuildHasher> Trace for OrderedMap<V, S> {
49    custom_trace!(this, {
50        for (k, v) in this.map.iter() {
51            if let MapKey::Key(key) = k {
52                mark(key);
53            }
54            mark(v);
55        }
56    });
57}
58
59impl<V: Debug> Debug for OrderedMap<V> {
60    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
61        self.map.fmt(formatter)
62    }
63}
64
65impl<V> Default for OrderedMap<V> {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71impl<V> OrderedMap<V> {
72    pub fn new() -> Self {
73        OrderedMap {
74            map: IndexMap::new(),
75            lock: 0,
76            empty_count: 0,
77        }
78    }
79
80    pub fn with_capacity(capacity: usize) -> Self {
81        OrderedMap {
82            map: IndexMap::with_capacity(capacity),
83            lock: 0,
84            empty_count: 0,
85        }
86    }
87
88    /// Return the number of key-value pairs in the map, including empty values.
89    ///
90    /// Computes in **O(1)** time.
91    pub fn full_len(&self) -> usize {
92        self.map.len()
93    }
94
95    /// Gets the number of key-value pairs in the map, not including empty values.
96    ///
97    /// Computes in **O(1)** time.
98    pub fn len(&self) -> usize {
99        self.map.len() - self.empty_count
100    }
101
102    /// Returns true if the map contains no elements.
103    ///
104    /// Computes in **O(1)** time.
105    pub fn is_empty(&self) -> bool {
106        self.len() == 0
107    }
108
109    /// Insert a key-value pair in the map.
110    ///
111    /// If an equivalent key already exists in the map: the key remains and
112    /// retains in its place in the order, its corresponding value is updated
113    /// with `value` and the older value is returned inside `Some(_)`.
114    ///
115    /// If no equivalent key existed in the map: the new key-value pair is
116    /// inserted, last in order, and `None` is returned.
117    ///
118    /// Computes in **O(1)** time (amortized average).
119    pub fn insert(&mut self, key: JsValue, value: V) -> Option<V> {
120        self.map.insert(MapKey::Key(key), Some(value)).flatten()
121    }
122
123    /// Remove the key-value pair equivalent to `key` and return
124    /// its value.
125    ///
126    /// Like `Vec::remove`, the pair is removed by shifting all of the
127    /// elements that follow it, preserving their relative order.
128    /// **This perturbs the index of all of those elements!**
129    ///
130    /// Return `None` if `key` is not in map.
131    ///
132    /// Computes in **O(n)** time (average).
133    pub fn remove(&mut self, key: &JsValue) -> Option<V> {
134        if self.lock == 0 {
135            self.map.shift_remove(key).flatten()
136        } else if self.map.contains_key(key) {
137            self.map.insert(MapKey::Empty(self.empty_count), None);
138            self.empty_count += 1;
139            self.map.swap_remove(key).flatten()
140        } else {
141            None
142        }
143    }
144
145    /// Removes all elements from the map and resets the counter of
146    /// empty entries.
147    pub fn clear(&mut self) {
148        self.map.clear();
149        self.map.shrink_to_fit();
150        self.empty_count = 0
151    }
152
153    /// Return a reference to the value stored for `key`, if it is present,
154    /// else `None`.
155    ///
156    /// Computes in **O(1)** time (average).
157    pub fn get(&self, key: &JsValue) -> Option<&V> {
158        self.map.get(key).map(Option::as_ref).flatten()
159    }
160
161    /// Get a key-value pair by index.
162    ///
163    /// Valid indices are 0 <= index < self.full_len().
164    ///
165    /// Computes in O(1) time.
166    pub fn get_index(&self, index: usize) -> Option<(&JsValue, &V)> {
167        if let (MapKey::Key(key), Some(value)) = self.map.get_index(index)? {
168            Some((key, value))
169        } else {
170            None
171        }
172    }
173
174    /// Return an iterator over the key-value pairs of the map, in their order
175    pub fn iter(&self) -> impl Iterator<Item = (&JsValue, &V)> {
176        self.map.iter().filter_map(|o| {
177            if let (MapKey::Key(key), Some(value)) = o {
178                Some((key, value))
179            } else {
180                None
181            }
182        })
183    }
184
185    /// Return `true` if an equivalent to `key` exists in the map.
186    ///
187    /// Computes in **O(1)** time (average).
188    pub fn contains_key(&self, key: &JsValue) -> bool {
189        self.map.contains_key(key)
190    }
191
192    /// Increases the lock counter and returns a lock object that will decrement the counter when dropped.
193    ///
194    /// This allows objects to be removed from the map during iteration without affecting the indexes until the iteration has completed.
195    pub(crate) fn lock(&mut self, map: JsObject) -> MapLock {
196        self.lock += 1;
197        MapLock(map)
198    }
199
200    /// Decreases the lock counter and, if 0, removes all empty entries.
201    fn unlock(&mut self) {
202        self.lock -= 1;
203        if self.lock == 0 {
204            self.map.retain(|k, _| matches!(k, MapKey::Key(_)));
205            self.empty_count = 0;
206        }
207    }
208}
209
210/// Increases the lock count of the map for the lifetime of the guard. This should not be dropped until iteration has completed.
211#[derive(Debug, Trace)]
212pub(crate) struct MapLock(JsObject);
213
214impl Clone for MapLock {
215    fn clone(&self) -> Self {
216        let mut map = self.0.borrow_mut();
217        let map = map.as_map_mut().expect("MapLock does not point to a map");
218        map.lock(self.0.clone())
219    }
220}
221
222impl Finalize for MapLock {
223    fn finalize(&self) {
224        let mut map = self.0.borrow_mut();
225        let map = map.as_map_mut().expect("MapLock does not point to a map");
226        map.unlock();
227    }
228}