javascript/core/
value.rs

1use std::{cell::RefCell, rc::Rc};
2
3use crate::{
4    JSError,
5    core::{BinaryOp, Expr, PropertyKey, Statement, evaluate_statements, get_well_known_symbol_rc, utf8_to_utf16},
6    js_array::is_array,
7    js_class::ClassDefinition,
8    js_promise::JSPromise,
9    raise_eval_error, raise_type_error,
10};
11
12#[derive(Clone, Debug)]
13pub struct JSMap {
14    pub entries: Vec<(Value, Value)>, // key-value pairs
15}
16
17#[derive(Clone, Debug)]
18pub struct JSSet {
19    pub values: Vec<Value>,
20}
21
22#[derive(Clone, Debug)]
23pub struct JSWeakMap {
24    pub entries: Vec<(std::rc::Weak<RefCell<JSObjectData>>, Value)>, // weak key-value pairs
25}
26
27#[derive(Clone, Debug)]
28pub struct JSWeakSet {
29    pub values: Vec<std::rc::Weak<RefCell<JSObjectData>>>, // weak values
30}
31
32#[derive(Clone, Debug)]
33pub struct JSGenerator {
34    pub params: Vec<String>,
35    pub body: Vec<Statement>,
36    pub env: JSObjectDataPtr, // captured environment
37    pub state: GeneratorState,
38}
39
40#[derive(Clone, Debug)]
41pub struct JSProxy {
42    pub target: Value,  // The target object being proxied
43    pub handler: Value, // The handler object with traps
44    pub revoked: bool,  // Whether this proxy has been revoked
45}
46
47#[derive(Clone, Debug)]
48pub struct JSArrayBuffer {
49    pub data: Vec<u8>,  // The underlying byte buffer
50    pub detached: bool, // Whether the buffer has been detached
51}
52
53#[derive(Clone, Debug)]
54pub struct JSDataView {
55    pub buffer: Rc<RefCell<JSArrayBuffer>>, // Reference to the underlying ArrayBuffer
56    pub byte_offset: usize,                 // Starting byte offset in the buffer
57    pub byte_length: usize,                 // Length in bytes
58}
59
60#[derive(Clone, Debug, PartialEq)]
61pub enum TypedArrayKind {
62    Int8,
63    Uint8,
64    Uint8Clamped,
65    Int16,
66    Uint16,
67    Int32,
68    Uint32,
69    Float32,
70    Float64,
71    BigInt64,
72    BigUint64,
73}
74
75#[derive(Clone, Debug)]
76pub struct JSTypedArray {
77    pub kind: TypedArrayKind,
78    pub buffer: Rc<RefCell<JSArrayBuffer>>, // Reference to the underlying ArrayBuffer
79    pub byte_offset: usize,                 // Starting byte offset in the buffer
80    pub length: usize,                      // Number of elements
81}
82
83impl JSTypedArray {
84    /// Get the size in bytes of each element in this TypedArray
85    pub fn element_size(&self) -> usize {
86        match self.kind {
87            TypedArrayKind::Int8 | TypedArrayKind::Uint8 | TypedArrayKind::Uint8Clamped => 1,
88            TypedArrayKind::Int16 | TypedArrayKind::Uint16 => 2,
89            TypedArrayKind::Int32 | TypedArrayKind::Uint32 | TypedArrayKind::Float32 => 4,
90            TypedArrayKind::Float64 | TypedArrayKind::BigInt64 | TypedArrayKind::BigUint64 => 8,
91        }
92    }
93
94    /// Get a value at the specified index
95    pub fn get(&self, index: usize) -> Result<i64, JSError> {
96        if index >= self.length {
97            return Err(raise_type_error!("Index out of bounds"));
98        }
99
100        let buffer = self.buffer.borrow();
101        if buffer.detached {
102            return Err(raise_type_error!("ArrayBuffer is detached"));
103        }
104
105        let byte_index = self.byte_offset + index * self.element_size();
106        if byte_index + self.element_size() > buffer.data.len() {
107            return Err(raise_type_error!("Index out of bounds"));
108        }
109
110        match self.kind {
111            TypedArrayKind::Int8 => Ok(buffer.data[byte_index] as i8 as i64),
112            TypedArrayKind::Uint8 | TypedArrayKind::Uint8Clamped => Ok(buffer.data[byte_index] as i64),
113            TypedArrayKind::Int16 => {
114                let bytes = &buffer.data[byte_index..byte_index + 2];
115                Ok(i16::from_le_bytes([bytes[0], bytes[1]]) as i64)
116            }
117            TypedArrayKind::Uint16 => {
118                let bytes = &buffer.data[byte_index..byte_index + 2];
119                Ok(u16::from_le_bytes([bytes[0], bytes[1]]) as i64)
120            }
121            TypedArrayKind::Int32 => {
122                let bytes = &buffer.data[byte_index..byte_index + 4];
123                Ok(i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as i64)
124            }
125            TypedArrayKind::Uint32 => {
126                let bytes = &buffer.data[byte_index..byte_index + 4];
127                Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as i64)
128            }
129            TypedArrayKind::Float32 => {
130                let bytes = &buffer.data[byte_index..byte_index + 4];
131                let float_val = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
132                Ok(float_val as i64) // Simplified conversion
133            }
134            TypedArrayKind::Float64 => {
135                let bytes = &buffer.data[byte_index..byte_index + 8];
136                let float_val = f64::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]]);
137                Ok(float_val as i64) // Simplified conversion
138            }
139            TypedArrayKind::BigInt64 | TypedArrayKind::BigUint64 => {
140                // For BigInt types, return 0 for now (simplified)
141                Ok(0)
142            }
143        }
144    }
145
146    /// Set a value at the specified index
147    pub fn set(&mut self, index: usize, value: i64) -> Result<(), JSError> {
148        if index >= self.length {
149            return Err(raise_type_error!("Index out of bounds"));
150        }
151
152        let mut buffer = self.buffer.borrow_mut();
153        if buffer.detached {
154            return Err(raise_type_error!("ArrayBuffer is detached"));
155        }
156
157        let byte_index = self.byte_offset + index * self.element_size();
158        if byte_index + self.element_size() > buffer.data.len() {
159            return Err(raise_type_error!("Index out of bounds"));
160        }
161
162        match self.kind {
163            TypedArrayKind::Int8 => {
164                buffer.data[byte_index] = value as i8 as u8;
165            }
166            TypedArrayKind::Uint8 | TypedArrayKind::Uint8Clamped => {
167                buffer.data[byte_index] = value as u8;
168            }
169            TypedArrayKind::Int16 => {
170                let bytes = (value as i16).to_le_bytes();
171                buffer.data[byte_index..byte_index + 2].copy_from_slice(&bytes);
172            }
173            TypedArrayKind::Uint16 => {
174                let bytes = (value as u16).to_le_bytes();
175                buffer.data[byte_index..byte_index + 2].copy_from_slice(&bytes);
176            }
177            TypedArrayKind::Int32 => {
178                let bytes = (value as i32).to_le_bytes();
179                buffer.data[byte_index..byte_index + 4].copy_from_slice(&bytes);
180            }
181            TypedArrayKind::Uint32 => {
182                let bytes = (value as u32).to_le_bytes();
183                buffer.data[byte_index..byte_index + 4].copy_from_slice(&bytes);
184            }
185            TypedArrayKind::Float32 => {
186                let bytes = (value as f32).to_le_bytes();
187                buffer.data[byte_index..byte_index + 4].copy_from_slice(&bytes);
188            }
189            TypedArrayKind::Float64 => {
190                let bytes = (value as f64).to_le_bytes();
191                buffer.data[byte_index..byte_index + 8].copy_from_slice(&bytes);
192            }
193            TypedArrayKind::BigInt64 | TypedArrayKind::BigUint64 => {
194                // For BigInt types, do nothing for now (simplified)
195            }
196        }
197
198        Ok(())
199    }
200}
201
202impl JSDataView {
203    /// Get an 8-bit signed integer at the specified byte offset
204    pub fn get_int8(&self, offset: usize) -> Result<i8, JSError> {
205        self.check_bounds(offset, 1)?;
206        let buffer = self.buffer.borrow();
207        Ok(buffer.data[self.byte_offset + offset] as i8)
208    }
209
210    /// Get an 8-bit unsigned integer at the specified byte offset
211    pub fn get_uint8(&self, offset: usize) -> Result<u8, JSError> {
212        self.check_bounds(offset, 1)?;
213        let buffer = self.buffer.borrow();
214        Ok(buffer.data[self.byte_offset + offset])
215    }
216
217    /// Get a 16-bit signed integer at the specified byte offset
218    pub fn get_int16(&self, offset: usize, little_endian: bool) -> Result<i16, JSError> {
219        self.check_bounds(offset, 2)?;
220        let buffer = self.buffer.borrow();
221        let bytes = &buffer.data[self.byte_offset + offset..self.byte_offset + offset + 2];
222        if little_endian {
223            Ok(i16::from_le_bytes([bytes[0], bytes[1]]))
224        } else {
225            Ok(i16::from_be_bytes([bytes[0], bytes[1]]))
226        }
227    }
228
229    /// Get a 16-bit unsigned integer at the specified byte offset
230    pub fn get_uint16(&self, offset: usize, little_endian: bool) -> Result<u16, JSError> {
231        self.check_bounds(offset, 2)?;
232        let buffer = self.buffer.borrow();
233        let bytes = &buffer.data[self.byte_offset + offset..self.byte_offset + offset + 2];
234        if little_endian {
235            Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
236        } else {
237            Ok(u16::from_be_bytes([bytes[0], bytes[1]]))
238        }
239    }
240
241    /// Get a 32-bit signed integer at the specified byte offset
242    pub fn get_int32(&self, offset: usize, little_endian: bool) -> Result<i32, JSError> {
243        self.check_bounds(offset, 4)?;
244        let buffer = self.buffer.borrow();
245        let bytes = &buffer.data[self.byte_offset + offset..self.byte_offset + offset + 4];
246        if little_endian {
247            Ok(i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
248        } else {
249            Ok(i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
250        }
251    }
252
253    /// Get a 32-bit unsigned integer at the specified byte offset
254    pub fn get_uint32(&self, offset: usize, little_endian: bool) -> Result<u32, JSError> {
255        self.check_bounds(offset, 4)?;
256        let buffer = self.buffer.borrow();
257        let bytes = &buffer.data[self.byte_offset + offset..self.byte_offset + offset + 4];
258        if little_endian {
259            Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
260        } else {
261            Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
262        }
263    }
264
265    /// Get a 32-bit float at the specified byte offset
266    pub fn get_float32(&self, offset: usize, little_endian: bool) -> Result<f32, JSError> {
267        self.check_bounds(offset, 4)?;
268        let buffer = self.buffer.borrow();
269        let bytes = &buffer.data[self.byte_offset + offset..self.byte_offset + offset + 4];
270        if little_endian {
271            Ok(f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
272        } else {
273            Ok(f32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
274        }
275    }
276
277    /// Get a 64-bit float at the specified byte offset
278    pub fn get_float64(&self, offset: usize, little_endian: bool) -> Result<f64, JSError> {
279        self.check_bounds(offset, 8)?;
280        let buffer = self.buffer.borrow();
281        let bytes = &buffer.data[self.byte_offset + offset..self.byte_offset + offset + 8];
282        if little_endian {
283            Ok(f64::from_le_bytes([
284                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
285            ]))
286        } else {
287            Ok(f64::from_be_bytes([
288                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
289            ]))
290        }
291    }
292
293    /// Get a 64-bit signed BigInt at the specified byte offset
294    pub fn get_big_int64(&self, offset: usize, little_endian: bool) -> Result<i64, JSError> {
295        self.check_bounds(offset, 8)?;
296        let buffer = self.buffer.borrow();
297        let bytes = &buffer.data[self.byte_offset + offset..self.byte_offset + offset + 8];
298        if little_endian {
299            Ok(i64::from_le_bytes([
300                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
301            ]))
302        } else {
303            Ok(i64::from_be_bytes([
304                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
305            ]))
306        }
307    }
308
309    /// Get a 64-bit unsigned BigInt at the specified byte offset
310    pub fn get_big_uint64(&self, offset: usize, little_endian: bool) -> Result<u64, JSError> {
311        self.check_bounds(offset, 8)?;
312        let buffer = self.buffer.borrow();
313        let bytes = &buffer.data[self.byte_offset + offset..self.byte_offset + offset + 8];
314        if little_endian {
315            Ok(u64::from_le_bytes([
316                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
317            ]))
318        } else {
319            Ok(u64::from_be_bytes([
320                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
321            ]))
322        }
323    }
324
325    /// Set an 8-bit signed integer at the specified byte offset
326    pub fn set_int8(&mut self, offset: usize, value: i8) -> Result<(), JSError> {
327        self.check_bounds(offset, 1)?;
328        let mut buffer = self.buffer.borrow_mut();
329        buffer.data[self.byte_offset + offset] = value as u8;
330        Ok(())
331    }
332
333    /// Set an 8-bit unsigned integer at the specified byte offset
334    pub fn set_uint8(&mut self, offset: usize, value: u8) -> Result<(), JSError> {
335        self.check_bounds(offset, 1)?;
336        let mut buffer = self.buffer.borrow_mut();
337        buffer.data[self.byte_offset + offset] = value;
338        Ok(())
339    }
340
341    /// Set a 16-bit signed integer at the specified byte offset
342    pub fn set_int16(&mut self, offset: usize, value: i16, little_endian: bool) -> Result<(), JSError> {
343        self.check_bounds(offset, 2)?;
344        let mut buffer = self.buffer.borrow_mut();
345        let bytes = if little_endian { value.to_le_bytes() } else { value.to_be_bytes() };
346        buffer.data[self.byte_offset + offset..self.byte_offset + offset + 2].copy_from_slice(&bytes);
347        Ok(())
348    }
349
350    /// Set a 16-bit unsigned integer at the specified byte offset
351    pub fn set_uint16(&mut self, offset: usize, value: u16, little_endian: bool) -> Result<(), JSError> {
352        self.check_bounds(offset, 2)?;
353        let mut buffer = self.buffer.borrow_mut();
354        let bytes = if little_endian { value.to_le_bytes() } else { value.to_be_bytes() };
355        buffer.data[self.byte_offset + offset..self.byte_offset + offset + 2].copy_from_slice(&bytes);
356        Ok(())
357    }
358
359    /// Set a 32-bit signed integer at the specified byte offset
360    pub fn set_int32(&mut self, offset: usize, value: i32, little_endian: bool) -> Result<(), JSError> {
361        self.check_bounds(offset, 4)?;
362        let mut buffer = self.buffer.borrow_mut();
363        let bytes = if little_endian { value.to_le_bytes() } else { value.to_be_bytes() };
364        buffer.data[self.byte_offset + offset..self.byte_offset + offset + 4].copy_from_slice(&bytes);
365        Ok(())
366    }
367
368    /// Set a 32-bit unsigned integer at the specified byte offset
369    pub fn set_uint32(&mut self, offset: usize, value: u32, little_endian: bool) -> Result<(), JSError> {
370        self.check_bounds(offset, 4)?;
371        let mut buffer = self.buffer.borrow_mut();
372        let bytes = if little_endian { value.to_le_bytes() } else { value.to_be_bytes() };
373        buffer.data[self.byte_offset + offset..self.byte_offset + offset + 4].copy_from_slice(&bytes);
374        Ok(())
375    }
376
377    /// Set a 32-bit float at the specified byte offset
378    pub fn set_float32(&mut self, offset: usize, value: f32, little_endian: bool) -> Result<(), JSError> {
379        self.check_bounds(offset, 4)?;
380        let mut buffer = self.buffer.borrow_mut();
381        let bytes = if little_endian { value.to_le_bytes() } else { value.to_be_bytes() };
382        buffer.data[self.byte_offset + offset..self.byte_offset + offset + 4].copy_from_slice(&bytes);
383        Ok(())
384    }
385
386    /// Set a 64-bit float at the specified byte offset
387    pub fn set_float64(&mut self, offset: usize, value: f64, little_endian: bool) -> Result<(), JSError> {
388        self.check_bounds(offset, 8)?;
389        let mut buffer = self.buffer.borrow_mut();
390        let bytes = if little_endian { value.to_le_bytes() } else { value.to_be_bytes() };
391        buffer.data[self.byte_offset + offset..self.byte_offset + offset + 8].copy_from_slice(&bytes);
392        Ok(())
393    }
394
395    /// Set a 64-bit signed BigInt at the specified byte offset
396    pub fn set_big_int64(&mut self, offset: usize, value: i64, little_endian: bool) -> Result<(), JSError> {
397        self.check_bounds(offset, 8)?;
398        let mut buffer = self.buffer.borrow_mut();
399        let bytes = if little_endian { value.to_le_bytes() } else { value.to_be_bytes() };
400        buffer.data[self.byte_offset + offset..self.byte_offset + offset + 8].copy_from_slice(&bytes);
401        Ok(())
402    }
403
404    /// Set a 64-bit unsigned BigInt at the specified byte offset
405    pub fn set_big_uint64(&mut self, offset: usize, value: u64, little_endian: bool) -> Result<(), JSError> {
406        self.check_bounds(offset, 8)?;
407        let mut buffer = self.buffer.borrow_mut();
408        let bytes = if little_endian { value.to_le_bytes() } else { value.to_be_bytes() };
409        buffer.data[self.byte_offset + offset..self.byte_offset + offset + 8].copy_from_slice(&bytes);
410        Ok(())
411    }
412
413    /// Helper method to check bounds and buffer state
414    fn check_bounds(&self, offset: usize, size: usize) -> Result<(), JSError> {
415        let buffer = self.buffer.borrow();
416        if buffer.detached {
417            return Err(raise_type_error!("ArrayBuffer is detached"));
418        }
419        if offset + size > self.byte_length {
420            return Err(raise_type_error!("Offset out of bounds"));
421        }
422        Ok(())
423    }
424}
425
426#[derive(Clone, Debug)]
427pub enum GeneratorState {
428    NotStarted,
429    Running { pc: usize, stack: Vec<Value> },   // program counter and value stack
430    Suspended { pc: usize, stack: Vec<Value> }, // suspended at yield
431    Completed,
432}
433
434pub type JSObjectDataPtr = Rc<RefCell<JSObjectData>>;
435
436#[derive(Clone, Default)]
437pub struct JSObjectData {
438    pub properties: std::collections::HashMap<PropertyKey, Rc<RefCell<Value>>>,
439    pub constants: std::collections::HashSet<String>,
440    pub prototype: Option<Rc<RefCell<JSObjectData>>>,
441    pub is_function_scope: bool,
442}
443
444impl std::fmt::Debug for JSObjectData {
445    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
446        write!(
447            f,
448            "JSObjectData {{ properties: {}, constants: {}, prototype: {}, is_function_scope: {} }}",
449            self.properties.len(),
450            self.constants.len(),
451            self.prototype.is_some(),
452            self.is_function_scope
453        )
454    }
455}
456
457impl JSObjectData {
458    pub fn new() -> Self {
459        JSObjectData::default()
460    }
461
462    pub fn insert(&mut self, key: PropertyKey, val: Rc<RefCell<Value>>) {
463        self.properties.insert(key, val);
464    }
465
466    pub fn get(&self, key: &PropertyKey) -> Option<Rc<RefCell<Value>>> {
467        self.properties.get(key).cloned()
468    }
469
470    pub fn contains_key(&self, key: &PropertyKey) -> bool {
471        self.properties.contains_key(key)
472    }
473
474    pub fn remove(&mut self, key: &PropertyKey) -> Option<Rc<RefCell<Value>>> {
475        self.properties.remove(key)
476    }
477
478    pub fn keys(&self) -> std::collections::hash_map::Keys<'_, PropertyKey, Rc<RefCell<Value>>> {
479        self.properties.keys()
480    }
481
482    pub fn is_const(&self, key: &str) -> bool {
483        self.constants.contains(key)
484    }
485
486    pub fn set_const(&mut self, key: String) {
487        self.constants.insert(key);
488    }
489}
490
491#[derive(Clone, Debug)]
492pub struct SymbolData {
493    pub description: Option<String>,
494}
495
496#[derive(Clone)]
497pub enum Value {
498    Number(f64),
499    /// BigInt literal stored as string form (e.g. "123n" or "0x123n")
500    BigInt(String),
501    String(Vec<u16>), // UTF-16 code units
502    Boolean(bool),
503    Undefined,
504    Object(JSObjectDataPtr),                                         // Object with properties
505    Function(String),                                                // Function name
506    Closure(Vec<String>, Vec<Statement>, JSObjectDataPtr),           // parameters, body, captured environment
507    AsyncClosure(Vec<String>, Vec<Statement>, JSObjectDataPtr),      // parameters, body, captured environment
508    GeneratorFunction(Vec<String>, Vec<Statement>, JSObjectDataPtr), // parameters, body, captured environment
509    ClassDefinition(Rc<ClassDefinition>),                            // Class definition
510    Getter(Vec<Statement>, JSObjectDataPtr),                         // getter body, captured environment
511    Setter(Vec<String>, Vec<Statement>, JSObjectDataPtr),            // setter parameter, body, captured environment
512    Property {
513        // Property descriptor with getter/setter/value
514        value: Option<Rc<RefCell<Value>>>,
515        getter: Option<(Vec<Statement>, JSObjectDataPtr)>,
516        setter: Option<(Vec<String>, Vec<Statement>, JSObjectDataPtr)>,
517    },
518    Promise(Rc<RefCell<JSPromise>>),         // Promise object
519    Symbol(Rc<SymbolData>),                  // Symbol primitive with description
520    Map(Rc<RefCell<JSMap>>),                 // Map object
521    Set(Rc<RefCell<JSSet>>),                 // Set object
522    WeakMap(Rc<RefCell<JSWeakMap>>),         // WeakMap object
523    WeakSet(Rc<RefCell<JSWeakSet>>),         // WeakSet object
524    Generator(Rc<RefCell<JSGenerator>>),     // Generator object
525    Proxy(Rc<RefCell<JSProxy>>),             // Proxy object
526    ArrayBuffer(Rc<RefCell<JSArrayBuffer>>), // ArrayBuffer object
527    DataView(Rc<RefCell<JSDataView>>),       // DataView object
528    TypedArray(Rc<RefCell<JSTypedArray>>),   // TypedArray object
529}
530
531impl std::fmt::Debug for Value {
532    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
533        match self {
534            Value::Number(n) => write!(f, "Number({})", n),
535            Value::BigInt(s) => write!(f, "BigInt({})", s),
536            Value::String(s) => write!(f, "String({})", String::from_utf16_lossy(s)),
537            Value::Boolean(b) => write!(f, "Boolean({})", b),
538            Value::Undefined => write!(f, "Undefined"),
539            Value::Object(obj) => write!(f, "Object({:p})", Rc::as_ptr(obj)),
540            Value::Function(name) => write!(f, "Function({})", name),
541            Value::Closure(_, _, _) => write!(f, "Closure"),
542            Value::AsyncClosure(_, _, _) => write!(f, "AsyncClosure"),
543            Value::GeneratorFunction(_, _, _) => write!(f, "GeneratorFunction"),
544            Value::ClassDefinition(_) => write!(f, "ClassDefinition"),
545            Value::Getter(_, _) => write!(f, "Getter"),
546            Value::Setter(_, _, _) => write!(f, "Setter"),
547            Value::Property { .. } => write!(f, "Property"),
548            Value::Promise(p) => write!(f, "Promise({:p})", Rc::as_ptr(p)),
549            Value::Symbol(_) => write!(f, "Symbol"),
550            Value::Map(m) => write!(f, "Map({:p})", Rc::as_ptr(m)),
551            Value::Set(s) => write!(f, "Set({:p})", Rc::as_ptr(s)),
552            Value::WeakMap(wm) => write!(f, "WeakMap({:p})", Rc::as_ptr(wm)),
553            Value::WeakSet(ws) => write!(f, "WeakSet({:p})", Rc::as_ptr(ws)),
554            Value::Generator(g) => write!(f, "Generator({:p})", Rc::as_ptr(g)),
555            Value::Proxy(p) => write!(f, "Proxy({:p})", Rc::as_ptr(p)),
556            Value::ArrayBuffer(ab) => write!(f, "ArrayBuffer({:p})", Rc::as_ptr(ab)),
557            Value::DataView(dv) => write!(f, "DataView({:p})", Rc::as_ptr(dv)),
558            Value::TypedArray(ta) => write!(f, "TypedArray({:p})", Rc::as_ptr(ta)),
559        }
560    }
561}
562
563pub fn is_truthy(val: &Value) -> bool {
564    match val {
565        Value::BigInt(s) => {
566            // Simple check: treat bigint as falsy only when it's zero (0n, 0x0n, 0b0n, 0o0n).
567            let s_no_n = if s.ends_with('n') { &s[..s.len() - 1] } else { s.as_str() };
568            #[allow(clippy::if_same_then_else)]
569            let s_no_prefix = if s_no_n.starts_with("0x") || s_no_n.starts_with("0X") {
570                &s_no_n[2..]
571            } else if s_no_n.starts_with("0b") || s_no_n.starts_with("0B") {
572                &s_no_n[2..]
573            } else if s_no_n.starts_with("0o") || s_no_n.starts_with("0O") {
574                &s_no_n[2..]
575            } else {
576                s_no_n
577            };
578            !s_no_prefix.chars().all(|c| c == '0')
579        }
580        Value::Number(n) => *n != 0.0 && !n.is_nan(),
581        Value::String(s) => !s.is_empty(),
582        Value::Boolean(b) => *b,
583        Value::Undefined => false,
584        Value::Object(_) => true,
585        Value::Function(_) => true,
586        Value::Closure(_, _, _) => true,
587        Value::AsyncClosure(_, _, _) => true,
588        Value::GeneratorFunction(_, _, _) => true,
589        Value::ClassDefinition(_) => true,
590        Value::Getter(_, _) => true,
591        Value::Setter(_, _, _) => true,
592        Value::Property { .. } => true,
593        Value::Promise(_) => true,
594        Value::Symbol(_) => true,
595        Value::Map(_) => true,
596        Value::Set(_) => true,
597        Value::WeakMap(_) => true,
598        Value::WeakSet(_) => true,
599        Value::Generator(_) => true,
600        Value::Proxy(_) => true,
601        Value::ArrayBuffer(_) => true,
602        Value::DataView(_) => true,
603        Value::TypedArray(_) => true,
604    }
605}
606
607// Helper function to compare two values for equality
608pub fn values_equal(a: &Value, b: &Value) -> bool {
609    match (a, b) {
610        (Value::BigInt(sa), Value::BigInt(sb)) => sa == sb,
611        (Value::Number(na), Value::Number(nb)) => na == nb,
612        (Value::String(sa), Value::String(sb)) => sa == sb,
613        (Value::Boolean(ba), Value::Boolean(bb)) => ba == bb,
614        (Value::Undefined, Value::Undefined) => true,
615        (Value::Object(_), Value::Object(_)) => false, // Objects are not equal unless same reference
616        (Value::Symbol(sa), Value::Symbol(sb)) => Rc::ptr_eq(sa, sb), // Symbols are equal if same reference
617        _ => false,                                    // Different types are not equal
618    }
619}
620
621// Helper function to convert value to string for display
622pub fn value_to_string(val: &Value) -> String {
623    match val {
624        Value::Number(n) => n.to_string(),
625        Value::BigInt(s) => s.clone(),
626        Value::String(s) => String::from_utf16_lossy(s),
627        Value::Boolean(b) => b.to_string(),
628        Value::Undefined => "undefined".to_string(),
629        Value::Object(_) => "[object Object]".to_string(),
630        Value::Function(name) => format!("function {}", name),
631        Value::Closure(_, _, _) => "function".to_string(),
632        Value::AsyncClosure(_, _, _) => "function".to_string(),
633        Value::GeneratorFunction(_, _, _) => "function".to_string(),
634        Value::ClassDefinition(_) => "class".to_string(),
635        Value::Getter(_, _) => "getter".to_string(),
636        Value::Setter(_, _, _) => "setter".to_string(),
637        Value::Property { .. } => "[property]".to_string(),
638        Value::Promise(_) => "[object Promise]".to_string(),
639        Value::Symbol(desc) => match desc.description.as_ref() {
640            Some(d) => format!("Symbol({})", d),
641            None => "Symbol()".to_string(),
642        },
643        Value::Map(_) => "[object Map]".to_string(),
644        Value::Set(_) => "[object Set]".to_string(),
645        Value::WeakMap(_) => "[object WeakMap]".to_string(),
646        Value::WeakSet(_) => "[object WeakSet]".to_string(),
647        Value::Generator(_) => "[object Generator]".to_string(),
648        Value::Proxy(_) => "[object Proxy]".to_string(),
649        Value::ArrayBuffer(_) => "[object ArrayBuffer]".to_string(),
650        Value::DataView(_) => "[object DataView]".to_string(),
651        Value::TypedArray(_) => "[object TypedArray]".to_string(),
652    }
653}
654
655// Helper: perform ToPrimitive coercion with a given hint ('string', 'number', 'default')
656pub fn to_primitive(val: &Value, hint: &str) -> Result<Value, JSError> {
657    match val {
658        Value::Number(_) | Value::String(_) | Value::Boolean(_) | Value::Undefined | Value::Symbol(_) => Ok(val.clone()),
659        Value::Object(obj_map) => {
660            // Prefer explicit [Symbol.toPrimitive] if present and callable
661            if let Some(tp_sym) = get_well_known_symbol_rc("toPrimitive") {
662                let key = PropertyKey::Symbol(tp_sym.clone());
663                if let Some(method_rc) = obj_get_value(obj_map, &key)? {
664                    let method_val = method_rc.borrow().clone();
665                    match method_val {
666                        Value::Closure(params, body, captured_env) | Value::AsyncClosure(params, body, captured_env) => {
667                            // Create a new execution env and bind this
668                            let func_env = Rc::new(RefCell::new(JSObjectData::new()));
669                            func_env.borrow_mut().prototype = Some(captured_env.clone());
670                            env_set(&func_env, "this", Value::Object(obj_map.clone()))?;
671                            // Pass hint as first param if the function declares params
672                            if !params.is_empty() {
673                                env_set(&func_env, &params[0], Value::String(utf8_to_utf16(hint)))?;
674                            }
675                            let result = evaluate_statements(&func_env, &body)?;
676                            match result {
677                                Value::Number(_) | Value::String(_) | Value::Boolean(_) | Value::Symbol(_) => return Ok(result),
678                                _ => {
679                                    return Err(raise_type_error!("[Symbol.toPrimitive] must return a primitive"));
680                                }
681                            }
682                        }
683                        _ => {
684                            // Not a closure/minimally supported callable - fall through to default algorithm
685                        }
686                    }
687                }
688            }
689
690            // Default algorithm: order depends on hint
691            if hint == "string" {
692                // toString -> valueOf
693                let to_s = crate::js_object::handle_to_string_method(&Value::Object(obj_map.clone()), &[])?;
694                if matches!(to_s, Value::String(_) | Value::Number(_) | Value::Boolean(_)) {
695                    return Ok(to_s);
696                }
697                let val_of = crate::js_object::handle_value_of_method(&Value::Object(obj_map.clone()), &[])?;
698                if matches!(val_of, Value::String(_) | Value::Number(_) | Value::Boolean(_)) {
699                    return Ok(val_of);
700                }
701            } else {
702                // number or default: valueOf -> toString
703                let val_of = crate::js_object::handle_value_of_method(&Value::Object(obj_map.clone()), &[])?;
704                if matches!(val_of, Value::Number(_) | Value::String(_) | Value::Boolean(_)) {
705                    return Ok(val_of);
706                }
707                let to_s = crate::js_object::handle_to_string_method(&Value::Object(obj_map.clone()), &[])?;
708                if matches!(to_s, Value::String(_) | Value::Number(_) | Value::Boolean(_)) {
709                    return Ok(to_s);
710                }
711            }
712
713            Err(raise_type_error!("Cannot convert object to primitive"))
714        }
715        _ => Ok(val.clone()),
716    }
717}
718
719// Helper function to convert value to string for sorting
720pub fn value_to_sort_string(val: &Value) -> String {
721    match val {
722        Value::Number(n) => {
723            if n.is_nan() {
724                "NaN".to_string()
725            } else if *n == f64::INFINITY {
726                "Infinity".to_string()
727            } else if *n == f64::NEG_INFINITY {
728                "-Infinity".to_string()
729            } else {
730                n.to_string()
731            }
732        }
733        Value::BigInt(s) => s.clone(),
734        Value::String(s) => String::from_utf16_lossy(s),
735        Value::Boolean(b) => b.to_string(),
736        Value::Undefined => "undefined".to_string(),
737        Value::Object(_) => "[object Object]".to_string(),
738        Value::Function(name) => format!("[function {}]", name),
739        Value::Closure(_, _, _) | Value::AsyncClosure(_, _, _) | Value::GeneratorFunction(_, _, _) => "[function]".to_string(),
740        Value::ClassDefinition(_) => "[class]".to_string(),
741        Value::Getter(_, _) => "[getter]".to_string(),
742        Value::Setter(_, _, _) => "[setter]".to_string(),
743        Value::Property { .. } => "[property]".to_string(),
744        Value::Promise(_) => "[object Promise]".to_string(),
745        Value::Symbol(_) => "[object Symbol]".to_string(),
746        Value::Map(_) => "[object Map]".to_string(),
747        Value::Set(_) => "[object Set]".to_string(),
748        Value::WeakMap(_) => "[object WeakMap]".to_string(),
749        Value::WeakSet(_) => "[object WeakSet]".to_string(),
750        Value::Generator(_) => "[object Generator]".to_string(),
751        Value::Proxy(_) => "[object Proxy]".to_string(),
752        Value::ArrayBuffer(_) => "[object ArrayBuffer]".to_string(),
753        Value::DataView(_) => "[object DataView]".to_string(),
754        Value::TypedArray(_) => "[object TypedArray]".to_string(),
755    }
756}
757
758// Helper accessors for objects and environments
759pub fn obj_get_value(js_obj: &JSObjectDataPtr, key: &PropertyKey) -> Result<Option<Rc<RefCell<Value>>>, JSError> {
760    // Check if this object is a proxy wrapper
761    if let Some(proxy_val) = js_obj.borrow().get(&"__proxy__".into())
762        && let Value::Proxy(proxy) = &*proxy_val.borrow()
763    {
764        return crate::js_proxy::proxy_get_property(proxy, key);
765    }
766
767    // Search own properties and then walk the prototype chain until we find
768    // a matching property or run out of prototypes.
769    let mut current: Option<JSObjectDataPtr> = Some(js_obj.clone());
770    while let Some(cur) = current {
771        if let Some(val) = cur.borrow().get(key) {
772            // Found an own/inherited value on `cur`. For getters we bind `this` to
773            // the original object (`js_obj`) as per JS semantics.
774            let val_clone = val.borrow().clone();
775            match val_clone {
776                Value::Property { value, getter, .. } => {
777                    log::trace!("obj_get_value - property descriptor found for key {}", key);
778                    if let Some((body, env)) = getter {
779                        // Create a new environment with this bound to the original object
780                        let getter_env = Rc::new(RefCell::new(JSObjectData::new()));
781                        getter_env.borrow_mut().prototype = Some(env);
782                        env_set(&getter_env, "this", Value::Object(js_obj.clone()))?;
783                        let result = evaluate_statements(&getter_env, &body)?;
784                        return Ok(Some(Rc::new(RefCell::new(result))));
785                    } else if let Some(val_rc) = value {
786                        return Ok(Some(val_rc));
787                    } else {
788                        return Ok(Some(Rc::new(RefCell::new(Value::Undefined))));
789                    }
790                }
791                Value::Getter(body, env) => {
792                    log::trace!("obj_get_value - getter found for key {}", key);
793                    let getter_env = Rc::new(RefCell::new(JSObjectData::new()));
794                    getter_env.borrow_mut().prototype = Some(env);
795                    env_set(&getter_env, "this", Value::Object(js_obj.clone()))?;
796                    let result = evaluate_statements(&getter_env, &body)?;
797                    return Ok(Some(Rc::new(RefCell::new(result))));
798                }
799                _ => {
800                    log::trace!("obj_get_value - raw value found for key {}", key);
801                    return Ok(Some(val.clone()));
802                }
803            }
804        }
805        // Not found on this object; continue with prototype.
806        current = cur.borrow().prototype.clone();
807    }
808
809    // No own or inherited property found, fall back to special-case handling
810    // (well-known symbol fallbacks, array/string iterator helpers, etc.).
811
812    // Provide default well-known symbol fallbacks (non-own) for some built-ins.
813    if let PropertyKey::Symbol(sym_rc) = key {
814        // Support default Symbol.iterator for built-ins like Array and wrapped String objects.
815        if let Some(iter_sym_rc) = get_well_known_symbol_rc("iterator")
816            && let (Value::Symbol(iter_sd), Value::Symbol(req_sd)) = (&*iter_sym_rc.borrow(), &*sym_rc.borrow())
817            && Rc::ptr_eq(iter_sd, req_sd)
818        {
819            // Array default iterator
820            if is_array(js_obj) {
821                // next function body
822                let next_body = vec![
823                    Statement::Let("idx".to_string(), Some(Expr::Var("__i".to_string()))),
824                    Statement::If(
825                        Expr::Binary(
826                            Box::new(Expr::Var("idx".to_string())),
827                            BinaryOp::LessThan,
828                            Box::new(Expr::Property(Box::new(Expr::Var("__array".to_string())), "length".to_string())),
829                        ),
830                        vec![
831                            Statement::Let(
832                                "v".to_string(),
833                                Some(Expr::Index(
834                                    Box::new(Expr::Var("__array".to_string())),
835                                    Box::new(Expr::Var("idx".to_string())),
836                                )),
837                            ),
838                            Statement::Expr(Expr::Assign(
839                                Box::new(Expr::Var("__i".to_string())),
840                                Box::new(Expr::Binary(
841                                    Box::new(Expr::Var("idx".to_string())),
842                                    BinaryOp::Add,
843                                    Box::new(Expr::Value(Value::Number(1.0))),
844                                )),
845                            )),
846                            Statement::Return(Some(Expr::Object(vec![
847                                ("value".to_string(), Expr::Var("v".to_string())),
848                                ("done".to_string(), Expr::Value(Value::Boolean(false))),
849                            ]))),
850                        ],
851                        Some(vec![Statement::Return(Some(Expr::Object(vec![(
852                            "done".to_string(),
853                            Expr::Value(Value::Boolean(true)),
854                        )])))]),
855                    ),
856                ];
857
858                let arr_iter_body = vec![
859                    Statement::Let("__i".to_string(), Some(Expr::Value(Value::Number(0.0)))),
860                    Statement::Return(Some(Expr::Object(vec![(
861                        "next".to_string(),
862                        Expr::Function(Vec::new(), next_body),
863                    )]))),
864                ];
865
866                let captured_env = Rc::new(RefCell::new(JSObjectData::new()));
867                captured_env.borrow_mut().insert(
868                    PropertyKey::String("__array".to_string()),
869                    Rc::new(RefCell::new(Value::Object(js_obj.clone()))),
870                );
871                let closure = Value::Closure(Vec::new(), arr_iter_body, captured_env.clone());
872                return Ok(Some(Rc::new(RefCell::new(closure))));
873            }
874
875            // Map default iterator
876            if let Some(map_val) = js_obj.borrow().get(&"__map__".into())
877                && let Value::Map(map_rc) = &*map_val.borrow()
878            {
879                let map_entries = map_rc.borrow().entries.clone();
880
881                // next function body for Map iteration (returns [key, value] pairs)
882                let next_body = vec![
883                    Statement::Let("idx".to_string(), Some(Expr::Var("__i".to_string()))),
884                    Statement::If(
885                        Expr::Binary(
886                            Box::new(Expr::Var("idx".to_string())),
887                            BinaryOp::LessThan,
888                            Box::new(Expr::Value(Value::Number(map_entries.len() as f64))),
889                        ),
890                        vec![
891                            Statement::Let(
892                                "entry".to_string(),
893                                Some(Expr::Array(vec![
894                                    Expr::Property(
895                                        Box::new(Expr::Index(
896                                            Box::new(Expr::Var("__entries".to_string())),
897                                            Box::new(Expr::Var("idx".to_string())),
898                                        )),
899                                        "0".to_string(),
900                                    ),
901                                    Expr::Property(
902                                        Box::new(Expr::Index(
903                                            Box::new(Expr::Var("__entries".to_string())),
904                                            Box::new(Expr::Var("idx".to_string())),
905                                        )),
906                                        "1".to_string(),
907                                    ),
908                                ])),
909                            ),
910                            Statement::Expr(Expr::Assign(
911                                Box::new(Expr::Var("__i".to_string())),
912                                Box::new(Expr::Binary(
913                                    Box::new(Expr::Var("__i".to_string())),
914                                    BinaryOp::Add,
915                                    Box::new(Expr::Value(Value::Number(1.0))),
916                                )),
917                            )),
918                            Statement::Return(Some(Expr::Object(vec![
919                                ("value".to_string(), Expr::Var("entry".to_string())),
920                                ("done".to_string(), Expr::Value(Value::Boolean(false))),
921                            ]))),
922                        ],
923                        Some(vec![Statement::Return(Some(Expr::Object(vec![(
924                            "done".to_string(),
925                            Expr::Value(Value::Boolean(true)),
926                        )])))]),
927                    ),
928                ];
929
930                let map_iter_body = vec![
931                    Statement::Let("__i".to_string(), Some(Expr::Value(Value::Number(0.0)))),
932                    Statement::Return(Some(Expr::Object(vec![(
933                        "next".to_string(),
934                        Expr::Function(Vec::new(), next_body),
935                    )]))),
936                ];
937
938                let captured_env = Rc::new(RefCell::new(JSObjectData::new()));
939                // Store map entries in the closure environment
940                let mut entries_obj = JSObjectData::new();
941                for (i, (key, value)) in map_entries.iter().enumerate() {
942                    let mut entry_obj = JSObjectData::new();
943                    entry_obj.insert("0".into(), Rc::new(RefCell::new(key.clone())));
944                    entry_obj.insert("1".into(), Rc::new(RefCell::new(value.clone())));
945                    entries_obj.insert(
946                        i.to_string().into(),
947                        Rc::new(RefCell::new(Value::Object(Rc::new(RefCell::new(entry_obj))))),
948                    );
949                }
950                captured_env.borrow_mut().insert(
951                    "__entries".into(),
952                    Rc::new(RefCell::new(Value::Object(Rc::new(RefCell::new(entries_obj))))),
953                );
954
955                let closure = Value::Closure(Vec::new(), map_iter_body, captured_env.clone());
956                return Ok(Some(Rc::new(RefCell::new(closure))));
957            }
958
959            // Set default iterator
960            if let Some(set_val) = js_obj.borrow().get(&"__set__".into())
961                && let Value::Set(set_rc) = &*set_val.borrow()
962            {
963                let set_values = set_rc.borrow().values.clone();
964                // next function body for Set iteration (returns values)
965                let next_body = vec![
966                    Statement::Let("idx".to_string(), Some(Expr::Var("__i".to_string()))),
967                    Statement::If(
968                        Expr::Binary(
969                            Box::new(Expr::Var("idx".to_string())),
970                            BinaryOp::LessThan,
971                            Box::new(Expr::Value(Value::Number(set_values.len() as f64))),
972                        ),
973                        vec![
974                            Statement::Let(
975                                "value".to_string(),
976                                Some(Expr::Index(
977                                    Box::new(Expr::Var("__values".to_string())),
978                                    Box::new(Expr::Var("idx".to_string())),
979                                )),
980                            ),
981                            Statement::Expr(Expr::Assign(
982                                Box::new(Expr::Var("__i".to_string())),
983                                Box::new(Expr::Binary(
984                                    Box::new(Expr::Var("idx".to_string())),
985                                    BinaryOp::Add,
986                                    Box::new(Expr::Value(Value::Number(1.0))),
987                                )),
988                            )),
989                            Statement::Return(Some(Expr::Object(vec![
990                                ("value".to_string(), Expr::Var("value".to_string())),
991                                ("done".to_string(), Expr::Value(Value::Boolean(false))),
992                            ]))),
993                        ],
994                        Some(vec![Statement::Return(Some(Expr::Object(vec![(
995                            "done".to_string(),
996                            Expr::Value(Value::Boolean(true)),
997                        )])))]),
998                    ),
999                ];
1000
1001                let set_iter_body = vec![
1002                    Statement::Let("__i".to_string(), Some(Expr::Value(Value::Number(0.0)))),
1003                    Statement::Return(Some(Expr::Object(vec![(
1004                        "next".to_string(),
1005                        Expr::Function(Vec::new(), next_body),
1006                    )]))),
1007                ];
1008
1009                let captured_env = Rc::new(RefCell::new(JSObjectData::new()));
1010                // Store set values in the closure environment
1011                let mut values_obj = JSObjectData::new();
1012                for (i, value) in set_values.iter().enumerate() {
1013                    values_obj.insert(i.to_string().into(), Rc::new(RefCell::new(value.clone())));
1014                }
1015                captured_env.borrow_mut().insert(
1016                    "__values".into(),
1017                    Rc::new(RefCell::new(Value::Object(Rc::new(RefCell::new(values_obj))))),
1018                );
1019
1020                let closure = Value::Closure(Vec::new(), set_iter_body, captured_env.clone());
1021                return Ok(Some(Rc::new(RefCell::new(closure))));
1022            }
1023
1024            // Wrapped String iterator (for String objects)
1025            if let Some(wrapped) = js_obj.borrow().get(&"__value__".into())
1026                && let Value::String(_) = &*wrapped.borrow()
1027            {
1028                let next_body = vec![
1029                    Statement::Let("idx".to_string(), Some(Expr::Var("__i".to_string()))),
1030                    Statement::If(
1031                        Expr::Binary(
1032                            Box::new(Expr::Var("idx".to_string())),
1033                            BinaryOp::LessThan,
1034                            Box::new(Expr::Property(Box::new(Expr::Var("__s".to_string())), "length".to_string())),
1035                        ),
1036                        vec![
1037                            Statement::Let(
1038                                "v".to_string(),
1039                                Some(Expr::Index(
1040                                    Box::new(Expr::Var("__s".to_string())),
1041                                    Box::new(Expr::Var("idx".to_string())),
1042                                )),
1043                            ),
1044                            Statement::Expr(Expr::Assign(
1045                                Box::new(Expr::Var("__i".to_string())),
1046                                Box::new(Expr::Binary(
1047                                    Box::new(Expr::Var("idx".to_string())),
1048                                    BinaryOp::Add,
1049                                    Box::new(Expr::Value(Value::Number(1.0))),
1050                                )),
1051                            )),
1052                            Statement::Return(Some(Expr::Object(vec![
1053                                ("value".to_string(), Expr::Var("v".to_string())),
1054                                ("done".to_string(), Expr::Value(Value::Boolean(false))),
1055                            ]))),
1056                        ],
1057                        Some(vec![Statement::Return(Some(Expr::Object(vec![(
1058                            "done".to_string(),
1059                            Expr::Value(Value::Boolean(true)),
1060                        )])))]),
1061                    ),
1062                ];
1063
1064                let str_iter_body = vec![
1065                    Statement::Let("__i".to_string(), Some(Expr::Value(Value::Number(0.0)))),
1066                    Statement::Return(Some(Expr::Object(vec![(
1067                        "next".to_string(),
1068                        Expr::Function(Vec::new(), next_body),
1069                    )]))),
1070                ];
1071
1072                let captured_env = Rc::new(RefCell::new(JSObjectData::new()));
1073                captured_env.borrow_mut().insert(
1074                    PropertyKey::String("__s".to_string()),
1075                    Rc::new(RefCell::new(wrapped.borrow().clone())),
1076                );
1077                let closure = Value::Closure(Vec::new(), str_iter_body, captured_env.clone());
1078                return Ok(Some(Rc::new(RefCell::new(closure))));
1079            }
1080        }
1081        if let Some(tag_sym_rc) = get_well_known_symbol_rc("toStringTag")
1082            && let (Value::Symbol(tag_sd), Value::Symbol(req_sd)) = (&*tag_sym_rc.borrow(), &*sym_rc.borrow())
1083            && Rc::ptr_eq(tag_sd, req_sd)
1084        {
1085            if is_array(js_obj) {
1086                return Ok(Some(Rc::new(RefCell::new(Value::String(utf8_to_utf16("Array"))))));
1087            }
1088            if let Some(wrapped) = js_obj.borrow().get(&"__value__".into()) {
1089                match &*wrapped.borrow() {
1090                    Value::String(_) => return Ok(Some(Rc::new(RefCell::new(Value::String(utf8_to_utf16("String")))))),
1091                    Value::Number(_) => return Ok(Some(Rc::new(RefCell::new(Value::String(utf8_to_utf16("Number")))))),
1092                    Value::Boolean(_) => return Ok(Some(Rc::new(RefCell::new(Value::String(utf8_to_utf16("Boolean")))))),
1093                    _ => {}
1094                }
1095            }
1096            if js_obj.borrow().contains_key(&"__timestamp".into()) {
1097                return Ok(Some(Rc::new(RefCell::new(Value::String(utf8_to_utf16("Date"))))));
1098            }
1099        }
1100    }
1101
1102    Ok(None)
1103}
1104
1105pub fn obj_set_value(js_obj: &JSObjectDataPtr, key: &PropertyKey, val: Value) -> Result<(), JSError> {
1106    // Check if this object is a proxy wrapper
1107    if let Some(proxy_val) = js_obj.borrow().get(&"__proxy__".into())
1108        && let Value::Proxy(proxy) = &*proxy_val.borrow()
1109    {
1110        let success = crate::js_proxy::proxy_set_property(proxy, key, val)?;
1111        if !success {
1112            return Err(raise_eval_error!("Proxy set trap returned false"));
1113        }
1114        return Ok(());
1115    }
1116
1117    // Check if there's a setter for this property
1118    let existing_opt = js_obj.borrow().get(key);
1119    if let Some(existing) = existing_opt {
1120        match existing.borrow().clone() {
1121            Value::Property { value: _, getter, setter } => {
1122                if let Some((param, body, env)) = setter {
1123                    // Create a new environment with this bound to the object and the parameter
1124                    let setter_env = Rc::new(RefCell::new(JSObjectData::new()));
1125                    setter_env.borrow_mut().prototype = Some(env);
1126                    env_set(&setter_env, "this", Value::Object(js_obj.clone()))?;
1127                    env_set(&setter_env, &param[0], val)?;
1128                    let _v = evaluate_statements(&setter_env, &body)?;
1129                } else {
1130                    // No setter, update value
1131                    let value = Some(Rc::new(RefCell::new(val)));
1132                    let new_prop = Value::Property { value, getter, setter };
1133                    js_obj.borrow_mut().insert(key.clone(), Rc::new(RefCell::new(new_prop)));
1134                }
1135                return Ok(());
1136            }
1137            Value::Setter(param, body, env) => {
1138                // Create a new environment with this bound to the object and the parameter
1139                let setter_env = Rc::new(RefCell::new(JSObjectData::new()));
1140                setter_env.borrow_mut().prototype = Some(env);
1141                env_set(&setter_env, "this", Value::Object(js_obj.clone()))?;
1142                env_set(&setter_env, &param[0], val)?;
1143                evaluate_statements(&setter_env, &body)?;
1144                return Ok(());
1145            }
1146            _ => {}
1147        }
1148    }
1149    // No setter, just set the value normally
1150    js_obj.borrow_mut().insert(key.clone(), Rc::new(RefCell::new(val)));
1151    Ok(())
1152}
1153
1154pub fn obj_set_rc(map: &JSObjectDataPtr, key: &PropertyKey, val_rc: Rc<RefCell<Value>>) {
1155    map.borrow_mut().insert(key.clone(), val_rc);
1156}
1157
1158pub fn obj_delete(map: &JSObjectDataPtr, key: &PropertyKey) -> Result<bool, JSError> {
1159    // Check if this object is a proxy wrapper
1160    if let Some(proxy_val) = map.borrow().get(&"__proxy__".into())
1161        && let Value::Proxy(proxy) = &*proxy_val.borrow()
1162    {
1163        return crate::js_proxy::proxy_delete_property(proxy, key);
1164    }
1165
1166    map.borrow_mut().remove(key);
1167    Ok(true) // In JavaScript, delete always returns true
1168}
1169
1170pub fn env_get<T: AsRef<str>>(env: &JSObjectDataPtr, key: T) -> Option<Rc<RefCell<Value>>> {
1171    env.borrow().get(&key.as_ref().into())
1172}
1173
1174pub fn env_set<T: AsRef<str>>(env: &JSObjectDataPtr, key: T, val: Value) -> Result<(), JSError> {
1175    let key = key.as_ref();
1176    if env.borrow().is_const(key) {
1177        return Err(raise_type_error!(format!("Assignment to constant variable '{key}'")));
1178    }
1179    env.borrow_mut()
1180        .insert(PropertyKey::String(key.to_string()), Rc::new(RefCell::new(val)));
1181    Ok(())
1182}
1183
1184pub fn env_set_recursive<T: AsRef<str>>(env: &JSObjectDataPtr, key: T, val: Value) -> Result<(), JSError> {
1185    let key_str = key.as_ref();
1186    let mut current = env.clone();
1187    loop {
1188        if current.borrow().contains_key(&key_str.into()) {
1189            return env_set(&current, key_str, val);
1190        }
1191        let parent_opt = current.borrow().prototype.clone();
1192        if let Some(parent) = parent_opt {
1193            current = parent;
1194        } else {
1195            // if not found, set in current env
1196            return env_set(env, key_str, val);
1197        }
1198    }
1199}
1200
1201pub fn env_set_var(env: &JSObjectDataPtr, key: &str, val: Value) -> Result<(), JSError> {
1202    let mut current = env.clone();
1203    loop {
1204        if current.borrow().is_function_scope {
1205            return env_set(&current, key, val);
1206        }
1207        let parent_opt = current.borrow().prototype.clone();
1208        if let Some(parent) = parent_opt {
1209            current = parent;
1210        } else {
1211            // If no function scope found, set in current env (global)
1212            return env_set(env, key, val);
1213        }
1214    }
1215}
1216
1217pub fn env_set_const(env: &JSObjectDataPtr, key: &str, val: Value) {
1218    let mut env_mut = env.borrow_mut();
1219    env_mut.insert(PropertyKey::String(key.to_string()), Rc::new(RefCell::new(val)));
1220    env_mut.set_const(key.to_string());
1221}