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('\<SNR>\d*_FuncOne.*' ..
+ 'return FuncTwo().*' ..
+ '\d DCALL <SNR>\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 -- [email protected] -- 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 ///
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/vim_dev/202005092050.049KobIH020330%40masaka.moolenaar.net.