use crate::core::JSMap;
use crate::js_array::set_array_length;
use crate::{
core::{
Expr, JSObjectDataPtr, PropertyKey, Value, evaluate_expr, initialize_collection_from_iterable, new_js_object_data,
obj_get_key_value, obj_set_key_value, values_equal,
},
error::JSError,
};
use std::cell::RefCell;
use std::rc::Rc;
pub(crate) fn handle_map_constructor(args: &[Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
let map = Rc::new(RefCell::new(JSMap { entries: Vec::new() }));
initialize_collection_from_iterable(args, env, "Map", |entry| {
if let Value::Object(entry_obj) = entry
&& let (Some(key_val), Some(value_val)) = (
obj_get_key_value(&entry_obj, &"0".into())?,
obj_get_key_value(&entry_obj, &"1".into())?,
)
{
map.borrow_mut()
.entries
.push((key_val.borrow().clone(), value_val.borrow().clone()));
}
Ok(())
})?;
let map_obj = new_js_object_data();
map_obj
.borrow_mut()
.insert(PropertyKey::String("__map__".to_string()), Rc::new(RefCell::new(Value::Map(map))));
Ok(Value::Object(map_obj))
}
pub(crate) fn handle_map_instance_method(
map: &Rc<RefCell<JSMap>>,
method: &str,
args: &[Expr],
env: &JSObjectDataPtr,
) -> Result<Value, JSError> {
match method {
"set" => {
if args.len() != 2 {
return Err(raise_eval_error!("Map.prototype.set requires exactly two arguments"));
}
let key = evaluate_expr(env, &args[0])?;
let value = evaluate_expr(env, &args[1])?;
map.borrow_mut().entries.retain(|(k, _)| !values_equal(k, &key));
map.borrow_mut().entries.push((key, value));
Ok(Value::Map(map.clone()))
}
"get" => {
if args.len() != 1 {
return Err(raise_eval_error!("Map.prototype.get requires exactly one argument"));
}
let key = evaluate_expr(env, &args[0])?;
for (k, v) in &map.borrow().entries {
if values_equal(k, &key) {
return Ok(v.clone());
}
}
Ok(Value::Undefined)
}
"has" => {
if args.len() != 1 {
return Err(raise_eval_error!("Map.prototype.has requires exactly one argument"));
}
let key = evaluate_expr(env, &args[0])?;
let has_key = map.borrow().entries.iter().any(|(k, _)| values_equal(k, &key));
Ok(Value::Boolean(has_key))
}
"delete" => {
if args.len() != 1 {
return Err(raise_eval_error!("Map.prototype.delete requires exactly one argument"));
}
let key = evaluate_expr(env, &args[0])?;
let initial_len = map.borrow().entries.len();
map.borrow_mut().entries.retain(|(k, _)| !values_equal(k, &key));
let deleted = map.borrow().entries.len() < initial_len;
Ok(Value::Boolean(deleted))
}
"clear" => {
if !args.is_empty() {
return Err(raise_eval_error!("Map.prototype.clear takes no arguments"));
}
map.borrow_mut().entries.clear();
Ok(Value::Undefined)
}
"size" => {
if !args.is_empty() {
return Err(raise_eval_error!("Map.prototype.size is a getter"));
}
Ok(Value::Number(map.borrow().entries.len() as f64))
}
"keys" => {
if !args.is_empty() {
return Err(raise_eval_error!("Map.prototype.keys takes no arguments"));
}
let keys_array = crate::js_array::create_array(env)?;
for (i, (key, _)) in map.borrow().entries.iter().enumerate() {
obj_set_key_value(&keys_array, &i.to_string().into(), key.clone())?;
}
set_array_length(&keys_array, map.borrow().entries.len())?;
Ok(Value::Object(keys_array))
}
"values" => {
if !args.is_empty() {
return Err(raise_eval_error!("Map.prototype.values takes no arguments"));
}
let values_array = crate::js_array::create_array(env)?;
for (i, (_, value)) in map.borrow().entries.iter().enumerate() {
obj_set_key_value(&values_array, &i.to_string().into(), value.clone())?;
}
set_array_length(&values_array, map.borrow().entries.len())?;
Ok(Value::Object(values_array))
}
"entries" => {
if !args.is_empty() {
return Err(raise_eval_error!("Map.prototype.entries takes no arguments"));
}
let entries_array = crate::js_array::create_array(env)?;
for (i, (key, value)) in map.borrow().entries.iter().enumerate() {
let entry_array = crate::js_array::create_array(env)?;
obj_set_key_value(&entry_array, &"0".into(), key.clone())?;
obj_set_key_value(&entry_array, &"1".into(), value.clone())?;
set_array_length(&entry_array, 2)?;
obj_set_key_value(&entries_array, &i.to_string().into(), Value::Object(entry_array))?;
}
set_array_length(&entries_array, map.borrow().entries.len())?;
Ok(Value::Object(entries_array))
}
_ => Err(raise_eval_error!(format!("Map.prototype.{} is not implemented", method))),
}
}