summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2020-01-31 20:10:50 +0100
committerBram Moolenaar <Bram@vim.org>2020-01-31 20:10:50 +0100
commita259d8d30bc289764925fc42db1dbe774f0bb3f8 (patch)
treeff0e14025b46941dc7bac65ab1f93420e1b14083
parent92be6e3f46120bb8e6c8fca0a7868a08df8b3345 (diff)
downloadvim-git-a259d8d30bc289764925fc42db1dbe774f0bb3f8.tar.gz
patch 8.2.0185: Vim9 script: cannot use "if has()" to skip linesv8.2.0185
Problem: Vim9 script: cannot use "if has()" to skip lines. Solution: Evaluate constant expression at runtime.
-rw-r--r--src/evalfunc.c3
-rw-r--r--src/proto/evalfunc.pro1
-rw-r--r--src/testdir/test_vim9_script.vim30
-rw-r--r--src/userfunc.c10
-rw-r--r--src/version.c2
-rw-r--r--src/vim9compile.c299
6 files changed, 311 insertions, 34 deletions
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 15b799088..a79cc8932 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -100,7 +100,6 @@ static void f_getpos(typval_T *argvars, typval_T *rettv);
static void f_getreg(typval_T *argvars, typval_T *rettv);
static void f_getregtype(typval_T *argvars, typval_T *rettv);
static void f_gettagstack(typval_T *argvars, typval_T *rettv);
-static void f_has(typval_T *argvars, typval_T *rettv);
static void f_haslocaldir(typval_T *argvars, typval_T *rettv);
static void f_hasmapto(typval_T *argvars, typval_T *rettv);
static void f_hlID(typval_T *argvars, typval_T *rettv);
@@ -3261,7 +3260,7 @@ f_gettagstack(typval_T *argvars, typval_T *rettv)
/*
* "has()" function
*/
- static void
+ void
f_has(typval_T *argvars, typval_T *rettv)
{
int i;
diff --git a/src/proto/evalfunc.pro b/src/proto/evalfunc.pro
index 59ff35c69..14a1a56ac 100644
--- a/src/proto/evalfunc.pro
+++ b/src/proto/evalfunc.pro
@@ -17,6 +17,7 @@ buf_T *get_buf_arg(typval_T *arg);
win_T *get_optional_window(typval_T *argvars, int idx);
void execute_redir_str(char_u *value, int value_len);
void execute_common(typval_T *argvars, typval_T *rettv, int arg_off);
+void f_has(typval_T *argvars, typval_T *rettv);
void mzscheme_call_vim(char_u *name, typval_T *args, typval_T *rettv);
void range_list_materialize(list_T *list);
float_T vim_round(float_T f);
diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim
index 09bf3247a..749c264d4 100644
--- a/src/testdir/test_vim9_script.vim
+++ b/src/testdir/test_vim9_script.vim
@@ -368,5 +368,35 @@ def do_something():
EOF
endfunc
+def HasEval()
+ if has('eval')
+ echo 'yes'
+ else
+ echo 'no'
+ endif
+enddef
+
+def HasNothing()
+ if has('nothing')
+ echo 'yes'
+ else
+ echo 'no'
+ endif
+enddef
+
+def Test_compile_const_expr()
+ assert_equal("\nyes", execute('call HasEval()'))
+ let instr = execute('disassemble HasEval')
+ call assert_match('PUSHS "yes"', instr)
+ call assert_notmatch('PUSHS "no"', instr)
+ call assert_notmatch('JUMP', instr)
+
+ assert_equal("\nno", execute('call HasNothing()'))
+ instr = execute('disassemble HasNothing')
+ call assert_notmatch('PUSHS "yes"', instr)
+ call assert_match('PUSHS "no"', instr)
+ call assert_notmatch('JUMP', instr)
+enddef
+
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/userfunc.c b/src/userfunc.c
index 474812c06..bdd838730 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -2691,9 +2691,10 @@ ex_function(exarg_T *eap)
}
}
- // Check for ":append", ":change", ":insert".
+ // Check for ":append", ":change", ":insert". Not for :def.
p = skip_range(p, NULL);
- if ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p'))
+ if (eap->cmdidx != CMD_def
+ && ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p'))
|| (p[0] == 'c'
&& (!ASCII_ISALPHA(p[1]) || (p[1] == 'h'
&& (!ASCII_ISALPHA(p[2]) || (p[2] == 'a'
@@ -2701,7 +2702,10 @@ ex_function(exarg_T *eap)
|| !ASCII_ISALPHA(p[6])))))))
|| (p[0] == 'i'
&& (!ASCII_ISALPHA(p[1]) || (p[1] == 'n'
- && (!ASCII_ISALPHA(p[2]) || (p[2] == 's'))))))
+ && (!ASCII_ISALPHA(p[2])
+ || (p[2] == 's'
+ && (!ASCII_ISALPHA(p[3])
+ || p[3] == 'e'))))))))
skip_until = vim_strsave((char_u *)".");
// Check for ":python <<EOF", ":tcl <<EOF", etc.
diff --git a/src/version.c b/src/version.c
index 0c30f2568..e7a6f6fab 100644
--- a/src/version.c
+++ b/src/version.c
@@ -743,6 +743,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 185,
+/**/
184,
/**/
183,
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 772355a92..69a2d1b14 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -115,6 +115,8 @@ struct cctx_S {
garray_T ctx_imports; // imported items
+ int ctx_skip; // when TRUE skip commands, when FALSE skip
+ // commands after "else"
scope_T *ctx_scope; // current scope, NULL at toplevel
garray_T ctx_type_stack; // type of each item on the stack
@@ -3459,6 +3461,185 @@ new_scope(cctx_T *cctx, scopetype_T type)
}
/*
+ * Evaluate an expression that is a constant: has(arg)
+ * Return FAIL if the expression is not a constant.
+ */
+ static int
+evaluate_const_expr4(char_u **arg, cctx_T *cctx UNUSED, typval_T *tv)
+{
+ typval_T argvars[2];
+
+ if (STRNCMP("has(", *arg, 4) != 0)
+ return FAIL;
+ *arg = skipwhite(*arg + 4);
+
+ if (**arg == '"')
+ {
+ if (get_string_tv(arg, tv, TRUE) == FAIL)
+ return FAIL;
+ }
+ else if (**arg == '\'')
+ {
+ if (get_lit_string_tv(arg, tv, TRUE) == FAIL)
+ return FAIL;
+ }
+ else
+ return FAIL;
+
+ *arg = skipwhite(*arg);
+ if (**arg != ')')
+ return FAIL;
+ *arg = skipwhite(*arg + 1);
+
+ argvars[0] = *tv;
+ argvars[1].v_type = VAR_UNKNOWN;
+ tv->v_type = VAR_NUMBER;
+ tv->vval.v_number = 0;
+ f_has(argvars, tv);
+ clear_tv(&argvars[0]);
+
+ return OK;
+}
+
+static int evaluate_const_expr3(char_u **arg, cctx_T *cctx, typval_T *tv);
+
+/*
+ * Compile constant || or &&.
+ */
+ static int
+evaluate_const_and_or(char_u **arg, cctx_T *cctx, char *op, typval_T *tv)
+{
+ char_u *p = skipwhite(*arg);
+ int opchar = *op;
+
+ if (p[0] == opchar && p[1] == opchar)
+ {
+ int val = tv2bool(tv);
+
+ /*
+ * Repeat until there is no following "||" or "&&"
+ */
+ while (p[0] == opchar && p[1] == opchar)
+ {
+ typval_T tv2;
+
+ if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[2]))
+ return FAIL;
+
+ // eval the next expression
+ *arg = skipwhite(p + 2);
+ tv2.v_type = VAR_UNKNOWN;
+ if ((opchar == '|' ? evaluate_const_expr3(arg, cctx, &tv2)
+ : evaluate_const_expr4(arg, cctx, &tv2)) == FAIL)
+ {
+ clear_tv(&tv2);
+ return FAIL;
+ }
+ if ((opchar == '&') == val)
+ {
+ // false || tv2 or true && tv2: use tv2
+ clear_tv(tv);
+ *tv = tv2;
+ val = tv2bool(tv);
+ }
+ else
+ clear_tv(&tv2);
+ p = skipwhite(*arg);
+ }
+ }
+
+ return OK;
+}
+
+/*
+ * Evaluate an expression that is a constant: expr4 && expr4 && expr4
+ * Return FAIL if the expression is not a constant.
+ */
+ static int
+evaluate_const_expr3(char_u **arg, cctx_T *cctx, typval_T *tv)
+{
+ // evaluate the first expression
+ if (evaluate_const_expr4(arg, cctx, tv) == FAIL)
+ return FAIL;
+
+ // || and && work almost the same
+ return evaluate_const_and_or(arg, cctx, "&&", tv);
+}
+
+/*
+ * Evaluate an expression that is a constant: expr3 || expr3 || expr3
+ * Return FAIL if the expression is not a constant.
+ */
+ static int
+evaluate_const_expr2(char_u **arg, cctx_T *cctx, typval_T *tv)
+{
+ // evaluate the first expression
+ if (evaluate_const_expr3(arg, cctx, tv) == FAIL)
+ return FAIL;
+
+ // || and && work almost the same
+ return evaluate_const_and_or(arg, cctx, "||", tv);
+}
+
+/*
+ * Evaluate an expression that is a constant: expr2 ? expr1 : expr1
+ * E.g. for "has('feature')".
+ * This does not produce error messages. "tv" should be cleared afterwards.
+ * Return FAIL if the expression is not a constant.
+ */
+ static int
+evaluate_const_expr1(char_u **arg, cctx_T *cctx, typval_T *tv)
+{
+ char_u *p;
+
+ // evaluate the first expression
+ if (evaluate_const_expr2(arg, cctx, tv) == FAIL)
+ return FAIL;
+
+ p = skipwhite(*arg);
+ if (*p == '?')
+ {
+ int val = tv2bool(tv);
+ typval_T tv2;
+
+ if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1]))
+ return FAIL;
+
+ // evaluate the second expression; any type is accepted
+ clear_tv(tv);
+ *arg = skipwhite(p + 1);
+ if (evaluate_const_expr1(arg, cctx, tv) == FAIL)
+ return FAIL;
+
+ // Check for the ":".
+ p = skipwhite(*arg);
+ if (*p != ':' || !VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1]))
+ return FAIL;
+
+ // evaluate the third expression
+ *arg = skipwhite(p + 1);
+ tv2.v_type = VAR_UNKNOWN;
+ if (evaluate_const_expr1(arg, cctx, &tv2) == FAIL)
+ {
+ clear_tv(&tv2);
+ return FAIL;
+ }
+ if (val)
+ {
+ // use the expr after "?"
+ clear_tv(&tv2);
+ }
+ else
+ {
+ // use the expr after ":"
+ clear_tv(tv);
+ *tv = tv2;
+ }
+ }
+ return OK;
+}
+
+/*
* compile "if expr"
*
* "if expr" Produces instructions:
@@ -3496,18 +3677,34 @@ compile_if(char_u *arg, cctx_T *cctx)
char_u *p = arg;
garray_T *instr = &cctx->ctx_instr;
scope_T *scope;
+ typval_T tv;
- // compile "expr"
- if (compile_expr1(&p, cctx) == FAIL)
- return NULL;
+ // compile "expr"; if we know it evaluates to FALSE skip the block
+ tv.v_type = VAR_UNKNOWN;
+ if (evaluate_const_expr1(&p, cctx, &tv) == OK)
+ cctx->ctx_skip = tv2bool(&tv) ? FALSE : TRUE;
+ else
+ cctx->ctx_skip = MAYBE;
+ clear_tv(&tv);
+ if (cctx->ctx_skip == MAYBE)
+ {
+ p = arg;
+ if (compile_expr1(&p, cctx) == FAIL)
+ return NULL;
+ }
scope = new_scope(cctx, IF_SCOPE);
if (scope == NULL)
return NULL;
- // "where" is set when ":elseif", "else" or ":endif" is found
- scope->se_u.se_if.is_if_label = instr->ga_len;
- generate_JUMP(cctx, JUMP_IF_FALSE, 0);
+ if (cctx->ctx_skip == MAYBE)
+ {
+ // "where" is set when ":elseif", "else" or ":endif" is found
+ scope->se_u.se_if.is_if_label = instr->ga_len;
+ generate_JUMP(cctx, JUMP_IF_FALSE, 0);
+ }
+ else
+ scope->se_u.se_if.is_if_label = -1;
return p;
}
@@ -3519,6 +3716,7 @@ compile_elseif(char_u *arg, cctx_T *cctx)
garray_T *instr = &cctx->ctx_instr;
isn_T *isn;
scope_T *scope = cctx->ctx_scope;
+ typval_T tv;
if (scope == NULL || scope->se_type != IF_SCOPE)
{
@@ -3527,22 +3725,35 @@ compile_elseif(char_u *arg, cctx_T *cctx)
}
cctx->ctx_locals.ga_len = scope->se_local_count;
- // jump from previous block to the end
- if (compile_jump_to_end(&scope->se_u.se_if.is_end_label,
+ if (cctx->ctx_skip != TRUE)
+ {
+ 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;
- isn->isn_arg.jump.jump_where = instr->ga_len;
+ return NULL;
+ // previous "if" or "elseif" jumps here
+ isn = ((isn_T *)instr->ga_data) + scope->se_u.se_if.is_if_label;
+ isn->isn_arg.jump.jump_where = instr->ga_len;
+ }
- // compile "expr"
- if (compile_expr1(&p, cctx) == FAIL)
- return NULL;
+ // compile "expr"; if we know it evaluates to FALSE skip the block
+ tv.v_type = VAR_UNKNOWN;
+ if (evaluate_const_expr1(&p, cctx, &tv) == OK)
+ cctx->ctx_skip = tv2bool(&tv) ? FALSE : TRUE;
+ else
+ cctx->ctx_skip = MAYBE;
+ clear_tv(&tv);
+ if (cctx->ctx_skip == MAYBE)
+ {
+ p = arg;
+ if (compile_expr1(&p, cctx) == FAIL)
+ return NULL;
- // "where" is set when ":elseif", "else" or ":endif" is found
- scope->se_u.se_if.is_if_label = instr->ga_len;
- generate_JUMP(cctx, JUMP_IF_FALSE, 0);
+ // "where" is set when ":elseif", "else" or ":endif" is found
+ scope->se_u.se_if.is_if_label = instr->ga_len;
+ generate_JUMP(cctx, JUMP_IF_FALSE, 0);
+ }
+ else
+ scope->se_u.se_if.is_if_label = -1;
return p;
}
@@ -3562,14 +3773,26 @@ compile_else(char_u *arg, cctx_T *cctx)
}
cctx->ctx_locals.ga_len = scope->se_local_count;
- // jump from previous block to the end
- if (compile_jump_to_end(&scope->se_u.se_if.is_end_label,
+ // jump from previous block to the end, unless the else block is empty
+ if (cctx->ctx_skip == MAYBE)
+ {
+ if (compile_jump_to_end(&scope->se_u.se_if.is_end_label,
JUMP_ALWAYS, cctx) == FAIL)
- return NULL;
+ return NULL;
+ }
- // previous "if" or "elseif" jumps here
- isn = ((isn_T *)instr->ga_data) + scope->se_u.se_if.is_if_label;
- isn->isn_arg.jump.jump_where = instr->ga_len;
+ if (cctx->ctx_skip != TRUE)
+ {
+ if (scope->se_u.se_if.is_if_label >= 0)
+ {
+ // previous "if" or "elseif" jumps here
+ isn = ((isn_T *)instr->ga_data) + scope->se_u.se_if.is_if_label;
+ isn->isn_arg.jump.jump_where = instr->ga_len;
+ }
+ }
+
+ if (cctx->ctx_skip != MAYBE)
+ cctx->ctx_skip = !cctx->ctx_skip;
return p;
}
@@ -3591,12 +3814,15 @@ compile_endif(char_u *arg, cctx_T *cctx)
cctx->ctx_scope = scope->se_outer;
cctx->ctx_locals.ga_len = scope->se_local_count;
- // previous "if" or "elseif" jumps here
- isn = ((isn_T *)instr->ga_data) + scope->se_u.se_if.is_if_label;
- isn->isn_arg.jump.jump_where = instr->ga_len;
-
+ if (scope->se_u.se_if.is_if_label >= 0)
+ {
+ // previous "if" or "elseif" jumps here
+ isn = ((isn_T *)instr->ga_data) + scope->se_u.se_if.is_if_label;
+ isn->isn_arg.jump.jump_where = instr->ga_len;
+ }
// Fill in the "end" label in jumps at the end of the blocks.
compile_fill_jump_to_end(&ifscope->is_end_label, cctx);
+ cctx->ctx_skip = FALSE;
vim_free(scope);
return arg;
@@ -4326,6 +4552,12 @@ compile_def_function(ufunc_T *ufunc, int set_return_type)
if (p == ea.cmd && ea.cmdidx != CMD_SIZE)
{
+ if (cctx.ctx_skip == TRUE)
+ {
+ line += STRLEN(line);
+ continue;
+ }
+
// Expression or function call.
if (ea.cmdidx == CMD_eval)
{
@@ -4351,6 +4583,15 @@ compile_def_function(ufunc_T *ufunc, int set_return_type)
p = skipwhite(p);
+ if (cctx.ctx_skip == TRUE
+ && ea.cmdidx != CMD_elseif
+ && ea.cmdidx != CMD_else
+ && ea.cmdidx != CMD_endif)
+ {
+ line += STRLEN(line);
+ continue;
+ }
+
switch (ea.cmdidx)
{
case CMD_def: