vim-patch:partial:8.1.1939: code for handling v: variables in generic eval file (#35968)

Problem:    Code for handling v: variables in generic eval file.
Solution:   Move v: variables to evalvars.c. (Yegappan Lakshmanan,
            closes vim/vim#4872)

e5cdf153bc

Remove direct reference to "vimvars" for following functions:
- assert_error()
- get_vim_var_nr()
- get_vim_var_list()
- get_vim_var_dict()
- get_vim_var_str()
- set_cmdarg()
- set_reg_var()
- set_vcount()
- set_vexception()
- set_vthrowpoint()
- set_vim_var_bool()
- set_vim_var_dict()
- set_vim_var_list()
- set_vim_var_nr()
- set_vim_var_special()
- set_vim_var_string()
- set_vim_var_type()

Reorder functions based on v8.2.4930 for
eval_one_expr_in_str() and eval_all_expr_in_str().

Co-authored-by: Bram Moolenaar <Bram@vim.org>
This commit is contained in:
Jan Edmund Lazo
2025-10-04 00:28:30 -04:00
committed by GitHub
parent 04022c70f3
commit 96e9041a78
46 changed files with 647 additions and 607 deletions

View File

@@ -34,12 +34,14 @@
#include "nvim/globals.h"
#include "nvim/hashtab.h"
#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/os/os.h"
#include "nvim/register.h"
#include "nvim/runtime.h"
#include "nvim/search.h"
#include "nvim/strings.h"
#include "nvim/types_defs.h"
@@ -63,6 +65,49 @@ static const char e_setting_v_str_to_value_with_wrong_type[]
static const char e_missing_end_marker_str[] = N_("E990: Missing end marker '%s'");
static const char e_cannot_use_heredoc_here[] = N_("E991: Cannot use =<< here");
bool garbage_collect_vimvars(int copyID)
{
return set_ref_in_ht(&vimvarht, copyID, NULL);
}
bool garbage_collect_scriptvars(int copyID)
{
bool abort = false;
for (int i = 1; i <= script_items.ga_len; i++) {
abort = abort || set_ref_in_ht(&SCRIPT_VARS(i), copyID, NULL);
}
return abort;
}
/// Set an internal variable to a string value. Creates the variable if it does
/// not already exist.
void set_internal_string_var(const char *name, char *value) // NOLINT(readability-non-const-parameter)
FUNC_ATTR_NONNULL_ARG(1)
{
typval_T tv = {
.v_type = VAR_STRING,
.vval.v_string = value,
};
set_var(name, strlen(name), &tv, true);
}
/// List Vim variables.
static void list_vim_vars(int *first)
{
list_hashtable_vars(&vimvarht, "v:", false, first);
}
/// List script-local variables, if there is a script.
static void list_script_vars(int *first)
{
if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) {
list_hashtable_vars(&SCRIPT_VARS(current_sctx.sc_sid), "s:", false, first);
}
}
/// 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.
@@ -1290,6 +1335,325 @@ static int do_lock_var(lval_T *lp, char *name_end FUNC_ATTR_UNUSED, exarg_T *eap
return ret;
}
/// Get number v: variable value.
varnumber_T get_vim_var_nr(const VimVarIndex idx) FUNC_ATTR_PURE
{
typval_T *tv = get_vim_var_tv(idx);
return tv->vval.v_number;
}
/// Get List v: variable value. Caller must take care of reference count when
/// needed.
list_T *get_vim_var_list(const VimVarIndex idx) FUNC_ATTR_PURE
{
typval_T *tv = get_vim_var_tv(idx);
return tv->vval.v_list;
}
/// Get Dictionary v: variable value. Caller must take care of reference count
/// when needed.
dict_T *get_vim_var_dict(const VimVarIndex idx) FUNC_ATTR_PURE
{
typval_T *tv = get_vim_var_tv(idx);
return tv->vval.v_dict;
}
/// Get string v: variable value. Uses a static buffer, can only be used once.
/// If the String variable has never been set, return an empty string.
/// Never returns NULL.
char *get_vim_var_str(const VimVarIndex idx)
FUNC_ATTR_PURE FUNC_ATTR_NONNULL_RET
{
return (char *)tv_get_string(get_vim_var_tv(idx));
}
/// Set type of v: variable to the given type.
///
/// @param[in] idx Index of variable to set.
/// @param[in] type Type to set to.
void set_vim_var_type(const VimVarIndex idx, const VarType type)
{
typval_T *tv = get_vim_var_tv(idx);
tv->v_type = type;
}
/// Set number v: variable to the given value
/// Note that this does not set the type, use set_vim_var_type() for that.
///
/// @param[in] idx Index of variable to set.
/// @param[in] val Value to set to.
void set_vim_var_nr(const VimVarIndex idx, const varnumber_T val)
{
typval_T *tv = get_vim_var_tv(idx);
tv_clear(tv);
tv->vval.v_number = val;
}
/// Set boolean v: {true, false} to the given value
///
/// @param[in] idx Index of variable to set.
/// @param[in] val Value to set to.
void set_vim_var_bool(const VimVarIndex idx, const BoolVarValue val)
{
typval_T *tv = get_vim_var_tv(idx);
tv_clear(tv);
tv->v_type = VAR_BOOL;
tv->vval.v_bool = val;
}
/// Set special v: variable to the given value
///
/// @param[in] idx Index of variable to set.
/// @param[in] val Value to set to.
void set_vim_var_special(const VimVarIndex idx, const SpecialVarValue val)
{
typval_T *tv = get_vim_var_tv(idx);
tv_clear(tv);
tv->v_type = VAR_SPECIAL;
tv->vval.v_special = val;
}
/// Set v:char to character "c".
void set_vim_var_char(int c)
{
char buf[MB_MAXCHAR + 1];
buf[utf_char2bytes(c, buf)] = NUL;
set_vim_var_string(VV_CHAR, buf, -1);
}
/// Set string v: variable to the given string
///
/// @param[in] idx Index of variable to set.
/// @param[in] val Value to set to. Will be copied.
/// @param[in] len Length of that value or -1 in which case strlen() will be
/// used.
void set_vim_var_string(const VimVarIndex idx, const char *const val, const ptrdiff_t len)
{
typval_T *tv = get_vim_var_tv(idx);
tv_clear(tv);
tv->v_type = VAR_STRING;
if (val == NULL) {
tv->vval.v_string = NULL;
} else if (len == -1) {
tv->vval.v_string = xstrdup(val);
} else {
tv->vval.v_string = xstrndup(val, (size_t)len);
}
}
/// Set list v: variable to the given list
///
/// @param[in] idx Index of variable to set.
/// @param[in,out] val Value to set to. Reference count will be incremented.
void set_vim_var_list(const VimVarIndex idx, list_T *const val)
{
typval_T *tv = get_vim_var_tv(idx);
tv_clear(tv);
tv->v_type = VAR_LIST;
tv->vval.v_list = val;
if (val != NULL) {
tv_list_ref(val);
}
}
/// Set Dictionary v: variable to the given dictionary
///
/// @param[in] idx Index of variable to set.
/// @param[in,out] val Value to set to. Reference count will be incremented.
/// Also keys of the dictionary will be made read-only.
void set_vim_var_dict(const VimVarIndex idx, dict_T *const val)
{
typval_T *tv = get_vim_var_tv(idx);
tv_clear(tv);
tv->v_type = VAR_DICT;
tv->vval.v_dict = val;
if (val == NULL) {
return;
}
val->dv_refcount++;
// Set readonly
tv_dict_set_keys_readonly(val);
}
/// Set v:register if needed.
void set_reg_var(int c)
{
char regname;
if (c == 0 || c == ' ') {
regname = '"';
} else {
regname = (char)c;
}
// Avoid free/alloc when the value is already right.
typval_T *tv = get_vim_var_tv(VV_REG);
if (tv->vval.v_string == NULL || tv->vval.v_string[0] != c) {
set_vim_var_string(VV_REG, &regname, 1);
}
}
/// Get or set v:exception. If "oldval" == NULL, return the current value.
/// Otherwise, restore the value to "oldval" and return NULL.
/// Must always be called in pairs to save and restore v:exception! Does not
/// take care of memory allocations.
char *v_exception(char *oldval)
{
typval_T *tv = get_vim_var_tv(VV_EXCEPTION);
if (oldval == NULL) {
return tv->vval.v_string;
}
tv->vval.v_string = oldval;
return NULL;
}
/// Set v:cmdarg.
/// If "eap" != NULL, use "eap" to generate the value and return the old value.
/// If "oldarg" != NULL, restore the value to "oldarg" and return NULL.
/// Must always be called in pairs!
char *set_cmdarg(exarg_T *eap, char *oldarg)
{
typval_T *tv = get_vim_var_tv(VV_CMDARG);
char *oldval = tv->vval.v_string;
if (eap == NULL) {
goto error;
}
size_t len = 0;
if (eap->force_bin == FORCE_BIN) {
len += 6; // " ++bin"
} else if (eap->force_bin == FORCE_NOBIN) {
len += 8; // " ++nobin"
}
if (eap->read_edit) {
len += 7; // " ++edit"
}
if (eap->force_ff != 0) {
len += 10; // " ++ff=unix"
}
if (eap->force_enc != 0) {
len += strlen(eap->cmd + eap->force_enc) + 7;
}
if (eap->bad_char != 0) {
len += 7 + 4; // " ++bad=" + "keep" or "drop"
}
if (eap->mkdir_p != 0) {
len += 4; // " ++p"
}
const size_t newval_len = len + 1;
char *newval = xmalloc(newval_len);
size_t xlen = 0;
int rc = 0;
if (eap->force_bin == FORCE_BIN) {
rc = snprintf(newval, newval_len, " ++bin");
} else if (eap->force_bin == FORCE_NOBIN) {
rc = snprintf(newval, newval_len, " ++nobin");
} else {
*newval = NUL;
}
if (rc < 0) {
goto error;
}
xlen += (size_t)rc;
if (eap->read_edit) {
rc = snprintf(newval + xlen, newval_len - xlen, " ++edit");
if (rc < 0) {
goto error;
}
xlen += (size_t)rc;
}
if (eap->force_ff != 0) {
rc = snprintf(newval + xlen,
newval_len - xlen,
" ++ff=%s",
eap->force_ff == 'u' ? "unix"
: eap->force_ff == 'd' ? "dos" : "mac");
if (rc < 0) {
goto error;
}
xlen += (size_t)rc;
}
if (eap->force_enc != 0) {
rc = snprintf(newval + (xlen), newval_len - xlen, " ++enc=%s", eap->cmd + eap->force_enc);
if (rc < 0) {
goto error;
}
xlen += (size_t)rc;
}
if (eap->bad_char == BAD_KEEP) {
rc = snprintf(newval + xlen, newval_len - xlen, " ++bad=keep");
if (rc < 0) {
goto error;
}
xlen += (size_t)rc;
} else if (eap->bad_char == BAD_DROP) {
rc = snprintf(newval + xlen, newval_len - xlen, " ++bad=drop");
if (rc < 0) {
goto error;
}
xlen += (size_t)rc;
} else if (eap->bad_char != 0) {
rc = snprintf(newval + xlen, newval_len - xlen, " ++bad=%c", eap->bad_char);
if (rc < 0) {
goto error;
}
xlen += (size_t)rc;
}
if (eap->mkdir_p != 0) {
rc = snprintf(newval + xlen, newval_len - xlen, " ++p");
if (rc < 0) {
goto error;
}
xlen += (size_t)rc;
}
assert(xlen <= newval_len);
tv->vval.v_string = newval;
return oldval;
error:
xfree(oldval);
tv->vval.v_string = oldarg;
return NULL;
}
/// Get or set v:throwpoint. If "oldval" == NULL, return the current value.
/// Otherwise, restore the value to "oldval" and return NULL.
/// Must always be called in pairs to save and restore v:throwpoint! Does not
/// take care of memory allocations.
char *v_throwpoint(char *oldval)
{
typval_T *tv = get_vim_var_tv(VV_THROWPOINT);
if (oldval == NULL) {
return tv->vval.v_string;
}
tv->vval.v_string = oldval;
return NULL;
}
/// Set v:count to "count" and v:count1 to "count1".
///
/// @param set_prevcount if true, first set v:prevcount from v:count.
void set_vcount(int64_t count, int64_t count1, bool set_prevcount)
{
if (set_prevcount) {
get_vim_var_tv(VV_PREVCOUNT)->vval.v_number = get_vim_var_nr(VV_COUNT);
}
get_vim_var_tv(VV_COUNT)->vval.v_number = count;
get_vim_var_tv(VV_COUNT1)->vval.v_number = count1;
}
/// Get the value of internal variable "name".
/// Return OK or FAIL. If OK is returned "rettv" must be cleared.
///
@@ -1325,6 +1689,67 @@ int eval_variable(const char *name, int len, typval_T *rettv, dictitem_T **dip,
return ret;
}
/// Check if variable "name[len]" is a local variable or an argument.
/// If so, "*eval_lavars_used" is set to true.
void check_vars(const char *name, size_t len)
{
if (eval_lavars_used == NULL) {
return;
}
const char *varname;
hashtab_T *ht = find_var_ht(name, len, &varname);
if (ht == get_funccal_local_ht() || ht == get_funccal_args_ht()) {
if (find_var(name, len, NULL, true) != NULL) {
*eval_lavars_used = true;
}
}
}
/// Find variable "name" in the list of variables.
/// Careful: "a:0" variables don't have a name.
/// When "htp" is not NULL we are writing to the variable, set "htp" to the
/// hashtab_T used.
///
/// @return a pointer to it if found, NULL if not found.
dictitem_T *find_var(const char *const name, const size_t name_len, hashtab_T **htp,
int no_autoload)
{
const char *varname;
hashtab_T *const ht = find_var_ht(name, name_len, &varname);
if (htp != NULL) {
*htp = ht;
}
if (ht == NULL) {
return NULL;
}
dictitem_T *const ret = find_var_in_ht(ht, *name,
varname,
name_len - (size_t)(varname - name),
no_autoload || htp != NULL);
if (ret != NULL) {
return ret;
}
// Search in parent scope for lambda
return find_var_in_scoped_ht(name, name_len, no_autoload || htp != NULL);
}
/// Find the hashtable used for a variable
///
/// @param[in] name Variable name, possibly with scope prefix.
/// @param[in] name_len Variable name length.
/// @param[out] varname Will be set to the start of the name without scope
/// prefix.
///
/// @return Scope hashtab, NULL if name is not valid.
hashtab_T *find_var_ht(const char *name, const size_t name_len, const char **varname)
{
dict_T *d;
return find_var_ht_dict(name, name_len, varname, &d);
}
/// @return the string value of a (global/local) variable or
/// NULL when it doesn't exist.
///
@@ -1340,6 +1765,41 @@ char *get_var_value(const char *const name)
return (char *)tv_get_string(&v->di_tv);
}
/// Allocate a new hashtab for a sourced script. It will be used while
/// sourcing this script and when executing functions defined in the script.
void new_script_vars(scid_T id)
{
scriptvar_T *sv = xcalloc(1, sizeof(scriptvar_T));
init_var_dict(&sv->sv_dict, &sv->sv_var, VAR_SCOPE);
SCRIPT_ITEM(id)->sn_vars = sv;
}
/// Initialize dictionary "dict" as a scope and set variable "dict_var" to
/// point to it.
void init_var_dict(dict_T *dict, ScopeDictDictItem *dict_var, ScopeType scope)
{
hash_init(&dict->dv_hashtab);
dict->dv_lock = VAR_UNLOCKED;
dict->dv_scope = scope;
dict->dv_refcount = DO_NOT_FREE_CNT;
dict->dv_copyID = 0;
dict_var->di_tv.vval.v_dict = dict;
dict_var->di_tv.v_type = VAR_DICT;
dict_var->di_tv.v_lock = VAR_FIXED;
dict_var->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
dict_var->di_key[0] = NUL;
QUEUE_INIT(&dict->watchers);
}
/// Unreference a dictionary initialized by init_var_dict().
void unref_var_dict(dict_T *dict)
{
// Now the dict needs to be freed if no one else is using it, go back to
// normal reference counting.
dict->dv_refcount -= DO_NOT_FREE_CNT - 1;
tv_dict_unref(dict);
}
/// Clean up a list of internal variables.
/// Frees all allocated variables and the value they contain.
/// Clears hashtab "ht", does not free it.
@@ -1575,7 +2035,7 @@ 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.
bool type_error = false;
if (is_vimvarht(ht)
if (ht == &vimvarht
&& !before_set_vvar(varname, di, tv, copy, watched, &type_error)) {
if (type_error) {
semsg(_(e_setting_v_str_to_value_with_wrong_type), varname);
@@ -1589,7 +2049,7 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const 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()) {
if (ht == &vimvarht || ht == get_funccal_args_ht()) {
semsg(_(e_illvar), name);
return;
}
@@ -2053,6 +2513,30 @@ static void setwinvar(typval_T *argvars, int off)
}
}
// reset v:option_new, v:option_old, v:option_oldlocal, v:option_oldglobal,
// v:option_type, and v:option_command.
void reset_v_option_vars(void)
{
set_vim_var_string(VV_OPTION_NEW, NULL, -1);
set_vim_var_string(VV_OPTION_OLD, NULL, -1);
set_vim_var_string(VV_OPTION_OLDLOCAL, NULL, -1);
set_vim_var_string(VV_OPTION_OLDGLOBAL, NULL, -1);
set_vim_var_string(VV_OPTION_COMMAND, NULL, -1);
set_vim_var_string(VV_OPTION_TYPE, NULL, -1);
}
/// Add an assert error to v:errors.
void assert_error(garray_T *gap)
{
typval_T *tv = get_vim_var_tv(VV_ERRORS);
if (tv->v_type != VAR_LIST || tv->vval.v_list == NULL) {
// Make sure v:errors is a list.
set_vim_var_list(VV_ERRORS, tv_list_alloc(1));
}
tv_list_append_string(get_vim_var_list(VV_ERRORS), gap->ga_data, (ptrdiff_t)gap->ga_len);
}
bool var_exists(const char *var)
FUNC_ATTR_NONNULL_ALL
{