To: vim_dev@googlegroups.com Subject: Patch 8.2.0725 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.0725 Problem: Vim9: cannot call a function declared later in Vim9 script. Solution: Make two passes through the script file. Files: src/scriptfile.c, src/proto/scriptfile.pro, src/vim9script.c, src/vim9compile.c, src/vim9execute.c, src/proto/vim9compile.pro, src/userfunc.c, src/proto/userfunc.pro, src/evalvars.c, src/proto/evalvars.pro, src/vim.h, src/testdir/test_vim9_disassemble.vim *** ../vim-8.2.0724/src/scriptfile.c 2020-05-03 17:01:19.842024266 +0200 --- src/scriptfile.c 2020-05-09 21:52:21.128164355 +0200 *************** *** 998,1003 **** --- 998,1005 ---- int error; // TRUE if LF found after CR-LF #endif #ifdef FEAT_EVAL + garray_T lines_ga; // lines read in previous pass + int use_lines_ga; // next line to get from "lines_ga" linenr_T breakpoint; // next line with breakpoint or zero char_u *fname; // name of sourced file int dbg_tick; // debug_tick when breakpoint was set *************** *** 1017,1022 **** --- 1019,1042 ---- } /* + * Get the grow array to store script lines in. + */ + garray_T * + source_get_line_ga(void *cookie) + { + return &((struct source_cookie *)cookie)->lines_ga; + } + + /* + * Set the index to start reading from the grow array with script lines. + */ + void + source_use_line_ga(void *cookie) + { + ((struct source_cookie *)cookie)->use_lines_ga = 0; + } + + /* * Return the address holding the debug tick for a source cookie. */ int * *************** *** 1235,1240 **** --- 1255,1263 ---- cookie.finished = FALSE; #ifdef FEAT_EVAL + ga_init2(&cookie.lines_ga, sizeof(char_u *), 200); + cookie.use_lines_ga = -1; + // Check if this script has a breakpoint. cookie.breakpoint = dbg_find_breakpoint(TRUE, fname_exp, (linenr_T)0); cookie.fname = fname_exp; *************** *** 1447,1452 **** --- 1470,1478 ---- vim_free(cookie.nextline); vim_free(firstline); convert_setup(&cookie.conv, NULL, NULL); + #ifdef FEAT_EVAL + ga_clear_strings(&cookie.lines_ga); + #endif if (trigger_source_post) apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, FALSE, curbuf); *************** *** 1702,1707 **** --- 1728,1758 ---- // one now. if (sp->finished) line = NULL; + #ifdef FEAT_EVAL + else if (sp->use_lines_ga >= 0) + { + // Get a line that was read in ex_vim9script(). + for (;;) + { + if (sp->use_lines_ga >= sp->lines_ga.ga_len) + { + line = NULL; + break; + } + else + { + line = ((char_u **)(sp->lines_ga.ga_data))[sp->use_lines_ga]; + ((char_u **)(sp->lines_ga.ga_data))[sp->use_lines_ga] = NULL; + ++sp->use_lines_ga; + if (line != NULL) + break; + // Skip NULL lines, they are equivalent to blank lines. + ++sp->sourcing_lnum; + } + } + SOURCING_LNUM = sp->sourcing_lnum + 1; + } + #endif else if (sp->nextline == NULL) line = get_one_sourceline(sp); else *** ../vim-8.2.0724/src/proto/scriptfile.pro 2020-01-26 15:52:33.023833239 +0100 --- src/proto/scriptfile.pro 2020-05-09 19:28:49.405736342 +0200 *************** *** 19,24 **** --- 19,26 ---- void ex_source(exarg_T *eap); void ex_options(exarg_T *eap); linenr_T *source_breakpoint(void *cookie); + garray_T *source_get_line_ga(void *cookie); + void source_use_line_ga(void *cookie); int *source_dbg_tick(void *cookie); int source_level(void *cookie); int do_source(char_u *fname, int check_other, int is_vimrc, int *ret_sid); *** ../vim-8.2.0724/src/vim9script.c 2020-04-27 22:47:45.186176148 +0200 --- src/vim9script.c 2020-05-09 22:48:51.139530983 +0200 *************** *** 32,38 **** void ex_vim9script(exarg_T *eap) { ! scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); if (!getline_equal(eap->getline, eap->cookie, getsourceline)) { --- 32,42 ---- void ex_vim9script(exarg_T *eap) { ! scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); ! garray_T *gap; ! garray_T func_ga; ! int idx; ! ufunc_T *ufunc; if (!getline_equal(eap->getline, eap->cookie, getsourceline)) { *************** *** 47,58 **** --- 51,147 ---- current_sctx.sc_version = SCRIPT_VERSION_VIM9; si->sn_version = SCRIPT_VERSION_VIM9; si->sn_had_command = TRUE; + ga_init2(&func_ga, sizeof(ufunc_T *), 20); if (STRCMP(p_cpo, CPO_VIM) != 0) { si->sn_save_cpo = p_cpo; p_cpo = vim_strsave((char_u *)CPO_VIM); } + + // Make a pass through the script to find: + // - function declarations + // - variable and constant declarations + // - imports + // The types are recognized, so that they can be used when compiling a + // function. + gap = source_get_line_ga(eap->cookie); + for (;;) + { + char_u *line; + char_u *p; + + if (ga_grow(gap, 1) == FAIL) + return; + line = eap->getline(':', eap->cookie, 0, TRUE); + if (line == NULL) + break; + ((char_u **)(gap->ga_data))[gap->ga_len++] = line; + line = skipwhite(line); + p = line; + if (checkforcmd(&p, "function", 2) || checkforcmd(&p, "def", 3)) + { + int lnum_start = SOURCING_LNUM - 1; + + // Handle :function and :def by calling def_function(). + // It will read upto the matching :endded or :endfunction. + eap->cmdidx = *line == 'f' ? CMD_function : CMD_def; + eap->cmd = line; + eap->arg = p; + eap->forceit = FALSE; + ufunc = def_function(eap, NULL, NULL, FALSE); + + if (ufunc != NULL && *line == 'd' && ga_grow(&func_ga, 1) == OK) + { + // Add the function to the list of :def functions, so that it + // can be referenced by index. It's compiled below. + add_def_function(ufunc); + ((ufunc_T **)(func_ga.ga_data))[func_ga.ga_len++] = ufunc; + } + + // Store empty lines in place of the function, we don't need to + // process it again. + vim_free(((char_u **)(gap->ga_data))[--gap->ga_len]); + if (ga_grow(gap, SOURCING_LNUM - lnum_start) == OK) + while (lnum_start < SOURCING_LNUM) + { + // getsourceline() will skip over NULL lines. + ((char_u **)(gap->ga_data))[gap->ga_len++] = NULL; + ++lnum_start; + } + } + else if (checkforcmd(&p, "let", 3) || checkforcmd(&p, "const", 4)) + { + eap->cmd = line; + eap->arg = p; + eap->forceit = FALSE; + eap->cmdidx = *line == 'l' ? CMD_let: CMD_const; + + // The command will be executed again, it's OK to redefine the + // variable then. + ex_let_const(eap, TRUE); + } + else if (checkforcmd(&p, "import", 3)) + { + eap->arg = p; + ex_import(eap); + + // Store empty line, we don't need to process the command again. + vim_free(((char_u **)(gap->ga_data))[--gap->ga_len]); + ((char_u **)(gap->ga_data))[gap->ga_len++] = NULL; + } + } + + // Compile the :def functions. + for (idx = 0; idx < func_ga.ga_len; ++idx) + { + ufunc = ((ufunc_T **)(func_ga.ga_data))[idx]; + compile_def_function(ufunc, FALSE, NULL); + } + ga_clear(&func_ga); + + // Return to process the commands at the script level. + source_use_line_ga(eap->cookie); } /* *************** *** 64,70 **** * ":export {Name, ...}" */ void ! ex_export(exarg_T *eap UNUSED) { if (current_sctx.sc_version != SCRIPT_VERSION_VIM9) { --- 153,159 ---- * ":export {Name, ...}" */ void ! ex_export(exarg_T *eap) { if (current_sctx.sc_version != SCRIPT_VERSION_VIM9) { *** ../vim-8.2.0724/src/vim9compile.c 2020-05-09 18:28:30.397618068 +0200 --- src/vim9compile.c 2020-05-09 21:45:12.681643443 +0200 *************** *** 4441,4447 **** eap->cookie = cctx; eap->skip = cctx->ctx_skip == TRUE; eap->forceit = FALSE; ! ufunc = def_function(eap, name, cctx); if (ufunc == NULL || ufunc->uf_dfunc_idx < 0) return NULL; --- 4441,4447 ---- eap->cookie = cctx; eap->skip = cctx->ctx_skip == TRUE; eap->forceit = FALSE; ! ufunc = def_function(eap, name, cctx, TRUE); if (ufunc == NULL || ufunc->uf_dfunc_idx < 0) return NULL; *************** *** 6131,6136 **** --- 6131,6157 ---- } /* + * Add a function to the list of :def functions. + * This "sets ufunc->uf_dfunc_idx" but the function isn't compiled yet. + */ + int + add_def_function(ufunc_T *ufunc) + { + dfunc_T *dfunc; + + // Add the function to "def_functions". + if (ga_grow(&def_functions, 1) == FAIL) + return FAIL; + dfunc = ((dfunc_T *)def_functions.ga_data) + def_functions.ga_len; + CLEAR_POINTER(dfunc); + dfunc->df_idx = def_functions.ga_len; + ufunc->uf_dfunc_idx = dfunc->df_idx; + dfunc->df_ufunc = ufunc; + ++def_functions.ga_len; + return OK; + } + + /* * After ex_function() has collected all the function lines: parse and compile * the lines into instructions. * Adds the function to "def_functions". *************** *** 6154,6183 **** sctx_T save_current_sctx = current_sctx; int emsg_before = called_emsg; { ! dfunc_T *dfunc; // may be invalidated by compile_lambda() ! ! if (ufunc->uf_dfunc_idx >= 0) ! { ! // Redefining a function that was compiled before. ! dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx; ! ! // Free old instructions. ! delete_def_function_contents(dfunc); ! } ! else ! { ! // Add the function to "def_functions". ! if (ga_grow(&def_functions, 1) == FAIL) ! return; ! dfunc = ((dfunc_T *)def_functions.ga_data) + def_functions.ga_len; ! CLEAR_POINTER(dfunc); ! dfunc->df_idx = def_functions.ga_len; ! ufunc->uf_dfunc_idx = dfunc->df_idx; ! dfunc->df_ufunc = ufunc; ! ++def_functions.ga_len; ! } } CLEAR_FIELD(cctx); cctx.ctx_ufunc = ufunc; --- 6175,6190 ---- sctx_T save_current_sctx = current_sctx; int emsg_before = called_emsg; + if (ufunc->uf_dfunc_idx >= 0) { ! // Redefining a function that was compiled before. ! dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) ! + ufunc->uf_dfunc_idx; ! // Free old instructions. ! delete_def_function_contents(dfunc); } + else if (add_def_function(ufunc) == FAIL) + return; CLEAR_FIELD(cctx); cctx.ctx_ufunc = ufunc; *** ../vim-8.2.0724/src/vim9execute.c 2020-05-07 14:07:19.948220408 +0200 --- src/vim9execute.c 2020-05-09 22:20:48.377777547 +0200 *************** *** 669,674 **** --- 669,681 ---- ga_init2(&ectx.ec_stack, sizeof(typval_T), 500); if (ga_grow(&ectx.ec_stack, 20) == FAIL) return FAIL; + { + // Check the function was compiled, it is postponed in ex_vim9script(). + dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + + ufunc->uf_dfunc_idx; + if (dfunc->df_instr == NULL) + return FAIL; + } ectx.ec_dfunc_idx = ufunc->uf_dfunc_idx; ga_init2(&ectx.ec_trystack, sizeof(trycmd_T), 10); *** ../vim-8.2.0724/src/proto/vim9compile.pro 2020-05-01 19:29:05.002157723 +0200 --- src/proto/vim9compile.pro 2020-05-09 21:44:59.909687746 +0200 *************** *** 9,14 **** --- 9,15 ---- char_u *to_name_const_end(char_u *arg); int assignment_len(char_u *p, int *heredoc); int check_vim9_unlet(char_u *name); + int add_def_function(ufunc_T *ufunc); void compile_def_function(ufunc_T *ufunc, int set_return_type, cctx_T *outer_cctx); void delete_instr(isn_T *isn); void delete_def_function(ufunc_T *ufunc); *** ../vim-8.2.0724/src/userfunc.c 2020-05-04 23:24:41.118072992 +0200 --- src/userfunc.c 2020-05-09 21:33:27.048081467 +0200 *************** *** 2378,2384 **** * Returns a pointer to the function or NULL if no function defined. */ ufunc_T * ! def_function(exarg_T *eap, char_u *name_arg, void *context) { char_u *theline; char_u *line_to_free = NULL; --- 2378,2384 ---- * Returns a pointer to the function or NULL if no function defined. */ ufunc_T * ! def_function(exarg_T *eap, char_u *name_arg, void *context, int compile) { char_u *theline; char_u *line_to_free = NULL; *************** *** 3241,3246 **** --- 3241,3247 ---- p = ret_type; fp->uf_ret_type = parse_type(&p, &fp->uf_type_list); } + SOURCING_LNUM = lnum_save; } fp->uf_lines = newlines; *************** *** 3273,3280 **** is_export = FALSE; } ! // ":def Func()" needs to be compiled ! if (eap->cmdidx == CMD_def) compile_def_function(fp, FALSE, context); goto ret_free; --- 3274,3281 ---- is_export = FALSE; } ! // ":def Func()" may need to be compiled ! if (eap->cmdidx == CMD_def && compile) compile_def_function(fp, FALSE, context); goto ret_free; *************** *** 3304,3310 **** void ex_function(exarg_T *eap) { ! def_function(eap, NULL, NULL); } /* --- 3305,3311 ---- void ex_function(exarg_T *eap) { ! (void)def_function(eap, NULL, NULL, TRUE); } /* *** ../vim-8.2.0724/src/proto/userfunc.pro 2020-05-04 23:24:41.118072992 +0200 --- src/proto/userfunc.pro 2020-05-09 20:01:57.834140801 +0200 *************** *** 23,29 **** int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe); char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial); char_u *untrans_function_name(char_u *name); ! ufunc_T *def_function(exarg_T *eap, char_u *name_arg, void *context); void ex_function(exarg_T *eap); int eval_fname_script(char_u *p); int translated_function_exists(char_u *name, int is_global); --- 23,29 ---- int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe); char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial); char_u *untrans_function_name(char_u *name); ! ufunc_T *def_function(exarg_T *eap, char_u *name_arg, void *context, int compile); void ex_function(exarg_T *eap); int eval_fname_script(char_u *p); int translated_function_exists(char_u *name, int is_global); *** ../vim-8.2.0724/src/evalvars.c 2020-05-09 13:06:20.224712254 +0200 --- src/evalvars.c 2020-05-09 22:32:32.855017381 +0200 *************** *** 164,170 **** // for VIM_VERSION_ defines #include "version.h" - static void ex_let_const(exarg_T *eap, int is_const); static char_u *skip_var_one(char_u *arg, int include_type); static void list_glob_vars(int *first); static void list_buf_vars(int *first); --- 164,169 ---- *************** *** 698,708 **** void ex_const(exarg_T *eap) { ! ex_let_const(eap, TRUE); } ! static void ! ex_let_const(exarg_T *eap, int is_const) { char_u *arg = eap->arg; char_u *expr = NULL; --- 697,711 ---- void ex_const(exarg_T *eap) { ! ex_let_const(eap, FALSE); } ! /* ! * When "redefine" is TRUE the command will be executed again, redefining the ! * variable is OK then. ! */ ! void ! ex_let_const(exarg_T *eap, int redefine) { char_u *arg = eap->arg; char_u *expr = NULL; *************** *** 714,724 **** char_u *argend; int first = TRUE; int concat; ! int flags = is_const ? LET_IS_CONST : 0; // detect Vim9 assignment without ":let" or ":const" if (eap->arg == eap->cmd) flags |= LET_NO_COMMAND; argend = skip_var_list(arg, TRUE, &var_count, &semicolon); if (argend == NULL) --- 717,729 ---- char_u *argend; int first = TRUE; int concat; ! int flags = eap->cmdidx == CMD_const ? LET_IS_CONST : 0; // detect Vim9 assignment without ":let" or ":const" if (eap->arg == eap->cmd) flags |= LET_NO_COMMAND; + if (redefine) + flags |= LET_REDEFINE; argend = skip_var_list(arg, TRUE, &var_count, &semicolon); if (argend == NULL) *************** *** 2976,2981 **** --- 2981,2988 ---- if (flags & LET_IS_CONST) di->di_tv.v_lock |= VAR_LOCKED; + if (flags & LET_REDEFINE) + di->di_flags |= DI_FLAGS_RELOAD; } /* *** ../vim-8.2.0724/src/proto/evalvars.pro 2020-05-01 15:44:24.539895251 +0200 --- src/proto/evalvars.pro 2020-05-09 22:32:36.947002041 +0200 *************** *** 16,21 **** --- 16,22 ---- list_T *heredoc_get(exarg_T *eap, char_u *cmd, int script_get); void ex_let(exarg_T *eap); void ex_const(exarg_T *eap); + void ex_let_const(exarg_T *eap, int redefine); int ex_let_vars(char_u *arg_start, typval_T *tv, int copy, int semicolon, int var_count, int flags, char_u *op); char_u *skip_var_list(char_u *arg, int include_type, int *var_count, int *semicolon); void list_hashtable_vars(hashtab_T *ht, char *prefix, int empty, int *first); *** ../vim-8.2.0724/src/vim.h 2020-04-12 19:37:13.506297291 +0200 --- src/vim.h 2020-05-09 22:31:12.319320325 +0200 *************** *** 2133,2138 **** --- 2133,2139 ---- // Flags for assignment functions. #define LET_IS_CONST 1 // ":const" #define LET_NO_COMMAND 2 // "var = expr" without ":let" or ":const" + #define LET_REDEFINE 4 // variable can be redefined later #include "ex_cmds.h" // Ex command defines #include "spell.h" // spell checking stuff *** ../vim-8.2.0724/src/testdir/test_vim9_disassemble.vim 2020-05-09 18:28:30.401618052 +0200 --- src/testdir/test_vim9_disassemble.vim 2020-05-09 22:00:25.318501912 +0200 *************** *** 1045,1050 **** --- 1045,1075 ---- res3) enddef + def Test_vim9script_forward_func() + let lines =<< trim END + vim9script + def FuncOne(): string + return FuncTwo() + enddef + def FuncTwo(): string + return 'two' + enddef + let g:res_FuncOne = execute('disass FuncOne') + END + writefile(lines, 'Xdisassemble') + source Xdisassemble + + " check that the first function calls the second with DCALL + assert_match('\\d*_FuncOne.*' .. + 'return FuncTwo().*' .. + '\d DCALL \d\+_FuncTwo(argc 0).*' .. + '\d RETURN', + g:res_FuncOne) + + delete('Xdisassemble') + unlet g:res_FuncOne + enddef + def s:ConcatStrings(): string return 'one' .. 'two' .. 'three' enddef *** ../vim-8.2.0724/src/version.c 2020-05-09 18:44:52.637841293 +0200 --- src/version.c 2020-05-09 21:53:04.104016551 +0200 *************** *** 748,749 **** --- 748,751 ---- { /* Add new patch number below this line */ + /**/ + 725, /**/ -- hundred-and-one symptoms of being an internet addict: 83. Batteries in the TV remote now last for months. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///