Skip to main content

boa_engine/builtins/map/
ordered_map.rs

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