]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 8.2.0185: Vim9 script: cannot use "if has()" to skip lines v8.2.0185
authorBram Moolenaar <Bram@vim.org>
Fri, 31 Jan 2020 19:10:50 +0000 (20:10 +0100)
committerBram Moolenaar <Bram@vim.org>
Fri, 31 Jan 2020 19:10:50 +0000 (20:10 +0100)
Problem:    Vim9 script: cannot use "if has()" to skip lines.
Solution:   Evaluate constant expression at runtime.

src/evalfunc.c
src/proto/evalfunc.pro
src/testdir/test_vim9_script.vim
src/userfunc.c
src/version.c
src/vim9compile.c

index 15b799088d970f60c0b90b00aefce20630e1b76e..a79cc8932998177502358b9312c10fa94e62b24d 100644 (file)
@@ -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;
index 59ff35c69480243ff93325ab4e63f0df9faa3c0c..14a1a56ac2563083bede378a3fea0618eb2bcd97 100644 (file)
@@ -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);
index 09bf3247ab4007f61688edcf580520ac2832fbb6..749c264d42bca235630f05f4e32c629cd37e9683 100644 (file)
@@ -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
index 474812c0655271edbec6ca92401d87ec36f8cd96..bdd838730470efc781107aad9f85bc5114937257 100644 (file)
@@ -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.
index 0c30f256841c02134ca051c0ed3966f15d382854..e7a6f6fab04b029c8f1152d6eb0adfd750222487 100644 (file)
@@ -742,6 +742,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    185,
 /**/
     184,
 /**/
index 772355a9272c5d3dad753dbaf2525b47a471ad69..69a2d1b1427b6f560eee5d9c8419b98d7cd84b6f 100644 (file)
@@ -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
@@ -3458,6 +3460,185 @@ new_scope(cctx_T *cctx, scopetype_T type)
     return scope;
 }
 
+/*
+ * 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"
  *
@@ -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: