diff options
Diffstat (limited to 'src/nvim/eval.c')
-rw-r--r-- | src/nvim/eval.c | 312 |
1 files changed, 222 insertions, 90 deletions
diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 1d34af2230..cd2888883b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -176,6 +176,7 @@ static char *e_funcref = N_("E718: Funcref required"); static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary"); static char *e_nofunc = N_("E130: Unknown function: %s"); static char *e_illvar = N_("E461: Illegal variable name: %s"); +static char *e_cannot_mod = N_("E995: Cannot modify existing variable"); static const char *e_readonlyvar = N_( "E46: Cannot change read-only variable \"%.*s\""); @@ -776,10 +777,11 @@ var_redir_start( did_emsg = FALSE; tv.v_type = VAR_STRING; tv.vval.v_string = (char_u *)""; - if (append) - set_var_lval(redir_lval, redir_endp, &tv, TRUE, (char_u *)"."); - else - set_var_lval(redir_lval, redir_endp, &tv, TRUE, (char_u *)"="); + if (append) { + set_var_lval(redir_lval, redir_endp, &tv, true, false, (char_u *)"."); + } else { + set_var_lval(redir_lval, redir_endp, &tv, true, false, (char_u *)"="); + } clear_lval(redir_lval); err = did_emsg; did_emsg |= save_emsg; @@ -837,7 +839,7 @@ void var_redir_stop(void) redir_endp = (char_u *)get_lval(redir_varname, NULL, redir_lval, false, false, 0, FNE_CHECK_START); if (redir_endp != NULL && redir_lval->ll_name != NULL) { - set_var_lval(redir_lval, redir_endp, &tv, false, (char_u *)"."); + set_var_lval(redir_lval, redir_endp, &tv, false, false, (char_u *)"."); } clear_lval(redir_lval); } @@ -955,6 +957,88 @@ eval_to_bool( return retval; } +// Call eval1() and give an error message if not done at a lower level. +static int eval1_emsg(char_u **arg, typval_T *rettv, bool evaluate) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + const int did_emsg_before = did_emsg; + const int called_emsg_before = called_emsg; + + const int ret = eval1(arg, rettv, evaluate); + if (ret == FAIL) { + // Report the invalid expression unless the expression evaluation has + // been cancelled due to an aborting error, an interrupt, or an + // exception, or we already gave a more specific error. + // Also check called_emsg for when using assert_fails(). + if (!aborting() + && did_emsg == did_emsg_before + && called_emsg == called_emsg_before) { + emsgf(_(e_invexpr2), arg); + } + } + return ret; +} + +static int eval_expr_typval(const typval_T *expr, typval_T *argv, + int argc, typval_T *rettv) + FUNC_ATTR_NONNULL_ARG(1, 2, 4) +{ + int dummy; + + if (expr->v_type == VAR_FUNC) { + const char_u *const s = expr->vval.v_string; + if (s == NULL || *s == NUL) { + return FAIL; + } + if (call_func(s, (int)STRLEN(s), rettv, argc, argv, NULL, + 0L, 0L, &dummy, true, NULL, NULL) == FAIL) { + return FAIL; + } + } else if (expr->v_type == VAR_PARTIAL) { + partial_T *const partial = expr->vval.v_partial; + const char_u *const s = partial_name(partial); + if (s == NULL || *s == NUL) { + return FAIL; + } + if (call_func(s, (int)STRLEN(s), rettv, argc, argv, NULL, + 0L, 0L, &dummy, true, partial, NULL) == FAIL) { + return FAIL; + } + } else { + char buf[NUMBUFLEN]; + char_u *s = (char_u *)tv_get_string_buf_chk(expr, buf); + if (s == NULL) { + return FAIL; + } + s = skipwhite(s); + if (eval1_emsg(&s, rettv, true) == FAIL) { + return FAIL; + } + if (*s != NUL) { // check for trailing chars after expr + tv_clear(rettv); + emsgf(_(e_invexpr2), s); + return FAIL; + } + } + return OK; +} + +/// Like eval_to_bool() but using a typval_T instead of a string. +/// Works for string, funcref and partial. +static bool eval_expr_to_bool(const typval_T *expr, bool *error) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + typval_T argv, rettv; + + if (eval_expr_typval(expr, &argv, 0, &rettv) == FAIL) { + *error = true; + return false; + } + const bool res = (tv_get_number_chk(&rettv, error) != 0); + tv_clear(&rettv); + return res; +} + /// Top level evaluation function, returning a string /// /// @param[in] arg String to evaluate. @@ -1436,21 +1520,33 @@ int eval_foldexpr(char_u *arg, int *cp) return (int)retval; } -/* - * ":let" list all variable values - * ":let var1 var2" list variable values - * ":let var = expr" assignment command. - * ":let var += expr" assignment command. - * ":let var -= expr" assignment command. - * ":let var *= expr" assignment command. - * ":let var /= expr" assignment command. - * ":let var %= expr" assignment command. - * ":let var .= expr" assignment command. - * ":let var ..= expr" assignment command. - * ":let [var1, var2] = expr" unpack list. - */ +// ":cons[t] var = expr1" define constant +// ":cons[t] [name1, name2, ...] = expr1" define constants unpacking list +// ":cons[t] [name, ..., ; lastname] = expr" define constants unpacking list +void ex_const(exarg_T *eap) +{ + ex_let_const(eap, true); +} + +// ":let" list all variable values +// ":let var1 var2" list variable values +// ":let var = expr" assignment command. +// ":let var += expr" assignment command. +// ":let var -= expr" assignment command. +// ":let var *= expr" assignment command. +// ":let var /= expr" assignment command. +// ":let var %= expr" assignment command. +// ":let var .= expr" assignment command. +// ":let var ..= expr" assignment command. +// ":let [var1, var2] = expr" unpack list. +// ":let [name, ..., ; lastname] = expr" unpack list. void ex_let(exarg_T *eap) { + ex_let_const(eap, false); +} + +static void ex_let_const(exarg_T *eap, const bool is_const) +{ char_u *arg = eap->arg; char_u *expr = NULL; typval_T rettv; @@ -1512,7 +1608,8 @@ void ex_let(exarg_T *eap) } emsg_skip--; } else if (i != FAIL) { - (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, op); + (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, + is_const, op); tv_clear(&rettv); } } @@ -1530,9 +1627,10 @@ static int ex_let_vars( char_u *arg_start, typval_T *tv, - int copy, /* copy values from "tv", don't move */ - int semicolon, /* from skip_var_list() */ - int var_count, /* from skip_var_list() */ + int copy, // copy values from "tv", don't move + int semicolon, // from skip_var_list() + int var_count, // from skip_var_list() + int is_const, // lock variables for :const char_u *nextchars ) { @@ -1543,8 +1641,9 @@ ex_let_vars( /* * ":let var = expr" or ":for var in list" */ - if (ex_let_one(arg, tv, copy, nextchars, nextchars) == NULL) + if (ex_let_one(arg, tv, copy, is_const, nextchars, nextchars) == NULL) { return FAIL; + } return OK; } @@ -1572,8 +1671,8 @@ ex_let_vars( size_t rest_len = tv_list_len(l); while (*arg != ']') { arg = skipwhite(arg + 1); - arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, (const char_u *)",;]", - nextchars); + arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, is_const, + (const char_u *)",;]", nextchars); if (arg == NULL) { return FAIL; } @@ -1595,7 +1694,7 @@ ex_let_vars( ltv.vval.v_list = rest_list; tv_list_ref(rest_list); - arg = ex_let_one(skipwhite(arg + 1), <v, false, + arg = ex_let_one(skipwhite(arg + 1), <v, false, is_const, (char_u *)"]", nextchars); tv_clear(<v); if (arg == NULL) { @@ -1851,8 +1950,8 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) /// @return a pointer to the char just after the var name or NULL in case of /// error. static char_u *ex_let_one(char_u *arg, typval_T *const tv, - const bool copy, const char_u *const endchars, - const char_u *const op) + const bool copy, const bool is_const, + const char_u *const endchars, const char_u *const op) FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT { char_u *arg_end = NULL; @@ -1864,6 +1963,10 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, * ":let $VAR = expr": Set environment variable. */ if (*arg == '$') { + if (is_const) { + EMSG(_("E996: Cannot lock an environment variable")); + return NULL; + } // Find the end of the name. arg++; char *name = (char *)arg; @@ -1909,6 +2012,10 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, // ":let &l:option = expr": Set local option value. // ":let &g:option = expr": Set global option value. } else if (*arg == '&') { + if (is_const) { + EMSG(_("E996: Cannot lock an option")); + return NULL; + } // Find the end of the name. char *const p = (char *)find_option_end((const char **)&arg, &opt_flags); if (p == NULL @@ -1959,6 +2066,10 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, } // ":let @r = expr": Set register contents. } else if (*arg == '@') { + if (is_const) { + EMSG(_("E996: Cannot lock a register")); + return NULL; + } arg++; if (op != NULL && vim_strchr((char_u *)"+-*/%", *op) != NULL) { emsgf(_(e_letwrong), op); @@ -1998,7 +2109,7 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL) { EMSG(_(e_letunexp)); } else { - set_var_lval(&lv, p, tv, copy, op); + set_var_lval(&lv, p, tv, copy, is_const, op); arg_end = p; } } @@ -2363,7 +2474,7 @@ static void clear_lval(lval_T *lp) * "%" for "%=", "." for ".=" or "=" for "=". */ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, - int copy, const char_u *op) + int copy, const bool is_const, const char_u *op) { int cc; listitem_T *ri; @@ -2375,6 +2486,12 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, if (op != NULL && *op != '=') { typval_T tv; + if (is_const) { + EMSG(_(e_cannot_mod)); + *endp = cc; + return; + } + // handle +=, -=, *=, /=, %= and .= di = NULL; if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name), @@ -2390,7 +2507,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, tv_clear(&tv); } } else { - set_var(lp->ll_name, lp->ll_name_len, rettv, copy); + set_var_const(lp->ll_name, lp->ll_name_len, rettv, copy, is_const); } *endp = cc; } else if (tv_check_lock(lp->ll_newkey == NULL @@ -2401,6 +2518,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, listitem_T *ll_li = lp->ll_li; int ll_n1 = lp->ll_n1; + if (is_const) { + EMSG(_("E996: Cannot lock a range")); + return; + } + // Check whether any of the list items is locked for (ri = tv_list_first(rettv->vval.v_list); ri != NULL && ll_li != NULL; ) { @@ -2456,6 +2578,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, dict_T *dict = lp->ll_dict; bool watched = tv_dict_is_watched(dict); + if (is_const) { + EMSG(_("E996: Cannot lock a list or dict")); + return; + } + // Assign to a List or Dictionary item. if (lp->ll_newkey != NULL) { if (op != NULL && *op != '=') { @@ -2579,7 +2706,7 @@ bool next_for_item(void *fi_void, char_u *arg) } else { fi->fi_lw.lw_item = TV_LIST_ITEM_NEXT(fi->fi_list, item); return (ex_let_vars(arg, TV_LIST_ITEM_TV(item), true, - fi->fi_semicolon, fi->fi_varcount, NULL) == OK); + fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK); } } @@ -5165,7 +5292,7 @@ bool garbage_collect(bool testing) { Channel *data; map_foreach_value(channels, data, { - set_ref_in_callback_reader(&data->on_stdout, copyID, NULL, NULL); + set_ref_in_callback_reader(&data->on_data, copyID, NULL, NULL); set_ref_in_callback_reader(&data->on_stderr, copyID, NULL, NULL); set_ref_in_callback(&data->on_exit, copyID, NULL, NULL); }) @@ -6308,6 +6435,7 @@ call_func( partial_T *partial, // optional, can be NULL dict_T *selfdict_in // Dictionary for "self" ) + FUNC_ATTR_NONNULL_ARG(1, 3, 5, 9) { int ret = FAIL; int error = ERROR_NONE; @@ -8830,6 +8958,7 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) } hash_unlock(ht); } else { + assert(argvars[0].v_type == VAR_LIST); vimvars[VV_KEY].vv_type = VAR_NUMBER; for (listitem_T *li = tv_list_first(l); li != NULL;) { @@ -8860,44 +8989,17 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) } static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) + FUNC_ATTR_NONNULL_ARG(1, 2) { typval_T rettv; typval_T argv[3]; int retval = FAIL; - int dummy; tv_copy(tv, &vimvars[VV_VAL].vv_tv); argv[0] = vimvars[VV_KEY].vv_tv; argv[1] = vimvars[VV_VAL].vv_tv; - if (expr->v_type == VAR_FUNC) { - const char_u *const s = expr->vval.v_string; - if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL, - 0L, 0L, &dummy, true, NULL, NULL) == FAIL) { - goto theend; - } - } else if (expr->v_type == VAR_PARTIAL) { - partial_T *partial = expr->vval.v_partial; - - const char_u *const s = partial_name(partial); - if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL, - 0L, 0L, &dummy, true, partial, NULL) == FAIL) { - goto theend; - } - } else { - char buf[NUMBUFLEN]; - const char *s = tv_get_string_buf_chk(expr, buf); - if (s == NULL) { - goto theend; - } - s = (const char *)skipwhite((const char_u *)s); - if (eval1((char_u **)&s, &rettv, true) == FAIL) { - goto theend; - } - - if (*s != NUL) { // check for trailing chars after expr - emsgf(_(e_invexpr2), s); - goto theend; - } + if (eval_expr_typval(expr, argv, 2, &rettv) == FAIL) { + goto theend; } if (map) { // map(): replace the list item value. @@ -14498,10 +14600,10 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) long lnum_stop = 0; long time_limit = 0; - // Get the three pattern arguments: start, middle, end. + // Get the three pattern arguments: start, middle, end. Will result in an + // error if not a valid argument. char nbuf1[NUMBUFLEN]; char nbuf2[NUMBUFLEN]; - char nbuf3[NUMBUFLEN]; const char *spat = tv_get_string_chk(&argvars[0]); const char *mpat = tv_get_string_buf_chk(&argvars[1], nbuf1); const char *epat = tv_get_string_buf_chk(&argvars[2], nbuf2); @@ -14529,23 +14631,28 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) } // Optional fifth argument: skip expression. - const char *skip; + const typval_T *skip; if (argvars[3].v_type == VAR_UNKNOWN || argvars[4].v_type == VAR_UNKNOWN) { - skip = ""; + skip = NULL; } else { - skip = tv_get_string_buf_chk(&argvars[4], nbuf3); - if (skip == NULL) { + skip = &argvars[4]; + if (skip->v_type != VAR_FUNC + && skip->v_type != VAR_PARTIAL + && skip->v_type != VAR_STRING) { + emsgf(_(e_invarg2), tv_get_string(&argvars[4])); goto theend; // Type error. } if (argvars[5].v_type != VAR_UNKNOWN) { lnum_stop = tv_get_number_chk(&argvars[5], NULL); if (lnum_stop < 0) { + emsgf(_(e_invarg2), tv_get_string(&argvars[5])); goto theend; } if (argvars[6].v_type != VAR_UNKNOWN) { time_limit = tv_get_number_chk(&argvars[6], NULL); if (time_limit < 0) { + emsgf(_(e_invarg2), tv_get_string(&argvars[6])); goto theend; } } @@ -14553,7 +14660,7 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) } retval = do_searchpair( - (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, (char_u *)skip, + (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, skip, flags, match_pos, lnum_stop, time_limit); theend: @@ -14601,7 +14708,7 @@ do_searchpair( char_u *mpat, // middle pattern char_u *epat, // end pattern int dir, // BACKWARD or FORWARD - char_u *skip, // skip expression + const typval_T *skip, // skip expression int flags, // SP_SETPCMARK and other SP_ values pos_T *match_pos, linenr_T lnum_stop, // stop at this line if not zero @@ -14617,8 +14724,8 @@ do_searchpair( pos_T save_cursor; pos_T save_pos; int n; - int r; int nest = 1; + bool use_skip = false; int options = SEARCH_KEEP; proftime_T tm; size_t pat2_len; @@ -14648,6 +14755,13 @@ do_searchpair( options |= SEARCH_START; } + if (skip != NULL) { + // Empty string means to not use the skip expression. + if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC) { + use_skip = skip->vval.v_string != NULL && *skip->vval.v_string != NUL; + } + } + save_cursor = curwin->w_cursor; pos = curwin->w_cursor; clearpos(&firstpos); @@ -14677,12 +14791,12 @@ do_searchpair( /* clear the start flag to avoid getting stuck here */ options &= ~SEARCH_START; - /* If the skip pattern matches, ignore this match. */ - if (*skip != NUL) { + // If the skip pattern matches, ignore this match. + if (use_skip) { save_pos = curwin->w_cursor; curwin->w_cursor = pos; - bool err; - r = eval_to_bool(skip, &err, NULL, false); + bool err = false; + const bool r = eval_expr_to_bool(skip, &err); curwin->w_cursor = save_pos; if (err) { /* Evaluating {skip} caused an error, break here. */ @@ -17831,7 +17945,6 @@ static void add_timer_info(typval_T *rettv, timer_T *timer) di->di_tv.v_type = VAR_FUNC; di->di_tv.vval.v_string = vim_strsave(timer->callback.data.funcref); } - di->di_tv.v_lock = 0; } static void add_timer_info_all(typval_T *rettv) @@ -20102,6 +20215,24 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, const bool copy) FUNC_ATTR_NONNULL_ALL { + set_var_const(name, name_len, tv, copy, false); +} + +/// Set variable to the given value +/// +/// If the variable already exists, the value is updated. Otherwise the variable +/// is created. +/// +/// @param[in] name Variable name to set. +/// @param[in] name_len Length of the variable name. +/// @param tv Variable value. +/// @param[in] copy True if value in tv is to be copied. +/// @param[in] is_const True if value in tv is to be locked. +static void set_var_const(const char *name, const size_t name_len, + typval_T *const tv, const bool copy, + const bool is_const) + FUNC_ATTR_NONNULL_ALL +{ dictitem_T *v; hashtab_T *ht; dict_T *dict; @@ -20127,6 +20258,11 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, typval_T oldtv = TV_INITIAL_VALUE; if (v != NULL) { + if (is_const) { + EMSG(_(e_cannot_mod)); + return; + } + // existing variable, need to clear the value if (var_check_ro(v->di_flags, name, name_len) || tv_check_lock(v->di_tv.v_lock, name, name_len)) { @@ -20193,6 +20329,9 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, return; } v->di_flags = DI_FLAGS_ALLOC; + if (is_const) { + v->di_flags |= DI_FLAGS_LOCK; + } } if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) { @@ -20211,6 +20350,10 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, tv_clear(&oldtv); } } + + if (is_const) { + v->di_tv.v_lock |= VAR_LOCKED; + } } /// Check whether variable is read-only (DI_FLAGS_RO, DI_FLAGS_RO_SBX) @@ -20538,7 +20681,6 @@ void ex_execute(exarg_T *eap) char_u *arg = eap->arg; typval_T rettv; int ret = OK; - char_u *p; garray_T ga; int save_did_emsg = did_emsg; @@ -20547,17 +20689,8 @@ void ex_execute(exarg_T *eap) if (eap->skip) ++emsg_skip; while (*arg != NUL && *arg != '|' && *arg != '\n') { - p = arg; - if (eval1(&arg, &rettv, !eap->skip) == FAIL) { - /* - * Report the invalid expression unless the expression evaluation - * has been cancelled due to an aborting error, an interrupt, or an - * exception. - */ - if (!aborting() && did_emsg == save_did_emsg) { - EMSG2(_(e_invexpr2), p); - } - ret = FAIL; + ret = eval1_emsg(&arg, &rettv, !eap->skip); + if (ret == FAIL) { break; } @@ -21192,7 +21325,6 @@ void ex_function(exarg_T *eap) tv_clear(&fudi.fd_di->di_tv); } fudi.fd_di->di_tv.v_type = VAR_FUNC; - fudi.fd_di->di_tv.v_lock = 0; fudi.fd_di->di_tv.vval.v_string = vim_strsave(name); /* behave like "dict" was used */ |