javascript 0.1.13

A JavaScript engine implementation in Rust
Documentation
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;

/// Handle Map constructor calls
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(())
    })?;

    // Create a wrapper object for the Map
    let map_obj = new_js_object_data();
    // Store the actual map data
    map_obj
        .borrow_mut()
        .insert(PropertyKey::String("__map__".to_string()), Rc::new(RefCell::new(Value::Map(map))));

    Ok(Value::Object(map_obj))
}

/// Handle Map instance method calls
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])?;

            // Remove existing entry with same key
            map.borrow_mut().entries.retain(|(k, _)| !values_equal(k, &key));
            // Add new entry
            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"));
            }
            // Create an array of keys
            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 length
            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"));
            }
            // Create an array of values
            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 length
            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"));
            }
            // Create an array of [key, value] pairs
            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 length
            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))),
    }
}