eval: Add id() function and make printf("%p") return something useful (#6095)

This commit is contained in:
Nikolai Aleksandrovich Pavlov 2017-02-11 21:47:02 +03:00 committed by Justin M. Keyes
parent b1cf50c684
commit abdbfd26bc
7 changed files with 113 additions and 11 deletions

View File

@ -2036,6 +2036,7 @@ insert({list}, {item} [, {idx}])
invert({expr}) Number bitwise invert
isdirectory({directory}) Number TRUE if {directory} is a directory
islocked({expr}) Number TRUE if {expr} is locked
id({expr}) String identifier of the container
items({dict}) List key-value pairs in {dict}
jobclose({job}[, {stream}]) Number Closes a job stream(s)
jobpid({job}) Number Returns pid of a job.
@ -4629,6 +4630,22 @@ islocked({expr}) *islocked()* *E786*
< When {expr} is a variable that does not exist you get an error
message. Use |exists()| to check for existence.
id({expr}) *id()*
Returns a |String| which is a unique identifier of the
container type (|List|, |Dict| and |Partial|). It is
guaranteed that for the mentioned types `id(v1) ==# id(v2)`
returns true iff `type(v1) == type(v2) && v1 is v2` (note:
|v:_null_list| and |v:_null_dict| have the same `id()` with
different types because they are internally represented as
a NULL pointers). Currently `id()` returns a hexadecimal
representanion of the pointers to the containers (i.e. like
`0x994a40`), same as `printf("%p", {expr})`, but it is advised
against counting on exact format of return value.
It is not guaranteed that `id(no_longer_existing_container)`
will not be equal to some other `id()`: new containers may
reuse identifiers of the garbage-collected ones.
items({dict}) *items()*
Return a |List| with all the key-value pairs of {dict}. Each
|List| item is a list with two items: the key of a {dict}
@ -5500,6 +5517,7 @@ printf({fmt}, {expr1} ...) *printf()*
%g floating point number, as %f or %e depending on value
%G floating point number, as %f or %E depending on value
%% the % character itself
%p representation of the pointer to the container
Conversion specifications start with '%' and end with the
conversion type. All other characters are copied unchanged to

View File

@ -232,6 +232,12 @@ Additional differences:
itself.
- ShaDa file keeps search direction (|v:searchforward|), viminfo does not.
|printf()| returns something meaningful when used with `%p` argument: in Vim
it used to return useless address of the string (strings are copied to the
newly allocated memory all over the place) and fail on types which cannot be
coerced to strings. See |id()| for more details, currently it uses
`printf("%p", {expr})` internally.
==============================================================================
5. Missing legacy features *nvim-features-missing*
*if_lua* *if_perl* *if_mzscheme* *if_tcl*

View File

@ -498,6 +498,14 @@ static PMap(uint64_t) *jobs = NULL;
static uint64_t last_timer_id = 0;
static PMap(uint64_t) *timers = NULL;
/// Dummy va_list for passing to vim_snprintf
///
/// Used because:
/// - passing a NULL pointer doesn't work when va_list isn't a pointer
/// - locally in the function results in a "used before set" warning
/// - using va_start() to initialize it gives "function with fixed args" error
static va_list dummy_ap;
static const char *const msgpack_type_names[] = {
[kMPNil] = "nil",
[kMPBoolean] = "boolean",
@ -12122,6 +12130,16 @@ static void dict_list(typval_T *argvars, typval_T *rettv, int what)
}
}
/// "id()" function
static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr)
FUNC_ATTR_NONNULL_ALL
{
const int len = vim_vsnprintf(NULL, 0, "%p", dummy_ap, argvars);
rettv->v_type = VAR_STRING;
rettv->vval.v_string = xmalloc(len + 1);
vim_vsnprintf((char *)rettv->vval.v_string, len + 1, "%p", dummy_ap, argvars);
}
/*
* "items(dict)" function
*/
@ -13628,12 +13646,6 @@ static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = lnum;
}
/* This dummy va_list is here because:
* - passing a NULL pointer doesn't work when va_list isn't a pointer
* - locally in the function results in a "used before set" warning
* - using va_start() to initialize it gives "function with fixed args" error */
static va_list ap;
/*
* "printf()" function
*/
@ -13650,11 +13662,11 @@ static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/* Get the required length, allocate the buffer and do it for real. */
did_emsg = FALSE;
fmt = (char *)get_tv_string_buf(&argvars[0], buf);
len = vim_vsnprintf(NULL, 0, fmt, ap, argvars + 1);
len = vim_vsnprintf(NULL, 0, fmt, dummy_ap, argvars + 1);
if (!did_emsg) {
char *s = xmalloc(len + 1);
rettv->vval.v_string = (char_u *)s;
(void)vim_vsnprintf(s, len + 1, fmt, ap, argvars + 1);
(void)vim_vsnprintf(s, len + 1, fmt, dummy_ap, argvars + 1);
}
did_emsg |= saved_did_emsg;
}

