|
| 1 | +use crate::{ |
| 2 | + core::{Expr, JSObjectDataPtr, PropertyKey, Value, evaluate_expr}, |
| 3 | + error::JSError, |
| 4 | + raise_eval_error, |
| 5 | + unicode::utf8_to_utf16, |
| 6 | +}; |
| 7 | +use std::cell::RefCell; |
| 8 | +use std::rc::Rc; |
| 9 | + |
| 10 | +use crate::core::JSWeakMap; |
| 11 | + |
| 12 | +/// Handle WeakMap constructor calls |
| 13 | +pub(crate) fn handle_weakmap_constructor(args: &[Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> { |
| 14 | + let weakmap = Rc::new(RefCell::new(JSWeakMap { entries: Vec::new() })); |
| 15 | + |
| 16 | + if !args.is_empty() { |
| 17 | + if args.len() == 1 { |
| 18 | + // WeakMap(iterable) |
| 19 | + let iterable = evaluate_expr(env, &args[0])?; |
| 20 | + match iterable { |
| 21 | + Value::Object(obj) => { |
| 22 | + // Try to iterate over the object |
| 23 | + let mut i = 0; |
| 24 | + loop { |
| 25 | + let key = format!("{}", i); |
| 26 | + if let Some(entry_val) = obj_get_value(&obj, &key.into())? { |
| 27 | + let entry = entry_val.borrow().clone(); |
| 28 | + if let Value::Object(entry_obj) = entry |
| 29 | + && let (Some(key_val), Some(value_val)) = |
| 30 | + (obj_get_value(&entry_obj, &"0".into())?, obj_get_value(&entry_obj, &"1".into())?) |
| 31 | + { |
| 32 | + let key_obj = key_val.borrow().clone(); |
| 33 | + let value_obj = value_val.borrow().clone(); |
| 34 | + |
| 35 | + // Check if key is an object |
| 36 | + if let Value::Object(ref obj) = key_obj { |
| 37 | + let weak_key = Rc::downgrade(obj); |
| 38 | + weakmap.borrow_mut().entries.push((weak_key, value_obj)); |
| 39 | + } else { |
| 40 | + return Err(raise_eval_error!("WeakMap keys must be objects")); |
| 41 | + } |
| 42 | + } |
| 43 | + } else { |
| 44 | + break; |
| 45 | + } |
| 46 | + i += 1; |
| 47 | + } |
| 48 | + } |
| 49 | + _ => { |
| 50 | + return Err(raise_eval_error!("WeakMap constructor requires an iterable")); |
| 51 | + } |
| 52 | + } |
| 53 | + } else { |
| 54 | + return Err(raise_eval_error!("WeakMap constructor takes at most one argument")); |
| 55 | + } |
| 56 | + } |
| 57 | + |
| 58 | + Ok(Value::WeakMap(weakmap)) |
| 59 | +} |
| 60 | + |
| 61 | +/// Handle WeakMap instance method calls |
| 62 | +pub(crate) fn handle_weakmap_instance_method( |
| 63 | + weakmap: &Rc<RefCell<JSWeakMap>>, |
| 64 | + method: &str, |
| 65 | + args: &[Expr], |
| 66 | + env: &JSObjectDataPtr, |
| 67 | +) -> Result<Value, JSError> { |
| 68 | + match method { |
| 69 | + "set" => { |
| 70 | + if args.len() != 2 { |
| 71 | + return Err(raise_eval_error!("WeakMap.prototype.set requires exactly two arguments")); |
| 72 | + } |
| 73 | + let key = evaluate_expr(env, &args[0])?; |
| 74 | + let value = evaluate_expr(env, &args[1])?; |
| 75 | + |
| 76 | + // Check if key is an object |
| 77 | + let key_obj_rc = match key { |
| 78 | + Value::Object(ref obj) => obj.clone(), |
| 79 | + _ => return Err(raise_eval_error!("WeakMap keys must be objects")), |
| 80 | + }; |
| 81 | + |
| 82 | + let weak_key = Rc::downgrade(&key_obj_rc); |
| 83 | + |
| 84 | + // Remove existing entry with same key (if still alive) |
| 85 | + weakmap.borrow_mut().entries.retain(|(k, _)| { |
| 86 | + if let Some(strong_k) = k.upgrade() { |
| 87 | + !Rc::ptr_eq(&key_obj_rc, &strong_k) |
| 88 | + } else { |
| 89 | + false // Remove dead entries |
| 90 | + } |
| 91 | + }); |
| 92 | + |
| 93 | + // Add new entry |
| 94 | + weakmap.borrow_mut().entries.push((weak_key, value)); |
| 95 | + |
| 96 | + Ok(Value::WeakMap(weakmap.clone())) |
| 97 | + } |
| 98 | + "get" => { |
| 99 | + if args.len() != 1 { |
| 100 | + return Err(raise_eval_error!("WeakMap.prototype.get requires exactly one argument")); |
| 101 | + } |
| 102 | + let key = evaluate_expr(env, &args[0])?; |
| 103 | + |
| 104 | + let key_obj_rc = match key { |
| 105 | + Value::Object(ref obj) => obj, |
| 106 | + _ => return Ok(Value::Undefined), |
| 107 | + }; |
| 108 | + |
| 109 | + // Clean up dead entries and find the key |
| 110 | + let mut result = None; |
| 111 | + weakmap.borrow_mut().entries.retain(|(k, v)| { |
| 112 | + if let Some(strong_k) = k.upgrade() { |
| 113 | + if Rc::ptr_eq(key_obj_rc, &strong_k) { |
| 114 | + result = Some(v.clone()); |
| 115 | + } |
| 116 | + true // Keep alive entries |
| 117 | + } else { |
| 118 | + false // Remove dead entries |
| 119 | + } |
| 120 | + }); |
| 121 | + |
| 122 | + Ok(result.unwrap_or(Value::Undefined)) |
| 123 | + } |
| 124 | + "has" => { |
| 125 | + if args.len() != 1 { |
| 126 | + return Err(raise_eval_error!("WeakMap.prototype.has requires exactly one argument")); |
| 127 | + } |
| 128 | + let key = evaluate_expr(env, &args[0])?; |
| 129 | + |
| 130 | + let key_obj_rc = match key { |
| 131 | + Value::Object(ref obj) => obj, |
| 132 | + _ => return Ok(Value::Boolean(false)), |
| 133 | + }; |
| 134 | + |
| 135 | + // Clean up dead entries and check if key exists |
| 136 | + let mut found = false; |
| 137 | + weakmap.borrow_mut().entries.retain(|(k, _)| { |
| 138 | + if let Some(strong_k) = k.upgrade() { |
| 139 | + if Rc::ptr_eq(key_obj_rc, &strong_k) { |
| 140 | + found = true; |
| 141 | + } |
| 142 | + true // Keep alive entries |
| 143 | + } else { |
| 144 | + false // Remove dead entries |
| 145 | + } |
| 146 | + }); |
| 147 | + |
| 148 | + Ok(Value::Boolean(found)) |
| 149 | + } |
| 150 | + "delete" => { |
| 151 | + if args.len() != 1 { |
| 152 | + return Err(raise_eval_error!("WeakMap.prototype.delete requires exactly one argument")); |
| 153 | + } |
| 154 | + let key = evaluate_expr(env, &args[0])?; |
| 155 | + |
| 156 | + let key_obj_rc = match key { |
| 157 | + Value::Object(ref obj) => obj, |
| 158 | + _ => return Ok(Value::Boolean(false)), |
| 159 | + }; |
| 160 | + |
| 161 | + // Clean up dead entries and remove the key |
| 162 | + let mut deleted = false; |
| 163 | + weakmap.borrow_mut().entries.retain(|(k, _)| { |
| 164 | + if let Some(strong_k) = k.upgrade() { |
| 165 | + if Rc::ptr_eq(key_obj_rc, &strong_k) { |
| 166 | + deleted = true; |
| 167 | + false // Remove this entry |
| 168 | + } else { |
| 169 | + true // Keep other alive entries |
| 170 | + } |
| 171 | + } else { |
| 172 | + false // Remove dead entries |
| 173 | + } |
| 174 | + }); |
| 175 | + |
| 176 | + Ok(Value::Boolean(deleted)) |
| 177 | + } |
| 178 | + "toString" => { |
| 179 | + if !args.is_empty() { |
| 180 | + return Err(raise_eval_error!("WeakMap.prototype.toString takes no arguments")); |
| 181 | + } |
| 182 | + Ok(Value::String(utf8_to_utf16("[object WeakMap]"))) |
| 183 | + } |
| 184 | + _ => Err(raise_eval_error!(format!("WeakMap.prototype.{} is not implemented", method))), |
| 185 | + } |
| 186 | +} |
| 187 | + |
| 188 | +// Helper function to get object property value |
| 189 | +fn obj_get_value(js_obj: &JSObjectDataPtr, key: &PropertyKey) -> Result<Option<Rc<RefCell<Value>>>, JSError> { |
| 190 | + let mut current: Option<JSObjectDataPtr> = Some(js_obj.clone()); |
| 191 | + while let Some(cur) = current { |
| 192 | + if let Some(val) = cur.borrow().properties.get(key) { |
| 193 | + return Ok(Some(val.clone())); |
| 194 | + } |
| 195 | + current = cur.borrow().prototype.clone(); |
| 196 | + } |
| 197 | + Ok(None) |
| 198 | +} |
0 commit comments