Skip to main content

just_engine/runner/eval/
statement.rs

1//! Statement execution.
2//!
3//! This module provides statement execution logic for the JavaScript interpreter.
4
5use crate::parser::ast::{
6    ClassData, ExpressionPatternType, FunctionBodyData, FunctionData, LiteralType, NumberLiteralType,
7    PatternType, StatementType, DeclarationType, VariableDeclarationData, VariableDeclarationKind,
8    BlockStatementData, ExpressionType, SwitchCaseData, CatchClauseData, ForIteratorData,
9    VariableDeclarationOrPattern,
10};
11use crate::runner::ds::error::JErrorType;
12use crate::runner::ds::value::JsValue;
13use crate::runner::plugin::types::EvalContext;
14
15use super::types::{Completion, CompletionType, EvalResult};
16use super::expression::{evaluate_expression, to_boolean, create_function_object, evaluate_class};
17
18/// Execute a statement and return its completion.
19pub fn execute_statement(
20    stmt: &StatementType,
21    ctx: &mut EvalContext,
22) -> EvalResult {
23    match stmt {
24        StatementType::EmptyStatement { .. } => {
25            Ok(Completion::normal())
26        }
27
28        StatementType::ExpressionStatement { expression, .. } => {
29            let value = evaluate_expression(expression, ctx)?;
30            Ok(Completion::normal_with_value(value))
31        }
32
33        StatementType::BlockStatement(block) => {
34            execute_block_statement(block, ctx)
35        }
36
37        StatementType::DeclarationStatement(decl) => {
38            execute_declaration(decl, ctx)
39        }
40
41        StatementType::IfStatement { test, consequent, alternate, .. } => {
42            execute_if_statement(test, consequent, alternate.as_ref().map(|a| a.as_ref()), ctx)
43        }
44
45        StatementType::WhileStatement { test, body, .. } => {
46            execute_while_statement(test, body, ctx)
47        }
48
49        StatementType::DoWhileStatement { test, body, .. } => {
50            execute_do_while_statement(body, test, ctx)
51        }
52
53        StatementType::ForStatement { init, test, update, body, .. } => {
54            execute_for_statement(init.as_ref(), test.as_ref().map(|t| t.as_ref()), update.as_ref().map(|u| u.as_ref()), body, ctx)
55        }
56
57        StatementType::ForInStatement(data) => {
58            execute_for_in_statement(data, ctx)
59        }
60
61        StatementType::ForOfStatement(data) => {
62            execute_for_of_statement(data, ctx)
63        }
64
65        StatementType::SwitchStatement { discriminant, cases, .. } => {
66            execute_switch_statement(discriminant, cases, ctx)
67        }
68
69        StatementType::BreakStatement { .. } => {
70            Ok(Completion::break_completion(None))
71        }
72
73        StatementType::ContinueStatement { .. } => {
74            Ok(Completion::continue_completion(None))
75        }
76
77        StatementType::ReturnStatement { argument, .. } => {
78            let value = if let Some(arg) = argument {
79                evaluate_expression(arg, ctx)?
80            } else {
81                JsValue::Undefined
82            };
83            Ok(Completion::return_value(value))
84        }
85
86        StatementType::ThrowStatement { argument, .. } => {
87            let value = evaluate_expression(argument, ctx)?;
88            Ok(Completion {
89                completion_type: CompletionType::Throw,
90                value: Some(value),
91                target: None,
92            })
93        }
94
95        StatementType::TryStatement { block, handler, finalizer, .. } => {
96            execute_try_statement(block, handler.as_ref(), finalizer.as_ref(), ctx)
97        }
98
99        StatementType::DebuggerStatement { .. } => {
100            Ok(Completion::normal())
101        }
102
103        StatementType::FunctionBody(body) => {
104            // Execute each statement in the function body
105            execute_function_body(body, ctx)
106        }
107    }
108}
109
110/// Execute a block statement.
111fn execute_block_statement(
112    block: &BlockStatementData,
113    ctx: &mut EvalContext,
114) -> EvalResult {
115    // Create a new block scope for let/const bindings
116    ctx.push_block_scope();
117
118    let mut completion = Completion::normal();
119
120    for stmt in block.body.iter() {
121        completion = execute_statement(stmt, ctx)?;
122
123        if completion.is_abrupt() {
124            ctx.pop_block_scope();
125            return Ok(completion);
126        }
127    }
128
129    // Pop the block scope
130    ctx.pop_block_scope();
131
132    Ok(completion)
133}
134
135/// Execute a declaration.
136fn execute_declaration(
137    decl: &DeclarationType,
138    ctx: &mut EvalContext,
139) -> EvalResult {
140    match decl {
141        DeclarationType::VariableDeclaration(var_decl) => {
142            execute_variable_declaration(var_decl, ctx)
143        }
144
145        DeclarationType::FunctionOrGeneratorDeclaration(func_data) => {
146            execute_function_declaration(func_data, ctx)
147        }
148
149        DeclarationType::ClassDeclaration(class_data) => {
150            execute_class_declaration(class_data, ctx)
151        }
152    }
153}
154
155/// Execute a variable declaration.
156fn execute_variable_declaration(
157    var_decl: &VariableDeclarationData,
158    ctx: &mut EvalContext,
159) -> EvalResult {
160    let is_const = matches!(var_decl.kind, VariableDeclarationKind::Const);
161    let is_var = matches!(var_decl.kind, VariableDeclarationKind::Var);
162
163    for declarator in &var_decl.declarations {
164        // Evaluate initializer first (before potentially creating the binding)
165        let value = if let Some(init) = &declarator.init {
166            evaluate_expression(init, ctx)?
167        } else {
168            // const must have an initializer (checked by parser), var/let default to undefined
169            JsValue::Undefined
170        };
171
172        // Bind the pattern to the value
173        bind_pattern(&declarator.id, value, ctx, is_const, is_var)?;
174    }
175
176    Ok(Completion::normal())
177}
178
179/// Bind a pattern to a value, creating bindings for all identifiers in the pattern.
180fn bind_pattern(
181    pattern: &PatternType,
182    value: JsValue,
183    ctx: &mut EvalContext,
184    is_const: bool,
185    is_var: bool,
186) -> Result<(), JErrorType> {
187    match pattern {
188        PatternType::PatternWhichCanBeExpression(ExpressionPatternType::Identifier(id)) => {
189            // Simple identifier binding
190            let name = &id.name;
191            if is_var {
192                if ctx.has_var_binding(name) {
193                    ctx.set_var_binding(name, value)?;
194                } else {
195                    ctx.create_var_binding(name)?;
196                    ctx.initialize_var_binding(name, value)?;
197                }
198            } else {
199                ctx.create_binding(name, is_const)?;
200                ctx.initialize_binding(name, value)?;
201            }
202            Ok(())
203        }
204
205        PatternType::ObjectPattern { properties, .. } => {
206            // Object destructuring: { x, y } = obj or { x: renamed } = obj
207            for prop in properties {
208                let prop_data = &prop.0;
209
210                // Get the property key name
211                let key_name = get_property_key_name(&prop_data.key)?;
212
213                // Get the value from the object
214                let prop_value = get_property_value(&value, &key_name)?;
215
216                // Bind the value to the pattern
217                // For shorthand like { x }, value is the same pattern as key
218                // For renamed like { x: renamed }, value is the renamed pattern
219                bind_pattern(&prop_data.value, prop_value, ctx, is_const, is_var)?;
220            }
221            Ok(())
222        }
223
224        PatternType::ArrayPattern { elements, .. } => {
225            // Array destructuring: [a, b] = arr
226            for (index, element) in elements.iter().enumerate() {
227                if let Some(elem_pattern) = element {
228                    // Check if it's a rest element
229                    if let PatternType::RestElement { argument, .. } = elem_pattern.as_ref() {
230                        // Rest element collects remaining elements into an array
231                        let rest_value = get_rest_elements(&value, index)?;
232                        bind_pattern(argument, rest_value, ctx, is_const, is_var)?;
233                    } else {
234                        // Regular element
235                        let elem_value = get_array_element(&value, index)?;
236                        bind_pattern(elem_pattern, elem_value, ctx, is_const, is_var)?;
237                    }
238                }
239                // None means hole/skip - do nothing
240            }
241            Ok(())
242        }
243
244        PatternType::AssignmentPattern { left, right, .. } => {
245            // Default value pattern: x = default or { x = default } = obj
246            // Use default if value is undefined
247            let actual_value = if matches!(value, JsValue::Undefined) {
248                evaluate_expression(right, ctx)?
249            } else {
250                value
251            };
252            bind_pattern(left, actual_value, ctx, is_const, is_var)
253        }
254
255        PatternType::RestElement { argument, .. } => {
256            // Rest element should be handled in array context
257            // If we get here directly, just bind the value as-is
258            bind_pattern(argument, value, ctx, is_const, is_var)
259        }
260    }
261}
262
263/// Get the property key name from an expression (for destructuring).
264fn get_property_key_name(key_expr: &ExpressionType) -> Result<String, JErrorType> {
265    match key_expr {
266        ExpressionType::ExpressionWhichCanBePattern(ExpressionPatternType::Identifier(id)) => {
267            Ok(id.name.clone())
268        }
269        ExpressionType::Literal(lit_data) => {
270            match &lit_data.value {
271                LiteralType::StringLiteral(s) => Ok(s.clone()),
272                LiteralType::NumberLiteral(num) => {
273                    match num {
274                        NumberLiteralType::IntegerLiteral(n) => Ok(n.to_string()),
275                        NumberLiteralType::FloatLiteral(n) => Ok(n.to_string()),
276                    }
277                }
278                _ => Err(JErrorType::TypeError("Invalid property key in destructuring".to_string())),
279            }
280        }
281        _ => Err(JErrorType::TypeError("Computed property keys not yet supported in destructuring".to_string())),
282    }
283}
284
285/// Get a property value from an object (for destructuring).
286fn get_property_value(obj: &JsValue, key: &str) -> Result<JsValue, JErrorType> {
287    use crate::runner::ds::object_property::{PropertyDescriptor, PropertyKey};
288
289    match obj {
290        JsValue::Object(obj_ref) => {
291            let borrowed = obj_ref.borrow();
292            let base = borrowed.as_js_object().get_object_base();
293            let prop_key = PropertyKey::Str(key.to_string());
294
295            if let Some(PropertyDescriptor::Data(data)) = base.properties.get(&prop_key) {
296                Ok(data.value.clone())
297            } else {
298                Ok(JsValue::Undefined)
299            }
300        }
301        _ => Err(JErrorType::TypeError("Cannot destructure non-object".to_string())),
302    }
303}
304
305/// Get an element from an array by index (for destructuring).
306fn get_array_element(arr: &JsValue, index: usize) -> Result<JsValue, JErrorType> {
307    use crate::runner::ds::object_property::{PropertyDescriptor, PropertyKey};
308
309    match arr {
310        JsValue::Object(obj_ref) => {
311            let borrowed = obj_ref.borrow();
312            let base = borrowed.as_js_object().get_object_base();
313            let prop_key = PropertyKey::Str(index.to_string());
314
315            if let Some(PropertyDescriptor::Data(data)) = base.properties.get(&prop_key) {
316                Ok(data.value.clone())
317            } else {
318                Ok(JsValue::Undefined)
319            }
320        }
321        _ => Err(JErrorType::TypeError("Cannot destructure non-array".to_string())),
322    }
323}
324
325/// Get remaining elements from an array starting at index (for rest patterns).
326fn get_rest_elements(arr: &JsValue, start_index: usize) -> Result<JsValue, JErrorType> {
327    use crate::runner::ds::object::{JsObject, JsObjectType, ObjectType};
328    use crate::runner::ds::object_property::{PropertyDescriptor, PropertyDescriptorData, PropertyKey};
329    use crate::runner::ds::value::JsNumberType;
330    use crate::runner::plugin::types::SimpleObject;
331    use std::cell::RefCell;
332    use std::rc::Rc;
333
334    match arr {
335        JsValue::Object(obj_ref) => {
336            let borrowed = obj_ref.borrow();
337            let base = borrowed.as_js_object().get_object_base();
338
339            // Get original array length
340            let length = if let Some(PropertyDescriptor::Data(data)) =
341                base.properties.get(&PropertyKey::Str("length".to_string()))
342            {
343                match &data.value {
344                    JsValue::Number(JsNumberType::Integer(n)) => *n as usize,
345                    JsValue::Number(JsNumberType::Float(n)) => *n as usize,
346                    _ => 0,
347                }
348            } else {
349                0
350            };
351
352            // Create a new array with remaining elements
353            let mut rest_obj = SimpleObject::new();
354            let mut rest_index = 0;
355
356            for i in start_index..length {
357                let prop_key = PropertyKey::Str(i.to_string());
358                if let Some(PropertyDescriptor::Data(data)) = base.properties.get(&prop_key) {
359                    rest_obj.get_object_base_mut().properties.insert(
360                        PropertyKey::Str(rest_index.to_string()),
361                        PropertyDescriptor::Data(PropertyDescriptorData {
362                            value: data.value.clone(),
363                            writable: true,
364                            enumerable: true,
365                            configurable: true,
366                        }),
367                    );
368                    rest_index += 1;
369                }
370            }
371
372            // Set length
373            rest_obj.get_object_base_mut().properties.insert(
374                PropertyKey::Str("length".to_string()),
375                PropertyDescriptor::Data(PropertyDescriptorData {
376                    value: JsValue::Number(JsNumberType::Integer(rest_index as i64)),
377                    writable: true,
378                    enumerable: false,
379                    configurable: false,
380                }),
381            );
382
383            let obj: JsObjectType = Rc::new(RefCell::new(ObjectType::Ordinary(Box::new(rest_obj))));
384            Ok(JsValue::Object(obj))
385        }
386        _ => Err(JErrorType::TypeError("Cannot use rest with non-array".to_string())),
387    }
388}
389
390/// Execute a function declaration.
391fn execute_function_declaration(
392    func_data: &FunctionData,
393    ctx: &mut EvalContext,
394) -> EvalResult {
395    // Get the function name (id is mandatory for declarations)
396    let name = func_data.id.as_ref()
397        .ok_or_else(|| JErrorType::TypeError("Function declaration must have a name".to_string()))?
398        .name.clone();
399
400    // Create the function object
401    let func_value = create_function_object(func_data, ctx)?;
402
403    // Bind the function name in the variable environment (functions are hoisted like var)
404    if ctx.has_var_binding(&name) {
405        // Update existing binding
406        ctx.set_var_binding(&name, func_value)?;
407    } else {
408        // Create and initialize new binding
409        ctx.create_var_binding(&name)?;
410        ctx.initialize_var_binding(&name, func_value)?;
411    }
412
413    Ok(Completion::normal())
414}
415
416/// Execute a class declaration.
417fn execute_class_declaration(
418    class_data: &ClassData,
419    ctx: &mut EvalContext,
420) -> EvalResult {
421    // Get the class name (id is mandatory for declarations)
422    let name = class_data.id.as_ref()
423        .ok_or_else(|| JErrorType::TypeError("Class declaration must have a name".to_string()))?
424        .name.clone();
425
426    // Evaluate the class to get the constructor function
427    let class_value = evaluate_class(class_data, ctx)?;
428
429    // Bind the class name (classes are like let bindings - not hoisted to var scope)
430    ctx.create_binding(&name, false)?;
431    ctx.initialize_binding(&name, class_value)?;
432
433    Ok(Completion::normal())
434}
435
436/// Get the binding name from a pattern.
437fn get_binding_name(pattern: &PatternType) -> Result<String, JErrorType> {
438    match pattern {
439        PatternType::PatternWhichCanBeExpression(ExpressionPatternType::Identifier(id)) => {
440            Ok(id.name.clone())
441        }
442        PatternType::ObjectPattern { .. } => {
443            Err(JErrorType::TypeError("Object destructuring not yet supported".to_string()))
444        }
445        PatternType::ArrayPattern { .. } => {
446            Err(JErrorType::TypeError("Array destructuring not yet supported".to_string()))
447        }
448        PatternType::RestElement { .. } => {
449            Err(JErrorType::TypeError("Rest element not yet supported".to_string()))
450        }
451        PatternType::AssignmentPattern { .. } => {
452            Err(JErrorType::TypeError("Default value patterns not yet supported".to_string()))
453        }
454    }
455}
456
457/// Execute an if statement.
458fn execute_if_statement(
459    test: &ExpressionType,
460    consequent: &StatementType,
461    alternate: Option<&StatementType>,
462    ctx: &mut EvalContext,
463) -> EvalResult {
464    let test_value = evaluate_expression(test, ctx)?;
465
466    if to_boolean(&test_value) {
467        execute_statement(consequent, ctx)
468    } else if let Some(alt) = alternate {
469        execute_statement(alt, ctx)
470    } else {
471        Ok(Completion::normal())
472    }
473}
474
475/// Execute a while statement.
476fn execute_while_statement(
477    test: &ExpressionType,
478    body: &StatementType,
479    ctx: &mut EvalContext,
480) -> EvalResult {
481    let mut completion = Completion::normal();
482
483    loop {
484        let test_value = evaluate_expression(test, ctx)?;
485
486        if !to_boolean(&test_value) {
487            break;
488        }
489
490        completion = execute_statement(body, ctx)?;
491
492        match completion.completion_type {
493            CompletionType::Break => {
494                return Ok(Completion::normal_with_value(completion.get_value()));
495            }
496            CompletionType::Continue => {
497                continue;
498            }
499            CompletionType::Return | CompletionType::Throw | CompletionType::Yield => {
500                return Ok(completion);
501            }
502            CompletionType::Normal => {}
503        }
504    }
505
506    Ok(completion)
507}
508
509/// Execute a do-while statement.
510fn execute_do_while_statement(
511    body: &StatementType,
512    test: &ExpressionType,
513    ctx: &mut EvalContext,
514) -> EvalResult {
515    loop {
516        let completion = execute_statement(body, ctx)?;
517
518        match completion.completion_type {
519            CompletionType::Break => {
520                return Ok(Completion::normal_with_value(completion.get_value()));
521            }
522            CompletionType::Continue => {}
523            CompletionType::Return | CompletionType::Throw | CompletionType::Yield => {
524                return Ok(completion);
525            }
526            CompletionType::Normal => {}
527        }
528
529        let test_value = evaluate_expression(test, ctx)?;
530        if !to_boolean(&test_value) {
531            break;
532        }
533    }
534
535    Ok(Completion::normal())
536}
537
538/// Execute a for statement.
539fn execute_for_statement(
540    init: Option<&crate::parser::ast::VariableDeclarationOrExpression>,
541    test: Option<&ExpressionType>,
542    update: Option<&ExpressionType>,
543    body: &StatementType,
544    ctx: &mut EvalContext,
545) -> EvalResult {
546    use crate::parser::ast::VariableDeclarationOrExpression;
547
548    if let Some(init) = init {
549        match init {
550            VariableDeclarationOrExpression::VariableDeclaration(decl) => {
551                execute_variable_declaration(decl, ctx)?;
552            }
553            VariableDeclarationOrExpression::Expression(expr) => {
554                evaluate_expression(expr, ctx)?;
555            }
556        }
557    }
558
559    let mut completion = Completion::normal();
560
561    loop {
562        if let Some(test) = test {
563            let test_value = evaluate_expression(test, ctx)?;
564            if !to_boolean(&test_value) {
565                break;
566            }
567        }
568
569        completion = execute_statement(body, ctx)?;
570
571        match completion.completion_type {
572            CompletionType::Break => {
573                return Ok(Completion::normal_with_value(completion.get_value()));
574            }
575            CompletionType::Continue => {}
576            CompletionType::Return | CompletionType::Throw | CompletionType::Yield => {
577                return Ok(completion);
578            }
579            CompletionType::Normal => {}
580        }
581
582        if let Some(update) = update {
583            evaluate_expression(update, ctx)?;
584        }
585    }
586
587    Ok(completion)
588}
589
590/// Execute a function body.
591fn execute_function_body(
592    body: &FunctionBodyData,
593    ctx: &mut EvalContext,
594) -> EvalResult {
595    let mut completion = Completion::normal();
596
597    for stmt in body.body.iter() {
598        completion = execute_statement(stmt, ctx)?;
599
600        // Handle abrupt completions
601        match completion.completion_type {
602            CompletionType::Return | CompletionType::Throw | CompletionType::Yield => {
603                return Ok(completion);
604            }
605            CompletionType::Break | CompletionType::Continue => {
606                // Break/continue inside a function body without a loop is an error
607                // but for now we just return the completion
608                return Ok(completion);
609            }
610            CompletionType::Normal => {}
611        }
612    }
613
614    Ok(completion)
615}
616
617/// Execute a switch statement.
618fn execute_switch_statement(
619    discriminant: &ExpressionType,
620    cases: &[SwitchCaseData],
621    ctx: &mut EvalContext,
622) -> EvalResult {
623    use super::expression::strict_equality;
624
625    // Evaluate the discriminant
626    let switch_value = evaluate_expression(discriminant, ctx)?;
627
628    // Find the matching case (or default)
629    let mut found_match = false;
630    let mut default_index: Option<usize> = None;
631    let mut start_index: Option<usize> = None;
632
633    // First pass: find the matching case
634    for (i, case) in cases.iter().enumerate() {
635        if let Some(test) = &case.test {
636            // This is a case clause
637            let case_value = evaluate_expression(test, ctx)?;
638            if strict_equality(&switch_value, &case_value) {
639                start_index = Some(i);
640                found_match = true;
641                break;
642            }
643        } else {
644            // This is the default clause
645            default_index = Some(i);
646        }
647    }
648
649    // If no match found, use default if available
650    if !found_match {
651        start_index = default_index;
652    }
653
654    // Execute statements starting from the matched case (fall-through behavior)
655    let mut completion = Completion::normal();
656
657    if let Some(start) = start_index {
658        for case in cases.iter().skip(start) {
659            for stmt in &case.consequent {
660                completion = execute_statement(stmt, ctx)?;
661
662                match completion.completion_type {
663                    CompletionType::Break => {
664                        // Break exits the switch
665                        return Ok(Completion::normal_with_value(completion.get_value()));
666                    }
667                    CompletionType::Return | CompletionType::Throw | CompletionType::Continue | CompletionType::Yield => {
668                        // These propagate up
669                        return Ok(completion);
670                    }
671                    CompletionType::Normal => {}
672                }
673            }
674        }
675    }
676
677    Ok(completion)
678}
679
680/// Execute a try statement.
681fn execute_try_statement(
682    block: &BlockStatementData,
683    handler: Option<&CatchClauseData>,
684    finalizer: Option<&BlockStatementData>,
685    ctx: &mut EvalContext,
686) -> EvalResult {
687    // Execute the try block
688    let try_result = execute_block_statement(block, ctx);
689
690    let mut completion = match try_result {
691        Ok(comp) => {
692            // Check if it's a throw completion
693            if comp.completion_type == CompletionType::Throw {
694                // Handle the throw in catch if available
695                if let Some(catch_clause) = handler {
696                    handle_catch(comp, catch_clause, ctx)?
697                } else {
698                    comp
699                }
700            } else {
701                comp
702            }
703        }
704        Err(err) => {
705            // Runtime error - treat as throw
706            if let Some(catch_clause) = handler {
707                let throw_completion = Completion {
708                    completion_type: CompletionType::Throw,
709                    value: Some(error_to_js_value(&err)),
710                    target: None,
711                };
712                handle_catch(throw_completion, catch_clause, ctx)?
713            } else {
714                return Err(err);
715            }
716        }
717    };
718
719    // Execute the finally block if present
720    if let Some(finally_block) = finalizer {
721        let finally_result = execute_block_statement(finally_block, ctx)?;
722
723        // If finally has an abrupt completion, it overrides the try/catch result
724        if finally_result.is_abrupt() {
725            completion = finally_result;
726        }
727    }
728
729    Ok(completion)
730}
731
732/// Handle a catch clause.
733fn handle_catch(
734    thrown: Completion,
735    catch_clause: &CatchClauseData,
736    ctx: &mut EvalContext,
737) -> EvalResult {
738    // Create a new block scope for the catch clause
739    ctx.push_block_scope();
740
741    // Bind the error to the catch parameter
742    let param_name = get_binding_name(&catch_clause.param)?;
743    let error_value = thrown.value.unwrap_or(JsValue::Undefined);
744
745    ctx.create_binding(&param_name, false)?;
746    ctx.initialize_binding(&param_name, error_value)?;
747
748    // Execute the catch block
749    let result = execute_block_statement(&catch_clause.body, ctx);
750
751    // Pop the catch scope
752    ctx.pop_block_scope();
753
754    result
755}
756
757/// Convert a JErrorType to a JsValue for catching.
758fn error_to_js_value(err: &JErrorType) -> JsValue {
759    match err {
760        JErrorType::TypeError(msg) => JsValue::String(format!("TypeError: {}", msg)),
761        JErrorType::ReferenceError(msg) => JsValue::String(format!("ReferenceError: {}", msg)),
762        JErrorType::SyntaxError(msg) => JsValue::String(format!("SyntaxError: {}", msg)),
763        JErrorType::RangeError(msg) => JsValue::String(format!("RangeError: {}", msg)),
764        JErrorType::YieldValue(v) => v.clone(),
765    }
766}
767
768/// Execute a for-in statement (iterate over enumerable property keys).
769fn execute_for_in_statement(
770    data: &ForIteratorData,
771    ctx: &mut EvalContext,
772) -> EvalResult {
773    // Evaluate the right-hand side to get the object
774    let obj_value = evaluate_expression(&data.right, ctx)?;
775
776    // Get the property keys to iterate over
777    let keys: Vec<String> = match &obj_value {
778        JsValue::Object(obj) => {
779            let obj_ref = obj.borrow();
780            obj_ref.as_js_object().get_object_base().properties
781                .keys()
782                .filter_map(|k| match k {
783                    crate::runner::ds::object_property::PropertyKey::Str(s) => Some(s.clone()),
784                    _ => None,
785                })
786                .collect()
787        }
788        JsValue::String(s) => {
789            // For strings, iterate over indices
790            (0..s.len()).map(|i| i.to_string()).collect()
791        }
792        JsValue::Null | JsValue::Undefined => {
793            // for-in over null/undefined produces no iterations
794            return Ok(Completion::normal());
795        }
796        _ => Vec::new(),
797    };
798
799    // Execute the loop body for each key
800    let mut completion = Completion::normal();
801
802    for key in keys {
803        // Bind the key to the loop variable
804        bind_for_iterator_variable(&data.left, JsValue::String(key), ctx)?;
805
806        // Execute the body
807        completion = execute_statement(&data.body, ctx)?;
808
809        match completion.completion_type {
810            CompletionType::Break => {
811                return Ok(Completion::normal_with_value(completion.get_value()));
812            }
813            CompletionType::Continue => {
814                continue;
815            }
816            CompletionType::Return | CompletionType::Throw | CompletionType::Yield => {
817                return Ok(completion);
818            }
819            CompletionType::Normal => {}
820        }
821    }
822
823    Ok(completion)
824}
825
826/// Execute a for-of statement (iterate over iterable values).
827fn execute_for_of_statement(
828    data: &ForIteratorData,
829    ctx: &mut EvalContext,
830) -> EvalResult {
831    use crate::runner::ds::value::JsNumberType;
832    use crate::runner::ds::object_property::PropertyKey;
833
834    // Evaluate the right-hand side to get the iterable
835    let iterable_value = evaluate_expression(&data.right, ctx)?;
836
837    // Get the values to iterate over
838    let values: Vec<JsValue> = match &iterable_value {
839        JsValue::Object(obj) => {
840            let obj_ref = obj.borrow();
841            let base = obj_ref.as_js_object().get_object_base();
842
843            // Check for length property (array-like)
844            if let Some(prop) = base.properties.get(&PropertyKey::Str("length".to_string())) {
845                if let crate::runner::ds::object_property::PropertyDescriptor::Data(length_data) = prop {
846                    if let JsValue::Number(JsNumberType::Integer(len)) = &length_data.value {
847                        let len = *len as usize;
848                        let mut vals = Vec::with_capacity(len);
849                        for i in 0..len {
850                            let key = PropertyKey::Str(i.to_string());
851                            if let Some(prop) = base.properties.get(&key) {
852                                if let crate::runner::ds::object_property::PropertyDescriptor::Data(d) = prop {
853                                    vals.push(d.value.clone());
854                                } else {
855                                    vals.push(JsValue::Undefined);
856                                }
857                            } else {
858                                vals.push(JsValue::Undefined);
859                            }
860                        }
861                        drop(obj_ref);
862                        return execute_for_of_with_values(data, vals, ctx);
863                    }
864                }
865            }
866
867            // Otherwise, iterate over enumerable properties' values
868            base.properties
869                .values()
870                .filter_map(|prop| {
871                    if let crate::runner::ds::object_property::PropertyDescriptor::Data(d) = prop {
872                        if d.enumerable {
873                            Some(d.value.clone())
874                        } else {
875                            None
876                        }
877                    } else {
878                        None
879                    }
880                })
881                .collect()
882        }
883        JsValue::String(s) => {
884            // Iterate over characters
885            s.chars().map(|c| JsValue::String(c.to_string())).collect()
886        }
887        JsValue::Null | JsValue::Undefined => {
888            return Err(JErrorType::TypeError(
889                "Cannot iterate over null or undefined".to_string(),
890            ));
891        }
892        _ => {
893            return Err(JErrorType::TypeError(
894                "Object is not iterable".to_string(),
895            ));
896        }
897    };
898
899    execute_for_of_with_values(data, values, ctx)
900}
901
902/// Helper to execute for-of with a list of values.
903fn execute_for_of_with_values(
904    data: &ForIteratorData,
905    values: Vec<JsValue>,
906    ctx: &mut EvalContext,
907) -> EvalResult {
908    let mut completion = Completion::normal();
909
910    for value in values {
911        // Bind the value to the loop variable
912        bind_for_iterator_variable(&data.left, value, ctx)?;
913
914        // Execute the body
915        completion = execute_statement(&data.body, ctx)?;
916
917        match completion.completion_type {
918            CompletionType::Break => {
919                return Ok(Completion::normal_with_value(completion.get_value()));
920            }
921            CompletionType::Continue => {
922                continue;
923            }
924            CompletionType::Return | CompletionType::Throw | CompletionType::Yield => {
925                return Ok(completion);
926            }
927            CompletionType::Normal => {}
928        }
929    }
930
931    Ok(completion)
932}
933
934/// Bind a value to a for-in/for-of loop variable.
935fn bind_for_iterator_variable(
936    left: &VariableDeclarationOrPattern,
937    value: JsValue,
938    ctx: &mut EvalContext,
939) -> Result<(), JErrorType> {
940    match left {
941        VariableDeclarationOrPattern::VariableDeclaration(var_decl) => {
942            // Create and initialize the variable
943            for declarator in &var_decl.declarations {
944                let name = get_binding_name(&declarator.id)?;
945                let is_var = matches!(var_decl.kind, VariableDeclarationKind::Var);
946
947                if is_var {
948                    // Check if already exists
949                    if !ctx.has_binding(&name) {
950                        ctx.create_var_binding(&name)?;
951                    }
952                    ctx.initialize_var_binding(&name, value.clone())?;
953                } else {
954                    // let/const - create new binding each iteration
955                    if !ctx.has_binding(&name) {
956                        ctx.create_binding(&name, matches!(var_decl.kind, VariableDeclarationKind::Const))?;
957                        ctx.initialize_binding(&name, value.clone())?;
958                    } else {
959                        ctx.set_binding(&name, value.clone())?;
960                    }
961                }
962            }
963        }
964        VariableDeclarationOrPattern::Pattern(pattern) => {
965            // Simple pattern assignment
966            let name = get_binding_name(pattern)?;
967            ctx.set_binding(&name, value)?;
968        }
969    }
970    Ok(())
971}