View File

@ -168,6 +168,7 @@ return {
invert={args=1},
isdirectory={args=1},
islocked={args=1},
id={args=1},
items={args=1},
jobclose={args={1, 2}},
jobpid={args=1},

View File

@ -49,7 +49,7 @@ typedef enum {
typedef struct {
VarType v_type; ///< Variable type.
VarLockStatus v_lock; ///< Variable lock status.
union {
union typval_vval_union {
varnumber_T v_number; ///< Number, for VAR_NUMBER.
SpecialVarValue v_special; ///< Special value, for VAR_SPECIAL.
float_T v_float; ///< Floating-point number, for VAR_FLOAT.

View File

@ -11,6 +11,7 @@
#include "nvim/vim.h"
#include "nvim/ascii.h"
#include "nvim/assert.h"
#include "nvim/message.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
@ -3043,6 +3044,38 @@ static char *tv_str(typval_T *tvs, int *idxp)
return s;
}
/// Get pointer argument from the next entry in tvs
///
/// First entry is 1. Returns NULL for an error.
///
/// @param[in] tvs List of typval_T values.
/// @param[in,out] idxp Pointer to the index of the current value.
///
/// @return Pointer stored in typval_T or NULL.
static const void *tv_ptr(const typval_T *const tvs, int *const idxp)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
#define OFF(attr) offsetof(union typval_vval_union, attr)
STATIC_ASSERT(
OFF(v_string) == OFF(v_list)
&& OFF(v_string) == OFF(v_dict)
&& OFF(v_string) == OFF(v_partial)
&& sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_list)
&& sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_dict)
&& sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_partial),
"Strings, dictionaries, lists and partials are expected to be pointers, "
"so that all three of them can be accessed via v_string");
#undef OFF
const int idx = *idxp - 1;
if (tvs[idx].v_type == VAR_UNKNOWN) {
EMSG(_(e_printf));
return NULL;
} else {
(*idxp)++;
return tvs[idx].vval.v_string;
}
}
/*
* Get float argument from "idxp" entry in "tvs". First entry is 1.
*/
@ -3369,11 +3402,11 @@ int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap,
size_t size_t_arg = 0;
// only defined for p conversion
void *ptr_arg = NULL;
const void *ptr_arg = NULL;
if (fmt_spec == 'p') {
length_modifier = '\0';
ptr_arg = tvs ? (void *)tv_str(tvs, &arg_idx) : va_arg(ap, void *);
ptr_arg = tvs ? tv_ptr(tvs, &arg_idx) : va_arg(ap, void *);
if (ptr_arg) {
arg_sign = 1;
}

View File

@ -1,7 +1,10 @@
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local eq = helpers.eq
local eval = helpers.eval
local funcs = helpers.funcs
local meths = helpers.meths
local exc_exec = helpers.exc_exec
describe('printf()', function()
@ -57,4 +60,33 @@ describe('printf()', function()
it('errors out when %b modifier is used for a float', function()
eq('Vim(call):E805: Using a Float as a Number', exc_exec('call printf("%b", 3.1415926535)'))
end)
it('works with %p correctly', function()
local null_ret = nil
local seen_rets = {}
-- Collect all args in an array to avoid possible allocation of the same
-- address after freeing unreferenced values.
meths.set_var('__args', {})
local function check_printf(expr, is_null)
eq(0, exc_exec('call add(__args, ' .. expr .. ')'))
eq(0, exc_exec('let __result = printf("%p", __args[-1])'))
local id_ret = eval('id(__args[-1])')
eq(id_ret, meths.get_var('__result'))
if is_null then
if null_ret then
eq(null_ret, id_ret)
else
null_ret = id_ret
end
else
eq(nil, seen_rets[id_ret])
seen_rets[id_ret] = expr
end
meths.del_var('__result')
end
check_printf('v:_null_list', true)
check_printf('v:_null_dict', true)
check_printf('[]')
check_printf('{}')
check_printf('function("tr", ["a"])')
end)
end)