Mercurial > vim
diff src/vim9compile.c @ 19253:a8d2d3c8f0b3 v8.2.0185
patch 8.2.0185: Vim9 script: cannot use "if has()" to skip lines
Commit: https://github.com/vim/vim/commit/a259d8d30bc289764925fc42db1dbe774f0bb3f8
Author: Bram Moolenaar <Bram@vim.org>
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.
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Fri, 31 Jan 2020 20:15:05 +0100 |
parents | d776967d0f0d |
children | 5aab9c306181 |
line wrap: on
line diff
--- 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; - - // 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: