use crate::{
builtins::map::{add_entries_from_iterable, ordered_map::OrderedMap},
builtins::Map,
error::JsNativeError,
object::{JsFunction, JsMapIterator, JsObject, JsObjectType, ObjectData},
string::utf16,
value::TryFromJs,
Context, JsResult, JsValue,
};
use boa_gc::{Finalize, Trace};
use std::ops::Deref;
#[derive(Debug, Clone, Trace, Finalize)]
pub struct JsMap {
inner: JsObject,
}
impl JsMap {
#[inline]
pub fn new(context: &mut Context<'_>) -> Self {
let map = Self::create_map(context);
Self { inner: map }
}
pub fn from_js_iterable(iterable: &JsValue, context: &mut Context<'_>) -> JsResult<Self> {
let map = Self::create_map(context);
let adder = map
.get(utf16!("set"), context)
.expect("creating a map with the default prototype must not fail");
let _completion_record = add_entries_from_iterable(&map, iterable, &adder, context)?;
Ok(Self { inner: map })
}
#[inline]
pub fn from_object(object: JsObject) -> JsResult<Self> {
if object.is_map() {
Ok(Self { inner: object })
} else {
Err(JsNativeError::typ()
.with_message("object is not a Map")
.into())
}
}
fn create_map(context: &mut Context<'_>) -> JsObject {
let prototype = context.intrinsics().constructors().map().prototype();
JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::map(OrderedMap::new()),
)
}
#[inline]
pub fn entries(&self, context: &mut Context<'_>) -> JsResult<JsMapIterator> {
let iterator_record = Map::entries(&self.inner.clone().into(), &[], context)?
.get_iterator(context, None, None)?;
let map_iterator_object = iterator_record.iterator();
JsMapIterator::from_object(map_iterator_object.clone())
}
#[inline]
pub fn keys(&self, context: &mut Context<'_>) -> JsResult<JsMapIterator> {
let iterator_record = Map::keys(&self.inner.clone().into(), &[], context)?
.get_iterator(context, None, None)?;
let map_iterator_object = iterator_record.iterator();
JsMapIterator::from_object(map_iterator_object.clone())
}
pub fn set<K, V>(&self, key: K, value: V, context: &mut Context<'_>) -> JsResult<JsValue>
where
K: Into<JsValue>,
V: Into<JsValue>,
{
Map::set(
&self.inner.clone().into(),
&[key.into(), value.into()],
context,
)
}
#[inline]
pub fn get_size(&self, context: &mut Context<'_>) -> JsResult<JsValue> {
Map::get_size(&self.inner.clone().into(), &[], context)
}
pub fn delete<T>(&self, key: T, context: &mut Context<'_>) -> JsResult<JsValue>
where
T: Into<JsValue>,
{
Map::delete(&self.inner.clone().into(), &[key.into()], context)
}
pub fn get<T>(&self, key: T, context: &mut Context<'_>) -> JsResult<JsValue>
where
T: Into<JsValue>,
{
Map::get(&self.inner.clone().into(), &[key.into()], context)
}
#[inline]
pub fn clear(&self, context: &mut Context<'_>) -> JsResult<JsValue> {
Map::clear(&self.inner.clone().into(), &[], context)
}
pub fn has<T>(&self, key: T, context: &mut Context<'_>) -> JsResult<JsValue>
where
T: Into<JsValue>,
{
Map::has(&self.inner.clone().into(), &[key.into()], context)
}
#[inline]
pub fn for_each(
&self,
callback: JsFunction,
this_arg: JsValue,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
Map::for_each(
&self.inner.clone().into(),
&[callback.into(), this_arg],
context,
)
}
#[inline]
pub fn values(&self, context: &mut Context<'_>) -> JsResult<JsMapIterator> {
let iterator_record = Map::values(&self.inner.clone().into(), &[], context)?
.get_iterator(context, None, None)?;
let map_iterator_object = iterator_record.iterator();
JsMapIterator::from_object(map_iterator_object.clone())
}
}
impl From<JsMap> for JsObject {
#[inline]
fn from(o: JsMap) -> Self {
o.inner.clone()
}
}
impl From<JsMap> for JsValue {
#[inline]
fn from(o: JsMap) -> Self {
o.inner.clone().into()
}
}
impl Deref for JsMap {
type Target = JsObject;
#[inline]
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl JsObjectType for JsMap {}
impl TryFromJs for JsMap {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::Object(o) => Self::from_object(o.clone()),
_ => Err(JsNativeError::typ()
.with_message("value is not a Map object")
.into()),
}
}
}