# HG changeset patch # User Bram Moolenaar # Date 1580498105 -3600 # Node ID a8d2d3c8f0b353c12da91fe8f96b304fea17ee96 # Parent 2f4b2122c7d11974f2eae574bfcd49f8a0195e21 patch 8.2.0185: Vim9 script: cannot use "if has()" to skip lines Commit: https://github.com/vim/vim/commit/a259d8d30bc289764925fc42db1dbe774f0bb3f8 Author: Bram Moolenaar Date: Fri Jan 31 20:10:50 2020 +0100 patch 8.2.0185: Vim9 script: cannot use "if has()" to skip lines Problem: Vim9 script: cannot use "if has()" to skip lines. Solution: Evaluate constant expression at runtime. diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -100,7 +100,6 @@ static void f_getpos(typval_T *argvars, 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_ /* * "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 --- 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 --- 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 --- 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 <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; - - // compile "expr" - if (compile_expr1(&p, cctx) == FAIL) - return NULL; + typval_T tv; + + // 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; - - // compile "expr" - 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); + 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 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); + } + 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; - - // 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; + } + + 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 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 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: