To: vim_dev@googlegroups.com Subject: Patch 9.0.0470 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 9.0.0470 Problem: In a :def function all closures in a loop get the same variables. Solution: When in a loop and a closure refers to a variable declared in the loop, prepare for making a copy of variables for each closure. Files: src/vim9.h, src/vim9cmds.c, src/vim9instr.c, src/proto/vim9instr.pro, src/vim9compile.c, src/vim9execute.c, src/testdir/test_vim9_disassemble.vim *** ../vim-9.0.0469/src/vim9.h 2022-09-07 16:48:41.183678514 +0100 --- src/vim9.h 2022-09-15 16:50:26.471818923 +0100 *************** *** 122,127 **** --- 122,130 ---- // loop ISN_FOR, // get next item from a list, uses isn_arg.forloop + ISN_WHILE, // jump if condition false, store funcref count, uses + // isn_arg.whileloop + ISN_ENDLOOP, // handle variables for closures, uses isn_arg.endloop ISN_TRY, // add entry to ec_trystack, uses isn_arg.tryref ISN_THROW, // pop value of stack, store in v:exception *************** *** 240,245 **** --- 243,249 ---- JUMP_ALWAYS, JUMP_NEVER, JUMP_IF_FALSE, // pop and jump if false + JUMP_WHILE_FALSE, // pop and jump if false for :while JUMP_AND_KEEP_IF_TRUE, // jump if top of stack is truthy, drop if not JUMP_IF_COND_TRUE, // jump if top of stack is true, drop if not JUMP_IF_COND_FALSE, // jump if top of stack is false, drop if not *************** *** 263,268 **** --- 267,285 ---- int for_end; // position to jump to after done } forloop_T; + // arguments to ISN_WHILE + typedef struct { + int while_funcref_idx; // variable index for funcref count + int while_end; // position to jump to after done + } whileloop_T; + + // arguments to ISN_ENDLOOP + typedef struct { + short end_funcref_idx; // variable index of funcrefs.ga_len + short end_var_idx; // first variable declared in the loop + short end_var_count; // number of variables declared in the loop + } endloop_T; + // indirect arguments to ISN_TRY typedef struct { int try_catch; // position to jump to on throw *************** *** 446,451 **** --- 463,470 ---- jump_T jump; jumparg_T jumparg; forloop_T forloop; + whileloop_T whileloop; + endloop_T endloop; try_T tryref; trycont_T trycont; cbfunc_T bfunc; *************** *** 597,602 **** --- 616,624 ---- typedef struct { int ws_top_label; // instruction idx at WHILE endlabel_T *ws_end_label; // instructions to set end + int ws_funcref_idx; // index of var that holds funcref count + int ws_local_count; // ctx_locals.ga_len at :while + int ws_closure_count; // ctx_closure_count at :while } whilescope_T; /* *************** *** 605,610 **** --- 627,635 ---- typedef struct { int fs_top_label; // instruction idx at FOR endlabel_T *fs_end_label; // break instructions + int fs_funcref_idx; // index of var that holds funcref count + int fs_local_count; // ctx_locals.ga_len at :for + int fs_closure_count; // ctx_closure_count at :for } forscope_T; /* *************** *** 726,733 **** garray_T ctx_locals; // currently visible local variables ! int ctx_has_closure; // set to one if a closure was created in ! // the function skip_T ctx_skip; scope_T *ctx_scope; // current scope, NULL at toplevel --- 751,760 ---- garray_T ctx_locals; // currently visible local variables ! int ctx_has_closure; // set to one if a FUNCREF was used in the ! // function ! int ctx_closure_count; // incremented for each closure created in ! // the function. skip_T ctx_skip; scope_T *ctx_scope; // current scope, NULL at toplevel *** ../vim-9.0.0469/src/vim9cmds.c 2022-09-08 20:49:16.504630443 +0100 --- src/vim9cmds.c 2022-09-15 17:18:34.306558902 +0100 *************** *** 278,287 **** } /* ! * generate a jump to the ":endif"/":endfor"/":endwhile"/":finally"/":endtry". */ static int ! compile_jump_to_end(endlabel_T **el, jumpwhen_T when, cctx_T *cctx) { garray_T *instr = &cctx->ctx_instr; endlabel_T *endlabel = ALLOC_CLEAR_ONE(endlabel_T); --- 278,292 ---- } /* ! * Generate a jump to the ":endif"/":endfor"/":endwhile"/":finally"/":endtry". ! * "funcref_idx" is used for JUMP_WHILE_FALSE */ static int ! compile_jump_to_end( ! endlabel_T **el, ! jumpwhen_T when, ! int funcref_idx, ! cctx_T *cctx) { garray_T *instr = &cctx->ctx_instr; endlabel_T *endlabel = ALLOC_CLEAR_ONE(endlabel_T); *************** *** 292,298 **** *el = endlabel; endlabel->el_end_label = instr->ga_len; ! generate_JUMP(cctx, when, 0); return OK; } --- 297,306 ---- *el = endlabel; endlabel->el_end_label = instr->ga_len; ! if (when == JUMP_WHILE_FALSE) ! generate_WHILE(cctx, funcref_idx); ! else ! generate_JUMP(cctx, when, 0); return OK; } *************** *** 564,570 **** } if (compile_jump_to_end(&scope->se_u.se_if.is_end_label, ! JUMP_ALWAYS, cctx) == FAIL) return NULL; // previous "if" or "elseif" jumps here isn = ((isn_T *)instr->ga_data) + scope->se_u.se_if.is_if_label; --- 572,578 ---- } if (compile_jump_to_end(&scope->se_u.se_if.is_end_label, ! JUMP_ALWAYS, 0, cctx) == FAIL) return NULL; // previous "if" or "elseif" jumps here isn = ((isn_T *)instr->ga_data) + scope->se_u.se_if.is_if_label; *************** *** 695,701 **** { if (!cctx->ctx_had_return && compile_jump_to_end(&scope->se_u.se_if.is_end_label, ! JUMP_ALWAYS, cctx) == FAIL) return NULL; } --- 703,709 ---- { if (!cctx->ctx_had_return && compile_jump_to_end(&scope->se_u.se_if.is_end_label, ! JUMP_ALWAYS, 0, cctx) == FAIL) return NULL; } *************** *** 771,786 **** * Compile "for var in expr": * * Produces instructions: ! * PUSHNR -1 ! * STORE loop-idx Set index to -1 ! * EVAL expr result of "expr" on top of stack * top: FOR loop-idx, end Increment index, use list on bottom of stack * - if beyond end, jump to "end" * - otherwise get item from list and push it * STORE var Store item in "var" * ... body ... ! * JUMP top Jump back to repeat ! * end: DROP Drop the result of "expr" * * Compile "for [var1, var2] in expr" - as above, but instead of "STORE var": * UNPACK 2 Split item in 2 --- 779,795 ---- * Compile "for var in expr": * * Produces instructions: ! * STORE -1 in loop-idx Set index to -1 ! * EVAL expr Result of "expr" on top of stack * top: FOR loop-idx, end Increment index, use list on bottom of stack * - if beyond end, jump to "end" * - otherwise get item from list and push it + * - store ec_funcrefs in var "loop-idx" + 1 * STORE var Store item in "var" * ... body ... ! * ENDLOOP funcref-idx off count Only if closure uses local var ! * JUMP top Jump back to repeat ! * end: DROP Drop the result of "expr" * * Compile "for [var1, var2] in expr" - as above, but instead of "STORE var": * UNPACK 2 Split item in 2 *************** *** 801,807 **** --- 810,818 ---- size_t varlen; garray_T *instr = &cctx->ctx_instr; scope_T *scope; + forscope_T *forscope; lvar_T *loop_lvar; // loop iteration variable + lvar_T *funcref_lvar; lvar_T *var_lvar; // variable for "var" type_T *vartype; type_T *item_type = &t_any; *************** *** 845,862 **** scope = new_scope(cctx, FOR_SCOPE); if (scope == NULL) return NULL; // Reserve a variable to store the loop iteration counter and initialize it // to -1. loop_lvar = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number); if (loop_lvar == NULL) { - // out of memory drop_scope(cctx); ! return NULL; } generate_STORENR(cctx, loop_lvar->lv_idx, -1); // compile "expr", it remains on the stack until "endfor" arg = p; if (compile_expr0(&arg, cctx) == FAIL) --- 856,883 ---- scope = new_scope(cctx, FOR_SCOPE); if (scope == NULL) return NULL; + forscope = &scope->se_u.se_for; // Reserve a variable to store the loop iteration counter and initialize it // to -1. loop_lvar = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number); if (loop_lvar == NULL) { drop_scope(cctx); ! return NULL; // out of memory } generate_STORENR(cctx, loop_lvar->lv_idx, -1); + // Reserve a variable to store ec_funcrefs.ga_len, used in ISN_ENDLOOP. + // The variable index is always the loop var index plus one. + // It is not used when no closures are encountered, we don't know yet. + funcref_lvar = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number); + if (funcref_lvar == NULL) + { + drop_scope(cctx); + return NULL; // out of memory + } + // compile "expr", it remains on the stack until "endfor" arg = p; if (compile_expr0(&arg, cctx) == FAIL) *************** *** 901,907 **** generate_undo_cmdmods(cctx); // "for_end" is set when ":endfor" is found ! scope->se_u.se_for.fs_top_label = current_instr_idx(cctx); if (cctx->ctx_compile_type == CT_DEBUG) { --- 922,928 ---- generate_undo_cmdmods(cctx); // "for_end" is set when ":endfor" is found ! forscope->fs_top_label = current_instr_idx(cctx); if (cctx->ctx_compile_type == CT_DEBUG) { *************** *** 1019,1024 **** --- 1040,1050 ---- arg = skipwhite(p); vim_free(name); } + + forscope->fs_funcref_idx = funcref_lvar->lv_idx; + // remember the number of variables and closures, used in :endfor + forscope->fs_local_count = cctx->ctx_locals.ga_len; + forscope->fs_closure_count = cctx->ctx_closure_count; } return arg_end; *************** *** 1030,1035 **** --- 1056,1078 ---- } /* + * At :endfor and :endwhile: Generate an ISN_ENDLOOP instruction if any + * variable was declared that could be used by a new closure. + */ + static int + compile_loop_end( + int prev_local_count, + int prev_closure_count, + int funcref_idx, + cctx_T *cctx) + { + if (cctx->ctx_locals.ga_len > prev_local_count + && cctx->ctx_closure_count > prev_closure_count) + return generate_ENDLOOP(cctx, funcref_idx, prev_local_count); + return OK; + } + + /* * compile "endfor" */ char_u * *************** *** 1052,1057 **** --- 1095,1108 ---- cctx->ctx_scope = scope->se_outer; if (cctx->ctx_skip != SKIP_YES) { + // Handle the case that any local variables were declared that might be + // used in a closure. + if (compile_loop_end(forscope->fs_local_count, + forscope->fs_closure_count, + forscope->fs_funcref_idx, + cctx) == FAIL) + return NULL; + unwind_locals(cctx, scope->se_local_count); // At end of ":for" scope jump back to the FOR instruction. *************** *** 1080,1104 **** * compile "while expr" * * Produces instructions: ! * top: EVAL expr Push result of "expr" ! * JUMP_IF_FALSE end jump if false ! * ... body ... ! * JUMP top Jump back to repeat * end: * */ char_u * compile_while(char_u *arg, cctx_T *cctx) { ! char_u *p = arg; ! scope_T *scope; scope = new_scope(cctx, WHILE_SCOPE); if (scope == NULL) return NULL; // "endwhile" jumps back here, one before when profiling or using cmdmods ! scope->se_u.se_while.ws_top_label = current_instr_idx(cctx); // compile "expr" if (compile_expr0(&p, cctx) == FAIL) --- 1131,1172 ---- * compile "while expr" * * Produces instructions: ! * top: EVAL expr Push result of "expr" ! * WHILE funcref-idx end Jump if false ! * ... body ... ! * ENDLOOP funcref-idx off count only if closure uses local var ! * JUMP top Jump back to repeat * end: * */ char_u * compile_while(char_u *arg, cctx_T *cctx) { ! char_u *p = arg; ! scope_T *scope; ! whilescope_T *whilescope; ! lvar_T *funcref_lvar; scope = new_scope(cctx, WHILE_SCOPE); if (scope == NULL) return NULL; + whilescope = &scope->se_u.se_while; // "endwhile" jumps back here, one before when profiling or using cmdmods ! whilescope->ws_top_label = current_instr_idx(cctx); ! ! // Reserve a variable to store ec_funcrefs.ga_len, used in ISN_ENDLOOP. ! // It is not used when no closures are encountered, we don't know yet. ! funcref_lvar = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number); ! if (funcref_lvar == NULL) ! { ! drop_scope(cctx); ! return NULL; // out of memory ! } ! whilescope->ws_funcref_idx = funcref_lvar->lv_idx; ! // remember the number of variables and closures, used in :endwhile ! whilescope->ws_local_count = cctx->ctx_locals.ga_len; ! whilescope->ws_closure_count = cctx->ctx_closure_count; // compile "expr" if (compile_expr0(&p, cctx) == FAIL) *************** *** 1119,1126 **** generate_undo_cmdmods(cctx); // "while_end" is set when ":endwhile" is found ! if (compile_jump_to_end(&scope->se_u.se_while.ws_end_label, ! JUMP_IF_FALSE, cctx) == FAIL) return FAIL; } --- 1187,1194 ---- generate_undo_cmdmods(cctx); // "while_end" is set when ":endwhile" is found ! if (compile_jump_to_end(&whilescope->ws_end_label, ! JUMP_WHILE_FALSE, funcref_lvar->lv_idx, cctx) == FAIL) return FAIL; } *************** *** 1146,1151 **** --- 1214,1229 ---- cctx->ctx_scope = scope->se_outer; if (cctx->ctx_skip != SKIP_YES) { + whilescope_T *whilescope = &scope->se_u.se_while; + + // Handle the case that any local variables were declared that might be + // used in a closure. + if (compile_loop_end(whilescope->ws_local_count, + whilescope->ws_closure_count, + whilescope->ws_funcref_idx, + cctx) == FAIL) + return NULL; + unwind_locals(cctx, scope->se_local_count); #ifdef FEAT_PROFILE *************** *** 1250,1256 **** // Jump to the end of the FOR or WHILE loop. The instruction index will be // filled in later. ! if (compile_jump_to_end(el, JUMP_ALWAYS, cctx) == FAIL) return FAIL; return arg; --- 1328,1334 ---- // Jump to the end of the FOR or WHILE loop. The instruction index will be // filled in later. ! if (compile_jump_to_end(el, JUMP_ALWAYS, 0, cctx) == FAIL) return FAIL; return arg; *************** *** 1397,1403 **** #endif // Jump from end of previous block to :finally or :endtry if (compile_jump_to_end(&scope->se_u.se_try.ts_end_label, ! JUMP_ALWAYS, cctx) == FAIL) return NULL; // End :try or :catch scope: set value in ISN_TRY instruction --- 1475,1481 ---- #endif // Jump from end of previous block to :finally or :endtry if (compile_jump_to_end(&scope->se_u.se_try.ts_end_label, ! JUMP_ALWAYS, 0, cctx) == FAIL) return NULL; // End :try or :catch scope: set value in ISN_TRY instruction *** ../vim-9.0.0469/src/vim9instr.c 2022-09-08 19:51:39.734308338 +0100 --- src/vim9instr.c 2022-09-15 16:51:14.995683716 +0100 *************** *** 1284,1289 **** --- 1284,1310 ---- } /* + * Generate an ISN_WHILE instruction. Similar to ISN_JUMP for :while + */ + int + generate_WHILE(cctx_T *cctx, int funcref_idx) + { + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_WHILE)) == NULL) + return FAIL; + isn->isn_arg.whileloop.while_funcref_idx = funcref_idx; + isn->isn_arg.whileloop.while_end = 0; // filled in later + + if (stack->ga_len > 0) + --stack->ga_len; + + return OK; + } + + /* * Generate an ISN_JUMP_IF_ARG_SET instruction. */ int *************** *** 1312,1317 **** --- 1333,1357 ---- // type doesn't matter, will be stored next return push_type_stack(cctx, &t_any); } + + int + generate_ENDLOOP( + cctx_T *cctx, + int funcref_idx, + int prev_local_count) + { + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_ENDLOOP)) == NULL) + return FAIL; + isn->isn_arg.endloop.end_funcref_idx = funcref_idx; + isn->isn_arg.endloop.end_var_idx = prev_local_count; + isn->isn_arg.endloop.end_var_count = + cctx->ctx_locals.ga_len - prev_local_count; + return OK; + } + /* * Generate an ISN_TRYCONT instruction. */ *************** *** 2295,2300 **** --- 2335,2341 ---- case ISN_ECHOERR: case ISN_ECHOMSG: case ISN_ECHOWINDOW: + case ISN_ENDLOOP: case ISN_ENDTRY: case ISN_EXECCONCAT: case ISN_EXECUTE: *************** *** 2341,2350 **** case ISN_RETURN_VOID: case ISN_SHUFFLE: case ISN_SLICE: case ISN_STORE: case ISN_STOREINDEX: case ISN_STORENR: - case ISN_SOURCE: case ISN_STOREOUTER: case ISN_STORERANGE: case ISN_STOREREG: --- 2382,2391 ---- case ISN_RETURN_VOID: case ISN_SHUFFLE: case ISN_SLICE: + case ISN_SOURCE: case ISN_STORE: case ISN_STOREINDEX: case ISN_STORENR: case ISN_STOREOUTER: case ISN_STORERANGE: case ISN_STOREREG: *************** *** 2357,2362 **** --- 2398,2404 ---- case ISN_UNLETRANGE: case ISN_UNPACK: case ISN_USEDICT: + case ISN_WHILE: // nothing allocated break; } *** ../vim-9.0.0469/src/proto/vim9instr.pro 2022-09-08 19:51:39.734308338 +0100 --- src/proto/vim9instr.pro 2022-09-15 16:51:26.339652123 +0100 *************** *** 43,56 **** int generate_NEWFUNC(cctx_T *cctx, char_u *lambda_name, char_u *func_name); int generate_DEF(cctx_T *cctx, char_u *name, size_t len); int generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where); int generate_JUMP_IF_ARG_SET(cctx_T *cctx, int arg_off); int generate_FOR(cctx_T *cctx, int loop_idx); int generate_TRYCONT(cctx_T *cctx, int levels, int where); int check_internal_func_args(cctx_T *cctx, int func_idx, int argcount, int method_call, type2_T **argtypes, type2_T *shuffled_argtypes); int generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call); int generate_LISTAPPEND(cctx_T *cctx); int generate_BLOBAPPEND(cctx_T *cctx); - int check_args_on_stack(cctx_T *cctx, ufunc_T *ufunc, int argcount); int generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount); int generate_UCALL(cctx_T *cctx, char_u *name, int argcount); int check_func_args_from_type(cctx_T *cctx, type_T *type, int argcount, int at_top, char_u *name); --- 43,57 ---- int generate_NEWFUNC(cctx_T *cctx, char_u *lambda_name, char_u *func_name); int generate_DEF(cctx_T *cctx, char_u *name, size_t len); int generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where); + int generate_WHILE(cctx_T *cctx, int funcref_idx); int generate_JUMP_IF_ARG_SET(cctx_T *cctx, int arg_off); int generate_FOR(cctx_T *cctx, int loop_idx); + int generate_ENDLOOP(cctx_T *cctx, int funcref_idx, int prev_local_count); int generate_TRYCONT(cctx_T *cctx, int levels, int where); int check_internal_func_args(cctx_T *cctx, int func_idx, int argcount, int method_call, type2_T **argtypes, type2_T *shuffled_argtypes); int generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call); int generate_LISTAPPEND(cctx_T *cctx); int generate_BLOBAPPEND(cctx_T *cctx); int generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount); int generate_UCALL(cctx_T *cctx, char_u *name, int argcount); int check_func_args_from_type(cctx_T *cctx, type_T *type, int argcount, int at_top, char_u *name); *** ../vim-9.0.0469/src/vim9compile.c 2022-09-14 00:30:47.077316538 +0100 --- src/vim9compile.c 2022-09-15 16:49:57.859898708 +0100 *************** *** 3449,3456 **** --- 3449,3462 ---- } dfunc->df_varcount = dfunc->df_var_names.ga_len; dfunc->df_has_closure = cctx.ctx_has_closure; + if (cctx.ctx_outer_used) + { ufunc->uf_flags |= FC_CLOSURE; + if (outer_cctx != NULL) + ++outer_cctx->ctx_closure_count; + } + ufunc->uf_def_status = UF_COMPILED; } *** ../vim-9.0.0469/src/vim9execute.c 2022-09-11 11:49:19.098228660 +0100 --- src/vim9execute.c 2022-09-15 17:03:23.905543150 +0100 *************** *** 504,510 **** // - if needed: a counter for number of closures created in // ectx->ec_funcrefs. varcount = dfunc->df_varcount + dfunc->df_has_closure; ! if (GA_GROW_FAILS(&ectx->ec_stack, arg_to_add + STACK_FRAME_SIZE + varcount)) return FAIL; // If depth of calling is getting too high, don't execute the function. --- 504,511 ---- // - if needed: a counter for number of closures created in // ectx->ec_funcrefs. varcount = dfunc->df_varcount + dfunc->df_has_closure; ! if (GA_GROW_FAILS(&ectx->ec_stack, ! arg_to_add + STACK_FRAME_SIZE + varcount)) return FAIL; // If depth of calling is getting too high, don't execute the function. *************** *** 553,558 **** --- 554,561 ---- { typval_T *tv = STACK_TV_BOT(STACK_FRAME_SIZE + dfunc->df_varcount); + // Initialize the variable that counts how many closures were created. + // This is used in handle_closure_in_use(). tv->v_type = VAR_NUMBER; tv->vval.v_number = 0; } *************** *** 1821,1828 **** dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + ectx->ec_dfunc_idx; ! // The closure may need to find arguments and local variables in the ! // current stack. pt->pt_outer.out_stack = &ectx->ec_stack; pt->pt_outer.out_frame_idx = ectx->ec_frame_idx; if (ectx->ec_outer_ref != NULL) --- 1824,1831 ---- dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + ectx->ec_dfunc_idx; ! // The closure may need to find arguments and local variables of the ! // current function in the stack. pt->pt_outer.out_stack = &ectx->ec_stack; pt->pt_outer.out_frame_idx = ectx->ec_frame_idx; if (ectx->ec_outer_ref != NULL) *************** *** 1836,1843 **** } } ! // If this function returns and the closure is still being used, we ! // need to make a copy of the context (arguments and local variables). // Store a reference to the partial so we can handle that. if (GA_GROW_FAILS(&ectx->ec_funcrefs, 1)) { --- 1839,1847 ---- } } ! // If the function currently executing returns and the closure is still ! // being referenced, we need to make a copy of the context (arguments ! // and local variables) so that the closure can use it later. // Store a reference to the partial so we can handle that. if (GA_GROW_FAILS(&ectx->ec_funcrefs, 1)) { *************** *** 2477,2482 **** --- 2481,2487 ---- execute_for(isn_T *iptr, ectx_T *ectx) { typval_T *tv; + int jump = FALSE; typval_T *ltv = STACK_TV_BOT(-1); typval_T *idxtv = STACK_TV_VAR(iptr->isn_arg.forloop.for_idx); *************** *** 2492,2500 **** if (list == NULL || idxtv->vval.v_number >= list->lv_len) { ! // past the end of the list, jump to "endfor" ! ectx->ec_iidx = iptr->isn_arg.forloop.for_end; ! may_restore_cmdmod(&ectx->ec_funclocal); } else if (list->lv_first == &range_list_item) { --- 2497,2503 ---- if (list == NULL || idxtv->vval.v_number >= list->lv_len) { ! jump = TRUE; } else if (list->lv_first == &range_list_item) { *************** *** 2524,2532 **** ++idxtv->vval.v_number; if (str == NULL || str[idxtv->vval.v_number] == NUL) { ! // past the end of the string, jump to "endfor" ! ectx->ec_iidx = iptr->isn_arg.forloop.for_end; ! may_restore_cmdmod(&ectx->ec_funclocal); } else { --- 2527,2533 ---- ++idxtv->vval.v_number; if (str == NULL || str[idxtv->vval.v_number] == NUL) { ! jump = TRUE; } else { *************** *** 2557,2568 **** // The index is for the previous byte. ++idxtv->vval.v_number; ! if (blob == NULL ! || idxtv->vval.v_number >= blob_len(blob)) { ! // past the end of the blob, jump to "endfor" ! ectx->ec_iidx = iptr->isn_arg.forloop.for_end; ! may_restore_cmdmod(&ectx->ec_funclocal); } else { --- 2558,2566 ---- // The index is for the previous byte. ++idxtv->vval.v_number; ! if (blob == NULL || idxtv->vval.v_number >= blob_len(blob)) { ! jump = TRUE; } else { *************** *** 2580,2585 **** --- 2578,2610 ---- vartype_name(ltv->v_type)); return FAIL; } + + if (jump) + { + // past the end of the list/string/blob, jump to "endfor" + ectx->ec_iidx = iptr->isn_arg.forloop.for_end; + may_restore_cmdmod(&ectx->ec_funclocal); + } + else + { + // Store the current number of funcrefs, this may be used in + // ISN_LOOPEND. The variable index is always one more than the loop + // variable index. + tv = STACK_TV_VAR(iptr->isn_arg.forloop.for_idx + 1); + tv->vval.v_number = ectx->ec_funcrefs.ga_len; + } + + return OK; + } + + /* + * End of a for or while loop: Handle any variables used by a closure. + */ + static int + execute_endloop(isn_T *iptr UNUSED, ectx_T *ectx UNUSED) + { + // TODO + return OK; } *************** *** 3989,3994 **** --- 4014,4044 ---- } break; + // "while": jump to end if a condition is false + case ISN_WHILE: + { + int error = FALSE; + int jump = TRUE; + + tv = STACK_TV_BOT(-1); + SOURCING_LNUM = iptr->isn_lnum; + jump = !tv_get_bool_chk(tv, &error); + if (error) + goto on_error; + // drop the value from the stack + clear_tv(tv); + --ectx->ec_stack.ga_len; + if (jump) + ectx->ec_iidx = iptr->isn_arg.whileloop.while_end; + + // Store the current funccal count, may be used by + // ISN_LOOPEND later + tv = STACK_TV_VAR( + iptr->isn_arg.whileloop.while_funcref_idx); + tv->vval.v_number = ectx->ec_funcrefs.ga_len; + } + break; + // Jump if an argument with a default value was already set and not // v:none. case ISN_JUMP_IF_ARG_SET: *************** *** 4005,4010 **** --- 4055,4066 ---- goto theend; break; + // end of a for or while loop + case ISN_ENDLOOP: + if (execute_endloop(iptr, ectx) == FAIL) + goto theend; + break; + // start of ":try" block case ISN_TRY: { *************** *** 6185,6190 **** --- 6241,6249 ---- case JUMP_IF_FALSE: when = "JUMP_IF_FALSE"; break; + case JUMP_WHILE_FALSE: + when = "JUMP_WHILE_FALSE"; // unused + break; case JUMP_IF_COND_FALSE: when = "JUMP_IF_COND_FALSE"; break; *************** *** 6212,6217 **** --- 6271,6297 ---- } break; + case ISN_ENDLOOP: + { + endloop_T *endloop = &iptr->isn_arg.endloop; + + smsg("%s%4d ENDLOOP $%d save $%d - $%d", pfx, current, + endloop->end_funcref_idx, + endloop->end_var_idx, + endloop->end_var_idx + endloop->end_var_count - 1); + } + break; + + case ISN_WHILE: + { + whileloop_T *whileloop = &iptr->isn_arg.whileloop; + + smsg("%s%4d WHILE $%d -> %d", pfx, current, + whileloop->while_funcref_idx, + whileloop->while_end); + } + break; + case ISN_TRY: { try_T *try = &iptr->isn_arg.tryref; *** ../vim-9.0.0469/src/testdir/test_vim9_disassemble.vim 2022-09-03 21:35:50.188158217 +0100 --- src/testdir/test_vim9_disassemble.vim 2022-09-15 15:15:39.515207696 +0100 *************** *** 1466,1482 **** '\d NEWLIST size 0\_s*' .. '\d SETTYPE list\_s*' .. '\d STORE $0\_s*' .. 'for i in range(3)\_s*' .. '\d STORE -1 in $1\_s*' .. '\d PUSHNR 3\_s*' .. '\d BCALL range(argc 1)\_s*' .. '\d FOR $1 -> \d\+\_s*' .. ! '\d STORE $2\_s*' .. 'res->add(i)\_s*' .. '\d LOAD $0\_s*' .. ! '\d LOAD $2\_s*' .. '\d\+ LISTAPPEND\_s*' .. '\d\+ DROP\_s*' .. 'endfor\_s*' .. '\d\+ JUMP -> \d\+\_s*' .. '\d\+ DROP', --- 1466,1485 ---- '\d NEWLIST size 0\_s*' .. '\d SETTYPE list\_s*' .. '\d STORE $0\_s*' .. + 'for i in range(3)\_s*' .. '\d STORE -1 in $1\_s*' .. '\d PUSHNR 3\_s*' .. '\d BCALL range(argc 1)\_s*' .. '\d FOR $1 -> \d\+\_s*' .. ! '\d STORE $3\_s*' .. ! 'res->add(i)\_s*' .. '\d LOAD $0\_s*' .. ! '\d LOAD $3\_s*' .. '\d\+ LISTAPPEND\_s*' .. '\d\+ DROP\_s*' .. + 'endfor\_s*' .. '\d\+ JUMP -> \d\+\_s*' .. '\d\+ DROP', *************** *** 1498,1518 **** 'var res = ""\_s*' .. '\d PUSHS ""\_s*' .. '\d STORE $0\_s*' .. 'for str in eval(''\["one", "two"\]'')\_s*' .. '\d STORE -1 in $1\_s*' .. '\d PUSHS "\["one", "two"\]"\_s*' .. '\d BCALL eval(argc 1)\_s*' .. '\d FOR $1 -> \d\+\_s*' .. ! '\d STORE $2\_s*' .. 'res ..= str\_s*' .. '\d\+ LOAD $0\_s*' .. ! '\d\+ LOAD $2\_s*' .. '\d 2STRING_ANY stack\[-1\]\_s*' .. '\d\+ CONCAT size 2\_s*' .. '\d\+ STORE $0\_s*' .. 'endfor\_s*' .. '\d\+ JUMP -> 5\_s*' .. '\d\+ DROP\_s*' .. 'return res\_s*' .. '\d\+ LOAD $0\_s*' .. '\d\+ RETURN', --- 1501,1525 ---- 'var res = ""\_s*' .. '\d PUSHS ""\_s*' .. '\d STORE $0\_s*' .. + 'for str in eval(''\["one", "two"\]'')\_s*' .. '\d STORE -1 in $1\_s*' .. '\d PUSHS "\["one", "two"\]"\_s*' .. '\d BCALL eval(argc 1)\_s*' .. '\d FOR $1 -> \d\+\_s*' .. ! '\d STORE $3\_s*' .. ! 'res ..= str\_s*' .. '\d\+ LOAD $0\_s*' .. ! '\d\+ LOAD $3\_s*' .. '\d 2STRING_ANY stack\[-1\]\_s*' .. '\d\+ CONCAT size 2\_s*' .. '\d\+ STORE $0\_s*' .. + 'endfor\_s*' .. '\d\+ JUMP -> 5\_s*' .. '\d\+ DROP\_s*' .. + 'return res\_s*' .. '\d\+ LOAD $0\_s*' .. '\d\+ RETURN', *************** *** 1539,1550 **** '\d\+ NEWLIST size 2\_s*' .. '\d\+ FOR $0 -> 16\_s*' .. '\d\+ UNPACK 2\_s*' .. - '\d\+ STORE $1\_s*' .. '\d\+ STORE $2\_s*' .. 'echo x1 x2\_s*' .. - '\d\+ LOAD $1\_s*' .. '\d\+ LOAD $2\_s*' .. '\d\+ ECHO 2\_s*' .. 'endfor\_s*' .. '\d\+ JUMP -> 8\_s*' .. '\d\+ DROP\_s*' .. --- 1546,1559 ---- '\d\+ NEWLIST size 2\_s*' .. '\d\+ FOR $0 -> 16\_s*' .. '\d\+ UNPACK 2\_s*' .. '\d\+ STORE $2\_s*' .. + '\d\+ STORE $3\_s*' .. + 'echo x1 x2\_s*' .. '\d\+ LOAD $2\_s*' .. + '\d\+ LOAD $3\_s*' .. '\d\+ ECHO 2\_s*' .. + 'endfor\_s*' .. '\d\+ JUMP -> 8\_s*' .. '\d\+ DROP\_s*' .. *************** *** 1576,1607 **** '2 PUSHNR 2\_s*' .. '3 NEWLIST size 2\_s*' .. '4 FOR $0 -> 22\_s*' .. ! '5 STORE $1\_s*' .. 'try\_s*' .. '6 TRY catch -> 17, endtry -> 20\_s*' .. 'echo "ok"\_s*' .. '7 PUSHS "ok"\_s*' .. '8 ECHO 1\_s*' .. 'try\_s*' .. '9 TRY catch -> 13, endtry -> 15\_s*' .. 'echo "deeper"\_s*' .. '10 PUSHS "deeper"\_s*' .. '11 ECHO 1\_s*' .. 'catch\_s*' .. '12 JUMP -> 15\_s*' .. '13 CATCH\_s*' .. 'continue\_s*' .. '14 TRY-CONTINUE 2 levels -> 4\_s*' .. 'endtry\_s*' .. '15 ENDTRY\_s*' .. 'catch\_s*' .. '16 JUMP -> 20\_s*' .. '17 CATCH\_s*' .. 'echo "not ok"\_s*' .. '18 PUSHS "not ok"\_s*' .. '19 ECHO 1\_s*' .. 'endtry\_s*' .. '20 ENDTRY\_s*' .. 'endfor\_s*' .. '21 JUMP -> 4\_s*' .. '\d\+ DROP\_s*' .. --- 1585,1627 ---- '2 PUSHNR 2\_s*' .. '3 NEWLIST size 2\_s*' .. '4 FOR $0 -> 22\_s*' .. ! '5 STORE $2\_s*' .. ! 'try\_s*' .. '6 TRY catch -> 17, endtry -> 20\_s*' .. + 'echo "ok"\_s*' .. '7 PUSHS "ok"\_s*' .. '8 ECHO 1\_s*' .. + 'try\_s*' .. '9 TRY catch -> 13, endtry -> 15\_s*' .. + 'echo "deeper"\_s*' .. '10 PUSHS "deeper"\_s*' .. '11 ECHO 1\_s*' .. + 'catch\_s*' .. '12 JUMP -> 15\_s*' .. '13 CATCH\_s*' .. + 'continue\_s*' .. '14 TRY-CONTINUE 2 levels -> 4\_s*' .. + 'endtry\_s*' .. '15 ENDTRY\_s*' .. + 'catch\_s*' .. '16 JUMP -> 20\_s*' .. '17 CATCH\_s*' .. + 'echo "not ok"\_s*' .. '18 PUSHS "not ok"\_s*' .. '19 ECHO 1\_s*' .. + 'endtry\_s*' .. '20 ENDTRY\_s*' .. + 'endfor\_s*' .. '21 JUMP -> 4\_s*' .. '\d\+ DROP\_s*' .. *************** *** 2478,2484 **** '\d NEWLIST size 1\_s*' .. '\d CMDMOD_REV\_s*' .. '5 FOR $0 -> 8\_s*' .. ! '\d STORE $1\_s*' .. 'endfor\_s*' .. '\d JUMP -> 5\_s*' .. '8 DROP\_s*' .. --- 2498,2505 ---- '\d NEWLIST size 1\_s*' .. '\d CMDMOD_REV\_s*' .. '5 FOR $0 -> 8\_s*' .. ! '\d STORE $2\_s*' .. ! 'endfor\_s*' .. '\d JUMP -> 5\_s*' .. '8 DROP\_s*' .. *************** *** 2499,2505 **** '\d LOADG g:not\_s*' .. '\d COND2BOOL\_s*' .. '\d CMDMOD_REV\_s*' .. ! '\d JUMP_IF_FALSE -> 6\_s*' .. 'endwhile\_s*' .. '\d JUMP -> 0\_s*' .. --- 2520,2526 ---- '\d LOADG g:not\_s*' .. '\d COND2BOOL\_s*' .. '\d CMDMOD_REV\_s*' .. ! '\d WHILE $0 -> 6\_s*' .. 'endwhile\_s*' .. '\d JUMP -> 0\_s*' .. *************** *** 2691,2707 **** '4 STORE -1 in $0\_s*' .. '5 PUSHNR 0\_s*' .. '6 NEWLIST size 1\_s*' .. ! '7 DEBUG line 2-2 varcount 2\_s*' .. '8 FOR $0 -> 15\_s*' .. ! '9 STORE $1\_s*' .. 'echo a\_s*' .. ! '10 DEBUG line 3-3 varcount 2\_s*' .. ! '11 LOAD $1\_s*' .. '12 ECHO 1\_s*' .. 'endfor\_s*' .. ! '13 DEBUG line 4-4 varcount 2\_s*' .. '14 JUMP -> 7\_s*' .. '15 DROP\_s*' .. '16 RETURN void*', --- 2712,2728 ---- '4 STORE -1 in $0\_s*' .. '5 PUSHNR 0\_s*' .. '6 NEWLIST size 1\_s*' .. ! '7 DEBUG line 2-2 varcount 3\_s*' .. '8 FOR $0 -> 15\_s*' .. ! '9 STORE $2\_s*' .. 'echo a\_s*' .. ! '10 DEBUG line 3-3 varcount 3\_s*' .. ! '11 LOAD $2\_s*' .. '12 ECHO 1\_s*' .. 'endfor\_s*' .. ! '13 DEBUG line 4-4 varcount 3\_s*' .. '14 JUMP -> 7\_s*' .. '15 DROP\_s*' .. '16 RETURN void*', *** ../vim-9.0.0469/src/version.c 2022-09-15 12:43:20.476321981 +0100 --- src/version.c 2022-09-15 16:52:28.819478213 +0100 *************** *** 705,706 **** --- 705,708 ---- { /* Add new patch number below this line */ + /**/ + 470, /**/ -- hundred-and-one symptoms of being an internet addict: 84. Books in your bookcase bear the names Bongo, WinSock and Inside OLE /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///