# HG changeset patch # User Bram Moolenaar # Date 1620403204 -7200 # Node ID a4fda40e0bb986597fcdbdbe634f3a7a5c0e3e81 # Parent f5484f767f7012ce0a9fa0d364cd09db447667bb patch 8.2.2842: Vim9: skip argument to searchpair() is not compiled Commit: https://github.com/vim/vim/commit/f18332fb9e2e4208a97d800f096b02c6681780e7 Author: Bram Moolenaar Date: Fri May 7 17:55:55 2021 +0200 patch 8.2.2842: Vim9: skip argument to searchpair() is not compiled Problem: Vim9: skip argument to searchpair() is not compiled. Solution: Add VAR_INSTR. diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -309,6 +309,10 @@ eval_expr_typval(typval_T *expr, typval_ return FAIL; } } + else if (expr->v_type == VAR_INSTR) + { + return exe_typval_instr(expr, rettv); + } else { s = tv_get_string_buf_chk(expr, buf); @@ -1510,6 +1514,7 @@ tv_op(typval_T *tv1, typval_T *tv2, char case VAR_SPECIAL: case VAR_JOB: case VAR_CHANNEL: + case VAR_INSTR: break; case VAR_BLOB: @@ -4084,6 +4089,7 @@ check_can_index(typval_T *rettv, int eva case VAR_SPECIAL: case VAR_JOB: case VAR_CHANNEL: + case VAR_INSTR: if (verbose) emsg(_(e_cannot_index_special_variable)); return FAIL; @@ -4177,6 +4183,7 @@ eval_index_inner( case VAR_SPECIAL: case VAR_JOB: case VAR_CHANNEL: + case VAR_INSTR: break; // not evaluating, skipping over subscript case VAR_NUMBER: @@ -5067,6 +5074,11 @@ echo_string_core( } break; + case VAR_INSTR: + *tofree = NULL; + r = (char_u *)"instructions"; + break; + case VAR_FLOAT: #ifdef FEAT_FLOAT *tofree = NULL; @@ -5987,6 +5999,7 @@ item_copy( case VAR_SPECIAL: case VAR_JOB: case VAR_CHANNEL: + case VAR_INSTR: copy_tv(from, to); break; case VAR_LIST: diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -2989,6 +2989,7 @@ f_empty(typval_T *argvars, typval_T *ret case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: + case VAR_INSTR: internal_error_no_abort("f_empty(UNKNOWN)"); n = TRUE; break; @@ -6303,6 +6304,7 @@ f_len(typval_T *argvars, typval_T *rettv case VAR_PARTIAL: case VAR_JOB: case VAR_CHANNEL: + case VAR_INSTR: emsg(_("E701: Invalid type for len()")); break; } @@ -10215,6 +10217,7 @@ f_type(typval_T *argvars, typval_T *rett case VAR_JOB: n = VAR_TYPE_JOB; break; case VAR_CHANNEL: n = VAR_TYPE_CHANNEL; break; case VAR_BLOB: n = VAR_TYPE_BLOB; break; + case VAR_INSTR: n = VAR_TYPE_INSTR; break; case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -1912,6 +1912,7 @@ item_lock(typval_T *tv, int deep, int lo case VAR_SPECIAL: case VAR_JOB: case VAR_CHANNEL: + case VAR_INSTR: break; case VAR_BLOB: diff --git a/src/if_py_both.h b/src/if_py_both.h --- a/src/if_py_both.h +++ b/src/if_py_both.h @@ -6425,6 +6425,7 @@ ConvertToPyObject(typval_T *tv) case VAR_VOID: case VAR_CHANNEL: case VAR_JOB: + case VAR_INSTR: Py_INCREF(Py_None); return Py_None; case VAR_BOOL: diff --git a/src/json.c b/src/json.c --- a/src/json.c +++ b/src/json.c @@ -230,6 +230,7 @@ json_encode_item(garray_T *gap, typval_T case VAR_PARTIAL: case VAR_JOB: case VAR_CHANNEL: + case VAR_INSTR: semsg(_(e_cannot_json_encode_str), vartype_name(val->v_type)); return FAIL; diff --git a/src/proto/vim9execute.pro b/src/proto/vim9execute.pro --- a/src/proto/vim9execute.pro +++ b/src/proto/vim9execute.pro @@ -4,6 +4,7 @@ void funcstack_check_refcount(funcstack_ char_u *char_from_string(char_u *str, varnumber_T index); char_u *string_slice(char_u *str, varnumber_T first, varnumber_T last, int exclusive); int fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx); +int exe_typval_instr(typval_T *tv, typval_T *rettv); char_u *exe_substitute_instr(void); int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, partial_T *partial, typval_T *rettv); void ex_disassemble(exarg_T *eap); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1371,6 +1371,7 @@ typedef struct cbq_S cbq_T; typedef struct channel_S channel_T; typedef struct cctx_S cctx_T; typedef struct ectx_S ectx_T; +typedef struct instr_S instr_T; typedef enum { @@ -1389,6 +1390,7 @@ typedef enum VAR_DICT, // "v_dict" is used VAR_JOB, // "v_job" is used VAR_CHANNEL, // "v_channel" is used + VAR_INSTR, // "v_instr" is used } vartype_T; // A type specification. @@ -1429,6 +1431,7 @@ typedef struct channel_T *v_channel; // channel value (can be NULL!) #endif blob_T *v_blob; // blob value (can be NULL!) + instr_T *v_instr; // instructions to execute } vval; } typval_T; diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -974,6 +974,20 @@ def Test_searchcount() bwipe! enddef +def Test_searchpair() + new + setline(1, "here { and } there") + normal f{ + var col = 15 + assert_equal(1, searchpair('{', '', '}', '', 'col(".") > col')) + assert_equal(12, col('.')) + col = 8 + normal 0f{ + assert_equal(0, searchpair('{', '', '}', '', 'col(".") > col')) + assert_equal(6, col('.')) + bwipe! +enddef + def Test_set_get_bufline() # similar to Test_setbufline_getbufline() var lines =<< trim END diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -140,6 +140,35 @@ def Test_disassemble_substitute() res) enddef + +def s:SearchPair() + var col = 8 + searchpair("{", "", "}", "", "col('.') > col") +enddef + +def Test_disassemble_seachpair() + var res = execute('disass s:SearchPair') + assert_match('\d*_SearchPair.*' .. + ' var col = 8\_s*' .. + '\d STORE 8 in $0\_s*' .. + ' searchpair("{", "", "}", "", "col(''.'') > col")\_s*' .. + '\d PUSHS "{"\_s*' .. + '\d PUSHS ""\_s*' .. + '\d PUSHS "}"\_s*' .. + '\d PUSHS ""\_s*' .. + '\d INSTR\_s*' .. + ' 0 PUSHS "."\_s*' .. + ' 1 BCALL col(argc 1)\_s*' .. + ' 2 LOAD $0\_s*' .. + ' 3 COMPARENR >\_s*' .. + ' -------------\_s*' .. + '\d BCALL searchpair(argc 5)\_s*' .. + '\d DROP\_s*' .. + '\d RETURN 0', + res) +enddef + + def s:RedirVar() var result: string redir =>> result diff --git a/src/testing.c b/src/testing.c --- a/src/testing.c +++ b/src/testing.c @@ -1023,6 +1023,7 @@ f_test_refcount(typval_T *argvars, typva case VAR_FLOAT: case VAR_SPECIAL: case VAR_STRING: + case VAR_INSTR: break; case VAR_JOB: #ifdef FEAT_JOB_CHANNEL diff --git a/src/typval.c b/src/typval.c --- a/src/typval.c +++ b/src/typval.c @@ -91,6 +91,7 @@ free_tv(typval_T *varp) case VAR_VOID: case VAR_BOOL: case VAR_SPECIAL: + case VAR_INSTR: break; } vim_free(varp); @@ -153,6 +154,7 @@ clear_tv(typval_T *varp) case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: + case VAR_INSTR: break; } varp->v_lock = 0; @@ -236,6 +238,7 @@ tv_get_bool_or_number_chk(typval_T *varp case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: + case VAR_INSTR: internal_error_no_abort("tv_get_number(UNKNOWN)"); break; } @@ -333,6 +336,7 @@ tv_get_float(typval_T *varp) case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: + case VAR_INSTR: internal_error_no_abort("tv_get_float(UNKNOWN)"); break; } @@ -514,6 +518,7 @@ tv_get_string_buf_chk_strict(typval_T *v case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: + case VAR_INSTR: emsg(_(e_inval_string)); break; } @@ -614,6 +619,10 @@ copy_tv(typval_T *from, typval_T *to) ++to->vval.v_channel->ch_refcount; break; #endif + case VAR_INSTR: + to->vval.v_instr = from->vval.v_instr; + break; + case VAR_STRING: case VAR_FUNC: if (from->vval.v_string == NULL) @@ -1116,6 +1125,8 @@ tv_equal( #ifdef FEAT_JOB_CHANNEL return tv1->vval.v_channel == tv2->vval.v_channel; #endif + case VAR_INSTR: + return tv1->vval.v_instr == tv2->vval.v_instr; case VAR_PARTIAL: return tv1->vval.v_partial == tv2->vval.v_partial; diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -751,6 +751,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 2842, +/**/ 2841, /**/ 2840, diff --git a/src/vim.h b/src/vim.h --- a/src/vim.h +++ b/src/vim.h @@ -2030,6 +2030,7 @@ typedef int sock_T; #define VAR_TYPE_JOB 8 #define VAR_TYPE_CHANNEL 9 #define VAR_TYPE_BLOB 10 +#define VAR_TYPE_INSTR 11 #define DICT_MAXNEST 100 // maximum nesting of lists and dicts diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -20,6 +20,7 @@ typedef enum { ISN_ECHOERR, // echo Ex commands isn_arg.number items on top of stack ISN_RANGE, // compute range from isn_arg.string, push to stack ISN_SUBSTITUTE, // :s command with expression + ISN_INSTR, // instructions compiled from expression // get and set variables ISN_LOAD, // push local variable isn_arg.number @@ -411,6 +412,7 @@ struct isn_S { isn_outer_T outer; subs_T subs; cexpr_T cexpr; + isn_T *instr; } isn_arg; }; diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -615,6 +615,7 @@ may_generate_2STRING(int offset, cctx_T case VAR_DICT: case VAR_JOB: case VAR_CHANNEL: + case VAR_INSTR: to_string_error((*type)->tt_type); return FAIL; } @@ -3097,16 +3098,72 @@ theend: return res; } + static void +clear_instr_ga(garray_T *gap) +{ + int idx; + + for (idx = 0; idx < gap->ga_len; ++idx) + delete_instr(((isn_T *)gap->ga_data) + idx); + ga_clear(gap); +} + +/* + * Compile a string in a ISN_PUSHS instruction into an ISN_INSTR. + * Returns FAIL if compilation fails. + */ + static int +compile_string(isn_T *isn, cctx_T *cctx) +{ + char_u *s = isn->isn_arg.string; + garray_T save_ga = cctx->ctx_instr; + int expr_res; + int trailing_error; + int instr_count; + isn_T *instr = NULL; + + // Temporarily reset the list of instructions so that the jump labels are + // correct. + cctx->ctx_instr.ga_len = 0; + cctx->ctx_instr.ga_maxlen = 0; + cctx->ctx_instr.ga_data = NULL; + expr_res = compile_expr0(&s, cctx); + s = skipwhite(s); + trailing_error = *s != NUL; + + if (expr_res == FAIL || trailing_error) + { + if (trailing_error) + semsg(_(e_trailing_arg), s); + clear_instr_ga(&cctx->ctx_instr); + cctx->ctx_instr = save_ga; + return FAIL; + } + + // Move the generated instructions into the ISN_INSTR instruction, then + // restore the list of instructions. + instr_count = cctx->ctx_instr.ga_len; + instr = cctx->ctx_instr.ga_data; + instr[instr_count].isn_type = ISN_FINISH; + + cctx->ctx_instr = save_ga; + vim_free(isn->isn_arg.string); + isn->isn_type = ISN_INSTR; + isn->isn_arg.instr = instr; + return OK; +} + /* * Compile the argument expressions. * "arg" points to just after the "(" and is advanced to after the ")" */ static int -compile_arguments(char_u **arg, cctx_T *cctx, int *argcount) +compile_arguments(char_u **arg, cctx_T *cctx, int *argcount, int is_searchpair) { char_u *p = *arg; char_u *whitep = *arg; int must_end = FALSE; + int instr_count; for (;;) { @@ -3123,10 +3180,21 @@ compile_arguments(char_u **arg, cctx_T * return FAIL; } + instr_count = cctx->ctx_instr.ga_len; if (compile_expr0(&p, cctx) == FAIL) return FAIL; ++*argcount; + if (is_searchpair && *argcount == 5 + && cctx->ctx_instr.ga_len == instr_count + 1) + { + isn_T *isn = ((isn_T *)cctx->ctx_instr.ga_data) + instr_count; + + // {skip} argument of searchpair() can be compiled if not empty + if (isn->isn_type == ISN_PUSHS && *isn->isn_arg.string != NUL) + compile_string(isn, cctx); + } + if (*p != ',' && *skipwhite(p) == ',') { semsg(_(e_no_white_space_allowed_before_str_str), ",", p); @@ -3175,6 +3243,7 @@ compile_call( ufunc_T *ufunc = NULL; int res = FAIL; int is_autoload; + int is_searchpair; // we can evaluate "has('name')" at compile time if (varlen == 3 && STRNCMP(*arg, "has", 3) == 0) @@ -3216,8 +3285,11 @@ compile_call( vim_strncpy(namebuf, *arg, varlen); name = fname_trans_sid(namebuf, fname_buf, &tofree, &error); + // we handle the "skip" argument of searchpair() differently + is_searchpair = (varlen == 10 && STRNCMP(*arg, "searchpair", 10) == 0); + *arg = skipwhite(*arg + varlen + 1); - if (compile_arguments(arg, cctx, &argcount) == FAIL) + if (compile_arguments(arg, cctx, &argcount, is_searchpair) == FAIL) goto theend; is_autoload = vim_strchr(name, AUTOLOAD_CHAR) != NULL; @@ -4027,7 +4099,7 @@ compile_subscript( type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; *arg = skipwhite(p + 1); - if (compile_arguments(arg, cctx, &argcount) == FAIL) + if (compile_arguments(arg, cctx, &argcount, FALSE) == FAIL) return FAIL; if (generate_PCALL(cctx, argcount, name_start, type, TRUE) == FAIL) return FAIL; @@ -4080,7 +4152,7 @@ compile_subscript( return FAIL; } *arg = skipwhite(*arg + 1); - if (compile_arguments(arg, cctx, &argcount) == FAIL) + if (compile_arguments(arg, cctx, &argcount, FALSE) == FAIL) return FAIL; // Move the instructions for the arguments to before the @@ -6728,6 +6800,7 @@ compile_assignment(char_u *arg, exarg_T case VAR_ANY: case VAR_PARTIAL: case VAR_VOID: + case VAR_INSTR: case VAR_SPECIAL: // cannot happen generate_PUSHNR(cctx, 0); break; @@ -8536,16 +8609,6 @@ theend: } - static void -clear_instr_ga(garray_T *gap) -{ - int idx; - - for (idx = 0; idx < gap->ga_len; ++idx) - delete_instr(((isn_T *)gap->ga_data) + idx); - ga_clear(gap); -} - /* * :s/pat/repl/ */ @@ -8568,13 +8631,13 @@ compile_substitute(char_u *arg, exarg_T int expr_res; int trailing_error; int instr_count; - isn_T *instr = NULL; + isn_T *instr; isn_T *isn; cmd += 3; end = skip_substitute(cmd, delimiter); - // Temporarily reset the list of instructions so that the jumps + // Temporarily reset the list of instructions so that the jump // labels are correct. cctx->ctx_instr.ga_len = 0; cctx->ctx_instr.ga_maxlen = 0; @@ -8592,7 +8655,6 @@ compile_substitute(char_u *arg, exarg_T semsg(_(e_trailing_arg), cmd); clear_instr_ga(&cctx->ctx_instr); cctx->ctx_instr = save_ga; - vim_free(instr); return NULL; } @@ -9555,6 +9617,17 @@ delete_instr(isn_T *isn) } break; + case ISN_INSTR: + { + int idx; + isn_T *list = isn->isn_arg.instr; + + for (idx = 0; list[idx].isn_type != ISN_FINISH; ++idx) + delete_instr(list + idx); + vim_free(list); + } + break; + case ISN_LOADS: case ISN_STORES: vim_free(isn->isn_arg.loadstore.ls_name); diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -1259,6 +1259,12 @@ fill_partial_and_closure(partial_T *pt, return OK; } +// used for v_instr of typval of VAR_INSTR +struct instr_S { + ectx_T *instr_ectx; + isn_T *instr_instr; +}; + // used for substitute_instr typedef struct subs_expr_S { ectx_T *subs_ectx; @@ -1379,6 +1385,23 @@ exec_instructions(ectx_T *ectx) } break; + // push typeval VAR_INSTR with instructions to be executed + case ISN_INSTR: + { + if (GA_GROW(&ectx->ec_stack, 1) == FAIL) + return FAIL; + tv = STACK_TV_BOT(0); + tv->vval.v_instr = ALLOC_ONE(instr_T); + if (tv->vval.v_instr == NULL) + goto on_error; + ++ectx->ec_stack.ga_len; + + tv->v_type = VAR_INSTR; + tv->vval.v_instr->instr_ectx = ectx; + tv->vval.v_instr->instr_instr = iptr->isn_arg.instr; + } + break; + // execute :substitute with an expression case ISN_SUBSTITUTE: { @@ -3997,6 +4020,33 @@ done: } /* + * Execute the instructions from a VAR_INSTR typeval and put the result in + * "rettv". + * Return OK or FAIL. + */ + int +exe_typval_instr(typval_T *tv, typval_T *rettv) +{ + ectx_T *ectx = tv->vval.v_instr->instr_ectx; + isn_T *save_instr = ectx->ec_instr; + int save_iidx = ectx->ec_iidx; + int res; + + ectx->ec_instr = tv->vval.v_instr->instr_instr; + res = exec_instructions(ectx); + if (res == OK) + { + *rettv = *STACK_TV_BOT(-1); + --ectx->ec_stack.ga_len; + } + + ectx->ec_instr = save_instr; + ectx->ec_iidx = save_iidx; + + return res; +} + +/* * Execute the instructions from an ISN_SUBSTITUTE command, which are in * "substitute_instr". */ @@ -4436,6 +4486,14 @@ list_instructions(char *pfx, isn_T *inst } #endif break; + case ISN_INSTR: + { + smsg("%s%4d INSTR", pfx, current); + list_instructions(" ", iptr->isn_arg.instr, + INT_MAX, NULL); + msg(" -------------"); + } + break; case ISN_SUBSTITUTE: { subs_T *subs = &iptr->isn_arg.subs; @@ -5225,6 +5283,7 @@ tv2bool(typval_T *tv) case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: + case VAR_INSTR: break; } return FALSE; diff --git a/src/vim9type.c b/src/vim9type.c --- a/src/vim9type.c +++ b/src/vim9type.c @@ -950,6 +950,7 @@ equal_type(type_T *type1, type_T *type2) case VAR_BLOB: case VAR_JOB: case VAR_CHANNEL: + case VAR_INSTR: break; // not composite is always OK case VAR_LIST: case VAR_DICT: @@ -1097,6 +1098,7 @@ vartype_name(vartype_T type) case VAR_CHANNEL: return "channel"; case VAR_LIST: return "list"; case VAR_DICT: return "dict"; + case VAR_INSTR: return "instr"; case VAR_FUNC: case VAR_PARTIAL: return "func"; diff --git a/src/viminfo.c b/src/viminfo.c --- a/src/viminfo.c +++ b/src/viminfo.c @@ -1376,6 +1376,7 @@ write_viminfo_varlist(FILE *fp) case VAR_PARTIAL: case VAR_JOB: case VAR_CHANNEL: + case VAR_INSTR: continue; } fprintf(fp, "!%s\t%s\t", this_var->di_key, s);