use crate::{object::JsObject, JsValue};
use boa_gc::{custom_trace, Finalize, Trace};
use indexmap::{Equivalent, IndexMap};
use std::{
collections::hash_map::RandomState,
fmt::Debug,
hash::{BuildHasher, Hash, Hasher},
};
#[derive(PartialEq, Eq, Clone, Debug)]
pub(crate) enum MapKey {
Key(JsValue),
Empty(usize), }
impl Hash for MapKey {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Self::Key(v) => v.hash(state),
Self::Empty(e) => e.hash(state),
}
}
}
impl Equivalent<MapKey> for JsValue {
fn equivalent(&self, key: &MapKey) -> bool {
match key {
MapKey::Key(v) => v == self,
MapKey::Empty(_) => false,
}
}
}
#[derive(Clone)]
pub struct OrderedMap<V, S = RandomState> {
map: IndexMap<MapKey, Option<V>, S>,
lock: u32,
empty_count: usize,
}
impl<V: Trace, S: BuildHasher> Finalize for OrderedMap<V, S> {}
unsafe impl<V: Trace, S: BuildHasher> Trace for OrderedMap<V, S> {
custom_trace!(this, {
for (k, v) in this.map.iter() {
if let MapKey::Key(key) = k {
mark(key);
}
mark(v);
}
});
}
impl<V: Debug> Debug for OrderedMap<V> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
self.map.fmt(formatter)
}
}
impl<V> Default for OrderedMap<V> {
fn default() -> Self {
Self::new()
}
}
impl<V> OrderedMap<V> {
#[must_use]
pub fn new() -> Self {
Self {
map: IndexMap::new(),
lock: 0,
empty_count: 0,
}
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
map: IndexMap::with_capacity(capacity),
lock: 0,
empty_count: 0,
}
}
#[must_use]
pub fn full_len(&self) -> usize {
self.map.len()
}
#[must_use]
pub fn len(&self) -> usize {
self.map.len() - self.empty_count
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn insert(&mut self, key: JsValue, value: V) -> Option<V> {
self.map.insert(MapKey::Key(key), Some(value)).flatten()
}
pub fn remove(&mut self, key: &JsValue) -> Option<V> {
if self.lock == 0 {
self.map.shift_remove(key).flatten()
} else if self.map.contains_key(key) {
self.map.insert(MapKey::Empty(self.empty_count), None);
self.empty_count += 1;
self.map.swap_remove(key).flatten()
} else {
None
}
}
pub fn clear(&mut self) {
self.map.clear();
self.map.shrink_to_fit();
self.empty_count = 0;
}
pub fn get(&self, key: &JsValue) -> Option<&V> {
self.map.get(key).and_then(Option::as_ref)
}
#[must_use]
pub fn get_index(&self, index: usize) -> Option<(&JsValue, &V)> {
if let (MapKey::Key(key), Some(value)) = self.map.get_index(index)? {
Some((key, value))
} else {
None
}
}
pub fn iter(&self) -> impl Iterator<Item = (&JsValue, &V)> {
self.map.iter().filter_map(|o| {
if let (MapKey::Key(key), Some(value)) = o {
Some((key, value))
} else {
None
}
})
}
pub fn contains_key(&self, key: &JsValue) -> bool {
self.map.contains_key(key)
}
pub(crate) fn lock(&mut self, map: JsObject) -> MapLock {
self.lock += 1;
MapLock(map)
}
fn unlock(&mut self) {
self.lock -= 1;
if self.lock == 0 {
self.map.retain(|k, _| matches!(k, MapKey::Key(_)));
self.empty_count = 0;
}
}
}
#[derive(Debug, Trace)]
pub(crate) struct MapLock(JsObject);
impl Clone for MapLock {
fn clone(&self) -> Self {
let mut map = self.0.borrow_mut();
let map = map.as_map_mut().expect("MapLock does not point to a map");
map.lock(self.0.clone())
}
}
impl Finalize for MapLock {
fn finalize(&self) {
let mut map = self.0.borrow_mut();
let map = map.as_map_mut().expect("MapLock does not point to a map");
map.unlock();
}
}