diff --git a/src/ant.c b/src/ant.c index 7f1bfe4..dbe6adb 100644 --- a/src/ant.c +++ b/src/ant.c @@ -9665,6 +9665,7 @@ typedef struct { bool has_destructure; jsoff_t destructure_off; jsoff_t destructure_len; + int marker_index; } for_iter_ctx_t; static jsval_t for_iter_bind_var(struct js *js, for_iter_ctx_t *ctx, jsval_t value) { @@ -9687,22 +9688,29 @@ static jsval_t for_iter_exec_body(struct js *js, for_iter_ctx_t *ctx) { return js_block_or_stmt(js); } -static bool for_in_should_skip_key(struct js *js, jsval_t obj, const char *key, jsoff_t klen) { - if (streq(key, klen, STR_PROTO, STR_PROTO_LEN)) return true; - - const char *tag_sym_key = get_toStringTag_sym_key(); - if (streq(key, klen, tag_sym_key, strlen(tag_sym_key))) return true; - - jsoff_t obj_off = (jsoff_t)vdata(obj); - descriptor_entry_t *desc = lookup_descriptor(obj_off, key, klen); - if (desc && !desc->enumerable) return true; - - return false; +static inline bool for_iter_handle_continue(struct js *js, for_iter_ctx_t *ctx) { + if (!(label_flags & F_CONTINUE_LABEL)) return false; + if (is_this_loop_continue_target(ctx->marker_index)) { + clear_continue_label(); + js->flags &= ~(F_BREAK | F_NOEXEC); + return false; + } + js->flags |= F_BREAK; + return true; } static jsval_t for_in_iter_object(struct js *js, for_iter_ctx_t *ctx, jsval_t obj) { - jsval_t iter_obj = (vtype(obj) == T_FUNC) ? mkval(T_OBJ, vdata(obj)) : obj; - jsoff_t prop_off = loadoff(js, (jsoff_t) vdata(iter_obj)) & ~(3U | FLAGMASK); + uint8_t obj_type = vtype(obj); + + if (obj_type == T_NULL || obj_type == T_UNDEF) return js_mkundef(); + if (obj_type != T_OBJ && obj_type != T_ARR && obj_type != T_FUNC) return js_mkerr(js, "for-in requires object"); + + jsval_t iter_obj = (obj_type == T_FUNC) ? mkval(T_OBJ, vdata(obj)) : obj; + jsoff_t iter_obj_off = (jsoff_t)vdata(iter_obj); + jsoff_t prop_off = loadoff(js, iter_obj_off) & ~(3U | FLAGMASK); + + const char *tag_sym_key = get_toStringTag_sym_key(); + size_t tag_sym_len = tag_sym_key ? strlen(tag_sym_key) : 0; while (prop_off < js->brk && prop_off != 0) { jsoff_t header = loadoff(js, prop_off); @@ -9712,7 +9720,14 @@ static jsval_t for_in_iter_object(struct js *js, for_iter_ctx_t *ctx, jsval_t ob jsoff_t klen = offtolen(loadoff(js, koff)); const char *key = (char *) &js->mem[koff + sizeof(koff)]; - if (!for_in_should_skip_key(js, iter_obj, key, klen)) { + bool should_skip = streq(key, klen, STR_PROTO, STR_PROTO_LEN); + if (!should_skip && tag_sym_key) should_skip = streq(key, klen, tag_sym_key, tag_sym_len); + if (!should_skip) { + descriptor_entry_t *desc = lookup_descriptor(iter_obj_off, key, klen); + if (desc && !desc->enumerable) should_skip = true; + } + + if (!should_skip) { jsval_t key_str = js_mkstr(js, key, klen); jsval_t err = for_iter_bind_var(js, ctx, key_str); @@ -9720,8 +9735,8 @@ static jsval_t for_in_iter_object(struct js *js, for_iter_ctx_t *ctx, jsval_t ob jsval_t v = for_iter_exec_body(js, ctx); if (is_err(v)) return v; + if (for_iter_handle_continue(js, ctx)) break; if (js->flags & F_BREAK) break; - if (label_flags & F_CONTINUE_LABEL) { js->flags |= F_BREAK; break; } if (js->flags & F_RETURN) return v; } @@ -9776,8 +9791,8 @@ static jsval_t for_of_iter_array(struct js *js, for_iter_ctx_t *ctx, jsval_t ite jsval_t v = for_iter_exec_body(js, ctx); if (is_err(v)) return v; + if (for_iter_handle_continue(js, ctx)) break; if (js->flags & F_BREAK) break; - if (label_flags & F_CONTINUE_LABEL) { js->flags |= F_BREAK; break; } if (js->flags & F_RETURN) return v; } @@ -9796,8 +9811,8 @@ static jsval_t for_of_iter_string(struct js *js, for_iter_ctx_t *ctx, jsval_t it jsval_t v = for_iter_exec_body(js, ctx); if (is_err(v)) return v; + if (for_iter_handle_continue(js, ctx)) break; if (js->flags & F_BREAK) break; - if (label_flags & F_CONTINUE_LABEL) { js->flags |= F_BREAK; break; } if (js->flags & F_RETURN) return v; } @@ -9850,8 +9865,8 @@ static jsval_t for_of_iter_object(struct js *js, for_iter_ctx_t *ctx, jsval_t it jsval_t v = for_iter_exec_body(js, ctx); if (is_err(v)) return v; + if (for_iter_handle_continue(js, ctx)) break; if (js->flags & F_BREAK) break; - if (label_flags & F_CONTINUE_LABEL) { js->flags |= F_BREAK; break; } if (js->flags & F_RETURN) return v; } @@ -9983,75 +9998,16 @@ static jsval_t js_for(struct js *js) { if (exe) { jsval_t obj = resolveprop(js, obj_expr); - uint8_t obj_type = vtype(obj); - - if (obj_type == T_NULL || obj_type == T_UNDEF) { - res = js_mkundef(); - goto done; - } - - if (obj_type != T_OBJ && obj_type != T_ARR && obj_type != T_FUNC) { - res = js_mkerr(js, "for-in requires object"); - goto done; - } - - jsval_t iter_obj = obj; - if (vtype(obj) == T_FUNC) { - iter_obj = mkval(T_OBJ, vdata(obj)); - } - - jsoff_t prop_off = loadoff(js, (jsoff_t) vdata(iter_obj)) & ~(3U | FLAGMASK); - - while (prop_off < js->brk && prop_off != 0) { - jsoff_t header = loadoff(js, prop_off); - - if (is_slot_prop(header)) { - prop_off = next_prop(header); - continue; - } - - jsoff_t koff = loadoff(js, prop_off + (jsoff_t) sizeof(prop_off)); - jsoff_t klen = offtolen(loadoff(js, koff)); - - const char *key = (char *) &js->mem[koff + sizeof(koff)]; - const char *tag_sym_key = get_toStringTag_sym_key(); - bool should_skip = streq(key, klen, STR_PROTO, STR_PROTO_LEN) || streq(key, klen, tag_sym_key, strlen(tag_sym_key)); - - if (!should_skip) { - jsoff_t iter_obj_off = (jsoff_t)vdata(iter_obj); - descriptor_entry_t *desc = lookup_descriptor(iter_obj_off, key, klen); - if (desc && !desc->enumerable) should_skip = true; - } - - if (!should_skip) { - jsval_t key_str = js_mkstr(js, key, klen); - const char *var_name = &js->code[var_name_off]; - jsoff_t existing = lkp_scope(js, js->scope, var_name, var_name_len); - - if (existing > 0) saveval(js, existing + sizeof(jsoff_t) * 2, key_str); else { - jsval_t x = mkprop(js, js->scope, js_mkstr(js, var_name, var_name_len), key_str, is_const_var ? CONSTMASK : 0); - if (is_err(x)) { res = x; goto done; } - } - - js->pos = body_start; - js->consumed = 1; - js->flags = (flags & ~F_NOEXEC) | F_LOOP; - v = js_block_or_stmt(js); - if (is_err(v)) { res = v; goto done; } - - if (label_flags & F_CONTINUE_LABEL) { - if (is_this_loop_continue_target(marker_index)) { - clear_continue_label(); - js->flags &= ~(F_BREAK | F_NOEXEC); - } else { js->flags |= F_BREAK; break; } - } - - if (js->flags & F_BREAK) break; - if (js->flags & F_RETURN) { res = v; goto done; } - } - - prop_off = loadoff(js, prop_off) & ~(3U | FLAGMASK); - } + for_iter_ctx_t ctx = { + body_start, body_end, + var_name_off, var_name_len, + is_const_var, flags, + has_destructure, destructure_off, + destructure_len, marker_index + }; + res = for_in_iter_object(js, &ctx, obj); + if (is_err(res)) goto done; + if (js->flags & F_RETURN) goto done; } js->pos = body_end; @@ -10074,7 +10030,13 @@ static jsval_t js_for(struct js *js) { if (exe) { jsval_t iterable = resolveprop(js, iter_expr); uint8_t itype = vtype(iterable); - for_iter_ctx_t ctx = { body_start, body_end, var_name_off, var_name_len, is_const_var, flags, has_destructure, destructure_off, destructure_len }; + for_iter_ctx_t ctx = { + body_start, body_end, + var_name_off, var_name_len, + is_const_var, flags, + has_destructure, destructure_off, + destructure_len, marker_index + }; if (itype == T_ARR) res = for_of_iter_array(js, &ctx, iterable); else if (itype == T_STR) res = for_of_iter_string(js, &ctx, iterable); diff --git a/tests/test_labeled_loops.cjs b/tests/test_labeled_loops.cjs new file mode 100644 index 0000000..e3aab92 --- /dev/null +++ b/tests/test_labeled_loops.cjs @@ -0,0 +1,179 @@ +// Test labeled break and continue in loops +console.log('=== Labeled Loop Tests ==='); + +// Test 1: Labeled break in nested for loops +console.log('\nTest 1: Labeled break in nested for loops'); +let result1 = ''; +outer1: for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + if (i === 1 && j === 1) { + break outer1; + } + result1 += `(${i},${j})`; + } +} +console.log('Result: ' + result1); +console.log('Expected: (0,0)(0,1)(0,2)(1,0)'); + +// Test 2: Labeled continue in nested for loops +console.log('\nTest 2: Labeled continue in nested for loops'); +let result2 = ''; +outer2: for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + if (j === 1) { + continue outer2; + } + result2 += `(${i},${j})`; + } +} +console.log('Result: ' + result2); +console.log('Expected: (0,0)(1,0)(2,0)'); + +// Test 3: Labeled break in for-of loop +console.log('\nTest 3: Labeled break in for-of loop'); +let result3 = ''; +const arr1 = [[1,2,3], [4,5,6], [7,8,9]]; +outer3: for (const row of arr1) { + for (const val of row) { + if (val === 5) { + break outer3; + } + result3 += val + ','; + } +} +console.log('Result: ' + result3); +console.log('Expected: 1,2,3,4,'); + +// Test 4: Labeled continue in for-of loop +console.log('\nTest 4: Labeled continue in for-of loop'); +let result4 = ''; +const arr2 = [[1,2], [3,4], [5,6]]; +outer4: for (const row of arr2) { + for (const val of row) { + if (val % 2 === 0) { + continue outer4; + } + result4 += val + ','; + } +} +console.log('Result: ' + result4); +console.log('Expected: 1,3,5,'); + +// Test 5: Labeled break in for-in loop +console.log('\nTest 5: Labeled break in for-in loop'); +let count5 = 0; +let brokeEarly5 = false; +const obj1 = { a: 1, b: 2, c: 3 }; +outer5: for (const key1 in obj1) { + for (const key2 in obj1) { + count5++; + if (count5 === 4) { + brokeEarly5 = true; + break outer5; + } + } +} +console.log('Count: ' + count5 + ', Broke early: ' + brokeEarly5); +console.log('Expected: Count: 4, Broke early: true'); + +// Test 6: Labeled continue in for-in loop +console.log('\nTest 6: Labeled continue in for-in loop'); +let outerCount6 = 0; +let innerCount6 = 0; +const obj2 = { a: 1, b: 2, c: 3 }; +outer6: for (const key1 in obj2) { + outerCount6++; + for (const key2 in obj2) { + innerCount6++; + continue outer6; // Should skip rest of inner loop + } +} +// Each outer iteration should only do 1 inner iteration before continuing outer +console.log('Outer: ' + outerCount6 + ', Inner: ' + innerCount6); +console.log('Expected: Outer: 3, Inner: 3'); + +// Test 7: Labeled break in while loop +console.log('\nTest 7: Labeled break in while loop'); +let result7 = ''; +let i7 = 0; +outer7: while (i7 < 3) { + let j7 = 0; + while (j7 < 3) { + if (i7 === 1 && j7 === 1) { + break outer7; + } + result7 += `(${i7},${j7})`; + j7++; + } + i7++; +} +console.log('Result: ' + result7); +console.log('Expected: (0,0)(0,1)(0,2)(1,0)'); + +// Test 8: Labeled continue in while loop +console.log('\nTest 8: Labeled continue in while loop'); +let result8 = ''; +let i8 = 0; +outer8: while (i8 < 3) { + let j8 = 0; + while (j8 < 3) { + if (j8 === 1) { + i8++; + continue outer8; + } + result8 += `(${i8},${j8})`; + j8++; + } + i8++; +} +console.log('Result: ' + result8); +console.log('Expected: (0,0)(1,0)(2,0)'); + +// Test 9: Triple nested loops with labeled break +console.log('\nTest 9: Triple nested with labeled break'); +let result9 = ''; +outer9: for (let i = 0; i < 2; i++) { + middle9: for (let j = 0; j < 2; j++) { + for (let k = 0; k < 2; k++) { + if (i === 1 && j === 0 && k === 1) { + break outer9; + } + result9 += `(${i},${j},${k})`; + } + } +} +console.log('Result: ' + result9); +console.log('Expected: (0,0,0)(0,0,1)(0,1,0)(0,1,1)(1,0,0)'); + +// Test 10: Triple nested with middle label break +console.log('\nTest 10: Triple nested with middle label break'); +let result10 = ''; +outer10: for (let i = 0; i < 2; i++) { + middle10: for (let j = 0; j < 2; j++) { + for (let k = 0; k < 2; k++) { + if (j === 1 && k === 0) { + break middle10; + } + result10 += `(${i},${j},${k})`; + } + } +} +console.log('Result: ' + result10); +console.log('Expected: (0,0,0)(0,0,1)(1,0,0)(1,0,1)'); + +// Test 11: for-of with string iteration and labeled continue +console.log('\nTest 11: for-of string with labeled continue'); +let result11 = ''; +const strings = ['ab', 'cd', 'ef']; +outer11: for (const str of strings) { + for (const ch of str) { + if (ch === 'c' || ch === 'e') { + continue outer11; + } + result11 += ch; + } +} +console.log('Result: ' + result11); +console.log('Expected: ab'); + +console.log('\n=== All labeled loop tests completed ===');