javascript/core/
value.rs

1#![allow(clippy::collapsible_if, clippy::collapsible_match)]
2
3use num_bigint::BigInt;
4use std::sync::{Arc, Mutex};
5use std::{cell::RefCell, rc::Rc};
6
7use crate::js_date::is_date_object;
8use crate::{
9    JSError,
10    core::{BinaryOp, Expr, PropertyKey, Statement, evaluate_statements, get_well_known_symbol_rc, utf8_to_utf16},
11    js_array::is_array,
12    js_class::ClassDefinition,
13    js_promise::JSPromise,
14    raise_eval_error, raise_type_error,
15};
16
17#[derive(Clone, Debug)]
18pub struct JSMap {
19    pub entries: Vec<(Value, Value)>, // key-value pairs
20}
21
22#[derive(Clone, Debug)]
23pub struct JSSet {
24    pub values: Vec<Value>,
25}
26
27#[derive(Clone, Debug)]
28pub struct JSWeakMap {
29    pub entries: Vec<(std::rc::Weak<RefCell<JSObjectData>>, Value)>, // weak key-value pairs
30}
31
32#[derive(Clone, Debug)]
33pub struct JSWeakSet {
34    pub values: Vec<std::rc::Weak<RefCell<JSObjectData>>>, // weak values
35}
36
37#[derive(Clone, Debug)]
38pub struct JSGenerator {
39    pub params: Vec<(String, Option<Box<Expr>>)>,
40    pub body: Vec<Statement>,
41    pub env: JSObjectDataPtr, // captured environment
42    pub state: GeneratorState,
43}
44
45#[derive(Clone, Debug)]
46pub struct JSProxy {
47    pub target: Value,  // The target object being proxied
48    pub handler: Value, // The handler object with traps
49    pub revoked: bool,  // Whether this proxy has been revoked
50}
51
52#[derive(Clone, Debug)]
53pub struct JSArrayBuffer {
54    // Use an Arc<Mutex<Vec<u8>>> so the underlying bytes can be shared
55    // safely between threads (for SharedArrayBuffer semantics) while
56    // remaining compatible with the existing Rc<RefCell<JSArrayBuffer>>
57    // wrapper used across the project.
58    pub data: Arc<Mutex<Vec<u8>>>, // The underlying byte buffer
59    pub detached: bool,            // Whether the buffer has been detached
60    pub shared: bool,              // Whether this buffer was created as a SharedArrayBuffer
61}
62
63#[derive(Clone, Debug)]
64pub struct JSDataView {
65    pub buffer: Rc<RefCell<JSArrayBuffer>>, // Reference to the underlying ArrayBuffer
66    pub byte_offset: usize,                 // Starting byte offset in the buffer
67    pub byte_length: usize,                 // Length in bytes
68}
69
70#[derive(Clone, Debug, PartialEq)]
71pub enum TypedArrayKind {
72    Int8,
73    Uint8,
74    Uint8Clamped,
75    Int16,
76    Uint16,
77    Int32,
78    Uint32,
79    Float32,
80    Float64,
81    BigInt64,
82    BigUint64,
83}
84
85#[derive(Clone, Debug)]
86pub struct JSTypedArray {
87    pub kind: TypedArrayKind,
88    pub buffer: Rc<RefCell<JSArrayBuffer>>, // Reference to the underlying ArrayBuffer
89    pub byte_offset: usize,                 // Starting byte offset in the buffer
90    pub length: usize,                      // Number of elements
91}
92
93pub fn parse_bigint_string(raw: &str) -> Result<BigInt, JSError> {
94    let s = if let Some(st) = raw.strip_suffix('n') { st } else { raw };
95    let (radix, num_str) = if s.starts_with("0x") || s.starts_with("0X") {
96        (16, &s[2..])
97    } else if s.starts_with("0b") || s.starts_with("0B") {
98        (2, &s[2..])
99    } else if s.starts_with("0o") || s.starts_with("0O") {
100        (8, &s[2..])
101    } else {
102        (10, s)
103    };
104    BigInt::parse_bytes(num_str.as_bytes(), radix).ok_or(raise_eval_error!("invalid bigint"))
105}
106
107impl JSTypedArray {
108    /// Get the size in bytes of each element in this TypedArray
109    pub fn element_size(&self) -> usize {
110        match self.kind {
111            TypedArrayKind::Int8 | TypedArrayKind::Uint8 | TypedArrayKind::Uint8Clamped => 1,
112            TypedArrayKind::Int16 | TypedArrayKind::Uint16 => 2,
113            TypedArrayKind::Int32 | TypedArrayKind::Uint32 | TypedArrayKind::Float32 => 4,
114            TypedArrayKind::Float64 | TypedArrayKind::BigInt64 | TypedArrayKind::BigUint64 => 8,
115        }
116    }
117
118    /// Get a value at the specified index
119    pub fn get(&self, index: usize) -> Result<i64, JSError> {
120        if index >= self.length {
121            return Err(raise_type_error!("Index out of bounds"));
122        }
123
124        let buffer = self.buffer.borrow();
125        if buffer.detached {
126            return Err(raise_type_error!("ArrayBuffer is detached"));
127        }
128
129        let byte_index = self.byte_offset + index * self.element_size();
130        let data_lock = buffer.data.lock().unwrap();
131        if byte_index + self.element_size() > data_lock.len() {
132            return Err(raise_type_error!("Index out of bounds"));
133        }
134
135        match self.kind {
136            TypedArrayKind::Int8 => Ok(data_lock[byte_index] as i8 as i64),
137            TypedArrayKind::Uint8 | TypedArrayKind::Uint8Clamped => Ok(data_lock[byte_index] as i64),
138            TypedArrayKind::Int16 => {
139                let bytes = &data_lock[byte_index..byte_index + 2];
140                Ok(i16::from_le_bytes([bytes[0], bytes[1]]) as i64)
141            }
142            TypedArrayKind::Uint16 => {
143                let bytes = &data_lock[byte_index..byte_index + 2];
144                Ok(u16::from_le_bytes([bytes[0], bytes[1]]) as i64)
145            }
146            TypedArrayKind::Int32 => {
147                let bytes = &data_lock[byte_index..byte_index + 4];
148                Ok(i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as i64)
149            }
150            TypedArrayKind::Uint32 => {
151                let bytes = &data_lock[byte_index..byte_index + 4];
152                Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as i64)
153            }
154            TypedArrayKind::Float32 => {
155                let bytes = &data_lock[byte_index..byte_index + 4];
156                let float_val = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
157                Ok(float_val as i64) // Simplified conversion
158            }
159            TypedArrayKind::Float64 => {
160                let bytes = &data_lock[byte_index..byte_index + 8];
161                let float_val = f64::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]]);
162                Ok(float_val as i64) // Simplified conversion
163            }
164            TypedArrayKind::BigInt64 | TypedArrayKind::BigUint64 => {
165                // For BigInt types, return 0 for now (simplified)
166                Ok(0)
167            }
168        }
169    }
170
171    /// Set a value at the specified index
172    pub fn set(&mut self, index: usize, value: i64) -> Result<(), JSError> {
173        if index >= self.length {
174            return Err(raise_type_error!("Index out of bounds"));
175        }
176        let buffer = self.buffer.borrow_mut();
177        if buffer.detached {
178            return Err(raise_type_error!("ArrayBuffer is detached"));
179        }
180
181        let byte_index = self.byte_offset + index * self.element_size();
182        let mut data_lock = buffer.data.lock().unwrap();
183        if byte_index + self.element_size() > data_lock.len() {
184            return Err(raise_type_error!("Index out of bounds"));
185        }
186
187        match self.kind {
188            TypedArrayKind::Int8 => {
189                data_lock[byte_index] = value as i8 as u8;
190            }
191            TypedArrayKind::Uint8 | TypedArrayKind::Uint8Clamped => {
192                data_lock[byte_index] = value as u8;
193            }
194            TypedArrayKind::Int16 => {
195                let bytes = (value as i16).to_le_bytes();
196                data_lock[byte_index..byte_index + 2].copy_from_slice(&bytes);
197            }
198            TypedArrayKind::Uint16 => {
199                let bytes = (value as u16).to_le_bytes();
200                data_lock[byte_index..byte_index + 2].copy_from_slice(&bytes);
201            }
202            TypedArrayKind::Int32 => {
203                let bytes = (value as i32).to_le_bytes();
204                data_lock[byte_index..byte_index + 4].copy_from_slice(&bytes);
205            }
206            TypedArrayKind::Uint32 => {
207                let bytes = (value as u32).to_le_bytes();
208                data_lock[byte_index..byte_index + 4].copy_from_slice(&bytes);
209            }
210            TypedArrayKind::Float32 => {
211                let bytes = (value as f32).to_le_bytes();
212                data_lock[byte_index..byte_index + 4].copy_from_slice(&bytes);
213            }
214            TypedArrayKind::Float64 => {
215                let bytes = (value as f64).to_le_bytes();
216                data_lock[byte_index..byte_index + 8].copy_from_slice(&bytes);
217            }
218            TypedArrayKind::BigInt64 | TypedArrayKind::BigUint64 => {
219                // For BigInt types, do nothing for now (simplified)
220            }
221        }
222
223        Ok(())
224    }
225}
226
227impl JSDataView {
228    /// Get an 8-bit signed integer at the specified byte offset
229    pub fn get_int8(&self, offset: usize) -> Result<i8, JSError> {
230        self.check_bounds(offset, 1)?;
231        let buffer = self.buffer.borrow();
232        let data_lock = buffer.data.lock().unwrap();
233        Ok(data_lock[self.byte_offset + offset] as i8)
234    }
235
236    /// Get an 8-bit unsigned integer at the specified byte offset
237    pub fn get_uint8(&self, offset: usize) -> Result<u8, JSError> {
238        self.check_bounds(offset, 1)?;
239        let buffer = self.buffer.borrow();
240        let data_lock = buffer.data.lock().unwrap();
241        Ok(data_lock[self.byte_offset + offset])
242    }
243
244    /// Get a 16-bit signed integer at the specified byte offset
245    pub fn get_int16(&self, offset: usize, little_endian: bool) -> Result<i16, JSError> {
246        self.check_bounds(offset, 2)?;
247        let buffer = self.buffer.borrow();
248        let data_lock = buffer.data.lock().unwrap();
249        let bytes = &data_lock[self.byte_offset + offset..self.byte_offset + offset + 2];
250        if little_endian {
251            Ok(i16::from_le_bytes([bytes[0], bytes[1]]))
252        } else {
253            Ok(i16::from_be_bytes([bytes[0], bytes[1]]))
254        }
255    }
256
257    /// Get a 16-bit unsigned integer at the specified byte offset
258    pub fn get_uint16(&self, offset: usize, little_endian: bool) -> Result<u16, JSError> {
259        self.check_bounds(offset, 2)?;
260        let buffer = self.buffer.borrow();
261        let data_lock = buffer.data.lock().unwrap();
262        let bytes = &data_lock[self.byte_offset + offset..self.byte_offset + offset + 2];
263        if little_endian {
264            Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
265        } else {
266            Ok(u16::from_be_bytes([bytes[0], bytes[1]]))
267        }
268    }
269
270    /// Get a 32-bit signed integer at the specified byte offset
271    pub fn get_int32(&self, offset: usize, little_endian: bool) -> Result<i32, JSError> {
272        self.check_bounds(offset, 4)?;
273        let buffer = self.buffer.borrow();
274        let data_lock = buffer.data.lock().unwrap();
275        let bytes = &data_lock[self.byte_offset + offset..self.byte_offset + offset + 4];
276        if little_endian {
277            Ok(i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
278        } else {
279            Ok(i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
280        }
281    }
282
283    /// Get a 32-bit unsigned integer at the specified byte offset
284    pub fn get_uint32(&self, offset: usize, little_endian: bool) -> Result<u32, JSError> {
285        self.check_bounds(offset, 4)?;
286        let buffer = self.buffer.borrow();
287        let data_lock = buffer.data.lock().unwrap();
288        let bytes = &data_lock[self.byte_offset + offset..self.byte_offset + offset + 4];
289        if little_endian {
290            Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
291        } else {
292            Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
293        }
294    }
295
296    /// Get a 32-bit float at the specified byte offset
297    pub fn get_float32(&self, offset: usize, little_endian: bool) -> Result<f32, JSError> {
298        self.check_bounds(offset, 4)?;
299        let buffer = self.buffer.borrow();
300        let data_lock = buffer.data.lock().unwrap();
301        let bytes = &data_lock[self.byte_offset + offset..self.byte_offset + offset + 4];
302        if little_endian {
303            Ok(f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
304        } else {
305            Ok(f32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
306        }
307    }
308
309    /// Get a 64-bit float at the specified byte offset
310    pub fn get_float64(&self, offset: usize, little_endian: bool) -> Result<f64, JSError> {
311        self.check_bounds(offset, 8)?;
312        let buffer = self.buffer.borrow();
313        let data_lock = buffer.data.lock().unwrap();
314        let bytes = &data_lock[self.byte_offset + offset..self.byte_offset + offset + 8];
315        if little_endian {
316            Ok(f64::from_le_bytes([
317                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
318            ]))
319        } else {
320            Ok(f64::from_be_bytes([
321                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
322            ]))
323        }
324    }
325
326    /// Get a 64-bit signed BigInt at the specified byte offset
327    pub fn get_big_int64(&self, offset: usize, little_endian: bool) -> Result<i64, JSError> {
328        self.check_bounds(offset, 8)?;
329        let buffer = self.buffer.borrow();
330        let data_lock = buffer.data.lock().unwrap();
331        let bytes = &data_lock[self.byte_offset + offset..self.byte_offset + offset + 8];
332        if little_endian {
333            Ok(i64::from_le_bytes([
334                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
335            ]))
336        } else {
337            Ok(i64::from_be_bytes([
338                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
339            ]))
340        }
341    }
342
343    /// Get a 64-bit unsigned BigInt at the specified byte offset
344    pub fn get_big_uint64(&self, offset: usize, little_endian: bool) -> Result<u64, JSError> {
345        self.check_bounds(offset, 8)?;
346        let buffer = self.buffer.borrow();
347        let data_lock = buffer.data.lock().unwrap();
348        let bytes = &data_lock[self.byte_offset + offset..self.byte_offset + offset + 8];
349        if little_endian {
350            Ok(u64::from_le_bytes([
351                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
352            ]))
353        } else {
354            Ok(u64::from_be_bytes([
355                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
356            ]))
357        }
358    }
359
360    /// Set an 8-bit signed integer at the specified byte offset
361    pub fn set_int8(&mut self, offset: usize, value: i8) -> Result<(), JSError> {
362        self.check_bounds(offset, 1)?;
363        let buffer = self.buffer.borrow_mut();
364        let mut data_lock = buffer.data.lock().unwrap();
365        data_lock[self.byte_offset + offset] = value as u8;
366        Ok(())
367    }
368
369    /// Set an 8-bit unsigned integer at the specified byte offset
370    pub fn set_uint8(&mut self, offset: usize, value: u8) -> Result<(), JSError> {
371        self.check_bounds(offset, 1)?;
372        let buffer = self.buffer.borrow_mut();
373        let mut data_lock = buffer.data.lock().unwrap();
374        data_lock[self.byte_offset + offset] = value;
375        Ok(())
376    }
377
378    /// Set a 16-bit signed integer at the specified byte offset
379    pub fn set_int16(&mut self, offset: usize, value: i16, little_endian: bool) -> Result<(), JSError> {
380        self.check_bounds(offset, 2)?;
381        let buffer = self.buffer.borrow_mut();
382        let bytes = if little_endian { value.to_le_bytes() } else { value.to_be_bytes() };
383        let mut data_lock = buffer.data.lock().unwrap();
384        data_lock[self.byte_offset + offset..self.byte_offset + offset + 2].copy_from_slice(&bytes);
385        Ok(())
386    }
387
388    /// Set a 16-bit unsigned integer at the specified byte offset
389    pub fn set_uint16(&mut self, offset: usize, value: u16, little_endian: bool) -> Result<(), JSError> {
390        self.check_bounds(offset, 2)?;
391        let buffer = self.buffer.borrow_mut();
392        let bytes = if little_endian { value.to_le_bytes() } else { value.to_be_bytes() };
393        let mut data_lock = buffer.data.lock().unwrap();
394        data_lock[self.byte_offset + offset..self.byte_offset + offset + 2].copy_from_slice(&bytes);
395        Ok(())
396    }
397
398    /// Set a 32-bit signed integer at the specified byte offset
399    pub fn set_int32(&mut self, offset: usize, value: i32, little_endian: bool) -> Result<(), JSError> {
400        self.check_bounds(offset, 4)?;
401        let buffer = self.buffer.borrow_mut();
402        let bytes = if little_endian { value.to_le_bytes() } else { value.to_be_bytes() };
403        let mut data_lock = buffer.data.lock().unwrap();
404        data_lock[self.byte_offset + offset..self.byte_offset + offset + 4].copy_from_slice(&bytes);
405        Ok(())
406    }
407
408    /// Set a 32-bit unsigned integer at the specified byte offset
409    pub fn set_uint32(&mut self, offset: usize, value: u32, little_endian: bool) -> Result<(), JSError> {
410        self.check_bounds(offset, 4)?;
411        let buffer = self.buffer.borrow_mut();
412        let bytes = if little_endian { value.to_le_bytes() } else { value.to_be_bytes() };
413        let mut data_lock = buffer.data.lock().unwrap();
414        data_lock[self.byte_offset + offset..self.byte_offset + offset + 4].copy_from_slice(&bytes);
415        Ok(())
416    }
417
418    /// Set a 32-bit float at the specified byte offset
419    pub fn set_float32(&mut self, offset: usize, value: f32, little_endian: bool) -> Result<(), JSError> {
420        self.check_bounds(offset, 4)?;
421        let buffer = self.buffer.borrow_mut();
422        let bytes = if little_endian { value.to_le_bytes() } else { value.to_be_bytes() };
423        let mut data_lock = buffer.data.lock().unwrap();
424        data_lock[self.byte_offset + offset..self.byte_offset + offset + 4].copy_from_slice(&bytes);
425        Ok(())
426    }
427
428    /// Set a 64-bit float at the specified byte offset
429    pub fn set_float64(&mut self, offset: usize, value: f64, little_endian: bool) -> Result<(), JSError> {
430        self.check_bounds(offset, 8)?;
431        let buffer = self.buffer.borrow_mut();
432        let bytes = if little_endian { value.to_le_bytes() } else { value.to_be_bytes() };
433        let mut data_lock = buffer.data.lock().unwrap();
434        data_lock[self.byte_offset + offset..self.byte_offset + offset + 8].copy_from_slice(&bytes);
435        Ok(())
436    }
437
438    /// Set a 64-bit signed BigInt at the specified byte offset
439    pub fn set_big_int64(&mut self, offset: usize, value: i64, little_endian: bool) -> Result<(), JSError> {
440        self.check_bounds(offset, 8)?;
441        let buffer = self.buffer.borrow_mut();
442        let bytes = if little_endian { value.to_le_bytes() } else { value.to_be_bytes() };
443        let mut data_lock = buffer.data.lock().unwrap();
444        data_lock[self.byte_offset + offset..self.byte_offset + offset + 8].copy_from_slice(&bytes);
445        Ok(())
446    }
447
448    /// Set a 64-bit unsigned BigInt at the specified byte offset
449    pub fn set_big_uint64(&mut self, offset: usize, value: u64, little_endian: bool) -> Result<(), JSError> {
450        self.check_bounds(offset, 8)?;
451        let buffer = self.buffer.borrow_mut();
452        let bytes = if little_endian { value.to_le_bytes() } else { value.to_be_bytes() };
453        let mut data_lock = buffer.data.lock().unwrap();
454        data_lock[self.byte_offset + offset..self.byte_offset + offset + 8].copy_from_slice(&bytes);
455        Ok(())
456    }
457
458    /// Helper method to check bounds and buffer state
459    fn check_bounds(&self, offset: usize, size: usize) -> Result<(), JSError> {
460        let buffer = self.buffer.borrow();
461        if buffer.detached {
462            return Err(raise_type_error!("ArrayBuffer is detached"));
463        }
464        if offset + size > self.byte_length {
465            return Err(raise_type_error!("Offset out of bounds"));
466        }
467        Ok(())
468    }
469}
470
471#[derive(Clone, Debug)]
472pub enum GeneratorState {
473    NotStarted,
474    Running { pc: usize, stack: Vec<Value> },   // program counter and value stack
475    Suspended { pc: usize, stack: Vec<Value> }, // suspended at yield
476    Completed,
477}
478
479pub type JSObjectDataPtr = Rc<RefCell<JSObjectData>>;
480
481#[inline]
482pub fn new_js_object_data() -> JSObjectDataPtr {
483    Rc::new(RefCell::new(JSObjectData::new()))
484}
485
486#[derive(Clone, Default)]
487pub struct JSObjectData {
488    pub properties: std::collections::HashMap<PropertyKey, Rc<RefCell<Value>>>,
489    pub constants: std::collections::HashSet<String>,
490    /// Tracks keys that should not be enumerated by `Object.keys` / `Object.values`.
491    pub non_enumerable: std::collections::HashSet<PropertyKey>,
492    /// Tracks keys that are non-writable (read-only)
493    pub non_writable: std::collections::HashSet<PropertyKey>,
494    /// Tracks keys that are non-configurable
495    pub non_configurable: std::collections::HashSet<PropertyKey>,
496    pub prototype: Option<Rc<RefCell<JSObjectData>>>,
497    pub is_function_scope: bool,
498}
499
500impl std::fmt::Debug for JSObjectData {
501    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
502        write!(
503            f,
504            "JSObjectData {{ properties: {}, constants: {}, prototype: {}, is_function_scope: {} }}",
505            self.properties.len(),
506            self.constants.len(),
507            self.prototype.is_some(),
508            self.is_function_scope
509        )
510    }
511}
512
513impl JSObjectData {
514    pub fn new() -> Self {
515        JSObjectData::default()
516    }
517
518    pub fn insert(&mut self, key: PropertyKey, val: Rc<RefCell<Value>>) {
519        self.properties.insert(key, val);
520    }
521
522    /// Mark a property key as non-enumerable on this object
523    pub fn set_non_enumerable(&mut self, key: PropertyKey) {
524        self.non_enumerable.insert(key);
525    }
526
527    /// Mark a property key as non-writable on this object
528    pub fn set_non_writable(&mut self, key: PropertyKey) {
529        self.non_writable.insert(key);
530    }
531
532    /// Mark a property key as non-configurable on this object
533    pub fn set_non_configurable(&mut self, key: PropertyKey) {
534        self.non_configurable.insert(key);
535    }
536
537    /// Check whether a key is writable (default true)
538    pub fn is_writable(&self, key: &PropertyKey) -> bool {
539        !self.non_writable.contains(key)
540    }
541
542    /// Check whether a key is configurable (default true)
543    pub fn is_configurable(&self, key: &PropertyKey) -> bool {
544        !self.non_configurable.contains(key)
545    }
546
547    /// Check whether a key is enumerable (default true)
548    pub fn is_enumerable(&self, key: &PropertyKey) -> bool {
549        !self.non_enumerable.contains(key)
550    }
551
552    pub fn get(&self, key: &PropertyKey) -> Option<Rc<RefCell<Value>>> {
553        self.properties.get(key).cloned()
554    }
555
556    pub fn contains_key(&self, key: &PropertyKey) -> bool {
557        self.properties.contains_key(key)
558    }
559
560    pub fn remove(&mut self, key: &PropertyKey) -> Option<Rc<RefCell<Value>>> {
561        self.properties.remove(key)
562    }
563
564    pub fn keys(&self) -> std::collections::hash_map::Keys<'_, PropertyKey, Rc<RefCell<Value>>> {
565        self.properties.keys()
566    }
567
568    pub fn is_const(&self, key: &str) -> bool {
569        self.constants.contains(key)
570    }
571
572    pub fn set_const(&mut self, key: String) {
573        self.constants.insert(key);
574    }
575}
576
577#[derive(Clone, Debug)]
578pub struct SymbolData {
579    pub description: Option<String>,
580}
581
582pub type ValuePtr = Rc<RefCell<Value>>;
583
584#[derive(Debug, Clone)]
585pub enum Value {
586    Number(f64),
587    BigInt(BigInt),
588    String(Vec<u16>), // UTF-16 code units
589    Boolean(bool),
590    Undefined,
591    Null,
592    Object(JSObjectDataPtr),                                                         // Object with properties
593    Function(String),                                                                // Function name
594    Closure(Vec<(String, Option<Box<Expr>>)>, Vec<Statement>, JSObjectDataPtr),      // parameters, body, captured environment
595    AsyncClosure(Vec<(String, Option<Box<Expr>>)>, Vec<Statement>, JSObjectDataPtr), // parameters, body, captured environment
596    GeneratorFunction(Option<String>, Vec<(String, Option<Box<Expr>>)>, Vec<Statement>, JSObjectDataPtr), // optional name, parameters, body, captured environment
597    ClassDefinition(Rc<ClassDefinition>),                                                                 // Class definition
598    Getter(Vec<Statement>, JSObjectDataPtr), // getter body, captured environment
599    Setter(Vec<(String, Option<Box<Expr>>)>, Vec<Statement>, JSObjectDataPtr), // setter parameter, body, captured environment
600    Property {
601        // Property descriptor with getter/setter/value
602        value: Option<Rc<RefCell<Value>>>,
603        getter: Option<(Vec<Statement>, JSObjectDataPtr)>,
604        #[allow(clippy::type_complexity)]
605        setter: Option<(Vec<(String, Option<Box<Expr>>)>, Vec<Statement>, JSObjectDataPtr)>,
606    },
607    Promise(Rc<RefCell<JSPromise>>),         // Promise object
608    Symbol(Rc<SymbolData>),                  // Symbol primitive with description
609    Map(Rc<RefCell<JSMap>>),                 // Map object
610    Set(Rc<RefCell<JSSet>>),                 // Set object
611    WeakMap(Rc<RefCell<JSWeakMap>>),         // WeakMap object
612    WeakSet(Rc<RefCell<JSWeakSet>>),         // WeakSet object
613    Generator(Rc<RefCell<JSGenerator>>),     // Generator object
614    Proxy(Rc<RefCell<JSProxy>>),             // Proxy object
615    ArrayBuffer(Rc<RefCell<JSArrayBuffer>>), // ArrayBuffer object
616    DataView(Rc<RefCell<JSDataView>>),       // DataView object
617    TypedArray(Rc<RefCell<JSTypedArray>>),   // TypedArray object
618}
619
620impl std::fmt::Display for Value {
621    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
622        write!(f, "{}", value_to_string(self))
623    }
624}
625
626pub fn is_truthy(val: &Value) -> bool {
627    match val {
628        Value::BigInt(b) => b != &BigInt::from(0),
629        Value::Number(n) => *n != 0.0 && !n.is_nan(),
630        Value::String(s) => !s.is_empty(),
631        Value::Boolean(b) => *b,
632        Value::Undefined => false,
633        Value::Null => false,
634        Value::Object(_) => true,
635        Value::Function(_) => true,
636        Value::Closure(_, _, _) => true,
637        Value::AsyncClosure(_, _, _) => true,
638        Value::GeneratorFunction(..) => true,
639        Value::ClassDefinition(_) => true,
640        Value::Getter(_, _) => true,
641        Value::Setter(_, _, _) => true,
642        Value::Property { .. } => true,
643        Value::Promise(_) => true,
644        Value::Symbol(_) => true,
645        Value::Map(_) => true,
646        Value::Set(_) => true,
647        Value::WeakMap(_) => true,
648        Value::WeakSet(_) => true,
649        Value::Generator(_) => true,
650        Value::Proxy(_) => true,
651        Value::ArrayBuffer(_) => true,
652        Value::DataView(_) => true,
653        Value::TypedArray(_) => true,
654    }
655}
656
657// Helper function to compare two values for equality
658pub fn values_equal(a: &Value, b: &Value) -> bool {
659    match (a, b) {
660        (Value::BigInt(sa), Value::BigInt(sb)) => sa == sb,
661        (Value::Number(na), Value::Number(nb)) => na == nb,
662        (Value::String(sa), Value::String(sb)) => sa == sb,
663        (Value::Boolean(ba), Value::Boolean(bb)) => ba == bb,
664        (Value::Undefined, Value::Undefined) => true,
665        (Value::Null, Value::Null) => true,
666        (Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b), // Objects equal only if same reference
667        (Value::Symbol(sa), Value::Symbol(sb)) => Rc::ptr_eq(sa, sb), // Symbols are equal if same reference
668        _ => false,                                               // Different types are not equal
669    }
670}
671
672// Helper function to convert value to string for display
673pub fn value_to_string(val: &Value) -> String {
674    match val {
675        Value::Number(n) => n.to_string(),
676        Value::BigInt(b) => format!("{b}n"),
677        Value::String(s) => format!("\"{}\"", String::from_utf16_lossy(s)),
678        Value::Boolean(b) => b.to_string(),
679        Value::Undefined => "undefined".to_string(),
680        Value::Null => "null".to_string(),
681        Value::Object(obj) => {
682            // Handle RegExp objects specially so they display as /pattern/flags
683            if crate::js_regexp::is_regex_object(obj) {
684                if let Ok(pat) = crate::js_regexp::get_regex_literal_pattern(obj) {
685                    return pat;
686                } else {
687                    return "[object RegExp]".to_string();
688                }
689            }
690            // Check if this is a function object (has __closure__ property)
691            let has_closure = get_own_property(obj, &"__closure__".into()).is_some();
692            log::trace!("DEBUG: has_closure = {has_closure}");
693            if has_closure {
694                "function".to_string()
695            } else if let Some(length_rc) = get_own_property(obj, &"length".into()) {
696                if let Value::Number(len) = &*length_rc.borrow() {
697                    if len.is_finite() && *len >= 0.0 && len.fract() == 0.0 {
698                        let len_usize = *len as usize;
699                        let mut parts = Vec::new();
700                        for i in 0..len_usize {
701                            if let Some(val_rc) = get_own_property(obj, &i.to_string().into()) {
702                                let val = val_rc.borrow();
703                                let display = match *val {
704                                    Value::Null => "null".to_string(),
705                                    Value::Undefined => "undefined".to_string(),
706                                    _ => value_to_string(&val),
707                                };
708                                parts.push(display);
709                            } else {
710                                parts.push("undefined".to_string());
711                            }
712                        }
713                        format!("[{}]", parts.join(", "))
714                    } else {
715                        "[object Object]".to_string()
716                    }
717                } else {
718                    "[object Object]".to_string()
719                }
720            } else {
721                "[object Object]".to_string()
722            }
723        }
724        Value::Function(name) => format!("function {}", name),
725        Value::Closure(_, _, _) => "function".to_string(),
726        Value::AsyncClosure(_, _, _) => "function".to_string(),
727        Value::GeneratorFunction(..) => "function".to_string(),
728        Value::ClassDefinition(_) => "class".to_string(),
729        Value::Getter(_, _) => "getter".to_string(),
730        Value::Setter(_, _, _) => "setter".to_string(),
731        Value::Property { .. } => "[property]".to_string(),
732        Value::Promise(_) => "[object Promise]".to_string(),
733        Value::Symbol(desc) => match desc.description.as_ref() {
734            Some(d) => format!("Symbol({})", d),
735            None => "Symbol()".to_string(),
736        },
737        Value::Map(_) => "[object Map]".to_string(),
738        Value::Set(_) => "[object Set]".to_string(),
739        Value::WeakMap(_) => "[object WeakMap]".to_string(),
740        Value::WeakSet(_) => "[object WeakSet]".to_string(),
741        Value::Generator(_) => "[object Generator]".to_string(),
742        Value::Proxy(_) => "[object Proxy]".to_string(),
743        Value::ArrayBuffer(_) => "[object ArrayBuffer]".to_string(),
744        Value::DataView(_) => "[object DataView]".to_string(),
745        Value::TypedArray(_) => "[object TypedArray]".to_string(),
746    }
747}
748
749// Helper: extract a closure (params, body, env) from a Value. This accepts
750// either a direct `Value::Closure` or an object wrapper that stores the
751// executable closure under the internal `"__closure__"` property.
752#[allow(clippy::type_complexity)]
753pub fn extract_closure_from_value(val: &Value) -> Option<(Vec<(String, Option<Box<Expr>>)>, Vec<Statement>, JSObjectDataPtr)> {
754    match val {
755        Value::Closure(params, body, env) => Some((params.clone(), body.clone(), env.clone())),
756        Value::AsyncClosure(params, body, env) => Some((params.clone(), body.clone(), env.clone())),
757        Value::GeneratorFunction(_, params, body, env) => Some((params.clone(), body.clone(), env.clone())),
758        Value::Object(obj_map) => {
759            if let Ok(Some(cl_rc)) = obj_get_key_value(obj_map, &"__closure__".into()) {
760                match &*cl_rc.borrow() {
761                    Value::Closure(params, body, env) => Some((params.clone(), body.clone(), env.clone())),
762                    Value::AsyncClosure(params, body, env) => Some((params.clone(), body.clone(), env.clone())),
763                    Value::GeneratorFunction(_, params, body, env) => Some((params.clone(), body.clone(), env.clone())),
764                    _ => None,
765                }
766            } else {
767                None
768            }
769        }
770        _ => None,
771    }
772}
773
774// Helper: perform ToPrimitive coercion with a given hint ('string', 'number', 'default')
775pub fn to_primitive(val: &Value, hint: &str, env: &JSObjectDataPtr) -> Result<Value, JSError> {
776    match val {
777        Value::Number(_) | Value::String(_) | Value::Boolean(_) | Value::Undefined | Value::Null | Value::Symbol(_) => Ok(val.clone()),
778        Value::Object(obj_map) => {
779            // Prefer explicit [Symbol.toPrimitive] if present and callable
780            if let Some(tp_sym) = get_well_known_symbol_rc("toPrimitive") {
781                let key = PropertyKey::Symbol(tp_sym.clone());
782                if let Some(method_rc) = obj_get_key_value(obj_map, &key)? {
783                    let method_val = method_rc.borrow().clone();
784                    // Accept direct closures or function-objects that wrap a closure
785                    if let Some((params, body, captured_env)) = extract_closure_from_value(&method_val) {
786                        // Create a new execution env and bind this
787                        let func_env = new_js_object_data();
788                        func_env.borrow_mut().prototype = Some(captured_env.clone());
789                        env_set(&func_env, "this", Value::Object(obj_map.clone()))?;
790                        // Pass hint as first param if the function declares params
791                        if !params.is_empty() {
792                            let name = &params[0].0;
793                            env_set(&func_env, name.as_str(), Value::String(utf8_to_utf16(hint)))?;
794                        }
795                        let result = evaluate_statements(&func_env, &body)?;
796                        match result {
797                            Value::Number(_) | Value::String(_) | Value::Boolean(_) | Value::BigInt(_) | Value::Symbol(_) => {
798                                return Ok(result);
799                            }
800                            _ => {
801                                return Err(raise_type_error!("[Symbol.toPrimitive] must return a primitive"));
802                            }
803                        }
804                    } else {
805                        // Not a closure/minimally supported callable - fall through to default algorithm
806                    }
807                }
808            }
809
810            // Default algorithm: order depends on hint
811            if hint == "string" {
812                // toString -> valueOf
813                let to_s = crate::js_object::handle_to_string_method(&Value::Object(obj_map.clone()), &[], env)?;
814                if matches!(to_s, Value::String(_) | Value::Number(_) | Value::Boolean(_) | Value::BigInt(_)) {
815                    return Ok(to_s);
816                }
817                let val_of = crate::js_object::handle_value_of_method(&Value::Object(obj_map.clone()), &[], env)?;
818                if matches!(val_of, Value::String(_) | Value::Number(_) | Value::Boolean(_) | Value::BigInt(_)) {
819                    return Ok(val_of);
820                }
821            } else {
822                // number or default: valueOf -> toString
823                let val_of = crate::js_object::handle_value_of_method(&Value::Object(obj_map.clone()), &[], env)?;
824                if matches!(val_of, Value::Number(_) | Value::String(_) | Value::Boolean(_) | Value::BigInt(_)) {
825                    return Ok(val_of);
826                }
827                let to_s = crate::js_object::handle_to_string_method(&Value::Object(obj_map.clone()), &[], env)?;
828                if matches!(to_s, Value::String(_) | Value::Number(_) | Value::Boolean(_) | Value::BigInt(_)) {
829                    return Ok(to_s);
830                }
831            }
832
833            Err(raise_type_error!("Cannot convert object to primitive"))
834        }
835        _ => Ok(val.clone()),
836    }
837}
838
839// Helper function to convert value to string for sorting
840pub fn value_to_sort_string(val: &Value) -> String {
841    match val {
842        Value::Number(n) => {
843            if n.is_nan() {
844                "NaN".to_string()
845            } else if *n == f64::INFINITY {
846                "Infinity".to_string()
847            } else if *n == f64::NEG_INFINITY {
848                "-Infinity".to_string()
849            } else {
850                n.to_string()
851            }
852        }
853        Value::BigInt(b) => b.to_string(),
854        Value::String(s) => String::from_utf16_lossy(s),
855        Value::Boolean(b) => b.to_string(),
856        Value::Undefined => "undefined".to_string(),
857        Value::Null => "null".to_string(),
858        Value::Object(_) => "[object Object]".to_string(),
859        Value::Function(name) => format!("[function {}]", name),
860        Value::Closure(..) | Value::AsyncClosure(..) | Value::GeneratorFunction(..) => "[function]".to_string(),
861        Value::ClassDefinition(_) => "[class]".to_string(),
862        Value::Getter(_, _) => "[getter]".to_string(),
863        Value::Setter(_, _, _) => "[setter]".to_string(),
864        Value::Property { .. } => "[property]".to_string(),
865        Value::Promise(_) => "[object Promise]".to_string(),
866        Value::Symbol(_) => "[object Symbol]".to_string(),
867        Value::Map(_) => "[object Map]".to_string(),
868        Value::Set(_) => "[object Set]".to_string(),
869        Value::WeakMap(_) => "[object WeakMap]".to_string(),
870        Value::WeakSet(_) => "[object WeakSet]".to_string(),
871        Value::Generator(_) => "[object Generator]".to_string(),
872        Value::Proxy(_) => "[object Proxy]".to_string(),
873        Value::ArrayBuffer(_) => "[object ArrayBuffer]".to_string(),
874        Value::DataView(_) => "[object DataView]".to_string(),
875        Value::TypedArray(_) => "[object TypedArray]".to_string(),
876    }
877}
878
879// Helper accessors for objects and environments
880pub fn obj_get_key_value(js_obj: &JSObjectDataPtr, key: &PropertyKey) -> Result<Option<Rc<RefCell<Value>>>, JSError> {
881    // Check if this object is a proxy wrapper
882    // Avoid holding a Ref borrow across calls by extracting the optional Rc first.
883    let proxy_opt = get_own_property(js_obj, &"__proxy__".into());
884    if let Some(proxy_val_rc) = proxy_opt {
885        if let Value::Proxy(proxy) = &*proxy_val_rc.borrow() {
886            return crate::js_proxy::proxy_get_property(proxy, key);
887        }
888    }
889
890    // Search own properties and then walk the prototype chain until we find
891    // a matching property or run out of prototypes.
892    let mut current: Option<JSObjectDataPtr> = Some(js_obj.clone());
893    while let Some(cur) = current {
894        let val_opt = get_own_property(&cur, key);
895        if let Some(val) = val_opt {
896            // Found an own/inherited value on `cur`. For getters we bind `this` to
897            // the original object (`js_obj`) as per JS semantics.
898            let val_clone = val.borrow().clone();
899            match val_clone {
900                Value::Property { value, getter, .. } => {
901                    log::trace!("obj_get_key_value - property descriptor found for key {}", key);
902                    if let Some((body, env)) = getter {
903                        // Create a new environment with this bound to the original object
904                        let getter_env = new_js_object_data();
905                        getter_env.borrow_mut().prototype = Some(env);
906                        env_set(&getter_env, "this", Value::Object(js_obj.clone()))?;
907                        let result = evaluate_statements(&getter_env, &body)?;
908                        if let Value::Object(ref obj_ptr) = result {
909                            let ptr = Rc::as_ptr(obj_ptr);
910                            log::trace!("obj_get_key_value - getter returned object ptr={:p} for key {}", ptr, key);
911                        }
912                        return Ok(Some(Rc::new(RefCell::new(result))));
913                    } else if let Some(val_rc) = value {
914                        // If the stored value is an object, log its pointer
915                        if let Value::Object(ref obj_ptr) = *val_rc.borrow() {
916                            log::trace!("obj_get_key_value - returning object ptr={:p} for key {}", Rc::as_ptr(obj_ptr), key);
917                        }
918                        return Ok(Some(val_rc));
919                    } else {
920                        return Ok(Some(Rc::new(RefCell::new(Value::Undefined))));
921                    }
922                }
923                Value::Getter(body, env) => {
924                    log::trace!("obj_get_key_value - getter found for key {}", key);
925                    let getter_env = new_js_object_data();
926                    getter_env.borrow_mut().prototype = Some(env);
927                    env_set(&getter_env, "this", Value::Object(js_obj.clone()))?;
928                    let result = evaluate_statements(&getter_env, &body)?;
929                    if let Value::Object(ref obj_ptr) = result {
930                        let ptr = Rc::as_ptr(obj_ptr);
931                        log::trace!("obj_get_key_value - getter returned object ptr={:p} for key {}", ptr, key);
932                    }
933                    return Ok(Some(Rc::new(RefCell::new(result))));
934                }
935                _ => {
936                    log::trace!("obj_get_key_value - raw value found for key {}", key);
937                    if let Value::Object(ref obj_ptr) = *val.borrow() {
938                        log::trace!("obj_get_key_value - returning object ptr={:p} for key {}", Rc::as_ptr(obj_ptr), key);
939                    }
940                    return Ok(Some(val.clone()));
941                }
942            }
943        }
944        // Not found on this object; continue with prototype.
945        current = cur.borrow().prototype.clone();
946    }
947
948    // No own or inherited property found, fall back to special-case handling
949    // (well-known symbol fallbacks, array/string iterator helpers, etc.).
950
951    // Helper: build an iterator closure given the `next` function body and a
952    // captured environment. This avoids duplicating the common pattern:
953    //   function() { let __i = 0; return { next: function() { ... } } }
954    fn make_iterator_closure(next_body: Vec<Statement>, captured_env: JSObjectDataPtr) -> Value {
955        let iter_body = vec![
956            Statement::Let("__i".to_string(), Some(Expr::Value(Value::Number(0.0)))),
957            Statement::Return(Some(Expr::Object(vec![(
958                "next".to_string(),
959                Expr::Function(None, Vec::new(), next_body),
960            )]))),
961        ];
962        Value::Closure(Vec::new(), iter_body, captured_env)
963    }
964
965    // Provide default well-known symbol fallbacks (non-own) for some built-ins.
966    if let PropertyKey::Symbol(sym_rc) = key {
967        // Support default Symbol.iterator for built-ins like Array and wrapped String objects.
968        if let Some(iter_sym_rc) = get_well_known_symbol_rc("iterator")
969            && let (Value::Symbol(iter_sd), Value::Symbol(req_sd)) = (&*iter_sym_rc.borrow(), &*sym_rc.borrow())
970            && Rc::ptr_eq(iter_sd, req_sd)
971        {
972            // Array default iterator
973            if is_array(js_obj) {
974                // next function body
975                let next_body = vec![
976                    Statement::Let("idx".to_string(), Some(Expr::Var("__i".to_string()))),
977                    Statement::If(
978                        Expr::Binary(
979                            Box::new(Expr::Var("idx".to_string())),
980                            BinaryOp::LessThan,
981                            Box::new(Expr::Property(Box::new(Expr::Var("__array".to_string())), "length".to_string())),
982                        ),
983                        vec![
984                            Statement::Let(
985                                "v".to_string(),
986                                Some(Expr::Index(
987                                    Box::new(Expr::Var("__array".to_string())),
988                                    Box::new(Expr::Var("idx".to_string())),
989                                )),
990                            ),
991                            Statement::Expr(Expr::Assign(
992                                Box::new(Expr::Var("__i".to_string())),
993                                Box::new(Expr::Binary(
994                                    Box::new(Expr::Var("idx".to_string())),
995                                    BinaryOp::Add,
996                                    Box::new(Expr::Value(Value::Number(1.0))),
997                                )),
998                            )),
999                            Statement::Return(Some(Expr::Object(vec![
1000                                ("value".to_string(), Expr::Var("v".to_string())),
1001                                ("done".to_string(), Expr::Value(Value::Boolean(false))),
1002                            ]))),
1003                        ],
1004                        Some(vec![Statement::Return(Some(Expr::Object(vec![(
1005                            "done".to_string(),
1006                            Expr::Value(Value::Boolean(true)),
1007                        )])))]),
1008                    ),
1009                ];
1010
1011                let captured_env = new_js_object_data();
1012                captured_env.borrow_mut().insert(
1013                    PropertyKey::String("__array".to_string()),
1014                    Rc::new(RefCell::new(Value::Object(js_obj.clone()))),
1015                );
1016                let closure = make_iterator_closure(next_body, captured_env.clone());
1017                return Ok(Some(Rc::new(RefCell::new(closure))));
1018            }
1019
1020            // Map default iterator
1021            let map_opt = get_own_property(js_obj, &"__map__".into());
1022            if let Some(map_val) = map_opt {
1023                if let Value::Map(map_rc) = &*map_val.borrow() {
1024                    let map_entries = map_rc.borrow().entries.clone();
1025
1026                    // next function body for Map iteration (returns [key, value] pairs)
1027                    let next_body = vec![
1028                        Statement::Let("idx".to_string(), Some(Expr::Var("__i".to_string()))),
1029                        Statement::If(
1030                            Expr::Binary(
1031                                Box::new(Expr::Var("idx".to_string())),
1032                                BinaryOp::LessThan,
1033                                Box::new(Expr::Value(Value::Number(map_entries.len() as f64))),
1034                            ),
1035                            vec![
1036                                Statement::Let(
1037                                    "entry".to_string(),
1038                                    Some(Expr::Array(vec![
1039                                        Expr::Property(
1040                                            Box::new(Expr::Index(
1041                                                Box::new(Expr::Var("__entries".to_string())),
1042                                                Box::new(Expr::Var("idx".to_string())),
1043                                            )),
1044                                            "0".to_string(),
1045                                        ),
1046                                        Expr::Property(
1047                                            Box::new(Expr::Index(
1048                                                Box::new(Expr::Var("__entries".to_string())),
1049                                                Box::new(Expr::Var("idx".to_string())),
1050                                            )),
1051                                            "1".to_string(),
1052                                        ),
1053                                    ])),
1054                                ),
1055                                Statement::Expr(Expr::Assign(
1056                                    Box::new(Expr::Var("__i".to_string())),
1057                                    Box::new(Expr::Binary(
1058                                        Box::new(Expr::Var("__i".to_string())),
1059                                        BinaryOp::Add,
1060                                        Box::new(Expr::Value(Value::Number(1.0))),
1061                                    )),
1062                                )),
1063                                Statement::Return(Some(Expr::Object(vec![
1064                                    ("value".to_string(), Expr::Var("entry".to_string())),
1065                                    ("done".to_string(), Expr::Value(Value::Boolean(false))),
1066                                ]))),
1067                            ],
1068                            Some(vec![Statement::Return(Some(Expr::Object(vec![(
1069                                "done".to_string(),
1070                                Expr::Value(Value::Boolean(true)),
1071                            )])))]),
1072                        ),
1073                    ];
1074
1075                    let captured_env = new_js_object_data();
1076                    // Store map entries in the closure environment
1077                    let mut entries_obj = JSObjectData::new();
1078                    for (i, (key, value)) in map_entries.iter().enumerate() {
1079                        let mut entry_obj = JSObjectData::new();
1080                        entry_obj.insert("0".into(), Rc::new(RefCell::new(key.clone())));
1081                        entry_obj.insert("1".into(), Rc::new(RefCell::new(value.clone())));
1082                        entries_obj.insert(
1083                            i.to_string().into(),
1084                            Rc::new(RefCell::new(Value::Object(Rc::new(RefCell::new(entry_obj))))),
1085                        );
1086                    }
1087                    captured_env.borrow_mut().insert(
1088                        "__entries".into(),
1089                        Rc::new(RefCell::new(Value::Object(Rc::new(RefCell::new(entries_obj))))),
1090                    );
1091
1092                    let closure = make_iterator_closure(next_body, captured_env.clone());
1093                    return Ok(Some(Rc::new(RefCell::new(closure))));
1094                }
1095            }
1096
1097            // String default iterator
1098            if let Some(val_rc) = get_own_property(js_obj, &"__value__".into()) {
1099                if let Value::String(s) = &*val_rc.borrow() {
1100                    // next function body for string iteration (returns whole Unicode characters)
1101                    let next_body = vec![
1102                        Statement::Let("idx".to_string(), Some(Expr::Var("__i".to_string()))),
1103                        // if idx < __str.length then proceed
1104                        Statement::If(
1105                            Expr::Binary(
1106                                Box::new(Expr::Var("idx".to_string())),
1107                                BinaryOp::LessThan,
1108                                Box::new(Expr::Property(Box::new(Expr::Var("__str".to_string())), "length".to_string())),
1109                            ),
1110                            vec![
1111                                // first = __str.charCodeAt(idx)
1112                                Statement::Let(
1113                                    "first".to_string(),
1114                                    Some(Expr::Call(
1115                                        Box::new(Expr::Property(Box::new(Expr::Var("__str".to_string())), "charCodeAt".to_string())),
1116                                        vec![Expr::Var("idx".to_string())],
1117                                    )),
1118                                ),
1119                                // if first is high surrogate (>=0xD800 && <=0xDBFF)
1120                                Statement::If(
1121                                    Expr::LogicalAnd(
1122                                        Box::new(Expr::Binary(
1123                                            Box::new(Expr::Var("first".to_string())),
1124                                            BinaryOp::GreaterEqual,
1125                                            Box::new(Expr::Value(Value::Number(0xD800 as f64))),
1126                                        )),
1127                                        Box::new(Expr::Binary(
1128                                            Box::new(Expr::Var("first".to_string())),
1129                                            BinaryOp::LessEqual,
1130                                            Box::new(Expr::Value(Value::Number(0xDBFF as f64))),
1131                                        )),
1132                                    ),
1133                                    vec![
1134                                        // second = __str.charCodeAt(idx + 1)
1135                                        Statement::Let(
1136                                            "second".to_string(),
1137                                            Some(Expr::Call(
1138                                                Box::new(Expr::Property(
1139                                                    Box::new(Expr::Var("__str".to_string())),
1140                                                    "charCodeAt".to_string(),
1141                                                )),
1142                                                vec![Expr::Binary(
1143                                                    Box::new(Expr::Var("idx".to_string())),
1144                                                    BinaryOp::Add,
1145                                                    Box::new(Expr::Value(Value::Number(1.0))),
1146                                                )],
1147                                            )),
1148                                        ),
1149                                        // if second is low surrogate (>=0xDC00 && <=0xDFFF)
1150                                        Statement::If(
1151                                            Expr::LogicalAnd(
1152                                                Box::new(Expr::Binary(
1153                                                    Box::new(Expr::Var("second".to_string())),
1154                                                    BinaryOp::GreaterEqual,
1155                                                    Box::new(Expr::Value(Value::Number(0xDC00 as f64))),
1156                                                )),
1157                                                Box::new(Expr::Binary(
1158                                                    Box::new(Expr::Var("second".to_string())),
1159                                                    BinaryOp::LessEqual,
1160                                                    Box::new(Expr::Value(Value::Number(0xDFFF as f64))),
1161                                                )),
1162                                            ),
1163                                            vec![
1164                                                // ch = __str.substring(idx, idx+2)
1165                                                Statement::Let(
1166                                                    "ch".to_string(),
1167                                                    Some(Expr::Call(
1168                                                        Box::new(Expr::Property(
1169                                                            Box::new(Expr::Var("__str".to_string())),
1170                                                            "substring".to_string(),
1171                                                        )),
1172                                                        vec![
1173                                                            Expr::Var("idx".to_string()),
1174                                                            Expr::Binary(
1175                                                                Box::new(Expr::Var("idx".to_string())),
1176                                                                BinaryOp::Add,
1177                                                                Box::new(Expr::Value(Value::Number(2.0))),
1178                                                            ),
1179                                                        ],
1180                                                    )),
1181                                                ),
1182                                                // __i = idx + 2
1183                                                Statement::Expr(Expr::Assign(
1184                                                    Box::new(Expr::Var("__i".to_string())),
1185                                                    Box::new(Expr::Binary(
1186                                                        Box::new(Expr::Var("idx".to_string())),
1187                                                        BinaryOp::Add,
1188                                                        Box::new(Expr::Value(Value::Number(2.0))),
1189                                                    )),
1190                                                )),
1191                                                Statement::Return(Some(Expr::Object(vec![
1192                                                    ("value".to_string(), Expr::Var("ch".to_string())),
1193                                                    ("done".to_string(), Expr::Value(Value::Boolean(false))),
1194                                                ]))),
1195                                            ],
1196                                            // else: fallthrough to single-unit char
1197                                            None,
1198                                        ),
1199                                    ],
1200                                    // else: fallthrough to single-unit char
1201                                    None,
1202                                ),
1203                                // Single-unit char fallback: ch = __str.charAt(idx)
1204                                Statement::Let(
1205                                    "ch".to_string(),
1206                                    Some(Expr::Call(
1207                                        Box::new(Expr::Property(Box::new(Expr::Var("__str".to_string())), "charAt".to_string())),
1208                                        vec![Expr::Var("idx".to_string())],
1209                                    )),
1210                                ),
1211                                Statement::Expr(Expr::Assign(
1212                                    Box::new(Expr::Var("__i".to_string())),
1213                                    Box::new(Expr::Binary(
1214                                        Box::new(Expr::Var("idx".to_string())),
1215                                        BinaryOp::Add,
1216                                        Box::new(Expr::Value(Value::Number(1.0))),
1217                                    )),
1218                                )),
1219                                Statement::Return(Some(Expr::Object(vec![
1220                                    ("value".to_string(), Expr::Var("ch".to_string())),
1221                                    ("done".to_string(), Expr::Value(Value::Boolean(false))),
1222                                ]))),
1223                            ],
1224                            Some(vec![Statement::Return(Some(Expr::Object(vec![(
1225                                "done".to_string(),
1226                                Expr::Value(Value::Boolean(true)),
1227                            )])))]),
1228                        ),
1229                    ];
1230
1231                    let captured_env = new_js_object_data();
1232                    captured_env.borrow_mut().insert(
1233                        PropertyKey::String("__str".to_string()),
1234                        Rc::new(RefCell::new(Value::String(s.clone()))),
1235                    );
1236                    let closure = make_iterator_closure(next_body, captured_env.clone());
1237                    return Ok(Some(Rc::new(RefCell::new(closure))));
1238                }
1239            }
1240
1241            // Set default iterator
1242            let set_opt = get_own_property(js_obj, &"__set__".into());
1243            if let Some(set_val) = set_opt {
1244                if let Value::Set(set_rc) = &*set_val.borrow() {
1245                    let set_values = set_rc.borrow().values.clone();
1246                    // next function body for Set iteration (returns values)
1247                    let next_body = vec![
1248                        Statement::Let("idx".to_string(), Some(Expr::Var("__i".to_string()))),
1249                        Statement::If(
1250                            Expr::Binary(
1251                                Box::new(Expr::Var("idx".to_string())),
1252                                BinaryOp::LessThan,
1253                                Box::new(Expr::Value(Value::Number(set_values.len() as f64))),
1254                            ),
1255                            vec![
1256                                Statement::Let(
1257                                    "value".to_string(),
1258                                    Some(Expr::Index(
1259                                        Box::new(Expr::Var("__values".to_string())),
1260                                        Box::new(Expr::Var("idx".to_string())),
1261                                    )),
1262                                ),
1263                                Statement::Expr(Expr::Assign(
1264                                    Box::new(Expr::Var("__i".to_string())),
1265                                    Box::new(Expr::Binary(
1266                                        Box::new(Expr::Var("idx".to_string())),
1267                                        BinaryOp::Add,
1268                                        Box::new(Expr::Value(Value::Number(1.0))),
1269                                    )),
1270                                )),
1271                                Statement::Return(Some(Expr::Object(vec![
1272                                    ("value".to_string(), Expr::Var("value".to_string())),
1273                                    ("done".to_string(), Expr::Value(Value::Boolean(false))),
1274                                ]))),
1275                            ],
1276                            Some(vec![Statement::Return(Some(Expr::Object(vec![(
1277                                "done".to_string(),
1278                                Expr::Value(Value::Boolean(true)),
1279                            )])))]),
1280                        ),
1281                    ];
1282
1283                    let set_iter_body = vec![
1284                        Statement::Let("__i".to_string(), Some(Expr::Value(Value::Number(0.0)))),
1285                        Statement::Return(Some(Expr::Object(vec![(
1286                            "next".to_string(),
1287                            Expr::Function(None, Vec::new(), next_body),
1288                        )]))),
1289                    ];
1290
1291                    let captured_env = new_js_object_data();
1292                    // Store set values in the closure environment
1293                    let mut values_obj = JSObjectData::new();
1294                    for (i, value) in set_values.iter().enumerate() {
1295                        values_obj.insert(i.to_string().into(), Rc::new(RefCell::new(value.clone())));
1296                    }
1297                    captured_env.borrow_mut().insert(
1298                        "__values".into(),
1299                        Rc::new(RefCell::new(Value::Object(Rc::new(RefCell::new(values_obj))))),
1300                    );
1301
1302                    let closure = Value::Closure(Vec::new(), set_iter_body, captured_env.clone());
1303                    return Ok(Some(Rc::new(RefCell::new(closure))));
1304                }
1305            }
1306
1307            // Wrapped String iterator (for String objects)
1308            let wrapped_opt = get_own_property(js_obj, &"__value__".into());
1309            if let Some(wrapped) = wrapped_opt {
1310                if let Value::String(_) = &*wrapped.borrow() {
1311                    let next_body = vec![
1312                        Statement::Let("idx".to_string(), Some(Expr::Var("__i".to_string()))),
1313                        Statement::If(
1314                            Expr::Binary(
1315                                Box::new(Expr::Var("idx".to_string())),
1316                                BinaryOp::LessThan,
1317                                Box::new(Expr::Property(Box::new(Expr::Var("__s".to_string())), "length".to_string())),
1318                            ),
1319                            vec![
1320                                Statement::Let(
1321                                    "v".to_string(),
1322                                    Some(Expr::Index(
1323                                        Box::new(Expr::Var("__s".to_string())),
1324                                        Box::new(Expr::Var("idx".to_string())),
1325                                    )),
1326                                ),
1327                                Statement::Expr(Expr::Assign(
1328                                    Box::new(Expr::Var("__i".to_string())),
1329                                    Box::new(Expr::Binary(
1330                                        Box::new(Expr::Var("idx".to_string())),
1331                                        BinaryOp::Add,
1332                                        Box::new(Expr::Value(Value::Number(1.0))),
1333                                    )),
1334                                )),
1335                                Statement::Return(Some(Expr::Object(vec![
1336                                    ("value".to_string(), Expr::Var("v".to_string())),
1337                                    ("done".to_string(), Expr::Value(Value::Boolean(false))),
1338                                ]))),
1339                            ],
1340                            Some(vec![Statement::Return(Some(Expr::Object(vec![(
1341                                "done".to_string(),
1342                                Expr::Value(Value::Boolean(true)),
1343                            )])))]),
1344                        ),
1345                    ];
1346
1347                    let str_iter_body = vec![
1348                        Statement::Let("__i".to_string(), Some(Expr::Value(Value::Number(0.0)))),
1349                        Statement::Return(Some(Expr::Object(vec![(
1350                            "next".to_string(),
1351                            Expr::Function(None, Vec::new(), next_body),
1352                        )]))),
1353                    ];
1354
1355                    let captured_env = new_js_object_data();
1356                    captured_env.borrow_mut().insert(
1357                        PropertyKey::String("__s".to_string()),
1358                        Rc::new(RefCell::new(wrapped.borrow().clone())),
1359                    );
1360                    let closure = Value::Closure(Vec::new(), str_iter_body, captured_env.clone());
1361                    return Ok(Some(Rc::new(RefCell::new(closure))));
1362                }
1363            }
1364        }
1365        if let Some(tag_sym_rc) = get_well_known_symbol_rc("toStringTag")
1366            && let (Value::Symbol(tag_sd), Value::Symbol(req_sd)) = (&*tag_sym_rc.borrow(), &*sym_rc.borrow())
1367            && Rc::ptr_eq(tag_sd, req_sd)
1368        {
1369            if is_array(js_obj) {
1370                return Ok(Some(Rc::new(RefCell::new(Value::String(utf8_to_utf16("Array"))))));
1371            }
1372            let wrapped_opt2 = get_own_property(js_obj, &"__value__".into());
1373            if let Some(wrapped) = wrapped_opt2 {
1374                match &*wrapped.borrow() {
1375                    Value::String(_) => return Ok(Some(Rc::new(RefCell::new(Value::String(utf8_to_utf16("String")))))),
1376                    Value::Number(_) => return Ok(Some(Rc::new(RefCell::new(Value::String(utf8_to_utf16("Number")))))),
1377                    Value::Boolean(_) => return Ok(Some(Rc::new(RefCell::new(Value::String(utf8_to_utf16("Boolean")))))),
1378                    _ => {}
1379                }
1380            }
1381            if is_date_object(js_obj) {
1382                return Ok(Some(Rc::new(RefCell::new(Value::String(utf8_to_utf16("Date"))))));
1383            }
1384        }
1385    }
1386
1387    Ok(None)
1388}
1389
1390pub fn obj_set_key_value(js_obj: &JSObjectDataPtr, key: &PropertyKey, val: Value) -> Result<(), JSError> {
1391    // Check if this object is a proxy wrapper
1392    let proxy_opt = get_own_property(js_obj, &"__proxy__".into());
1393    if let Some(proxy_val) = proxy_opt {
1394        if let Value::Proxy(proxy) = &*proxy_val.borrow() {
1395            let success = crate::js_proxy::proxy_set_property(proxy, key, val)?;
1396            if !success {
1397                return Err(raise_eval_error!("Proxy set trap returned false"));
1398            }
1399            return Ok(());
1400        }
1401    }
1402
1403    // Check if there's a setter for this property
1404    let existing_opt = get_own_property(js_obj, key);
1405    if let Some(existing) = existing_opt {
1406        // If property exists and is non-writable on this object, disallow assignment
1407        if !js_obj.borrow().is_writable(key) {
1408            return Err(raise_type_error!(format!("Cannot assign to read-only property '{}'", key)));
1409        }
1410
1411        match existing.borrow().clone() {
1412            Value::Property { value: _, getter, setter } => {
1413                if let Some((param, body, env)) = setter {
1414                    // Create a new environment with this bound to the object and the parameter
1415                    let setter_env = new_js_object_data();
1416                    setter_env.borrow_mut().prototype = Some(env);
1417                    env_set(&setter_env, "this", Value::Object(js_obj.clone()))?;
1418                    let name = &param[0].0;
1419                    env_set(&setter_env, name.as_str(), val)?;
1420                    let _v = evaluate_statements(&setter_env, &body)?;
1421                } else {
1422                    // No setter, update value
1423                    let value = Some(Rc::new(RefCell::new(val)));
1424                    let new_prop = Value::Property { value, getter, setter };
1425                    if let PropertyKey::String(s) = key {
1426                        // generic debug: avoid embedding any specific script identifiers
1427                        if s == "message" {
1428                            // Try to include any debug id set on the instance for correlation
1429                            let dbg_id = get_own_property(js_obj, &"__dbg_ptr__".into())
1430                                .map(|r| format!("{:?}", r.borrow()))
1431                                .unwrap_or_else(|| "<none>".to_string());
1432                            log::debug!(
1433                                "DBG obj_set_key_value - inserting 'message' on obj ptr={js_obj:p} dbg_id={dbg_id} value={new_prop:?}"
1434                            );
1435                        }
1436                    }
1437                    // Log the property insertion (object target pointer + value info)
1438                    let val_ptr_info = match &new_prop {
1439                        Value::Object(o) => format!("object_ptr={:p}", Rc::as_ptr(o)),
1440                        other => format!("value={:?}", other),
1441                    };
1442                    log::debug!(
1443                        "DBG obj_set_key_value - setting existing prop '{}' on obj ptr={:p} -> {}",
1444                        key,
1445                        Rc::as_ptr(js_obj),
1446                        val_ptr_info
1447                    );
1448                    js_obj.borrow_mut().insert(key.clone(), Rc::new(RefCell::new(new_prop)));
1449                }
1450                return Ok(());
1451            }
1452            Value::Setter(param, body, env) => {
1453                // Create a new environment with this bound to the object and the parameter
1454                let setter_env = new_js_object_data();
1455                setter_env.borrow_mut().prototype = Some(env);
1456                env_set(&setter_env, "this", Value::Object(js_obj.clone()))?;
1457                let name = &param[0].0;
1458                env_set(&setter_env, name.as_str(), val)?;
1459                evaluate_statements(&setter_env, &body)?;
1460                return Ok(());
1461            }
1462            _ => {}
1463        }
1464    }
1465    // No setter, just set the value normally
1466    // Update array length if setting an indexed property
1467    if let PropertyKey::String(s) = key {
1468        if let Ok(index) = s.parse::<usize>() {
1469            if let Some(length_rc) = get_own_property(js_obj, &"length".into()) {
1470                if let Value::Number(current_len) = &*length_rc.borrow() {
1471                    if index >= *current_len as usize {
1472                        let new_len = (index + 1) as f64;
1473                        obj_set_key_value(js_obj, &"length".into(), Value::Number(new_len))?;
1474                    }
1475                }
1476            }
1477        }
1478    }
1479    if let PropertyKey::String(s) = key {
1480        if s == "message" {
1481            let dbg_id = get_own_property(js_obj, &"__dbg_ptr__".into())
1482                .map(|r| format!("{:?}", r.borrow()))
1483                .unwrap_or_else(|| "<none>".to_string());
1484            log::debug!("DBG obj_set_key_value - direct insert 'message' on obj ptr={js_obj:p} dbg_id={dbg_id} value={val:?}");
1485        }
1486    }
1487    // Log general property insertion (target pointer + value pointer/type) for diagnostics
1488    let val_ptr_info = match &val {
1489        Value::Object(o) => format!("object_ptr={:p}", Rc::as_ptr(o)),
1490        other => format!("value={:?}", other),
1491    };
1492    log::debug!(
1493        "DBG obj_set_key_value - direct insert prop '{}' on obj ptr={:p} -> {}",
1494        key,
1495        Rc::as_ptr(js_obj),
1496        val_ptr_info
1497    );
1498    js_obj.borrow_mut().insert(key.clone(), Rc::new(RefCell::new(val)));
1499    Ok(())
1500}
1501
1502pub fn obj_set_rc(map: &JSObjectDataPtr, key: &PropertyKey, val_rc: Rc<RefCell<Value>>) {
1503    map.borrow_mut().insert(key.clone(), val_rc);
1504}
1505
1506pub fn obj_delete(map: &JSObjectDataPtr, key: &PropertyKey) -> Result<bool, JSError> {
1507    // Check if this object is a proxy wrapper
1508    let proxy_opt = get_own_property(map, &"__proxy__".into());
1509    if let Some(proxy_val) = proxy_opt {
1510        if let Value::Proxy(proxy) = &*proxy_val.borrow() {
1511            return crate::js_proxy::proxy_delete_property(proxy, key);
1512        }
1513    }
1514
1515    // If property is non-configurable, deletion fails (return false)
1516    if !map.borrow().is_configurable(key) {
1517        return Ok(false);
1518    }
1519
1520    map.borrow_mut().remove(key);
1521    Ok(true)
1522}
1523
1524pub fn env_get<T: AsRef<str>>(env: &JSObjectDataPtr, key: T) -> Option<Rc<RefCell<Value>>> {
1525    get_own_property(env, &key.as_ref().into())
1526}
1527
1528/// Helper to get an own property Rc<Value> from an object without holding
1529/// the object's borrow across later inner borrows. This centralizes the
1530/// `obj.borrow().get(key)` pattern so callers can safely borrow the inner Rc.
1531pub fn get_own_property(obj: &JSObjectDataPtr, key: &PropertyKey) -> Option<Rc<RefCell<Value>>> {
1532    obj.borrow().get(key)
1533}
1534
1535pub fn env_set<T: AsRef<str>>(env: &JSObjectDataPtr, key: T, val: Value) -> Result<(), JSError> {
1536    let key = key.as_ref();
1537    if env.borrow().is_const(key) {
1538        return Err(raise_type_error!(format!("Assignment to constant variable '{key}'")));
1539    }
1540    env.borrow_mut()
1541        .insert(PropertyKey::String(key.to_string()), Rc::new(RefCell::new(val)));
1542    Ok(())
1543}
1544
1545pub fn env_set_recursive<T: AsRef<str>>(env: &JSObjectDataPtr, key: T, val: Value) -> Result<(), JSError> {
1546    let key_str = key.as_ref();
1547    let mut current = env.clone();
1548    loop {
1549        if get_own_property(&current, &key_str.into()).is_some() {
1550            return env_set(&current, key_str, val);
1551        }
1552        let parent_opt = current.borrow().prototype.clone();
1553        if let Some(parent) = parent_opt {
1554            current = parent;
1555        } else {
1556            // if not found, set in current env
1557            return env_set(env, key_str, val);
1558        }
1559    }
1560}
1561
1562pub fn env_set_var(env: &JSObjectDataPtr, key: &str, val: Value) -> Result<(), JSError> {
1563    let mut current = env.clone();
1564    loop {
1565        if current.borrow().is_function_scope {
1566            return env_set(&current, key, val);
1567        }
1568        let parent_opt = current.borrow().prototype.clone();
1569        if let Some(parent) = parent_opt {
1570            current = parent;
1571        } else {
1572            // If no function scope found, set in current env (global)
1573            return env_set(env, key, val);
1574        }
1575    }
1576}
1577
1578pub fn env_set_const(env: &JSObjectDataPtr, key: &str, val: Value) {
1579    let mut env_mut = env.borrow_mut();
1580    env_mut.insert(PropertyKey::String(key.to_string()), Rc::new(RefCell::new(val)));
1581    env_mut.set_const(key.to_string());
1582}