diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 22:39:54 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 22:39:54 +0000 |
commit | 21cb7d04c387e4198ca8098a884c78b56ffcf4c2 (patch) | |
tree | 84fe5690df1551f0bb2bdfe1a13aacd29ebc1de7 /src/nvim/eval/vars.c | |
parent | d9c904f85a23a496df4eb6be42aa43f007b22d50 (diff) | |
parent | 4a8bf24ac690004aedf5540fa440e788459e5e34 (diff) | |
download | rneovim-colorcolchar.tar.gz rneovim-colorcolchar.tar.bz2 rneovim-colorcolchar.zip |
Merge remote-tracking branch 'upstream/master' into colorcolcharcolorcolchar
Diffstat (limited to 'src/nvim/eval/vars.c')
-rw-r--r-- | src/nvim/eval/vars.c | 1064 |
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); } |