aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/eval.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/eval.c')
-rw-r--r--src/nvim/eval.c312
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), &ltv, false,
+ arg = ex_let_one(skipwhite(arg + 1), &ltv, false, is_const,
(char_u *)"]", nextchars);
tv_clear(&ltv);
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 */