To: vim_dev@googlegroups.com Subject: Patch 8.2.0185 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.0185 Problem: Vim9 script: cannot use "if has()" to skip lines. Solution: Evaluate constant expression at runtime. Files: src/vim9compile.c, src/evalfunc.c, src/proto/evalfunc.pro, src/userfunc.c, src/testdir/test_vim9_script.vim *** ../vim-8.2.0184/src/vim9compile.c 2020-01-29 21:27:17.570406758 +0100 --- src/vim9compile.c 2020-01-30 22:32:50.358687823 +0100 *************** *** 115,120 **** --- 115,122 ---- 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,3464 **** --- 3461,3645 ---- } /* + * 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,3513 **** char_u *p = arg; garray_T *instr = &cctx->ctx_instr; scope_T *scope; ! // compile "expr" ! 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); return p; } --- 3677,3710 ---- char_u *p = arg; garray_T *instr = &cctx->ctx_instr; scope_T *scope; + 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; ! 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,3524 **** --- 3716,3722 ---- 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,3548 **** } 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_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 p; } --- 3725,3759 ---- } cctx->ctx_locals.ga_len = scope->se_local_count; ! 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 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,3575 **** } 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_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 p; } --- 3773,3798 ---- } cctx->ctx_locals.ga_len = scope->se_local_count; ! // 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; ! } ! 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,3602 **** 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; ! // Fill in the "end" label in jumps at the end of the blocks. compile_fill_jump_to_end(&ifscope->is_end_label, cctx); vim_free(scope); return arg; --- 3814,3828 ---- cctx->ctx_scope = scope->se_outer; cctx->ctx_locals.ga_len = scope->se_local_count; ! 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,4331 **** --- 4552,4563 ---- 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,4356 **** --- 4583,4597 ---- 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: *** ../vim-8.2.0184/src/evalfunc.c 2020-01-30 16:40:07.653560032 +0100 --- src/evalfunc.c 2020-01-30 20:28:16.535994185 +0100 *************** *** 100,106 **** 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); --- 100,105 ---- *************** *** 3261,3267 **** /* * "has()" function */ ! static void f_has(typval_T *argvars, typval_T *rettv) { int i; --- 3260,3266 ---- /* * "has()" function */ ! void f_has(typval_T *argvars, typval_T *rettv) { int i; *** ../vim-8.2.0184/src/proto/evalfunc.pro 2020-01-26 15:52:33.023833239 +0100 --- src/proto/evalfunc.pro 2020-01-30 20:28:07.476026595 +0100 *************** *** 17,22 **** --- 17,23 ---- 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); *** ../vim-8.2.0184/src/userfunc.c 2020-01-28 23:09:20.123384203 +0100 --- src/userfunc.c 2020-01-30 22:15:18.398810033 +0100 *************** *** 2691,2699 **** } } ! // Check for ":append", ":change", ":insert". p = skip_range(p, NULL); ! if ((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' --- 2691,2700 ---- } } ! // Check for ":append", ":change", ":insert". Not for :def. p = skip_range(p, NULL); ! 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,2707 **** || !ASCII_ISALPHA(p[6]))))))) || (p[0] == 'i' && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n' ! && (!ASCII_ISALPHA(p[2]) || (p[2] == 's')))))) skip_until = vim_strsave((char_u *)"."); // Check for ":python <