aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/eval.c465
-rw-r--r--src/nvim/eval/executor.c324
-rw-r--r--src/nvim/eval/funcs.c13
3 files changed, 521 insertions, 281 deletions
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 247948ffe9..02694b5f3a 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -122,6 +122,8 @@ static const char e_empty_function_name[]
= N_("E1192: Empty function name");
static const char e_argument_of_str_must_be_list_string_dictionary_or_blob[]
= N_("E1250: Argument of %s must be a List, String, Dictionary or Blob");
+static const char e_cannot_use_partial_here[]
+ = N_("E1265: Cannot use a partial here");
static char * const namespace_char = "abglstvw";
@@ -152,6 +154,12 @@ typedef struct {
int fi_byte_idx; // byte index in fi_string
} forinfo_T;
+typedef enum {
+ GLV_FAIL,
+ GLV_OK,
+ GLV_STOP,
+} glv_status_T;
+
// values for vv_flags:
#define VV_COMPAT 1 // compatible, also used without "v:"
#define VV_RO 2 // read-only
@@ -1381,22 +1389,207 @@ Object eval_foldtext(win_T *wp)
return retval;
}
+/// Get an Dict lval variable that can be assigned a value to: "name",
+/// "name[expr]", "name[expr][expr]", "name.key", "name.key[expr]" etc.
+/// "name" points to the start of the name.
+/// If "rettv" is not NULL it points to the value to be assigned.
+/// "unlet" is true for ":unlet": slightly different behavior when something is
+/// wrong; must end in space or cmd separator.
+///
+/// flags:
+/// GLV_QUIET: do not give error messages
+/// GLV_READ_ONLY: will not change the variable
+/// GLV_NO_AUTOLOAD: do not use script autoloading
+///
+/// The Dict is returned in 'lp'. Returns GLV_OK on success and GLV_FAIL on
+/// failure. Returns GLV_STOP to stop processing the characters following
+/// 'key_end'.
+static glv_status_T get_lval_dict_item(char *name, lval_T *lp, char *key, int len, char **key_end,
+ typval_T *var1, int flags, bool unlet, typval_T *rettv)
+{
+ bool quiet = flags & GLV_QUIET;
+ char *p = *key_end;
+
+ if (len == -1) {
+ // "[key]": get key from "var1"
+ key = (char *)tv_get_string(var1); // is number or string
+ }
+ lp->ll_list = NULL;
+
+ // a NULL dict is equivalent with an empty dict
+ if (lp->ll_tv->vval.v_dict == NULL) {
+ lp->ll_tv->vval.v_dict = tv_dict_alloc();
+ lp->ll_tv->vval.v_dict->dv_refcount++;
+ }
+ lp->ll_dict = lp->ll_tv->vval.v_dict;
+
+ lp->ll_di = tv_dict_find(lp->ll_dict, key, len);
+
+ // When assigning to a scope dictionary check that a function and
+ // variable name is valid (only variable name unless it is l: or
+ // g: dictionary). Disallow overwriting a builtin function.
+ if (rettv != NULL && lp->ll_dict->dv_scope != 0) {
+ char prevval;
+ if (len != -1) {
+ prevval = key[len];
+ key[len] = NUL;
+ } else {
+ prevval = 0; // Avoid compiler warning.
+ }
+ bool wrong = ((lp->ll_dict->dv_scope == VAR_DEF_SCOPE
+ && tv_is_func(*rettv)
+ && var_wrong_func_name(key, lp->ll_di == NULL))
+ || !valid_varname(key));
+ if (len != -1) {
+ key[len] = prevval;
+ }
+ if (wrong) {
+ return GLV_FAIL;
+ }
+ }
+
+ if (lp->ll_di != NULL && tv_is_luafunc(&lp->ll_di->di_tv)
+ && len == -1 && rettv == NULL) {
+ semsg(e_illvar, "v:['lua']");
+ return GLV_FAIL;
+ }
+
+ if (lp->ll_di == NULL) {
+ // Can't add "v:" or "a:" variable.
+ if (lp->ll_dict == &vimvardict
+ || &lp->ll_dict->dv_hashtab == get_funccal_args_ht()) {
+ semsg(_(e_illvar), name);
+ return GLV_FAIL;
+ }
+
+ // Key does not exist in dict: may need to add it.
+ if (*p == '[' || *p == '.' || unlet) {
+ if (!quiet) {
+ semsg(_(e_dictkey), key);
+ }
+ return GLV_FAIL;
+ }
+ if (len == -1) {
+ lp->ll_newkey = xstrdup(key);
+ } else {
+ lp->ll_newkey = xmemdupz(key, (size_t)len);
+ }
+ *key_end = p;
+ return GLV_STOP;
+ // existing variable, need to check if it can be changed
+ } else if (!(flags & GLV_READ_ONLY)
+ && (var_check_ro(lp->ll_di->di_flags, name, (size_t)(p - name))
+ || var_check_lock(lp->ll_di->di_flags, name, (size_t)(p - name)))) {
+ return GLV_FAIL;
+ }
+
+ lp->ll_tv = &lp->ll_di->di_tv;
+
+ return GLV_OK;
+}
+
+/// Get an blob lval variable that can be assigned a value to: "name",
+/// "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", etc.
+///
+/// 'var1' specifies the starting blob index and 'var2' specifies the ending
+/// blob index. If the first index is not specified in a range, then 'empty1'
+/// is true. If 'quiet' is true, then error messages are not displayed for
+/// invalid indexes.
+///
+/// The blob is returned in 'lp'. Returns OK on success and FAIL on failure.
+static int get_lval_blob(lval_T *lp, typval_T *var1, typval_T *var2, bool empty1, bool quiet)
+{
+ const int bloblen = tv_blob_len(lp->ll_tv->vval.v_blob);
+
+ // Get the number and item for the only or first index of the List.
+ if (empty1) {
+ lp->ll_n1 = 0;
+ } else {
+ // Is number or string.
+ lp->ll_n1 = (int)tv_get_number(var1);
+ }
+
+ if (tv_blob_check_index(bloblen, lp->ll_n1, quiet) == FAIL) {
+ return FAIL;
+ }
+ if (lp->ll_range && !lp->ll_empty2) {
+ lp->ll_n2 = (int)tv_get_number(var2);
+ if (tv_blob_check_range(bloblen, lp->ll_n1, lp->ll_n2, quiet) == FAIL) {
+ return FAIL;
+ }
+ }
+
+ lp->ll_blob = lp->ll_tv->vval.v_blob;
+ lp->ll_tv = NULL;
+
+ return OK;
+}
+
+/// Get a List lval variable that can be assigned a value to: "name",
+/// "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", etc.
+///
+/// 'var1' specifies the starting List index and 'var2' specifies the ending
+/// List index. If the first index is not specified in a range, then 'empty1'
+/// is true. If 'quiet' is true, then error messages are not displayed for
+/// invalid indexes.
+///
+/// The List is returned in 'lp'. Returns OK on success and FAIL on failure.
+static int get_lval_list(lval_T *lp, typval_T *var1, typval_T *var2, bool empty1, int flags,
+ bool quiet)
+{
+ // Get the number and item for the only or first index of the List.
+ if (empty1) {
+ lp->ll_n1 = 0;
+ } else {
+ // Is number or string.
+ lp->ll_n1 = (int)tv_get_number(var1);
+ }
+
+ lp->ll_dict = NULL;
+ lp->ll_list = lp->ll_tv->vval.v_list;
+ lp->ll_li = tv_list_check_range_index_one(lp->ll_list, &lp->ll_n1, quiet);
+ if (lp->ll_li == NULL) {
+ return FAIL;
+ }
+
+ // May need to find the item or absolute index for the second
+ // index of a range.
+ // When no index given: "lp->ll_empty2" is true.
+ // Otherwise "lp->ll_n2" is set to the second index.
+ if (lp->ll_range && !lp->ll_empty2) {
+ lp->ll_n2 = (int)tv_get_number(var2); // Is number or string.
+ if (tv_list_check_range_index_two(lp->ll_list,
+ &lp->ll_n1, lp->ll_li,
+ &lp->ll_n2, quiet) == FAIL) {
+ return FAIL;
+ }
+ }
+
+ lp->ll_tv = TV_LIST_ITEM_TV(lp->ll_li);
+
+ return OK;
+}
+
/// Get the lval of a list/dict/blob subitem starting at "p". Loop
/// until no more [idx] or .key is following.
///
+/// If "rettv" is not NULL it points to the value to be assigned.
+/// "unlet" is true for ":unlet".
+///
/// @param[in] flags @see GetLvalFlags.
///
/// @return A pointer to the character after the subscript on success or NULL on
/// failure.
static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv, hashtab_T *ht,
- dictitem_T *v, int unlet, int flags)
+ dictitem_T *v, bool unlet, int flags)
{
- int quiet = flags & GLV_QUIET;
+ bool quiet = flags & GLV_QUIET;
typval_T var1;
var1.v_type = VAR_UNKNOWN;
typval_T var2;
var2.v_type = VAR_UNKNOWN;
bool empty1 = false;
+ int rc = FAIL;
// Loop until no more [idx] or .key is following.
while (*p == '[' || (*p == '.' && p[1] != '=' && p[1] != '.')) {
@@ -1426,7 +1619,7 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv
if (!quiet) {
emsg(_("E708: [:] must come last"));
}
- return NULL;
+ goto done;
}
int len = -1;
@@ -1450,12 +1643,11 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv
} else {
empty1 = false;
if (eval1(&p, &var1, &EVALARG_EVALUATE) == FAIL) { // Recursive!
- return NULL;
+ goto done;
}
if (!tv_check_str(&var1)) {
// Not a number or string.
- tv_clear(&var1);
- return NULL;
+ goto done;
}
p = skipwhite(p);
}
@@ -1466,8 +1658,7 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv
if (!quiet) {
emsg(_(e_cannot_slice_dictionary));
}
- tv_clear(&var1);
- return NULL;
+ goto done;
}
if (rettv != NULL
&& !(rettv->v_type == VAR_LIST && rettv->vval.v_list != NULL)
@@ -1475,8 +1666,7 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv
if (!quiet) {
emsg(_("E709: [:] requires a List or Blob value"));
}
- tv_clear(&var1);
- return NULL;
+ goto done;
}
p = skipwhite(p + 1);
if (*p == ']') {
@@ -1485,14 +1675,11 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv
lp->ll_empty2 = false;
// Recursive!
if (eval1(&p, &var2, &EVALARG_EVALUATE) == FAIL) {
- tv_clear(&var1);
- return NULL;
+ goto done;
}
if (!tv_check_str(&var2)) {
// Not a number or string.
- tv_clear(&var1);
- tv_clear(&var2);
- return NULL;
+ goto done;
}
}
lp->ll_range = true;
@@ -1504,9 +1691,7 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv
if (!quiet) {
emsg(_(e_missbrac));
}
- tv_clear(&var1);
- tv_clear(&var2);
- return NULL;
+ goto done;
}
// Skip to past ']'.
@@ -1514,142 +1699,37 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv
}
if (lp->ll_tv->v_type == VAR_DICT) {
- if (len == -1) {
- // "[key]": get key from "var1"
- key = (char *)tv_get_string(&var1); // is number or string
- }
- lp->ll_list = NULL;
- lp->ll_dict = lp->ll_tv->vval.v_dict;
- lp->ll_di = tv_dict_find(lp->ll_dict, key, len);
-
- // When assigning to a scope dictionary check that a function and
- // variable name is valid (only variable name unless it is l: or
- // g: dictionary). Disallow overwriting a builtin function.
- if (rettv != NULL && lp->ll_dict->dv_scope != 0) {
- char prevval;
- if (len != -1) {
- prevval = key[len];
- key[len] = NUL;
- } else {
- prevval = 0; // Avoid compiler warning.
- }
- bool wrong = ((lp->ll_dict->dv_scope == VAR_DEF_SCOPE
- && tv_is_func(*rettv)
- && var_wrong_func_name(key, lp->ll_di == NULL))
- || !valid_varname(key));
- if (len != -1) {
- key[len] = prevval;
- }
- if (wrong) {
- tv_clear(&var1);
- return NULL;
- }
+ glv_status_T glv_status = get_lval_dict_item(name, lp, key, len, &p, &var1,
+ flags, unlet, rettv);
+ if (glv_status == GLV_FAIL) {
+ goto done;
}
-
- if (lp->ll_di != NULL && tv_is_luafunc(&lp->ll_di->di_tv)
- && len == -1 && rettv == NULL) {
- tv_clear(&var1);
- semsg(e_illvar, "v:['lua']");
- return NULL;
- }
-
- if (lp->ll_di == NULL) {
- // Can't add "v:" or "a:" variable.
- if (lp->ll_dict == &vimvardict
- || &lp->ll_dict->dv_hashtab == get_funccal_args_ht()) {
- semsg(_(e_illvar), name);
- tv_clear(&var1);
- return NULL;
- }
-
- // Key does not exist in dict: may need to add it.
- if (*p == '[' || *p == '.' || unlet) {
- if (!quiet) {
- semsg(_(e_dictkey), key);
- }
- tv_clear(&var1);
- return NULL;
- }
- if (len == -1) {
- lp->ll_newkey = xstrdup(key);
- } else {
- lp->ll_newkey = xmemdupz(key, (size_t)len);
- }
- tv_clear(&var1);
+ if (glv_status == GLV_STOP) {
break;
- // existing variable, need to check if it can be changed
- } else if (!(flags & GLV_READ_ONLY)
- && (var_check_ro(lp->ll_di->di_flags, name, (size_t)(p - name))
- || var_check_lock(lp->ll_di->di_flags, name, (size_t)(p - name)))) {
- tv_clear(&var1);
- return NULL;
}
-
- tv_clear(&var1);
- lp->ll_tv = &lp->ll_di->di_tv;
} else if (lp->ll_tv->v_type == VAR_BLOB) {
- // Get the number and item for the only or first index of the List.
- if (empty1) {
- lp->ll_n1 = 0;
- } else {
- // Is number or string.
- lp->ll_n1 = (int)tv_get_number(&var1);
+ if (get_lval_blob(lp, &var1, &var2, empty1, quiet) == FAIL) {
+ goto done;
}
- tv_clear(&var1);
-
- const int bloblen = tv_blob_len(lp->ll_tv->vval.v_blob);
- if (tv_blob_check_index(bloblen, lp->ll_n1, quiet) == FAIL) {
- tv_clear(&var2);
- return NULL;
- }
- if (lp->ll_range && !lp->ll_empty2) {
- lp->ll_n2 = (int)tv_get_number(&var2);
- tv_clear(&var2);
- if (tv_blob_check_range(bloblen, lp->ll_n1, lp->ll_n2, quiet) == FAIL) {
- return NULL;
- }
- }
- lp->ll_blob = lp->ll_tv->vval.v_blob;
- lp->ll_tv = NULL;
break;
} else {
- // Get the number and item for the only or first index of the List.
- if (empty1) {
- lp->ll_n1 = 0;
- } else {
- // Is number or string.
- lp->ll_n1 = (int)tv_get_number(&var1);
+ if (get_lval_list(lp, &var1, &var2, empty1, flags, quiet) == FAIL) {
+ goto done;
}
- tv_clear(&var1);
-
- lp->ll_dict = NULL;
- lp->ll_list = lp->ll_tv->vval.v_list;
- lp->ll_li = tv_list_check_range_index_one(lp->ll_list, &lp->ll_n1, quiet);
- if (lp->ll_li == NULL) {
- tv_clear(&var2);
- return NULL;
- }
-
- // May need to find the item or absolute index for the second
- // index of a range.
- // When no index given: "lp->ll_empty2" is true.
- // Otherwise "lp->ll_n2" is set to the second index.
- if (lp->ll_range && !lp->ll_empty2) {
- lp->ll_n2 = (int)tv_get_number(&var2); // Is number or string.
- tv_clear(&var2);
- if (tv_list_check_range_index_two(lp->ll_list,
- &lp->ll_n1, lp->ll_li,
- &lp->ll_n2, quiet) == FAIL) {
- return NULL;
- }
- }
-
- lp->ll_tv = TV_LIST_ITEM_TV(lp->ll_li);
}
+
+ tv_clear(&var1);
+ tv_clear(&var2);
+ var1.v_type = VAR_UNKNOWN;
+ var2.v_type = VAR_UNKNOWN;
}
+ rc = OK;
+
+done:
tv_clear(&var1);
- return p;
+ tv_clear(&var2);
+ return rc == OK ? p : NULL;
}
/// Get an lvalue
@@ -3481,20 +3561,22 @@ static int eval_method(char **const arg, typval_T *const rettv, evalarg_T *const
int len;
char *name = *arg;
char *lua_funcname = NULL;
+ char *alias = NULL;
if (strnequal(name, "v:lua.", 6)) {
lua_funcname = name + 6;
*arg = (char *)skip_luafunc_name(lua_funcname);
*arg = skipwhite(*arg); // to detect trailing whitespace later
len = (int)(*arg - lua_funcname);
} else {
- char *alias;
len = get_name_len((const char **)arg, &alias, evaluate, true);
if (alias != NULL) {
name = alias;
}
}
- int ret;
+ char *tofree = NULL;
+ int ret = OK;
+
if (len <= 0) {
if (verbose) {
if (lua_funcname == NULL) {
@@ -3505,25 +3587,79 @@ static int eval_method(char **const arg, typval_T *const rettv, evalarg_T *const
}
ret = FAIL;
} else {
- if (**arg != '(') {
- if (verbose) {
- semsg(_(e_missingparen), name);
- }
- ret = FAIL;
- } else if (ascii_iswhite((*arg)[-1])) {
- if (verbose) {
- emsg(_(e_nowhitespace));
+ *arg = skipwhite(*arg);
+
+ // If there is no "(" immediately following, but there is further on,
+ // it can be "dict.Func()", "list[nr]", etc.
+ // Does not handle anything where "(" is part of the expression.
+ char *paren;
+ if (**arg != '(' && lua_funcname == NULL && alias == NULL
+ && (paren = vim_strchr(*arg, '(')) != NULL) {
+ *arg = name;
+ *paren = NUL;
+ typval_T ref;
+ ref.v_type = VAR_UNKNOWN;
+ if (eval7(arg, &ref, evalarg, false) == FAIL) {
+ *arg = name + len;
+ ret = FAIL;
+ } else if (*skipwhite(*arg) != NUL) {
+ if (verbose) {
+ semsg(_(e_trailing_arg), *arg);
+ }
+ ret = FAIL;
+ } else if (ref.v_type == VAR_FUNC && ref.vval.v_string != NULL) {
+ name = ref.vval.v_string;
+ ref.vval.v_string = NULL;
+ tofree = name;
+ len = (int)strlen(name);
+ } else if (ref.v_type == VAR_PARTIAL && ref.vval.v_partial != NULL) {
+ if (ref.vval.v_partial->pt_argc > 0 || ref.vval.v_partial->pt_dict != NULL) {
+ if (verbose) {
+ emsg(_(e_cannot_use_partial_here));
+ }
+ ret = FAIL;
+ } else {
+ name = xstrdup(partial_name(ref.vval.v_partial));
+ tofree = name;
+ if (name == NULL) {
+ ret = FAIL;
+ name = *arg;
+ } else {
+ len = (int)strlen(name);
+ }
+ }
+ } else {
+ if (verbose) {
+ semsg(_(e_not_callable_type_str), name);
+ }
+ ret = FAIL;
}
- ret = FAIL;
- } else if (lua_funcname != NULL) {
- if (evaluate) {
- rettv->v_type = VAR_PARTIAL;
- rettv->vval.v_partial = vvlua_partial;
- rettv->vval.v_partial->pt_refcount++;
+ tv_clear(&ref);
+ *paren = '(';
+ }
+
+ if (ret == OK) {
+ if (**arg != '(') {
+ if (verbose) {
+ semsg(_(e_missingparen), name);
+ }
+ ret = FAIL;
+ } else if (ascii_iswhite((*arg)[-1])) {
+ if (verbose) {
+ emsg(_(e_nowhitespace));
+ }
+ ret = FAIL;
+ } else if (lua_funcname != NULL) {
+ if (evaluate) {
+ rettv->v_type = VAR_PARTIAL;
+ rettv->vval.v_partial = vvlua_partial;
+ rettv->vval.v_partial->pt_refcount++;
+ }
+ ret = call_func_rettv(arg, evalarg, rettv, evaluate, NULL, &base, lua_funcname);
+ } else {
+ ret = eval_func(arg, evalarg, name, len, rettv,
+ evaluate ? EVAL_EVALUATE : 0, &base);
}
- ret = call_func_rettv(arg, evalarg, rettv, evaluate, NULL, &base, lua_funcname);
- } else {
- ret = eval_func(arg, evalarg, name, len, rettv, evaluate ? EVAL_EVALUATE : 0, &base);
}
}
@@ -3532,6 +3668,11 @@ static int eval_method(char **const arg, typval_T *const rettv, evalarg_T *const
if (evaluate) {
tv_clear(&base);
}
+ xfree(tofree);
+
+ if (alias != NULL) {
+ xfree(alias);
+ }
return ret;
}
@@ -3670,7 +3811,7 @@ static int check_can_index(typval_T *rettv, bool evaluate, bool verbose)
/// slice() function
void f_slice(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- if (check_can_index(argvars, true, false) != OK) {
+ if (check_can_index(&argvars[0], true, false) != OK) {
return;
}
diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c
index 3255e78d09..5b92f217d1 100644
--- a/src/nvim/eval/executor.c
+++ b/src/nvim/eval/executor.c
@@ -21,6 +21,174 @@
char *e_list_index_out_of_range_nr
= N_("E684: List index out of range: %" PRId64);
+/// Handle "blob1 += blob2".
+/// Returns OK or FAIL.
+static int tv_op_blob(typval_T *tv1, const typval_T *tv2, const char *op)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (*op != '+' || tv2->v_type != VAR_BLOB) {
+ return FAIL;
+ }
+
+ // Blob += Blob
+ if (tv2->vval.v_blob == NULL) {
+ return OK;
+ }
+
+ if (tv1->vval.v_blob == NULL) {
+ tv1->vval.v_blob = tv2->vval.v_blob;
+ tv1->vval.v_blob->bv_refcount++;
+ return OK;
+ }
+
+ blob_T *const b1 = tv1->vval.v_blob;
+ blob_T *const b2 = tv2->vval.v_blob;
+ const int len = tv_blob_len(b2);
+
+ for (int i = 0; i < len; i++) {
+ ga_append(&b1->bv_ga, tv_blob_get(b2, i));
+ }
+
+ return OK;
+}
+
+/// Handle "list1 += list2".
+/// Returns OK or FAIL.
+static int tv_op_list(typval_T *tv1, const typval_T *tv2, const char *op)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (*op != '+' || tv2->v_type != VAR_LIST) {
+ return FAIL;
+ }
+
+ // List += List
+ if (tv2->vval.v_list == NULL) {
+ return OK;
+ }
+
+ if (tv1->vval.v_list == NULL) {
+ tv1->vval.v_list = tv2->vval.v_list;
+ tv_list_ref(tv1->vval.v_list);
+ } else {
+ tv_list_extend(tv1->vval.v_list, tv2->vval.v_list, NULL);
+ }
+
+ return OK;
+}
+
+/// Handle number operations:
+/// nr += nr , nr -= nr , nr *=nr , nr /= nr , nr %= nr
+///
+/// Returns OK or FAIL.
+static int tv_op_number(typval_T *tv1, const typval_T *tv2, const char *op)
+ FUNC_ATTR_NONNULL_ALL
+{
+ varnumber_T n = tv_get_number(tv1);
+ if (tv2->v_type == VAR_FLOAT) {
+ float_T f = (float_T)n;
+ if (*op == '%') {
+ return FAIL;
+ }
+ switch (*op) {
+ case '+':
+ f += tv2->vval.v_float; break;
+ case '-':
+ f -= tv2->vval.v_float; break;
+ case '*':
+ f *= tv2->vval.v_float; break;
+ case '/':
+ f /= tv2->vval.v_float; break;
+ }
+ tv_clear(tv1);
+ tv1->v_type = VAR_FLOAT;
+ tv1->vval.v_float = f;
+ } else {
+ switch (*op) {
+ case '+':
+ n += tv_get_number(tv2); break;
+ case '-':
+ n -= tv_get_number(tv2); break;
+ case '*':
+ n *= tv_get_number(tv2); break;
+ case '/':
+ n = num_divide(n, tv_get_number(tv2)); break;
+ case '%':
+ n = num_modulus(n, tv_get_number(tv2)); break;
+ }
+ tv_clear(tv1);
+ tv1->v_type = VAR_NUMBER;
+ tv1->vval.v_number = n;
+ }
+
+ return OK;
+}
+
+/// Handle "str1 .= str2"
+/// Returns OK or FAIL.
+static int tv_op_string(typval_T *tv1, const typval_T *tv2, const char *op)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (tv2->v_type == VAR_FLOAT) {
+ return FAIL;
+ }
+
+ // str .= str
+ const char *tvs = tv_get_string(tv1);
+ char numbuf[NUMBUFLEN];
+ char *const s = concat_str(tvs, tv_get_string_buf(tv2, numbuf));
+ tv_clear(tv1);
+ tv1->v_type = VAR_STRING;
+ tv1->vval.v_string = s;
+
+ return OK;
+}
+
+/// Handle "tv1 += tv2", "tv1 -= tv2", "tv1 *= tv2", "tv1 /= tv2", "tv1 %= tv2"
+/// and "tv1 .= tv2"
+/// Returns OK or FAIL.
+static int tv_op_nr_or_string(typval_T *tv1, const typval_T *tv2, const char *op)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (tv2->v_type == VAR_LIST) {
+ return FAIL;
+ }
+
+ if (vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
+ return tv_op_number(tv1, tv2, op);
+ }
+
+ return tv_op_string(tv1, tv2, op);
+}
+
+/// Handle "f1 += f2", "f1 -= f2", "f1 *= f2", "f1 /= f2".
+/// Returns OK or FAIL.
+static int tv_op_float(typval_T *tv1, const typval_T *tv2, const char *op)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (*op == '%' || *op == '.'
+ || (tv2->v_type != VAR_FLOAT
+ && tv2->v_type != VAR_NUMBER
+ && tv2->v_type != VAR_STRING)) {
+ return FAIL;
+ }
+
+ const float_T f = (tv2->v_type == VAR_FLOAT
+ ? tv2->vval.v_float
+ : (float_T)tv_get_number(tv2));
+ switch (*op) {
+ case '+':
+ tv1->vval.v_float += f; break;
+ case '-':
+ tv1->vval.v_float -= f; break;
+ case '*':
+ tv1->vval.v_float *= f; break;
+ case '/':
+ tv1->vval.v_float /= f; break;
+ }
+
+ return OK;
+}
+
/// Handle tv1 += tv2, -=, *=, /=, %=, .=
///
/// @param[in,out] tv1 First operand, modified typval.
@@ -29,125 +197,45 @@ char *e_list_index_out_of_range_nr
///
/// @return OK or FAIL.
int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, const char *const op)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NO_SANITIZE_UNDEFINED
+ FUNC_ATTR_NONNULL_ALL
{
- // Can't do anything with a Funcref, a Dict or special value on the right.
- if (tv2->v_type != VAR_FUNC && tv2->v_type != VAR_DICT
- && tv2->v_type != VAR_BOOL && tv2->v_type != VAR_SPECIAL) {
- switch (tv1->v_type) {
- case VAR_DICT:
- case VAR_FUNC:
- case VAR_PARTIAL:
- case VAR_BOOL:
- case VAR_SPECIAL:
- break;
- case VAR_BLOB:
- if (*op != '+' || tv2->v_type != VAR_BLOB) {
- break;
- }
- // Blob += Blob
- if (tv1->vval.v_blob != NULL && tv2->vval.v_blob != NULL) {
- blob_T *const b1 = tv1->vval.v_blob;
- blob_T *const b2 = tv2->vval.v_blob;
- for (int i = 0; i < tv_blob_len(b2); i++) {
- ga_append(&b1->bv_ga, tv_blob_get(b2, i));
- }
- }
- return OK;
- case VAR_LIST:
- if (*op != '+' || tv2->v_type != VAR_LIST) {
- break;
- }
- // List += List
- if (tv1->vval.v_list != NULL && tv2->vval.v_list != NULL) {
- tv_list_extend(tv1->vval.v_list, tv2->vval.v_list, NULL);
- }
- return OK;
- case VAR_NUMBER:
- case VAR_STRING:
- if (tv2->v_type == VAR_LIST) {
- break;
- }
- if (vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
- // nr += nr or nr -= nr, nr *= nr, nr /= nr, nr %= nr
- varnumber_T n = tv_get_number(tv1);
- if (tv2->v_type == VAR_FLOAT) {
- float_T f = (float_T)n;
-
- if (*op == '%') {
- break;
- }
- switch (*op) {
- case '+':
- f += tv2->vval.v_float; break;
- case '-':
- f -= tv2->vval.v_float; break;
- case '*':
- f *= tv2->vval.v_float; break;
- case '/':
- f /= tv2->vval.v_float; break;
- }
- tv_clear(tv1);
- tv1->v_type = VAR_FLOAT;
- tv1->vval.v_float = f;
- } else {
- switch (*op) {
- case '+':
- n += tv_get_number(tv2); break;
- case '-':
- n -= tv_get_number(tv2); break;
- case '*':
- n *= tv_get_number(tv2); break;
- case '/':
- n = num_divide(n, tv_get_number(tv2)); break;
- case '%':
- n = num_modulus(n, tv_get_number(tv2)); break;
- }
- tv_clear(tv1);
- tv1->v_type = VAR_NUMBER;
- tv1->vval.v_number = n;
- }
- } else {
- // str .= str
- if (tv2->v_type == VAR_FLOAT) {
- break;
- }
- const char *tvs = tv_get_string(tv1);
- char numbuf[NUMBUFLEN];
- char *const s =
- concat_str(tvs, tv_get_string_buf(tv2, numbuf));
- tv_clear(tv1);
- tv1->v_type = VAR_STRING;
- tv1->vval.v_string = s;
- }
- return OK;
- case VAR_FLOAT: {
- if (*op == '%' || *op == '.'
- || (tv2->v_type != VAR_FLOAT
- && tv2->v_type != VAR_NUMBER
- && tv2->v_type != VAR_STRING)) {
- break;
- }
- const float_T f = (tv2->v_type == VAR_FLOAT
- ? tv2->vval.v_float
- : (float_T)tv_get_number(tv2));
- switch (*op) {
- case '+':
- tv1->vval.v_float += f; break;
- case '-':
- tv1->vval.v_float -= f; break;
- case '*':
- tv1->vval.v_float *= f; break;
- case '/':
- tv1->vval.v_float /= f; break;
- }
- return OK;
- }
- case VAR_UNKNOWN:
- abort();
- }
+ // Can't do anything with a Funcref or Dict on the right.
+ // v:true and friends only work with "..=".
+ if (tv2->v_type == VAR_FUNC || tv2->v_type == VAR_DICT
+ || ((tv2->v_type == VAR_BOOL || tv2->v_type == VAR_SPECIAL) && *op == '.')) {
+ semsg(_(e_letwrong), op);
+ return FAIL;
+ }
+
+ int retval = FAIL;
+
+ switch (tv1->v_type) {
+ case VAR_DICT:
+ case VAR_FUNC:
+ case VAR_PARTIAL:
+ case VAR_BOOL:
+ case VAR_SPECIAL:
+ break;
+ case VAR_BLOB:
+ retval = tv_op_blob(tv1, tv2, op);
+ break;
+ case VAR_LIST:
+ retval = tv_op_list(tv1, tv2, op);
+ break;
+ case VAR_NUMBER:
+ case VAR_STRING:
+ retval = tv_op_nr_or_string(tv1, tv2, op);
+ break;
+ case VAR_FLOAT:
+ retval = tv_op_float(tv1, tv2, op);
+ break;
+ case VAR_UNKNOWN:
+ abort();
+ }
+
+ if (retval != OK) {
+ semsg(_(e_letwrong), op);
}
- semsg(_(e_letwrong), op);
- return FAIL;
+ return retval;
}
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 22e7f383a5..bd977523c6 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -3880,6 +3880,7 @@ static varnumber_T indexof_blob(blob_T *b, varnumber_T startidx, typval_T *expr)
}
}
+ const int called_emsg_start = called_emsg;
for (varnumber_T idx = startidx; idx < tv_blob_len(b); idx++) {
set_vim_var_nr(VV_KEY, idx);
set_vim_var_nr(VV_VAL, tv_blob_get(b, (int)idx));
@@ -3887,6 +3888,10 @@ static varnumber_T indexof_blob(blob_T *b, varnumber_T startidx, typval_T *expr)
if (indexof_eval_expr(expr)) {
return idx;
}
+
+ if (called_emsg != called_emsg_start) {
+ return -1;
+ }
}
return -1;
@@ -3916,6 +3921,7 @@ static varnumber_T indexof_list(list_T *l, varnumber_T startidx, typval_T *expr)
}
}
+ const int called_emsg_start = called_emsg;
for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) {
set_vim_var_nr(VV_KEY, idx);
tv_copy(TV_LIST_ITEM_TV(item), get_vim_var_tv(VV_VAL));
@@ -3926,6 +3932,10 @@ static varnumber_T indexof_list(list_T *l, varnumber_T startidx, typval_T *expr)
if (found) {
return idx;
}
+
+ if (called_emsg != called_emsg_start) {
+ return -1;
+ }
}
return -1;
@@ -3942,7 +3952,8 @@ static void f_indexof(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- if ((argvars[1].v_type == VAR_STRING && argvars[1].vval.v_string == NULL)
+ if ((argvars[1].v_type == VAR_STRING
+ && (argvars[1].vval.v_string == NULL || *argvars[1].vval.v_string == NUL))
|| (argvars[1].v_type == VAR_FUNC && argvars[1].vval.v_partial == NULL)) {
return;
}