MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add for-let support to coroutine and scope management

+394 -65
+175
examples/spec/async_loops.js
··· 1 + import { test, summary } from './helpers.js'; 2 + 3 + console.log('Async Loop Closure Tests\n'); 4 + 5 + let results = {}; 6 + 7 + function delay(ms) { 8 + return new Promise(resolve => setTimeout(resolve, ms)); 9 + } 10 + 11 + async function testForLetClosure() { 12 + const fns = []; 13 + for (let i = 0; i < 3; i++) { 14 + await delay(5); 15 + fns.push(() => i); 16 + } 17 + return fns.map(f => f()).join(','); 18 + } 19 + testForLetClosure().then(v => { 20 + results.forLetClosure = v; 21 + }); 22 + 23 + async function testBlockScopedCapture() { 24 + const fns = []; 25 + for (let i = 0; i < 3; i++) { 26 + const captured = i * 10; 27 + await delay(5); 28 + fns.push(() => captured); 29 + } 30 + return fns.map(f => f()).join(','); 31 + } 32 + testBlockScopedCapture().then(v => { 33 + results.blockScopedCapture = v; 34 + }); 35 + 36 + async function testNestedLoops() { 37 + const fns = []; 38 + for (let i = 0; i < 2; i++) { 39 + for (let j = 0; j < 2; j++) { 40 + const captured = `${i}-${j}`; 41 + await delay(5); 42 + fns.push(() => captured); 43 + } 44 + } 45 + return fns.map(f => f()).join(','); 46 + } 47 + testNestedLoops().then(v => { 48 + results.nestedLoops = v; 49 + }); 50 + 51 + async function testWhileLoop() { 52 + let i = 0; 53 + const values = []; 54 + while (i < 3) { 55 + const captured = i; 56 + await delay(5); 57 + values.push(captured); 58 + i++; 59 + } 60 + return values.join(','); 61 + } 62 + testWhileLoop().then(v => { 63 + results.whileLoop = v; 64 + }); 65 + 66 + async function testMultipleAwaits() { 67 + const fns = []; 68 + for (let i = 0; i < 3; i++) { 69 + const captured = i; 70 + await delay(5); 71 + await delay(5); 72 + fns.push(() => captured); 73 + } 74 + return fns.map(f => f()).join(','); 75 + } 76 + testMultipleAwaits().then(v => { 77 + results.multipleAwaits = v; 78 + }); 79 + 80 + async function loopTask(id) { 81 + const taskResults = []; 82 + for (let i = 0; i < 3; i++) { 83 + const captured = `${id}:${i}`; 84 + await delay(5); 85 + taskResults.push(() => captured); 86 + } 87 + return taskResults.map(fn => fn()); 88 + } 89 + 90 + async function testSequentialTasks() { 91 + const a = await loopTask('A'); 92 + const b = await loopTask('B'); 93 + return { a: a.join(','), b: b.join(',') }; 94 + } 95 + testSequentialTasks().then(v => { 96 + results.sequentialTasks = v; 97 + }); 98 + 99 + async function testAsyncArrowInLoop() { 100 + const fns = []; 101 + for (let i = 0; i < 3; i++) { 102 + const captured = i; 103 + fns.push(async () => { 104 + await delay(5); 105 + return captured; 106 + }); 107 + } 108 + const values = await Promise.all(fns.map(f => f())); 109 + return values.join(','); 110 + } 111 + testAsyncArrowInLoop().then(v => { 112 + results.asyncArrowInLoop = v; 113 + }); 114 + 115 + async function testTryCatchInLoop() { 116 + const values = []; 117 + for (let i = 0; i < 3; i++) { 118 + const captured = i; 119 + try { 120 + await delay(5); 121 + values.push(captured); 122 + } catch (e) { 123 + values.push('error'); 124 + } 125 + } 126 + return values.join(','); 127 + } 128 + testTryCatchInLoop().then(v => { 129 + results.tryCatchInLoop = v; 130 + }); 131 + 132 + async function testDoWhile() { 133 + let i = 0; 134 + const values = []; 135 + do { 136 + const captured = i; 137 + await delay(5); 138 + values.push(captured); 139 + i++; 140 + } while (i < 3); 141 + return values.join(','); 142 + } 143 + testDoWhile().then(v => { 144 + results.doWhile = v; 145 + }); 146 + 147 + async function testConditionalAwait() { 148 + const fns = []; 149 + for (let i = 0; i < 3; i++) { 150 + const captured = i; 151 + if (i % 2 === 0) { 152 + await delay(5); 153 + } 154 + fns.push(() => captured); 155 + } 156 + return fns.map(f => f()).join(','); 157 + } 158 + testConditionalAwait().then(v => { 159 + results.conditionalAwait = v; 160 + }); 161 + 162 + setTimeout(() => { 163 + test('for-let with async closure', results.forLetClosure, '0,1,2'); 164 + test('block-scoped capture', results.blockScopedCapture, '0,10,20'); 165 + test('nested loops', results.nestedLoops, '0-0,0-1,1-0,1-1'); 166 + test('while loop const', results.whileLoop, '0,1,2'); 167 + test('multiple awaits per iteration', results.multipleAwaits, '0,1,2'); 168 + test('sequential tasks A', results.sequentialTasks.a, 'A:0,A:1,A:2'); 169 + test('sequential tasks B', results.sequentialTasks.b, 'B:0,B:1,B:2'); 170 + test('async arrow in loop', results.asyncArrowInLoop, '0,1,2'); 171 + test('try-catch in loop', results.tryCatchInLoop, '0,1,2'); 172 + test('do-while with const', results.doWhile, '0,1,2'); 173 + test('conditional await', results.conditionalAwait, '0,1,2'); 174 + summary(); 175 + }, 200);
+12
include/internal.h
··· 3 3 4 4 #include "ant.h" 5 5 6 + struct for_let_ctx { 7 + const char *var_name; // interned variable name 8 + jsoff_t var_len; // length of var name 9 + jsoff_t prop_off; // offset of var property in loop scope 10 + jsval_t body_scope; // loop body scope for capturing block-scoped vars 11 + }; 12 + 6 13 struct js { 7 14 jsoff_t css; // max observed C stack size 8 15 const char *code; // currently parsed code snippet ··· 48 55 bool gc_suppress; // suppress GC during microtask batch processing 49 56 int eval_depth; // recursion depth of js_eval calls 50 57 int parse_depth; // recursion depth of parser (for stack overflow protection) 58 + 59 + // for-let loop context stack 60 + struct for_let_ctx *for_let_stack; 61 + int for_let_stack_len; 62 + int for_let_stack_cap; 51 63 }; 52 64 53 65 enum {
+190 -65
src/ant.c
··· 59 59 _Static_assert(sizeof(double) == sizeof(uint64_t), "double and uint64_t must have same size"); 60 60 61 61 #if defined(__STDC_IEC_559__) || defined(__GCC_IEC_559) 62 - /* IEEE 754 compliance guaranteed */ 63 62 #elif defined(__FAST_MATH__) 64 63 #error "NaN-boxing is incompatible with -ffast-math" 65 64 #elif DBL_MANT_DIG != 53 || DBL_MAX_EXP != 1024 ··· 115 114 mco_coro* mco; 116 115 bool mco_started; 117 116 bool is_ready; 117 + struct for_let_ctx *for_let_stack; 118 + int for_let_stack_len; 119 + int for_let_stack_cap; 118 120 } coroutine_t; 119 121 120 122 typedef struct { ··· 906 908 } 907 909 } 908 910 909 - static void free_coroutine(coroutine_t *coro); 910 911 static bool has_ready_coroutines(void); 911 912 static bool coro_stack_size_initialized = false; 913 + 914 + static void free_coroutine(coroutine_t *coro); 915 + static void for_let_swap_with_coro(struct js *js, coroutine_t *coro); 912 916 913 917 static size_t calculate_coro_stack_size(void) { 914 918 if (coro_stack_size_initialized) return 0; ··· 1012 1016 for (coroutine_t *temp = pending_coroutines.head, *next; temp; temp = next) { 1013 1017 next = temp->next; 1014 1018 if (!temp->is_ready || !temp->mco || mco_status(temp->mco) != MCO_SUSPENDED) continue; 1019 + remove_coroutine(temp); 1015 1020 1016 - remove_coroutine(temp); 1021 + jsval_t saved_scope = temp->js->scope; 1022 + temp->js->scope = temp->scope; 1023 + 1024 + for_let_swap_with_coro(temp->js, temp); 1017 1025 mco_result res = mco_resume(temp->mco); 1018 1026 1027 + temp->scope = temp->js->scope; 1028 + temp->js->scope = saved_scope; 1029 + for_let_swap_with_coro(temp->js, temp); 1030 + 1019 1031 if (res == MCO_SUCCESS && mco_status(temp->mco) != MCO_DEAD) { 1020 1032 temp->is_ready = false; 1021 1033 enqueue_coroutine(temp); ··· 1128 1140 coro->mco = mco; 1129 1141 coro->mco_started = false; 1130 1142 coro->is_ready = true; 1143 + coro->for_let_stack = NULL; 1144 + coro->for_let_stack_len = 0; 1145 + coro->for_let_stack_cap = 0; 1131 1146 1132 1147 ctx->coro = coro; 1133 1148 enqueue_coroutine(coro); 1134 1149 1150 + jsval_t saved_scope = js->scope; 1151 + js->scope = coro->scope; 1152 + for_let_swap_with_coro(js, coro); 1135 1153 res = mco_resume(mco); 1154 + 1155 + coro->scope = js->scope; 1156 + js->scope = saved_scope; 1157 + for_let_swap_with_coro(js, coro); 1158 + 1136 1159 if (res != MCO_SUCCESS && mco_status(mco) != MCO_DEAD) { 1137 1160 remove_coroutine(coro); 1138 1161 free_coroutine(coro); ··· 1158 1181 coro->mco = NULL; 1159 1182 } 1160 1183 if (coro->args) CORO_FREE(coro->args); 1184 + if (coro->for_let_stack) free(coro->for_let_stack); 1161 1185 CORO_FREE(coro); 1162 1186 } 1163 1187 } ··· 4610 4634 static void mkscope(struct js *js) { (void)js_mkscope(js); } 4611 4635 static void delscope(struct js *js) { (void)js_delscope(js); } 4612 4636 4637 + static void for_let_push(struct js *js, const char *var_name, jsoff_t var_len, jsoff_t prop_off, jsval_t body_scope) { 4638 + if (js->for_let_stack_len >= js->for_let_stack_cap) { 4639 + int new_cap = js->for_let_stack_cap ? js->for_let_stack_cap * 2 : 4; 4640 + js->for_let_stack = realloc(js->for_let_stack, new_cap * sizeof(struct for_let_ctx)); 4641 + js->for_let_stack_cap = new_cap; 4642 + } 4643 + js->for_let_stack[js->for_let_stack_len++] = (struct for_let_ctx){var_name, var_len, prop_off, body_scope}; 4644 + } 4645 + 4646 + static inline void for_let_set_body_scope(struct js *js, jsval_t body_scope) { 4647 + if (js->for_let_stack_len > 0) js->for_let_stack[js->for_let_stack_len - 1].body_scope = body_scope; 4648 + } 4649 + 4650 + static inline void for_let_pop(struct js *js) { 4651 + if (js->for_let_stack_len > 0) js->for_let_stack_len--; 4652 + } 4653 + 4654 + static inline struct for_let_ctx *for_let_current(struct js *js) { 4655 + return js->for_let_stack_len > 0 ? &js->for_let_stack[js->for_let_stack_len - 1] : NULL; 4656 + } 4657 + 4658 + static void for_let_swap_with_coro(struct js *js, coroutine_t *coro) { 4659 + struct for_let_ctx *tmp_stack = js->for_let_stack; 4660 + int tmp_len = js->for_let_stack_len; 4661 + int tmp_cap = js->for_let_stack_cap; 4662 + 4663 + js->for_let_stack = coro->for_let_stack; 4664 + js->for_let_stack_len = coro->for_let_stack_len; 4665 + js->for_let_stack_cap = coro->for_let_stack_cap; 4666 + 4667 + coro->for_let_stack = tmp_stack; 4668 + coro->for_let_stack_len = tmp_len; 4669 + coro->for_let_stack_cap = tmp_cap; 4670 + } 4671 + 4672 + static void copy_body_scope_props(struct js *js, jsval_t body_scope, jsval_t closure_scope, const char *skip_var, jsoff_t skip_len) { 4673 + if (vtype(body_scope) != T_OBJ) return; 4674 + jsoff_t prop_off = loadoff(js, (jsoff_t)vdata(body_scope)) & ~(3U | FLAGMASK); 4675 + 4676 + while (prop_off < js->brk && prop_off != 0) { 4677 + jsoff_t header = loadoff(js, prop_off); 4678 + if (is_slot_prop(header)) { prop_off = next_prop(header); continue; } 4679 + 4680 + jsoff_t koff = loadoff(js, prop_off + (jsoff_t)sizeof(prop_off)); 4681 + jsoff_t klen = offtolen(loadoff(js, koff)); 4682 + const char *key = (char *)&js->mem[koff + sizeof(koff)]; 4683 + jsval_t val = loadval(js, prop_off + (jsoff_t)(sizeof(prop_off) + sizeof(koff))); 4684 + 4685 + prop_off = next_prop(header); 4686 + if (is_internal_prop(key, klen)) continue; 4687 + if (skip_var && klen == skip_len && memcmp(key, skip_var, klen) == 0) continue; 4688 + 4689 + jsval_t key_str = js_mkstr(js, key, klen); 4690 + mkprop(js, closure_scope, key_str, val, 0); 4691 + } 4692 + } 4693 + 4694 + static jsval_t for_let_capture_scope(struct js *js) { 4695 + struct for_let_ctx *flc = for_let_current(js); 4696 + if (!flc || flc->prop_off == 0) return js->scope; 4697 + 4698 + jsval_t loop_var_val = resolveprop(js, mkval(T_PROP, flc->prop_off)); 4699 + jsval_t closure_scope = js_mkscope(js); 4700 + if (is_err(closure_scope)) return closure_scope; 4701 + 4702 + jsval_t var_key = js_mkstr(js, flc->var_name, flc->var_len); 4703 + mkprop(js, closure_scope, var_key, loop_var_val, 0); 4704 + 4705 + if (vtype(flc->body_scope) == T_OBJ) { 4706 + copy_body_scope_props(js, flc->body_scope, closure_scope, flc->var_name, flc->var_len); 4707 + } 4708 + delscope(js); 4709 + return closure_scope; 4710 + } 4711 + 4613 4712 static void scope_clear_props(struct js *js, jsval_t scope) { 4614 4713 jsoff_t off = (jsoff_t)vdata(scope); 4615 4714 jsoff_t header = loadoff(js, off); ··· 8076 8175 set_slot(js, func_obj, SLOT_CODE, str); 8077 8176 jsval_t name_key = js_mkstr(js, "name", 4); 8078 8177 setprop(js, func_obj, name_key, key); 8079 - set_slot(js, func_obj, SLOT_SCOPE, js->scope); 8178 + 8179 + jsval_t closure_scope = for_let_capture_scope(js); 8180 + if (is_err(closure_scope)) return closure_scope; 8181 + set_slot(js, func_obj, SLOT_SCOPE, closure_scope); 8080 8182 jsval_t val = mkval(T_FUNC, (unsigned long) vdata(func_obj)); 8081 8183 8082 8184 if (is_getter || is_setter) { ··· 8262 8364 } 8263 8365 8264 8366 if (!(flags & F_NOEXEC)) { 8265 - set_slot(js, func_obj, SLOT_SCOPE, js->scope); 8367 + jsval_t closure_scope = for_let_capture_scope(js); 8368 + if (is_err(closure_scope)) return closure_scope; 8369 + set_slot(js, func_obj, SLOT_SCOPE, closure_scope); 8266 8370 if (flags & F_STRICT) set_slot(js, func_obj, SLOT_STRICT, js_mktrue()); 8267 8371 } 8268 8372 ··· 8422 8526 set_slot(js, func_obj, SLOT_ASYNC, js_mktrue()); 8423 8527 jsval_t async_proto = get_slot(js, js_glob(js), SLOT_ASYNC_PROTO); 8424 8528 if (vtype(async_proto) == T_FUNC) set_proto(js, func_obj, async_proto); 8425 - if (!(flags & F_NOEXEC)) set_slot(js, func_obj, SLOT_SCOPE, js->scope); 8529 + if (!(flags & F_NOEXEC)) { 8530 + jsval_t closure_scope = for_let_capture_scope(js); 8531 + if (is_err(closure_scope)) return closure_scope; 8532 + set_slot(js, func_obj, SLOT_SCOPE, closure_scope); 8533 + } 8426 8534 return mkval(T_FUNC, (unsigned long) vdata(func_obj)); 8427 8535 } 8428 8536 return mkcoderef((jsoff_t) id_start, (jsoff_t) id_len); ··· 8573 8681 } 8574 8682 8575 8683 if (!(flags & F_NOEXEC)) { 8576 - set_slot(js, func_obj, SLOT_SCOPE, js->scope); 8684 + jsval_t closure_scope = for_let_capture_scope(js); 8685 + if (is_err(closure_scope)) return closure_scope; 8686 + set_slot(js, func_obj, SLOT_SCOPE, closure_scope); 8577 8687 set_slot(js, func_obj, SLOT_THIS, js->this_val); 8578 8688 } 8579 8689 ··· 9132 9242 js_parse_state_t saved; 9133 9243 JS_SAVE_STATE(js, saved); 9134 9244 uint8_t saved_flags = js->flags; 9135 - 9136 9245 mco_result mco_res = mco_yield(current_mco); 9137 - 9246 + 9138 9247 JS_RESTORE_STATE(js, saved); 9139 9248 js->flags = saved_flags; 9140 9249 ··· 9394 9503 9395 9504 jsval_t func_obj = mkobj(js, 0); 9396 9505 if (is_err(func_obj)) return func_obj; 9397 - 9398 9506 set_slot(js, func_obj, SLOT_CODE, str); 9399 9507 9400 9508 if (!(flags & F_NOEXEC)) { 9401 - set_slot(js, func_obj, SLOT_SCOPE, js->scope); 9509 + jsval_t closure_scope = for_let_capture_scope(js); 9510 + if (is_err(closure_scope)) return closure_scope; 9511 + set_slot(js, func_obj, SLOT_SCOPE, closure_scope); 9402 9512 set_slot(js, func_obj, SLOT_THIS, js->this_val); 9403 9513 } 9404 9514 ··· 9971 10081 jsval_t res3 = setprop(js, func_obj, name_key, name_val); 9972 10082 if (is_err(res3)) return res3; 9973 10083 if (exe) { 9974 - set_slot(js, func_obj, SLOT_SCOPE, js->scope); 10084 + jsval_t closure_scope = for_let_capture_scope(js); 10085 + if (is_err(closure_scope)) return closure_scope; 10086 + set_slot(js, func_obj, SLOT_SCOPE, closure_scope); 9975 10087 if (flags & F_STRICT) { 9976 10088 set_slot(js, func_obj, SLOT_STRICT, js_mktrue()); 9977 10089 } 9978 10090 } 10091 + 9979 10092 jsval_t func = mkval(T_FUNC, (unsigned long) vdata(func_obj)); 9980 - 9981 - jsval_t proto_setup = setup_func_prototype(js, func); 9982 - if (is_err(proto_setup)) return proto_setup; 10093 + jsval_t proto_setup = setup_func_prototype(js, func); 10094 + if (is_err(proto_setup)) return proto_setup; 9983 10095 9984 - if (exe) { 9985 - jsoff_t existing = lkp_scope(js, js->scope, name, nlen); 9986 - if (existing > 0) { 9987 - saveval(js, existing + sizeof(jsoff_t) * 2, func); 9988 - } else { 9989 - jsval_t x = mkprop(js, js->scope, js_mkstr(js, name, nlen), func, 0); 9990 - if (is_err(x)) return x; 9991 - } 9992 - } 10096 + if (exe) { 10097 + jsoff_t existing = lkp_scope(js, js->scope, name, nlen); 10098 + if (existing > 0) { 10099 + saveval(js, existing + sizeof(jsoff_t) * 2, func); 10100 + } else { 10101 + jsval_t x = mkprop(js, js->scope, js_mkstr(js, name, nlen), func, 0); 10102 + if (is_err(x)) return x; 10103 + } 10104 + } 9993 10105 9994 10106 return js_mkundef(); 9995 10107 } ··· 10038 10150 jsval_t res3 = setprop(js, func_obj, name_key, name_val); 10039 10151 if (is_err(res3)) return res3; 10040 10152 if (exe) { 10041 - set_slot(js, func_obj, SLOT_SCOPE, js->scope); 10042 - if (flags & F_STRICT) { 10043 - set_slot(js, func_obj, SLOT_STRICT, js_mktrue()); 10044 - } 10153 + jsval_t closure_scope = for_let_capture_scope(js); 10154 + if (is_err(closure_scope)) return closure_scope; 10155 + set_slot(js, func_obj, SLOT_SCOPE, closure_scope); 10156 + if (flags & F_STRICT) set_slot(js, func_obj, SLOT_STRICT, js_mktrue()); 10045 10157 } 10046 10158 jsval_t func = mkval(T_FUNC, (unsigned long) vdata(func_obj)); 10047 10159 ··· 10622 10734 } 10623 10735 if (!expect(js, TOK_RPAREN, &res)) goto done; 10624 10736 pos3 = js->pos; 10737 + 10738 + jsoff_t iter_var_prop_off = 0; 10739 + if (is_let_loop && let_var_len > 0 && exe) { 10740 + js->flags = flags; 10741 + mkscope(js); 10742 + jsval_t let_var_key = js_mkstr(js, &js->code[let_var_off], let_var_len); 10743 + jsoff_t outer_off = lkp_scope(js, upper(js, js->scope), &js->code[let_var_off], let_var_len); 10744 + jsval_t init_val = outer_off ? resolveprop(js, mkval(T_PROP, outer_off)) : js_mkundef(); 10745 + mkprop(js, js->scope, let_var_key, init_val, 0); 10746 + iter_var_prop_off = lkp(js, js->scope, &js->code[let_var_off], let_var_len); 10747 + const char *var_interned = intern_string(&js->code[let_var_off], let_var_len); 10748 + for_let_push(js, var_interned, let_var_len, iter_var_prop_off, js_mkundef()); 10749 + } 10750 + 10625 10751 loop_block_ctx_t loop_ctx = {0}; 10626 - if (exe) loop_block_init(js, &loop_ctx); 10752 + if (exe) { 10753 + loop_block_init(js, &loop_ctx); 10754 + if (is_let_loop && let_var_len > 0 && loop_ctx.needs_scope) for_let_set_body_scope(js, loop_ctx.loop_scope); 10755 + } 10756 + 10757 + js->flags |= F_NOEXEC; 10627 10758 v = js_block_or_stmt(js); 10759 + if (exe) js->flags = flags; 10628 10760 if (is_err2(&v, &res)) goto done; 10629 10761 pos4 = js->pos; 10762 + 10630 10763 while (!(flags & F_NOEXEC)) { 10631 10764 js->flags = flags, js->pos = pos1, js->consumed = 1; 10632 10765 if (next(js) != TOK_SEMICOLON) { ··· 10635 10768 if (!js_truthy(js, v)) break; 10636 10769 } 10637 10770 10638 - bool iter_scope = false; 10639 - jsval_t loop_var_val = js_mkundef(); 10640 - if (is_let_loop && let_var_len > 0) { 10641 - jsoff_t var_off = lkp_scope(js, js->scope, &js->code[let_var_off], let_var_len); 10642 - if (var_off != 0) { 10643 - loop_var_val = resolveprop(js, mkval(T_PROP, var_off)); 10644 - } 10645 - mkscope(js); 10646 - iter_scope = true; 10647 - jsval_t var_key = js_mkstr(js, &js->code[let_var_off], let_var_len); 10648 - mkprop(js, js->scope, var_key, loop_var_val, 0); 10649 - } 10650 - 10651 10771 js->flags |= F_LOOP; 10652 10772 js->pos = pos3; 10653 10773 js->consumed = 1; 10654 10774 loop_block_clear(js, &loop_ctx); 10655 10775 v = loop_block_exec(js, &loop_ctx); 10656 10776 if (is_err2(&v, &res)) { 10657 - if (iter_scope) delscope(js); 10658 10777 loop_block_cleanup(js, &loop_ctx); 10659 - goto done; 10660 - } 10661 - 10662 - if (is_let_loop && let_var_len > 0) { 10663 - jsoff_t iter_var_off = lkp(js, js->scope, &js->code[let_var_off], let_var_len); 10664 - if (iter_var_off != 0) { 10665 - loop_var_val = resolveprop(js, mkval(T_PROP, iter_var_off)); 10778 + if (is_let_loop && let_var_len > 0) { 10779 + for_let_pop(js); delscope(js); 10666 10780 } 10667 - delscope(js); 10668 - jsoff_t outer_var_off = lkp_scope(js, js->scope, &js->code[let_var_off], let_var_len); 10669 - if (outer_var_off != 0) { 10670 - saveval(js, outer_var_off + sizeof(jsoff_t) * 2, loop_var_val); 10671 - } 10781 + goto done; 10672 10782 } 10673 10783 10674 10784 if (label_flags & F_CONTINUE_LABEL) { ··· 10680 10790 if (next(js) != TOK_RPAREN) { 10681 10791 v = js_expr_comma(js); 10682 10792 if (is_err2(&v, &res)) goto done; 10683 - } 10684 - continue; 10793 + } continue; 10685 10794 } 10686 10795 } 10687 10796 10688 - if (js->flags & F_BREAK) { 10689 - break; 10690 - } 10797 + if (js->flags & F_BREAK) break; 10798 + if (js->flags & F_RETURN) { res = v; break; } 10691 10799 10692 - if (js->flags & F_RETURN) { 10693 - res = v; 10694 - break; 10695 - } 10696 10800 js->flags = flags, js->pos = pos2, js->consumed = 1; 10697 10801 if (next(js) != TOK_RPAREN) { 10698 10802 v = js_expr_comma(js); ··· 10700 10804 } 10701 10805 } 10702 10806 if (exe) loop_block_cleanup(js, &loop_ctx); 10807 + if (is_let_loop && let_var_len > 0 && exe) { 10808 + for_let_pop(js); delscope(js); 10809 + } 10703 10810 js->pos = pos4, js->tok = TOK_SEMICOLON, js->consumed = 0; 10704 10811 done: 10705 10812 if (use_label_stack && label_stack && utarray_len(label_stack) > 0) { ··· 10745 10852 if (!expect(js, TOK_RPAREN, &res)) goto done; 10746 10853 10747 10854 jsoff_t body_start = js->pos; 10748 - if (exe) loop_block_init(js, &loop_ctx); 10855 + if (exe) { 10856 + js->flags = flags; 10857 + loop_block_init(js, &loop_ctx); 10858 + js->flags |= F_NOEXEC; 10859 + } 10749 10860 10750 10861 v = js_block_or_stmt(js); 10751 10862 if (is_err(v)) { res = v; goto done; } ··· 22141 22252 js->errmsg = NULL; 22142 22253 } 22143 22254 22255 + if (js->for_let_stack) { 22256 + free(js->for_let_stack); 22257 + js->for_let_stack = NULL; 22258 + } 22259 + 22144 22260 if (js->owns_mem) ANT_GC_FREE((void *)((uint8_t *)js - 0)); 22145 22261 } 22146 22262 ··· 22452 22568 FWD_VAL(coro->result); 22453 22569 FWD_VAL(coro->async_func); 22454 22570 FWD_VAL(coro->yield_value); 22571 + for (int i = 0; i < coro->for_let_stack_len; i++) { 22572 + FWD_VAL(coro->for_let_stack[i].body_scope); 22573 + FWD_OFF(coro->for_let_stack[i].prop_off); 22574 + } 22455 22575 if (coro->mco) { 22456 22576 async_exec_context_t *actx = (async_exec_context_t *)mco_get_user_data(coro->mco); 22457 22577 if (actx) { ··· 22489 22609 set_entry_t *se, *se_tmp; 22490 22610 HASH_ITER(hh, *set_reg->head, se, se_tmp) FWD_VAL(se->value); 22491 22611 } 22612 + } 22613 + 22614 + for (int i = 0; i < js->for_let_stack_len; i++) { 22615 + FWD_VAL(js->for_let_stack[i].body_scope); 22616 + FWD_OFF(js->for_let_stack[i].prop_off); 22492 22617 } 22493 22618 22494 22619 memset(intern_prop_cache, 0, sizeof(intern_prop_cache));
+6
tests/test_captured.js
··· 1 + const funcs = []; 2 + for (let i = 0; i < 3; i++) { 3 + const captured = i; 4 + funcs.push(() => captured); 5 + } 6 + console.log(funcs[0](), funcs[1](), funcs[2]());
+5
tests/test_closure.js
··· 1 + const funcs = []; 2 + for (let i = 0; i < 3; i++) { 3 + funcs.push(() => i); 4 + } 5 + console.log(funcs[0](), funcs[1](), funcs[2]());
+6
tests/test_closure2.js
··· 1 + const funcs = []; 2 + for (let i = 0; i < 3; i++) { 3 + const captured = i; 4 + funcs.push(() => captured); 5 + } 6 + console.log(funcs[0](), funcs[1](), funcs[2]());