aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/eval/vars.c
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-11-29 22:40:31 +0000
committerJosh Rahm <joshuarahm@gmail.com>2023-11-29 22:40:31 +0000
commit339e2d15cc26fe86988ea06468d912a46c8d6f29 (patch)
treea6167fc8fcfc6ae2dc102f57b2473858eac34063 /src/nvim/eval/vars.c
parent067dc73729267c0262438a6fdd66e586f8496946 (diff)
parent4a8bf24ac690004aedf5540fa440e788459e5e34 (diff)
downloadrneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.tar.gz
rneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.tar.bz2
rneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.zip
Merge remote-tracking branch 'upstream/master' into fix_repeatcmdline
Diffstat (limited to 'src/nvim/eval/vars.c')
-rw-r--r--src/nvim/eval/vars.c1064
1 files changed, 678 insertions, 386 deletions
diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c
index 3e593151fc..2968f75f4d 100644
--- a/src/nvim/eval/vars.c
+++ b/src/nvim/eval/vars.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// eval/vars.c: functions for dealing with variables
#include <assert.h>
@@ -8,9 +5,11 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
+#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
@@ -19,7 +18,6 @@
#include "nvim/eval/encode.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
#include "nvim/eval/window.h"
@@ -27,19 +25,23 @@
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
+#include "nvim/func_attr.h"
+#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/os.h"
#include "nvim/search.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -50,8 +52,102 @@
#define DICT_MAXNEST 100 // maximum nesting of lists and dicts
-static char *e_letunexp = N_("E18: Unexpected characters in :let");
-static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
+static const char *e_letunexp = N_("E18: Unexpected characters in :let");
+static const char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
+static const char e_setting_v_str_to_value_with_wrong_type[]
+ = N_("E963: Setting v:%s to value with wrong type");
+static const char e_cannot_use_heredoc_here[]
+ = N_("E991: Cannot use =<< here");
+
+/// Evaluate one Vim expression {expr} in string "p" and append the
+/// resulting string to "gap". "p" points to the opening "{".
+/// When "evaluate" is false only skip over the expression.
+/// Return a pointer to the character after "}", NULL for an error.
+char *eval_one_expr_in_str(char *p, garray_T *gap, bool evaluate)
+{
+ char *block_start = skipwhite(p + 1); // skip the opening {
+ char *block_end = block_start;
+
+ if (*block_start == NUL) {
+ semsg(_(e_missing_close_curly_str), p);
+ return NULL;
+ }
+ if (skip_expr(&block_end, NULL) == FAIL) {
+ return NULL;
+ }
+ block_end = skipwhite(block_end);
+ if (*block_end != '}') {
+ semsg(_(e_missing_close_curly_str), p);
+ return NULL;
+ }
+ if (evaluate) {
+ *block_end = NUL;
+ char *expr_val = eval_to_string(block_start, true);
+ *block_end = '}';
+ if (expr_val == NULL) {
+ return NULL;
+ }
+ ga_concat(gap, expr_val);
+ xfree(expr_val);
+ }
+
+ return block_end + 1;
+}
+
+/// Evaluate all the Vim expressions {expr} in "str" and return the resulting
+/// string in allocated memory. "{{" is reduced to "{" and "}}" to "}".
+/// Used for a heredoc assignment.
+/// Returns NULL for an error.
+char *eval_all_expr_in_str(char *str)
+{
+ garray_T ga;
+ ga_init(&ga, 1, 80);
+ char *p = str;
+
+ while (*p != NUL) {
+ bool escaped_brace = false;
+
+ // Look for a block start.
+ char *lit_start = p;
+ while (*p != '{' && *p != '}' && *p != NUL) {
+ p++;
+ }
+
+ if (*p != NUL && *p == p[1]) {
+ // Escaped brace, unescape and continue.
+ // Include the brace in the literal string.
+ p++;
+ escaped_brace = true;
+ } else if (*p == '}') {
+ semsg(_(e_stray_closing_curly_str), str);
+ ga_clear(&ga);
+ return NULL;
+ }
+
+ // Append the literal part.
+ ga_concat_len(&ga, lit_start, (size_t)(p - lit_start));
+
+ if (*p == NUL) {
+ break;
+ }
+
+ if (escaped_brace) {
+ // Skip the second brace.
+ p++;
+ continue;
+ }
+
+ // Evaluate the expression and append the result.
+ p = eval_one_expr_in_str(p, &ga, true);
+ if (p == NULL) {
+ ga_clear(&ga);
+ return NULL;
+ }
+ }
+ ga_append(&ga, NUL);
+
+ return ga.ga_data;
+}
/// Get a list of lines from a HERE document. The here document is a list of
/// lines surrounded by a marker.
@@ -65,64 +161,91 @@ static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
/// marker, then the leading indentation before the lines (matching the
/// indentation in the 'cmd' line) is stripped.
///
-/// @return a List with {lines} or NULL.
-static list_T *heredoc_get(exarg_T *eap, char *cmd)
+/// When getting lines for an embedded script (e.g. python, lua, perl, ruby,
+/// tcl, mzscheme), "script_get" is set to true. In this case, if the marker is
+/// missing, then '.' is accepted as a marker.
+///
+/// @return a List with {lines} or NULL on failure.
+list_T *heredoc_get(exarg_T *eap, char *cmd, bool script_get)
{
char *marker;
- char *p;
int marker_indent_len = 0;
int text_indent_len = 0;
char *text_indent = NULL;
+ char dot[] = ".";
if (eap->getline == NULL) {
- emsg(_("E991: cannot use =<< here"));
+ emsg(_(e_cannot_use_heredoc_here));
return NULL;
}
// Check for the optional 'trim' word before the marker
cmd = skipwhite(cmd);
- if (strncmp(cmd, "trim", 4) == 0
- && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) {
- cmd = skipwhite(cmd + 4);
-
- // Trim the indentation from all the lines in the here document.
- // The amount of indentation trimmed is the same as the indentation of
- // the first line after the :let command line. To find the end marker
- // the indent of the :let command line is trimmed.
- p = *eap->cmdlinep;
- while (ascii_iswhite(*p)) {
- p++;
- marker_indent_len++;
+ bool evalstr = false;
+ bool eval_failed = false;
+ while (true) {
+ if (strncmp(cmd, "trim", 4) == 0
+ && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) {
+ cmd = skipwhite(cmd + 4);
+
+ // Trim the indentation from all the lines in the here document.
+ // The amount of indentation trimmed is the same as the indentation
+ // of the first line after the :let command line. To find the end
+ // marker the indent of the :let command line is trimmed.
+ char *p = *eap->cmdlinep;
+ while (ascii_iswhite(*p)) {
+ p++;
+ marker_indent_len++;
+ }
+ text_indent_len = -1;
+
+ continue;
+ }
+ if (strncmp(cmd, "eval", 4) == 0
+ && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) {
+ cmd = skipwhite(cmd + 4);
+ evalstr = true;
+ continue;
}
- text_indent_len = -1;
+ break;
}
// The marker is the next word.
if (*cmd != NUL && *cmd != '"') {
marker = skipwhite(cmd);
- p = skiptowhite(marker);
+ char *p = skiptowhite(marker);
if (*skipwhite(p) != NUL && *skipwhite(p) != '"') {
semsg(_(e_trailing_arg), p);
return NULL;
}
*p = NUL;
- if (islower((uint8_t)(*marker))) {
+ if (!script_get && islower((uint8_t)(*marker))) {
emsg(_("E221: Marker cannot start with lower case letter"));
return NULL;
}
} else {
- emsg(_("E172: Missing marker"));
- return NULL;
+ // When getting lines for an embedded script, if the marker is missing,
+ // accept '.' as the marker.
+ if (script_get) {
+ marker = dot;
+ } else {
+ emsg(_("E172: Missing marker"));
+ return NULL;
+ }
}
+ char *theline = NULL;
list_T *l = tv_list_alloc(0);
- for (;;) {
+ while (true) {
int mi = 0;
int ti = 0;
- char *theline = eap->getline(NUL, eap->cookie, 0, false);
+ xfree(theline);
+ theline = eap->getline(NUL, eap->cookie, 0, false);
if (theline == NULL) {
- semsg(_("E990: Missing end marker '%s'"), marker);
+ if (!script_get) {
+ semsg(_("E990: Missing end marker '%s'"), marker);
+ }
break;
}
@@ -133,18 +256,24 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd)
mi = marker_indent_len;
}
if (strcmp(marker, theline + mi) == 0) {
- xfree(theline);
break;
}
+
+ // If expression evaluation failed in the heredoc, then skip till the
+ // end marker.
+ if (eval_failed) {
+ continue;
+ }
+
if (text_indent_len == -1 && *theline != NUL) {
// set the text indent from the first line.
- p = theline;
+ char *p = theline;
text_indent_len = 0;
while (ascii_iswhite(*p)) {
p++;
text_indent_len++;
}
- text_indent = xstrnsave(theline, (size_t)text_indent_len);
+ text_indent = xmemdupz(theline, (size_t)text_indent_len);
}
// with "trim": skip the indent matching the first line
if (text_indent != NULL) {
@@ -155,11 +284,28 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd)
}
}
- tv_list_append_string(l, theline + ti, -1);
- xfree(theline);
+ char *str = theline + ti;
+ if (evalstr && !eap->skip) {
+ str = eval_all_expr_in_str(str);
+ if (str == NULL) {
+ // expression evaluation failed
+ eval_failed = true;
+ continue;
+ }
+ xfree(theline);
+ theline = str;
+ }
+
+ tv_list_append_string(l, str, -1);
}
+ xfree(theline);
xfree(text_indent);
+ if (eval_failed) {
+ // expression evaluation in the heredoc failed
+ tv_list_free(l);
+ return NULL;
+ }
return l;
}
@@ -175,32 +321,23 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd)
/// ":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);
-}
-
+///
/// ":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);
-}
-
-static void ex_let_const(exarg_T *eap, const bool is_const)
+void ex_let(exarg_T *eap)
{
+ const bool is_const = eap->cmdidx == CMD_const;
char *arg = eap->arg;
char *expr = NULL;
typval_T rettv;
- int i;
int var_count = 0;
int semicolon = 0;
char op[2];
- char *argend;
+ const char *argend;
int first = true;
- argend = (char *)skip_var_list(arg, &var_count, &semicolon);
+ argend = skip_var_list(arg, &var_count, &semicolon);
if (argend == NULL) {
return;
}
@@ -208,14 +345,16 @@ static void ex_let_const(exarg_T *eap, const bool is_const)
argend--;
}
expr = skipwhite(argend);
- if (*expr != '=' && !((vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL
- && expr[1] == '=') || strncmp(expr, "..=", 3) == 0)) {
+ bool concat = strncmp(expr, "..=", 3) == 0;
+ bool has_assign = *expr == '=' || (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL
+ && expr[1] == '=');
+ if (!has_assign && !concat) {
// ":let" without "=": list variables
if (*arg == '[') {
emsg(_(e_invarg));
} else if (!ends_excmd(*arg)) {
// ":let var1 var2"
- arg = (char *)list_arg_vars(eap, (const char *)arg, &first);
+ arg = (char *)list_arg_vars(eap, arg, &first);
} else if (!eap->skip) {
// ":let"
list_glob_vars(&first);
@@ -227,50 +366,58 @@ static void ex_let_const(exarg_T *eap, const bool is_const)
list_vim_vars(&first);
}
eap->nextcmd = check_nextcmd(arg);
- } else if (expr[0] == '=' && expr[1] == '<' && expr[2] == '<') {
+ return;
+ }
+
+ if (expr[0] == '=' && expr[1] == '<' && expr[2] == '<') {
// HERE document
- list_T *l = heredoc_get(eap, expr + 3);
+ list_T *l = heredoc_get(eap, expr + 3, false);
if (l != NULL) {
tv_list_set_ret(&rettv, l);
if (!eap->skip) {
op[0] = '=';
op[1] = NUL;
- (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count,
- is_const, (char *)op);
+ (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, is_const, op);
}
tv_clear(&rettv);
}
- } else {
- op[0] = '=';
- op[1] = NUL;
- if (*expr != '=') {
- if (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL) {
- op[0] = *expr; // +=, -=, *=, /=, %= or .=
- if (expr[0] == '.' && expr[1] == '.') { // ..=
- expr++;
- }
- }
- expr += 2;
- } else {
- expr += 1;
- }
+ return;
+ }
- expr = skipwhite(expr);
+ rettv.v_type = VAR_UNKNOWN;
- if (eap->skip) {
- emsg_skip++;
- }
- i = eval0(expr, &rettv, &eap->nextcmd, !eap->skip);
- if (eap->skip) {
- if (i != FAIL) {
- tv_clear(&rettv);
+ op[0] = '=';
+ op[1] = NUL;
+ if (*expr != '=') {
+ if (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL) {
+ op[0] = *expr; // +=, -=, *=, /=, %= or .=
+ if (expr[0] == '.' && expr[1] == '.') { // ..=
+ expr++;
}
- emsg_skip--;
- } else if (i != FAIL) {
- (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count,
- is_const, (char *)op);
- tv_clear(&rettv);
}
+ expr += 2;
+ } else {
+ expr += 1;
+ }
+
+ expr = skipwhite(expr);
+
+ if (eap->skip) {
+ emsg_skip++;
+ }
+ evalarg_T evalarg;
+ fill_evalarg_from_eap(&evalarg, eap, eap->skip);
+ int eval_res = eval0(expr, &rettv, eap, &evalarg);
+ if (eap->skip) {
+ emsg_skip--;
+ }
+ clear_evalarg(&evalarg, eap);
+
+ if (!eap->skip && eval_res != FAIL) {
+ (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, is_const, op);
+ }
+ if (eval_res != FAIL) {
+ tv_clear(&rettv);
}
}
@@ -369,15 +516,13 @@ int ex_let_vars(char *arg_start, typval_T *tv, int copy, int semicolon, int var_
/// @return NULL for an error.
const char *skip_var_list(const char *arg, int *var_count, int *semicolon)
{
- const char *p;
- const char *s;
-
if (*arg == '[') {
+ const char *s;
// "[var, var]": find the matching ']'.
- p = arg;
- for (;;) {
+ const char *p = arg;
+ while (true) {
p = skipwhite(p + 1); // skip whites after '[', ';' or ','
- s = skip_var_one((char *)p);
+ s = skip_var_one(p);
if (s == p) {
semsg(_(e_invarg2), p);
return NULL;
@@ -400,7 +545,7 @@ const char *skip_var_list(const char *arg, int *var_count, int *semicolon)
}
return p + 1;
}
- return skip_var_one((char *)arg);
+ return skip_var_one(arg);
}
/// Skip one (assignable) variable name, including @r, $VAR, &option, d.key,
@@ -410,8 +555,8 @@ static const char *skip_var_one(const char *arg)
if (*arg == '@' && arg[1] != NUL) {
return arg + 2;
}
- return (char *)find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg,
- NULL, NULL, FNE_INCL_BR | FNE_CHECK_START);
+ return find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg,
+ NULL, NULL, FNE_INCL_BR | FNE_CHECK_START);
}
/// List variables for hashtab "ht" with prefix "prefix".
@@ -432,7 +577,7 @@ void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, int *firs
// apply :filter /pat/ to variable name
xstrlcpy(buf, prefix, IOSIZE);
- xstrlcat(buf, (char *)di->di_key, IOSIZE);
+ xstrlcat(buf, di->di_key, IOSIZE);
if (message_filtered(buf)) {
continue;
}
@@ -504,13 +649,12 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first)
if (tofree != NULL) {
name = tofree;
}
- if (get_var_tv(name, len, &tv, NULL, true, false)
- == FAIL) {
+ if (eval_variable(name, len, &tv, NULL, true, false) == FAIL) {
error = true;
} else {
// handle d.key, l[idx], f(expr)
const char *const arg_subsc = arg;
- if (handle_subscript(&arg, &tv, true, true, name, &name) == FAIL) {
+ if (handle_subscript(&arg, &tv, &EVALARG_EVALUATE, true) == FAIL) {
error = true;
} else {
if (arg == arg_subsc && len == 2 && name[1] == ':') {
@@ -553,12 +697,191 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first)
xfree(tofree);
}
- arg = (const char *)skipwhite(arg);
+ arg = skipwhite(arg);
}
return arg;
}
+/// Set an environment variable, part of ex_let_one().
+static char *ex_let_env(char *arg, typval_T *const tv, const bool is_const,
+ const char *const endchars, const char *const op)
+ FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (is_const) {
+ emsg(_("E996: Cannot lock an environment variable"));
+ return NULL;
+ }
+
+ // Find the end of the name.
+ char *arg_end = NULL;
+ arg++;
+ char *name = arg;
+ int len = get_env_len((const char **)&arg);
+ if (len == 0) {
+ semsg(_(e_invarg2), name - 1);
+ } else {
+ if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
+ semsg(_(e_letwrong), op);
+ } else if (endchars != NULL
+ && vim_strchr(endchars, (uint8_t)(*skipwhite(arg))) == NULL) {
+ emsg(_(e_letunexp));
+ } else if (!check_secure()) {
+ char *tofree = NULL;
+ const char c1 = name[len];
+ name[len] = NUL;
+ const char *p = tv_get_string_chk(tv);
+ if (p != NULL && op != NULL && *op == '.') {
+ char *s = vim_getenv(name);
+ if (s != NULL) {
+ tofree = concat_str(s, p);
+ p = tofree;
+ xfree(s);
+ }
+ }
+ if (p != NULL) {
+ vim_setenv_ext(name, p);
+ arg_end = arg;
+ }
+ name[len] = c1;
+ xfree(tofree);
+ }
+ }
+ return arg_end;
+}
+
+/// Set an option, part of ex_let_one().
+static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const,
+ const char *const endchars, const char *const op)
+ FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (is_const) {
+ emsg(_("E996: Cannot lock an option"));
+ return NULL;
+ }
+
+ // Find the end of the name.
+ char *arg_end = NULL;
+ int scope;
+ char *const p = (char *)find_option_end((const char **)&arg, &scope);
+ if (p == NULL
+ || (endchars != NULL
+ && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL)) {
+ emsg(_(e_letunexp));
+ return NULL;
+ }
+
+ const char c1 = *p;
+ *p = NUL;
+
+ uint32_t opt_p_flags;
+ bool hidden;
+ OptVal curval = get_option_value(arg, &opt_p_flags, scope, &hidden);
+ OptVal newval = NIL_OPTVAL;
+ if (curval.type == kOptValTypeNil && arg[0] != 't' && arg[1] != '_') {
+ semsg(_(e_unknown_option2), arg);
+ goto theend;
+ }
+ if (op != NULL && *op != '='
+ && ((curval.type != kOptValTypeString && *op == '.')
+ || (curval.type == kOptValTypeString && *op != '.'))) {
+ semsg(_(e_letwrong), op);
+ goto theend;
+ }
+
+ bool error;
+ newval = tv_to_optval(tv, arg, opt_p_flags, &error);
+ if (error) {
+ goto theend;
+ }
+
+ // Don't assume current and new values are of the same type in order to future-proof the code for
+ // when an option can have multiple types.
+ const bool is_num = ((curval.type == kOptValTypeNumber || curval.type == kOptValTypeBoolean)
+ && (newval.type == kOptValTypeNumber || newval.type == kOptValTypeBoolean));
+ const bool is_string = curval.type == kOptValTypeString && newval.type == kOptValTypeString;
+
+ if (op != NULL && *op != '=') {
+ if (!hidden && is_num) { // number or bool
+ OptInt cur_n = curval.type == kOptValTypeNumber ? curval.data.number : curval.data.boolean;
+ OptInt new_n = newval.type == kOptValTypeNumber ? newval.data.number : newval.data.boolean;
+
+ switch (*op) {
+ case '+':
+ new_n = cur_n + new_n; break;
+ case '-':
+ new_n = cur_n - new_n; break;
+ case '*':
+ new_n = cur_n * new_n; break;
+ case '/':
+ new_n = num_divide(cur_n, new_n); break;
+ case '%':
+ new_n = num_modulus(cur_n, new_n); break;
+ }
+
+ if (curval.type == kOptValTypeNumber) {
+ newval = NUMBER_OPTVAL(new_n);
+ } else {
+ newval = BOOLEAN_OPTVAL(TRISTATE_FROM_INT(new_n));
+ }
+ } else if (!hidden && is_string
+ && curval.data.string.data != NULL && newval.data.string.data != NULL) { // string
+ OptVal newval_old = newval;
+ newval = CSTR_AS_OPTVAL(concat_str(curval.data.string.data, newval.data.string.data));
+ optval_free(newval_old);
+ }
+ }
+
+ const char *err = set_option_value(arg, newval, scope);
+ arg_end = p;
+ if (err != NULL) {
+ emsg(_(err));
+ }
+
+theend:
+ *p = c1;
+ optval_free(curval);
+ optval_free(newval);
+ return arg_end;
+}
+
+/// Set a register, part of ex_let_one().
+static char *ex_let_register(char *arg, typval_T *const tv, const bool is_const,
+ const char *const endchars, const char *const op)
+ FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (is_const) {
+ emsg(_("E996: Cannot lock a register"));
+ return NULL;
+ }
+
+ char *arg_end = NULL;
+ arg++;
+ if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
+ semsg(_(e_letwrong), op);
+ } else if (endchars != NULL
+ && vim_strchr(endchars, (uint8_t)(*skipwhite(arg + 1))) == NULL) {
+ emsg(_(e_letunexp));
+ } else {
+ char *ptofree = NULL;
+ const char *p = tv_get_string_chk(tv);
+ if (p != NULL && op != NULL && *op == '.') {
+ char *s = get_reg_contents(*arg == '@' ? '"' : *arg, kGRegExprSrc);
+ if (s != NULL) {
+ ptofree = concat_str(s, p);
+ p = ptofree;
+ xfree(s);
+ }
+ }
+ if (p != NULL) {
+ write_reg_contents(*arg == '@' ? '"' : *arg, p, (ssize_t)strlen(p), false);
+ arg_end = arg + 1;
+ }
+ xfree(ptofree);
+ }
+ return arg_end;
+}
+
/// Set one item of `:let var = expr` or `:let [v1, v2] = list` to its value
///
/// @param[in] arg Start of the variable name.
@@ -575,176 +898,22 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo
FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
{
char *arg_end = NULL;
- int len;
- // ":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 = arg;
- len = get_env_len((const char **)&arg);
- if (len == 0) {
- semsg(_(e_invarg2), name - 1);
- } else {
- if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
- semsg(_(e_letwrong), op);
- } else if (endchars != NULL
- && vim_strchr(endchars, (uint8_t)(*skipwhite(arg))) == NULL) {
- emsg(_(e_letunexp));
- } else if (!check_secure()) {
- char *tofree = NULL;
- const char c1 = name[len];
- name[len] = NUL;
- const char *p = tv_get_string_chk(tv);
- if (p != NULL && op != NULL && *op == '.') {
- char *s = vim_getenv(name);
- if (s != NULL) {
- tofree = concat_str(s, p);
- p = (const char *)tofree;
- xfree(s);
- }
- }
- if (p != NULL) {
- vim_setenv_ext(name, p);
- arg_end = arg;
- }
- name[len] = c1;
- xfree(tofree);
- }
- }
+ // ":let $VAR = expr": Set environment variable.
+ return ex_let_env(arg, tv, is_const, endchars, op);
+ } else if (*arg == '&') {
// ":let &option = expr": Set option value.
// ":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.
- int scope;
- char *const p = (char *)find_option_end((const char **)&arg, &scope);
- if (p == NULL
- || (endchars != NULL
- && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL)) {
- emsg(_(e_letunexp));
- } else {
- varnumber_T n = 0;
- getoption_T opt_type;
- long numval;
- char *stringval = NULL;
- const char *s = NULL;
- bool failed = false;
- uint32_t opt_p_flags;
- char *tofree = NULL;
-
- const char c1 = *p;
- *p = NUL;
-
- opt_type = get_option_value(arg, &numval, &stringval, &opt_p_flags, scope);
- if (opt_type == gov_bool
- || opt_type == gov_number
- || opt_type == gov_hidden_bool
- || opt_type == gov_hidden_number) {
- // number, possibly hidden
- n = (long)tv_get_number(tv);
- }
-
- if ((opt_p_flags & P_FUNC) && tv_is_func(*tv)) {
- // If the option can be set to a function reference or a lambda
- // and the passed value is a function reference, then convert it to
- // the name (string) of the function reference.
- s = tofree = encode_tv2string(tv, NULL);
- } else if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) {
- // Avoid setting a string option to the text "v:false" or similar.
- s = tv_get_string_chk(tv);
- }
-
- if (op != NULL && *op != '=') {
- if (((opt_type == gov_bool || opt_type == gov_number) && *op == '.')
- || (opt_type == gov_string && *op != '.')) {
- semsg(_(e_letwrong), op);
- failed = true; // don't set the value
- } else {
- // number or bool
- if (opt_type == gov_number || opt_type == gov_bool) {
- switch (*op) {
- case '+':
- n = numval + n; break;
- case '-':
- n = numval - n; break;
- case '*':
- n = numval * n; break;
- case '/':
- n = num_divide(numval, n); break;
- case '%':
- n = num_modulus(numval, n); break;
- }
- s = NULL;
- } else if (opt_type == gov_string && stringval != NULL && s != NULL) {
- // string
- char *const oldstringval = stringval;
- stringval = concat_str(stringval, s);
- xfree(oldstringval);
- s = stringval;
- }
- }
- }
-
- if (!failed) {
- if (opt_type != gov_string || s != NULL) {
- char *err = set_option_value(arg, n, s, scope);
- arg_end = p;
- if (err != NULL) {
- emsg(_(err));
- }
- } else {
- emsg(_(e_stringreq));
- }
- }
- *p = c1;
- xfree(stringval);
- xfree(tofree);
- }
- // ":let @r = expr": Set register contents.
+ return ex_let_option(arg, tv, is_const, endchars, op);
} else if (*arg == '@') {
- if (is_const) {
- emsg(_("E996: Cannot lock a register"));
- return NULL;
- }
- arg++;
- if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
- semsg(_(e_letwrong), op);
- } else if (endchars != NULL
- && vim_strchr(endchars, (uint8_t)(*skipwhite(arg + 1))) == NULL) {
- emsg(_(e_letunexp));
- } else {
- char *s;
-
- char *ptofree = NULL;
- const char *p = tv_get_string_chk(tv);
- if (p != NULL && op != NULL && *op == '.') {
- s = get_reg_contents(*arg == '@' ? '"' : *arg, kGRegExprSrc);
- if (s != NULL) {
- ptofree = concat_str(s, p);
- p = (const char *)ptofree;
- xfree(s);
- }
- }
- if (p != NULL) {
- write_reg_contents(*arg == '@' ? '"' : *arg, p, (ssize_t)strlen(p), false);
- arg_end = arg + 1;
- }
- xfree(ptofree);
- }
+ // ":let @r = expr": Set register contents.
+ return ex_let_register(arg, tv, is_const, endchars, op);
+ } else if (eval_isnamec1(*arg) || *arg == '{') {
// ":let var = expr": Set internal variable.
// ":let {expr} = expr": Idem, name made with curly braces
- } else if (eval_isnamec1(*arg) || *arg == '{') {
lval_T lv;
-
char *const p = get_lval(arg, tv, &lv, false, false, 0, FNE_CHECK_START);
if (p != NULL && lv.ll_name != NULL) {
if (endchars != NULL && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL) {
@@ -803,7 +972,7 @@ static void ex_unletlock(exarg_T *eap, char *argstart, int deep, ex_unletlock_ca
do {
if (*arg == '$') {
- lv.ll_name = (const char *)arg;
+ lv.ll_name = arg;
lv.ll_tv = NULL;
arg++;
if (get_env_len((const char **)&arg) == 0) {
@@ -861,10 +1030,9 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_
{
int forceit = eap->forceit;
int ret = OK;
- int cc;
if (lp->ll_tv == NULL) {
- cc = (uint8_t)(*name_end);
+ int cc = (uint8_t)(*name_end);
*name_end = NUL;
// Environment variable, normal name or expanded name.
@@ -886,57 +1054,57 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_
lp->ll_name_len))) {
return FAIL;
} else if (lp->ll_range) {
- assert(lp->ll_list != NULL);
- // Delete a range of List items.
- listitem_T *const first_li = lp->ll_li;
- listitem_T *last_li = first_li;
- for (;;) {
- listitem_T *const li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li);
- if (value_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock,
- lp->ll_name,
- lp->ll_name_len)) {
- return false;
- }
- lp->ll_li = li;
- lp->ll_n1++;
- if (lp->ll_li == NULL || (!lp->ll_empty2 && lp->ll_n2 < lp->ll_n1)) {
- break;
- }
- last_li = lp->ll_li;
- }
- tv_list_remove_items(lp->ll_list, first_li, last_li);
+ tv_list_unlet_range(lp->ll_list, lp->ll_li, lp->ll_n1, !lp->ll_empty2, lp->ll_n2);
+ } else if (lp->ll_list != NULL) {
+ // unlet a List item.
+ tv_list_item_remove(lp->ll_list, lp->ll_li);
} else {
- if (lp->ll_list != NULL) {
- // unlet a List item.
- tv_list_item_remove(lp->ll_list, lp->ll_li);
- } else {
- // unlet a Dictionary item.
- dict_T *d = lp->ll_dict;
- assert(d != NULL);
- dictitem_T *di = lp->ll_di;
- bool watched = tv_dict_is_watched(d);
- char *key = NULL;
- typval_T oldtv;
+ // unlet a Dictionary item.
+ dict_T *d = lp->ll_dict;
+ assert(d != NULL);
+ dictitem_T *di = lp->ll_di;
+ bool watched = tv_dict_is_watched(d);
+ char *key = NULL;
+ typval_T oldtv;
- if (watched) {
- tv_copy(&di->di_tv, &oldtv);
- // need to save key because dictitem_remove will free it
- key = xstrdup((char *)di->di_key);
- }
+ if (watched) {
+ tv_copy(&di->di_tv, &oldtv);
+ // need to save key because dictitem_remove will free it
+ key = xstrdup(di->di_key);
+ }
- tv_dict_item_remove(d, di);
+ tv_dict_item_remove(d, di);
- if (watched) {
- tv_dict_watcher_notify(d, key, NULL, &oldtv);
- tv_clear(&oldtv);
- xfree(key);
- }
+ if (watched) {
+ tv_dict_watcher_notify(d, key, NULL, &oldtv);
+ tv_clear(&oldtv);
+ xfree(key);
}
}
return ret;
}
+/// Unlet one item or a range of items from a list.
+/// Return OK or FAIL.
+static void tv_list_unlet_range(list_T *const l, listitem_T *const li_first, const int n1_arg,
+ const bool has_n2, const int n2)
+{
+ assert(l != NULL);
+ // Delete a range of List items.
+ listitem_T *li_last = li_first;
+ int n1 = n1_arg;
+ while (true) {
+ listitem_T *const li = TV_LIST_ITEM_NEXT(l, li_last);
+ n1++;
+ if (li == NULL || (has_n2 && n2 < n1)) {
+ break;
+ }
+ li_last = li;
+ }
+ tv_list_remove_items(l, li_first, li_last);
+}
+
/// unlet a variable
///
/// @param[in] name Variable name to unlet.
@@ -1081,8 +1249,8 @@ static int do_lock_var(lval_T *lp, char *name_end FUNC_ATTR_UNUSED, exarg_T *eap
/// @param dip non-NULL when typval's dict item is needed
/// @param verbose may give error message
/// @param no_autoload do not use script autoloading
-int get_var_tv(const char *name, int len, typval_T *rettv, dictitem_T **dip, bool verbose,
- bool no_autoload)
+int eval_variable(const char *name, int len, typval_T *rettv, dictitem_T **dip, bool verbose,
+ bool no_autoload)
{
int ret = OK;
typval_T *tv = NULL;
@@ -1157,7 +1325,7 @@ void vars_clear_ext(hashtab_T *ht, int free_val)
}
}
hash_clear(ht);
- ht->ht_used = 0;
+ hash_init(ht);
}
/// Delete a variable from hashtab "ht" at item "hi".
@@ -1175,7 +1343,7 @@ void delete_var(hashtab_T *ht, hashitem_T *hi)
static void list_one_var(dictitem_T *v, const char *prefix, int *first)
{
char *const s = encode_tv2echo(&v->di_tv, NULL);
- list_one_var_a(prefix, (const char *)v->di_key, (ptrdiff_t)strlen((char *)v->di_key),
+ list_one_var_a(prefix, v->di_key, (ptrdiff_t)strlen(v->di_key),
v->di_tv.v_type, (s == NULL ? "" : s), first);
xfree(s);
}
@@ -1186,11 +1354,11 @@ static void list_one_var(dictitem_T *v, const char *prefix, int *first)
static void list_one_var_a(const char *prefix, const char *name, const ptrdiff_t name_len,
const VarType type, const char *string, int *first)
{
- // don't use msg() or msg_attr() to avoid overwriting "v:statusmsg"
+ // don't use msg() to avoid overwriting "v:statusmsg"
msg_start();
msg_puts(prefix);
if (name != NULL) { // "a:" vars don't have a name stored
- msg_puts_attr_len(name, name_len, 0);
+ msg_puts_len(name, name_len, 0);
}
msg_putchar(' ');
msg_advance(22);
@@ -1212,7 +1380,7 @@ static void list_one_var_a(const char *prefix, const char *name, const ptrdiff_t
msg_putchar(' ');
}
- msg_outtrans((char *)string);
+ msg_outtrans(string, 0);
if (type == VAR_FUNC || type == VAR_PARTIAL) {
msg_puts("()");
@@ -1223,6 +1391,62 @@ static void list_one_var_a(const char *prefix, const char *name, const ptrdiff_t
}
}
+/// Additional handling for setting a v: variable.
+///
+/// @return true if the variable should be set normally,
+/// false if nothing else needs to be done.
+bool before_set_vvar(const char *const varname, dictitem_T *const di, typval_T *const tv,
+ const bool copy, const bool watched, bool *const type_error)
+{
+ if (di->di_tv.v_type == VAR_STRING) {
+ typval_T oldtv = TV_INITIAL_VALUE;
+ if (watched) {
+ tv_copy(&di->di_tv, &oldtv);
+ }
+ XFREE_CLEAR(di->di_tv.vval.v_string);
+ if (copy || tv->v_type != VAR_STRING) {
+ const char *const val = tv_get_string(tv);
+ // Careful: when assigning to v:errmsg and tv_get_string()
+ // causes an error message the variable will already be set.
+ if (di->di_tv.vval.v_string == NULL) {
+ di->di_tv.vval.v_string = xstrdup(val);
+ }
+ } else {
+ // Take over the string to avoid an extra alloc/free.
+ di->di_tv.vval.v_string = tv->vval.v_string;
+ tv->vval.v_string = NULL;
+ }
+ // Notify watchers
+ if (watched) {
+ tv_dict_watcher_notify(&vimvardict, varname, &di->di_tv, &oldtv);
+ tv_clear(&oldtv);
+ }
+ return false;
+ } else if (di->di_tv.v_type == VAR_NUMBER) {
+ typval_T oldtv = TV_INITIAL_VALUE;
+ if (watched) {
+ tv_copy(&di->di_tv, &oldtv);
+ }
+ di->di_tv.vval.v_number = tv_get_number(tv);
+ if (strcmp(varname, "searchforward") == 0) {
+ set_search_direction(di->di_tv.vval.v_number ? '/' : '?');
+ } else if (strcmp(varname, "hlsearch") == 0) {
+ no_hlsearch = !di->di_tv.vval.v_number;
+ redraw_all_later(UPD_SOME_VALID);
+ }
+ // Notify watchers
+ if (watched) {
+ tv_dict_watcher_notify(&vimvardict, varname, &di->di_tv, &oldtv);
+ tv_clear(&oldtv);
+ }
+ return false;
+ } else if (di->di_tv.v_type != tv->v_type) {
+ *type_error = true;
+ return false;
+ }
+ return true;
+}
+
/// Set variable to the given value
///
/// If the variable already exists, the value is updated. Otherwise the variable
@@ -1252,31 +1476,29 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv,
const bool is_const)
FUNC_ATTR_NONNULL_ALL
{
- dictitem_T *v;
- hashtab_T *ht;
- dict_T *dict;
-
const char *varname;
- ht = find_var_ht_dict(name, name_len, &varname, &dict);
+ dict_T *dict;
+ hashtab_T *ht = find_var_ht_dict(name, name_len, &varname, &dict);
const bool watched = tv_dict_is_watched(dict);
if (ht == NULL || *varname == NUL) {
semsg(_(e_illvar), name);
return;
}
- v = find_var_in_ht(ht, 0, varname, name_len - (size_t)(varname - name), true);
+ const size_t varname_len = name_len - (size_t)(varname - name);
+ dictitem_T *di = find_var_in_ht(ht, 0, varname, varname_len, true);
// Search in parent scope which is possible to reference from lambda
- if (v == NULL) {
- v = find_var_in_scoped_ht(name, name_len, true);
+ if (di == NULL) {
+ di = find_var_in_scoped_ht(name, name_len, true);
}
- if (tv_is_func(*tv) && var_wrong_func_name(name, v == NULL)) {
+ if (tv_is_func(*tv) && var_wrong_func_name(name, di == NULL)) {
return;
}
typval_T oldtv = TV_INITIAL_VALUE;
- if (v != NULL) {
+ if (di != NULL) {
if (is_const) {
emsg(_(e_cannot_mod));
return;
@@ -1286,9 +1508,9 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv,
// - Whether the variable is read-only
// - Whether the variable value is locked
// - Whether the variable is locked
- if (var_check_ro(v->di_flags, name, name_len)
- || value_check_lock(v->di_tv.v_lock, name, name_len)
- || var_check_lock(v->di_flags, name, name_len)) {
+ if (var_check_ro(di->di_flags, name, name_len)
+ || value_check_lock(di->di_tv.v_lock, name, name_len)
+ || var_check_lock(di->di_flags, name, name_len)) {
return;
}
@@ -1296,42 +1518,19 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv,
// Handle setting internal v: variables separately where needed to
// prevent changing the type.
- if (is_vimvarht(ht)) {
- if (v->di_tv.v_type == VAR_STRING) {
- XFREE_CLEAR(v->di_tv.vval.v_string);
- if (copy || tv->v_type != VAR_STRING) {
- const char *const val = tv_get_string(tv);
-
- // Careful: when assigning to v:errmsg and tv_get_string()
- // causes an error message the variable will already be set.
- if (v->di_tv.vval.v_string == NULL) {
- v->di_tv.vval.v_string = xstrdup(val);
- }
- } else {
- // Take over the string to avoid an extra alloc/free.
- v->di_tv.vval.v_string = tv->vval.v_string;
- tv->vval.v_string = NULL;
- }
- return;
- } else if (v->di_tv.v_type == VAR_NUMBER) {
- v->di_tv.vval.v_number = tv_get_number(tv);
- if (strcmp(varname, "searchforward") == 0) {
- set_search_direction(v->di_tv.vval.v_number ? '/' : '?');
- } else if (strcmp(varname, "hlsearch") == 0) {
- no_hlsearch = !v->di_tv.vval.v_number;
- redraw_all_later(UPD_SOME_VALID);
- }
- return;
- } else if (v->di_tv.v_type != tv->v_type) {
- semsg(_("E963: setting %s to value with wrong type"), name);
- return;
+ bool type_error = false;
+ if (is_vimvarht(ht)
+ && !before_set_vvar(varname, di, tv, copy, watched, &type_error)) {
+ if (type_error) {
+ semsg(_(e_setting_v_str_to_value_with_wrong_type), varname);
}
+ return;
}
if (watched) {
- tv_copy(&v->di_tv, &oldtv);
+ tv_copy(&di->di_tv, &oldtv);
}
- tv_clear(&v->di_tv);
+ tv_clear(&di->di_tv);
} else { // Add a new variable.
// Can't add "v:" or "a:" variable.
if (is_vimvarht(ht) || ht == get_funccal_args_ht()) {
@@ -1347,28 +1546,28 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv,
// Make sure dict is valid
assert(dict != NULL);
- v = xmalloc(sizeof(dictitem_T) + strlen(varname));
- STRCPY(v->di_key, varname);
- if (hash_add(ht, (char *)v->di_key) == FAIL) {
- xfree(v);
+ di = xmalloc(offsetof(dictitem_T, di_key) + varname_len + 1);
+ memcpy(di->di_key, varname, varname_len + 1);
+ if (hash_add(ht, di->di_key) == FAIL) {
+ xfree(di);
return;
}
- v->di_flags = DI_FLAGS_ALLOC;
+ di->di_flags = DI_FLAGS_ALLOC;
if (is_const) {
- v->di_flags |= DI_FLAGS_LOCK;
+ di->di_flags |= DI_FLAGS_LOCK;
}
}
if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) {
- tv_copy(tv, &v->di_tv);
+ tv_copy(tv, &di->di_tv);
} else {
- v->di_tv = *tv;
- v->di_tv.v_lock = VAR_UNLOCKED;
+ di->di_tv = *tv;
+ di->di_tv.v_lock = VAR_UNLOCKED;
tv_init(tv);
}
if (watched) {
- tv_dict_watcher_notify(dict, (char *)v->di_key, &v->di_tv, &oldtv);
+ tv_dict_watcher_notify(dict, di->di_key, &di->di_tv, &oldtv);
tv_clear(&oldtv);
}
@@ -1376,7 +1575,7 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv,
// Like :lockvar! name: lock the value and what it contains, but only
// if the reference count is up to one. That locks only literal
// values.
- tv_item_lock(&v->di_tv, DICT_MAXNEST, true, true);
+ tv_item_lock(&di->di_tv, DICT_MAXNEST, true, true);
}
}
@@ -1569,7 +1768,7 @@ static void get_var_from(const char *varname, typval_T *rettv, typval_T *deftv,
tv_dict_set_ret(rettv, opts);
done = true;
}
- } else if (get_option_tv(&varname, rettv, true) == OK) {
+ } else if (eval_option(&varname, rettv, true) == OK) {
// Local option
done = true;
}
@@ -1639,28 +1838,121 @@ static void getwinvar(typval_T *argvars, typval_T *rettv, int off)
get_var_from(varname, rettv, &argvars[off + 2], 'w', tp, win, NULL);
}
-/// Set option "varname" to the value of "varp" for the current buffer/window.
-static void set_option_from_tv(const char *varname, typval_T *varp)
+/// Convert typval to option value for a particular option.
+///
+/// @param[in] tv typval to convert.
+/// @param[in] option Option name.
+/// @param[in] flags Option flags.
+/// @param[out] error Whether an error occurred.
+///
+/// @return Typval converted to OptVal. Must be freed by caller.
+/// Returns NIL_OPTVAL for invalid option name.
+static OptVal tv_to_optval(typval_T *tv, const char *option, uint32_t flags, bool *error)
{
- long numval = 0;
- const char *strval;
- bool error = false;
+ OptVal value = NIL_OPTVAL;
char nbuf[NUMBUFLEN];
-
- if (varp->v_type == VAR_BOOL) {
- if (is_string_option(varname)) {
+ bool err = false;
+
+ if ((flags & P_FUNC) && tv_is_func(*tv)) {
+ // If the option can be set to a function reference or a lambda
+ // and the passed value is a function reference, then convert it to
+ // the name (string) of the function reference.
+ char *strval = encode_tv2string(tv, NULL);
+ err = strval == NULL;
+ value = CSTR_AS_OPTVAL(strval);
+ } else if (flags & (P_NUM | P_BOOL)) {
+ varnumber_T n = (flags & P_NUM) ? tv_get_number_chk(tv, &err)
+ : tv_get_bool_chk(tv, &err);
+ // This could be either "0" or a string that's not a number.
+ // So we need to check if it's actually a number.
+ if (!err && tv->v_type == VAR_STRING && n == 0) {
+ unsigned idx;
+ for (idx = 0; tv->vval.v_string[idx] == '0'; idx++) {}
+ if (tv->vval.v_string[idx] != NUL || idx == 0) {
+ // There's another character after zeros or the string is empty.
+ // In both cases, we are trying to set a num option using a string.
+ err = true;
+ semsg(_("E521: Number required: &%s = '%s'"), option, tv->vval.v_string);
+ }
+ }
+ value = (flags & P_NUM) ? NUMBER_OPTVAL((OptInt)n) : BOOLEAN_OPTVAL(TRISTATE_FROM_INT(n));
+ } else if ((flags & P_STRING) || is_tty_option(option)) {
+ // Avoid setting string option to a boolean or a special value.
+ if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) {
+ const char *strval = tv_get_string_buf_chk(tv, nbuf);
+ err = strval == NULL;
+ value = CSTR_TO_OPTVAL(strval);
+ } else if (flags & P_STRING) {
+ err = true;
emsg(_(e_stringreq));
- return;
}
- numval = (long)varp->vval.v_number;
- strval = "0"; // avoid using "false"
} else {
- numval = (long)tv_get_number_chk(varp, &error);
- strval = tv_get_string_buf_chk(varp, nbuf);
+ abort(); // This should never happen.
+ }
+
+ if (error != NULL) {
+ *error = err;
}
- if (!error && strval != NULL) {
- set_option_value_give_err(varname, numval, strval, OPT_LOCAL);
+ return value;
+}
+
+/// Convert an option value to typval.
+///
+/// @param[in] value Option value to convert.
+///
+/// @return OptVal converted to typval.
+typval_T optval_as_tv(OptVal value)
+{
+ typval_T rettv = { .v_type = VAR_SPECIAL, .vval = { .v_special = kSpecialVarNull } };
+
+ switch (value.type) {
+ case kOptValTypeNil:
+ break;
+ case kOptValTypeBoolean:
+ switch (value.data.boolean) {
+ case kTrue:
+ rettv.v_type = VAR_BOOL;
+ rettv.vval.v_bool = kBoolVarTrue;
+ break;
+ case kFalse:
+ rettv.v_type = VAR_BOOL;
+ rettv.vval.v_bool = kBoolVarFalse;
+ break;
+ case kNone:
+ break; // return v:null for None boolean value
+ }
+ break;
+ case kOptValTypeNumber:
+ rettv.v_type = VAR_NUMBER;
+ rettv.vval.v_number = value.data.number;
+ break;
+ case kOptValTypeString:
+ rettv.v_type = VAR_STRING;
+ rettv.vval.v_string = value.data.string.data;
+ break;
+ }
+
+ return rettv;
+}
+
+/// Set option "varname" to the value of "varp" for the current buffer/window.
+static void set_option_from_tv(const char *varname, typval_T *varp)
+{
+ int opt_idx = findoption(varname);
+ if (opt_idx < 0) {
+ semsg(_(e_unknown_option2), varname);
+ return;
}
+ uint32_t opt_p_flags = get_option(opt_idx)->flags;
+
+ bool error = false;
+ OptVal value = tv_to_optval(varp, varname, opt_p_flags, &error);
+
+ if (!error) {
+ set_option_value_give_err(varname, value, OPT_LOCAL);
+ }
+
+ optval_free(value);
}
/// "setwinvar()" and "settabwinvar()" functions
@@ -1718,10 +2010,10 @@ bool var_exists(const char *var)
if (tofree != NULL) {
name = tofree;
}
- n = get_var_tv(name, len, &tv, NULL, false, true) == OK;
+ n = eval_variable(name, len, &tv, NULL, false, true) == OK;
if (n) {
// Handle d.key, l[idx], f(expr).
- n = handle_subscript(&var, &tv, true, false, name, &name) == OK;
+ n = handle_subscript(&var, &tv, &EVALARG_EVALUATE, false) == OK;
if (n) {
tv_clear(&tv);
}