mirror of
https://github.com/neovim/neovim.git
synced 2026-01-11 22:07:12 +10:00
1293 lines
40 KiB
C
1293 lines
40 KiB
C
/// @file encode.c
|
|
///
|
|
/// File containing functions for encoding and decoding VimL values.
|
|
///
|
|
/// Split out from eval.c.
|
|
|
|
#include <msgpack.h>
|
|
#include <inttypes.h>
|
|
#include <assert.h>
|
|
|
|
#include "nvim/eval/encode.h"
|
|
#include "nvim/buffer_defs.h" // vimconv_T
|
|
#include "nvim/eval.h"
|
|
#include "nvim/eval_defs.h"
|
|
#include "nvim/garray.h"
|
|
#include "nvim/mbyte.h"
|
|
#include "nvim/message.h"
|
|
#include "nvim/charset.h" // vim_isprintc()
|
|
#include "nvim/macros.h"
|
|
#include "nvim/ascii.h"
|
|
#include "nvim/vim.h" // For _()
|
|
#include "nvim/lib/kvec.h"
|
|
|
|
#define ga_concat(a, b) ga_concat(a, (char_u *)b)
|
|
#define utf_ptr2char(b) utf_ptr2char((char_u *)b)
|
|
#define utf_ptr2len(b) ((size_t)utf_ptr2len((char_u *)b))
|
|
#define utf_char2len(b) ((size_t)utf_char2len(b))
|
|
#define string_convert(a, b, c) \
|
|
((char *)string_convert((vimconv_T *)a, (char_u *)b, c))
|
|
#define convert_setup(vcp, from, to) \
|
|
(convert_setup(vcp, (char_u *)from, (char_u *)to))
|
|
|
|
/// Structure representing current VimL to messagepack conversion state
|
|
typedef struct {
|
|
enum {
|
|
kMPConvDict, ///< Convert dict_T *dictionary.
|
|
kMPConvList, ///< Convert list_T *list.
|
|
kMPConvPairs, ///< Convert mapping represented as a list_T* of pairs.
|
|
} type;
|
|
union {
|
|
struct {
|
|
dict_T *dict; ///< Currently converted dictionary.
|
|
hashitem_T *hi; ///< Currently converted dictionary item.
|
|
size_t todo; ///< Amount of items left to process.
|
|
} d; ///< State of dictionary conversion.
|
|
struct {
|
|
list_T *list; ///< Currently converted list.
|
|
listitem_T *li; ///< Currently converted list item.
|
|
} l; ///< State of list or generic mapping conversion.
|
|
} data; ///< Data to convert.
|
|
} MPConvStackVal;
|
|
|
|
/// Stack used to convert VimL values to messagepack.
|
|
typedef kvec_t(MPConvStackVal) MPConvStack;
|
|
|
|
const char *const encode_special_var_names[] = {
|
|
[kSpecialVarNull] = "null",
|
|
[kSpecialVarNone] = "none",
|
|
[kSpecialVarTrue] = "true",
|
|
[kSpecialVarFalse] = "false",
|
|
};
|
|
|
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
|
# include "eval/encode.c.generated.h"
|
|
#endif
|
|
|
|
/// Msgpack callback for writing to readfile()-style list
|
|
int encode_list_write(void *data, const char *buf, size_t len)
|
|
{
|
|
if (len == 0) {
|
|
return 0;
|
|
}
|
|
list_T *const list = (list_T *) data;
|
|
const char *const end = buf + len;
|
|
const char *line_end = buf;
|
|
if (list->lv_last == NULL) {
|
|
list_append_string(list, NULL, 0);
|
|
}
|
|
listitem_T *li = list->lv_last;
|
|
do {
|
|
const char *line_start = line_end;
|
|
line_end = xmemscan(line_start, NL, (size_t) (end - line_start));
|
|
if (line_end == line_start) {
|
|
list_append_allocated_string(list, NULL);
|
|
} else {
|
|
const size_t line_length = (size_t) (line_end - line_start);
|
|
char *str;
|
|
if (li == NULL) {
|
|
str = xmemdupz(line_start, line_length);
|
|
} else {
|
|
const size_t li_len = (li->li_tv.vval.v_string == NULL
|
|
? 0
|
|
: STRLEN(li->li_tv.vval.v_string));
|
|
li->li_tv.vval.v_string = xrealloc(li->li_tv.vval.v_string,
|
|
li_len + line_length + 1);
|
|
str = (char *) li->li_tv.vval.v_string + li_len;
|
|
memmove(str, line_start, line_length);
|
|
str[line_length] = 0;
|
|
}
|
|
for (size_t i = 0; i < line_length; i++) {
|
|
if (str[i] == NUL) {
|
|
str[i] = NL;
|
|
}
|
|
}
|
|
if (li == NULL) {
|
|
list_append_allocated_string(list, str);
|
|
} else {
|
|
li = NULL;
|
|
}
|
|
if (line_end == end - 1) {
|
|
list_append_allocated_string(list, NULL);
|
|
}
|
|
}
|
|
line_end++;
|
|
} while (line_end < end);
|
|
return 0;
|
|
}
|
|
|
|
/// Abort conversion to string after a recursion error.
|
|
static bool did_echo_string_emsg = false;
|
|
|
|
/// Show a error message when converting to msgpack value
|
|
///
|
|
/// @param[in] msg Error message to dump. Must contain exactly two %s that
|
|
/// will be replaced with what was being dumped: first with
|
|
/// something like “F” or “function argument”, second with path
|
|
/// to the failed value.
|
|
/// @param[in] mpstack Path to the failed value.
|
|
/// @param[in] objname Dumped object name.
|
|
///
|
|
/// @return FAIL.
|
|
static int conv_error(const char *const msg, const MPConvStack *const mpstack,
|
|
const char *const objname)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
garray_T msg_ga;
|
|
ga_init(&msg_ga, (int)sizeof(char), 80);
|
|
char *const key_msg = _("key %s");
|
|
char *const key_pair_msg = _("key %s at index %i from special map");
|
|
char *const idx_msg = _("index %i");
|
|
for (size_t i = 0; i < kv_size(*mpstack); i++) {
|
|
if (i != 0) {
|
|
ga_concat(&msg_ga, ", ");
|
|
}
|
|
MPConvStackVal v = kv_A(*mpstack, i);
|
|
switch (v.type) {
|
|
case kMPConvDict: {
|
|
typval_T key_tv = {
|
|
.v_type = VAR_STRING,
|
|
.vval = { .v_string = (v.data.d.hi == NULL
|
|
? v.data.d.dict->dv_hashtab.ht_array
|
|
: (v.data.d.hi - 1))->hi_key },
|
|
};
|
|
char *const key = encode_tv2string(&key_tv, NULL);
|
|
vim_snprintf((char *) IObuff, IOSIZE, key_msg, key);
|
|
xfree(key);
|
|
ga_concat(&msg_ga, IObuff);
|
|
break;
|
|
}
|
|
case kMPConvPairs:
|
|
case kMPConvList: {
|
|
int idx = 0;
|
|
const listitem_T *li;
|
|
for (li = v.data.l.list->lv_first;
|
|
li != NULL && li->li_next != v.data.l.li;
|
|
li = li->li_next) {
|
|
idx++;
|
|
}
|
|
if (v.type == kMPConvList
|
|
|| li == NULL
|
|
|| (li->li_tv.v_type != VAR_LIST
|
|
&& li->li_tv.vval.v_list->lv_len <= 0)) {
|
|
vim_snprintf((char *) IObuff, IOSIZE, idx_msg, idx);
|
|
ga_concat(&msg_ga, IObuff);
|
|
} else {
|
|
typval_T key_tv = li->li_tv.vval.v_list->lv_first->li_tv;
|
|
char *const key = encode_tv2echo(&key_tv, NULL);
|
|
vim_snprintf((char *) IObuff, IOSIZE, key_pair_msg, key, idx);
|
|
xfree(key);
|
|
ga_concat(&msg_ga, IObuff);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
EMSG3(msg, objname, (kv_size(*mpstack) == 0
|
|
? _("itself")
|
|
: (char *) msg_ga.ga_data));
|
|
ga_clear(&msg_ga);
|
|
return FAIL;
|
|
}
|
|
|
|
/// Convert readfile()-style list to a char * buffer with length
|
|
///
|
|
/// @param[in] list Converted list.
|
|
/// @param[out] ret_len Resulting buffer length.
|
|
/// @param[out] ret_buf Allocated buffer with the result or NULL if ret_len is
|
|
/// zero.
|
|
///
|
|
/// @return true in case of success, false in case of failure.
|
|
bool encode_vim_list_to_buf(const list_T *const list, size_t *const ret_len,
|
|
char **const ret_buf)
|
|
FUNC_ATTR_NONNULL_ARG(2,3) FUNC_ATTR_WARN_UNUSED_RESULT
|
|
{
|
|
size_t len = 0;
|
|
if (list != NULL) {
|
|
for (const listitem_T *li = list->lv_first;
|
|
li != NULL;
|
|
li = li->li_next) {
|
|
if (li->li_tv.v_type != VAR_STRING) {
|
|
return false;
|
|
}
|
|
len++;
|
|
if (li->li_tv.vval.v_string != 0) {
|
|
len += STRLEN(li->li_tv.vval.v_string);
|
|
}
|
|
}
|
|
if (len) {
|
|
len--;
|
|
}
|
|
}
|
|
*ret_len = len;
|
|
if (len == 0) {
|
|
*ret_buf = NULL;
|
|
return true;
|
|
}
|
|
ListReaderState lrstate = encode_init_lrstate(list);
|
|
char *const buf = xmalloc(len);
|
|
size_t read_bytes;
|
|
if (encode_read_from_list(&lrstate, buf, len, &read_bytes) != OK) {
|
|
assert(false);
|
|
}
|
|
assert(len == read_bytes);
|
|
*ret_buf = buf;
|
|
return true;
|
|
}
|
|
|
|
/// Read bytes from list
|
|
///
|
|
/// @param[in,out] state Structure describing position in list from which
|
|
/// reading should start. Is updated to reflect position
|
|
/// at which reading ended.
|
|
/// @param[out] buf Buffer to write to.
|
|
/// @param[in] nbuf Buffer length.
|
|
/// @param[out] read_bytes Is set to amount of bytes read.
|
|
///
|
|
/// @return OK when reading was finished, FAIL in case of error (i.e. list item
|
|
/// was not a string), NOTDONE if reading was successfull, but there are
|
|
/// more bytes to read.
|
|
int encode_read_from_list(ListReaderState *const state, char *const buf,
|
|
const size_t nbuf, size_t *const read_bytes)
|
|
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
|
|
{
|
|
char *const buf_end = buf + nbuf;
|
|
char *p = buf;
|
|
while (p < buf_end) {
|
|
for (size_t i = state->offset; i < state->li_length && p < buf_end; i++) {
|
|
const char ch = (char) state->li->li_tv.vval.v_string[state->offset++];
|
|
*p++ = (ch == NL ? NUL : ch);
|
|
}
|
|
if (p < buf_end) {
|
|
state->li = state->li->li_next;
|
|
if (state->li == NULL) {
|
|
*read_bytes = (size_t) (p - buf);
|
|
return OK;
|
|
}
|
|
*p++ = NL;
|
|
if (state->li->li_tv.v_type != VAR_STRING) {
|
|
*read_bytes = (size_t) (p - buf);
|
|
return FAIL;
|
|
}
|
|
state->offset = 0;
|
|
state->li_length = (state->li->li_tv.vval.v_string == NULL
|
|
? 0
|
|
: STRLEN(state->li->li_tv.vval.v_string));
|
|
}
|
|
}
|
|
*read_bytes = nbuf;
|
|
return (state->offset < state->li_length || state->li->li_next != NULL
|
|
? NOTDONE
|
|
: OK);
|
|
}
|
|
|
|
/// Code for checking whether container references itself
|
|
///
|
|
/// @param[in,out] val Container to check.
|
|
/// @param copyID_attr Name of the container attribute that holds copyID.
|
|
/// After checking whether value of this attribute is
|
|
/// copyID (variable) it is set to copyID.
|
|
#define CHECK_SELF_REFERENCE(val, copyID_attr, conv_type) \
|
|
do { \
|
|
if ((val)->copyID_attr == copyID) { \
|
|
CONV_RECURSE((val), conv_type); \
|
|
} \
|
|
(val)->copyID_attr = copyID; \
|
|
} while (0)
|
|
|
|
/// Define functions which convert VimL value to something else
|
|
///
|
|
/// Creates function `vim_to_{name}(firstargtype firstargname, typval_T *const
|
|
/// tv)` which returns OK or FAIL and helper functions.
|
|
///
|
|
/// @param firstargtype Type of the first argument. It will be used to return
|
|
/// the results.
|
|
/// @param firstargname Name of the first argument.
|
|
/// @param name Name of the target converter.
|
|
#define DEFINE_VIML_CONV_FUNCTIONS(scope, name, firstargtype, firstargname) \
|
|
static int name##_convert_one_value(firstargtype firstargname, \
|
|
MPConvStack *const mpstack, \
|
|
typval_T *const tv, \
|
|
const int copyID, \
|
|
const char *const objname) \
|
|
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \
|
|
{ \
|
|
switch (tv->v_type) { \
|
|
case VAR_STRING: { \
|
|
CONV_STRING(tv->vval.v_string, STRLEN(tv->vval.v_string)); \
|
|
break; \
|
|
} \
|
|
case VAR_NUMBER: { \
|
|
CONV_NUMBER(tv->vval.v_number); \
|
|
break; \
|
|
} \
|
|
case VAR_FLOAT: { \
|
|
CONV_FLOAT(tv->vval.v_float); \
|
|
break; \
|
|
} \
|
|
case VAR_FUNC: { \
|
|
CONV_FUNC(tv->vval.v_string); \
|
|
break; \
|
|
} \
|
|
case VAR_LIST: { \
|
|
if (tv->vval.v_list == NULL || tv->vval.v_list->lv_len == 0) { \
|
|
CONV_EMPTY_LIST(); \
|
|
break; \
|
|
} \
|
|
CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, kMPConvList); \
|
|
CONV_LIST_START(tv->vval.v_list); \
|
|
kv_push( \
|
|
MPConvStackVal, \
|
|
*mpstack, \
|
|
((MPConvStackVal) { \
|
|
.type = kMPConvList, \
|
|
.data = { \
|
|
.l = { \
|
|
.list = tv->vval.v_list, \
|
|
.li = tv->vval.v_list->lv_first, \
|
|
}, \
|
|
}, \
|
|
})); \
|
|
break; \
|
|
} \
|
|
case VAR_SPECIAL: { \
|
|
switch (tv->vval.v_special) { \
|
|
case kSpecialVarNull: { \
|
|
CONV_NIL(); \
|
|
break; \
|
|
} \
|
|
case kSpecialVarTrue: \
|
|
case kSpecialVarFalse: { \
|
|
CONV_BOOL(tv->vval.v_special == kSpecialVarTrue); \
|
|
break; \
|
|
} \
|
|
case kSpecialVarNone: { \
|
|
CONV_NONE_VAL(); \
|
|
break; \
|
|
} \
|
|
} \
|
|
break; \
|
|
} \
|
|
case VAR_DICT: { \
|
|
if (tv->vval.v_dict == NULL \
|
|
|| tv->vval.v_dict->dv_hashtab.ht_used == 0) { \
|
|
CONV_EMPTY_DICT(); \
|
|
break; \
|
|
} \
|
|
const dictitem_T *type_di; \
|
|
const dictitem_T *val_di; \
|
|
if (CONV_ALLOW_SPECIAL \
|
|
&& tv->vval.v_dict->dv_hashtab.ht_used == 2 \
|
|
&& (type_di = dict_find((dict_T *) tv->vval.v_dict, \
|
|
(char_u *) "_TYPE", -1)) != NULL \
|
|
&& type_di->di_tv.v_type == VAR_LIST \
|
|
&& (val_di = dict_find((dict_T *) tv->vval.v_dict, \
|
|
(char_u *) "_VAL", -1)) != NULL) { \
|
|
size_t i; \
|
|
for (i = 0; i < ARRAY_SIZE(eval_msgpack_type_lists); i++) { \
|
|
if (type_di->di_tv.vval.v_list == eval_msgpack_type_lists[i]) { \
|
|
break; \
|
|
} \
|
|
} \
|
|
if (i == ARRAY_SIZE(eval_msgpack_type_lists)) { \
|
|
goto name##_convert_one_value_regular_dict; \
|
|
} \
|
|
switch ((MessagePackType) i) { \
|
|
case kMPNil: { \
|
|
CONV_NIL(); \
|
|
break; \
|
|
} \
|
|
case kMPBoolean: { \
|
|
if (val_di->di_tv.v_type != VAR_NUMBER) { \
|
|
goto name##_convert_one_value_regular_dict; \
|
|
} \
|
|
CONV_BOOL(val_di->di_tv.vval.v_number); \
|
|
break; \
|
|
} \
|
|
case kMPInteger: { \
|
|
const list_T *val_list; \
|
|
varnumber_T sign; \
|
|
varnumber_T highest_bits; \
|
|
varnumber_T high_bits; \
|
|
varnumber_T low_bits; \
|
|
/* List of 4 integers; first is signed (should be 1 or -1, but */ \
|
|
/* this is not checked), second is unsigned and have at most */ \
|
|
/* one (sign is -1) or two (sign is 1) non-zero bits (number of */ \
|
|
/* bits is not checked), other unsigned and have at most 31 */ \
|
|
/* non-zero bits (number of bits is not checked).*/ \
|
|
if (val_di->di_tv.v_type != VAR_LIST \
|
|
|| (val_list = val_di->di_tv.vval.v_list) == NULL \
|
|
|| val_list->lv_len != 4 \
|
|
|| val_list->lv_first->li_tv.v_type != VAR_NUMBER \
|
|
|| (sign = val_list->lv_first->li_tv.vval.v_number) == 0 \
|
|
|| val_list->lv_first->li_next->li_tv.v_type != VAR_NUMBER \
|
|
|| (highest_bits = \
|
|
val_list->lv_first->li_next->li_tv.vval.v_number) < 0 \
|
|
|| val_list->lv_last->li_prev->li_tv.v_type != VAR_NUMBER \
|
|
|| (high_bits = \
|
|
val_list->lv_last->li_prev->li_tv.vval.v_number) < 0 \
|
|
|| val_list->lv_last->li_tv.v_type != VAR_NUMBER \
|
|
|| (low_bits = val_list->lv_last->li_tv.vval.v_number) < 0) { \
|
|
goto name##_convert_one_value_regular_dict; \
|
|
} \
|
|
uint64_t number = ((uint64_t) (((uint64_t) highest_bits) << 62) \
|
|
| (uint64_t) (((uint64_t) high_bits) << 31) \
|
|
| (uint64_t) low_bits); \
|
|
if (sign > 0) { \
|
|
CONV_UNSIGNED_NUMBER(number); \
|
|
} else { \
|
|
CONV_NUMBER(-number); \
|
|
} \
|
|
break; \
|
|
} \
|
|
case kMPFloat: { \
|
|
if (val_di->di_tv.v_type != VAR_FLOAT) { \
|
|
goto name##_convert_one_value_regular_dict; \
|
|
} \
|
|
CONV_FLOAT(val_di->di_tv.vval.v_float); \
|
|
break; \
|
|
} \
|
|
case kMPString: \
|
|
case kMPBinary: { \
|
|
const bool is_string = ((MessagePackType) i == kMPString); \
|
|
if (val_di->di_tv.v_type != VAR_LIST) { \
|
|
goto name##_convert_one_value_regular_dict; \
|
|
} \
|
|
size_t len; \
|
|
char *buf; \
|
|
if (!encode_vim_list_to_buf(val_di->di_tv.vval.v_list, &len, \
|
|
&buf)) { \
|
|
goto name##_convert_one_value_regular_dict; \
|
|
} \
|
|
if (is_string) { \
|
|
CONV_STR_STRING(buf, len); \
|
|
} else { \
|
|
CONV_STRING(buf, len); \
|
|
} \
|
|
xfree(buf); \
|
|
break; \
|
|
} \
|
|
case kMPArray: { \
|
|
if (val_di->di_tv.v_type != VAR_LIST) { \
|
|
goto name##_convert_one_value_regular_dict; \
|
|
} \
|
|
CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, lv_copyID, \
|
|
kMPConvList); \
|
|
CONV_LIST_START(val_di->di_tv.vval.v_list); \
|
|
kv_push(MPConvStackVal, *mpstack, ((MPConvStackVal) { \
|
|
.type = kMPConvList, \
|
|
.data = { \
|
|
.l = { \
|
|
.list = val_di->di_tv.vval.v_list, \
|
|
.li = val_di->di_tv.vval.v_list->lv_first, \
|
|
}, \
|
|
}, \
|
|
})); \
|
|
break; \
|
|
} \
|
|
case kMPMap: { \
|
|
if (val_di->di_tv.v_type != VAR_LIST) { \
|
|
goto name##_convert_one_value_regular_dict; \
|
|
} \
|
|
list_T *const val_list = val_di->di_tv.vval.v_list; \
|
|
if (val_list == NULL || val_list->lv_len == 0) { \
|
|
CONV_EMPTY_DICT(); \
|
|
break; \
|
|
} \
|
|
for (const listitem_T *li = val_list->lv_first; li != NULL; \
|
|
li = li->li_next) { \
|
|
if (li->li_tv.v_type != VAR_LIST \
|
|
|| li->li_tv.vval.v_list->lv_len != 2) { \
|
|
goto name##_convert_one_value_regular_dict; \
|
|
} \
|
|
} \
|
|
CHECK_SELF_REFERENCE(val_list, lv_copyID, kMPConvPairs); \
|
|
CONV_DICT_START(val_list->lv_len); \
|
|
kv_push(MPConvStackVal, *mpstack, ((MPConvStackVal) { \
|
|
.type = kMPConvPairs, \
|
|
.data = { \
|
|
.l = { \
|
|
.list = val_list, \
|
|
.li = val_list->lv_first, \
|
|
}, \
|
|
}, \
|
|
})); \
|
|
break; \
|
|
} \
|
|
case kMPExt: { \
|
|
const list_T *val_list; \
|
|
varnumber_T type; \
|
|
if (val_di->di_tv.v_type != VAR_LIST \
|
|
|| (val_list = val_di->di_tv.vval.v_list) == NULL \
|
|
|| val_list->lv_len != 2 \
|
|
|| (val_list->lv_first->li_tv.v_type != VAR_NUMBER) \
|
|
|| (type = val_list->lv_first->li_tv.vval.v_number) > INT8_MAX \
|
|
|| type < INT8_MIN \
|
|
|| (val_list->lv_last->li_tv.v_type != VAR_LIST)) { \
|
|
goto name##_convert_one_value_regular_dict; \
|
|
} \
|
|
size_t len; \
|
|
char *buf; \
|
|
if (!encode_vim_list_to_buf(val_list->lv_last->li_tv.vval.v_list, \
|
|
&len, &buf)) { \
|
|
goto name##_convert_one_value_regular_dict; \
|
|
} \
|
|
CONV_EXT_STRING(buf, len, type); \
|
|
xfree(buf); \
|
|
break; \
|
|
} \
|
|
} \
|
|
break; \
|
|
} \
|
|
name##_convert_one_value_regular_dict: \
|
|
CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, kMPConvDict); \
|
|
CONV_DICT_START(tv->vval.v_dict->dv_hashtab.ht_used); \
|
|
kv_push(MPConvStackVal, *mpstack, ((MPConvStackVal) { \
|
|
.type = kMPConvDict, \
|
|
.data = { \
|
|
.d = { \
|
|
.dict = tv->vval.v_dict, \
|
|
.hi = tv->vval.v_dict->dv_hashtab.ht_array, \
|
|
.todo = tv->vval.v_dict->dv_hashtab.ht_used, \
|
|
}, \
|
|
}, \
|
|
})); \
|
|
break; \
|
|
} \
|
|
case VAR_UNKNOWN: { \
|
|
EMSG2(_(e_intern2), #name "_convert_one_value()"); \
|
|
return FAIL; \
|
|
} \
|
|
} \
|
|
return OK; \
|
|
} \
|
|
\
|
|
scope int encode_vim_to_##name(firstargtype firstargname, typval_T *const tv, \
|
|
const char *const objname) \
|
|
FUNC_ATTR_WARN_UNUSED_RESULT \
|
|
{ \
|
|
const int copyID = get_copyID(); \
|
|
MPConvStack mpstack; \
|
|
kv_init(mpstack); \
|
|
if (name##_convert_one_value(firstargname, &mpstack, tv, copyID, objname) \
|
|
== FAIL) { \
|
|
goto encode_vim_to_##name##_error_ret; \
|
|
} \
|
|
while (kv_size(mpstack)) { \
|
|
MPConvStackVal *cur_mpsv = &kv_A(mpstack, kv_size(mpstack) - 1); \
|
|
typval_T *cur_tv = NULL; \
|
|
switch (cur_mpsv->type) { \
|
|
case kMPConvDict: { \
|
|
if (!cur_mpsv->data.d.todo) { \
|
|
(void) kv_pop(mpstack); \
|
|
cur_mpsv->data.d.dict->dv_copyID = copyID - 1; \
|
|
CONV_DICT_END(); \
|
|
continue; \
|
|
} else if (cur_mpsv->data.d.todo \
|
|
!= cur_mpsv->data.d.dict->dv_hashtab.ht_used) { \
|
|
CONV_DICT_BETWEEN_ITEMS(); \
|
|
} \
|
|
while (HASHITEM_EMPTY(cur_mpsv->data.d.hi)) { \
|
|
cur_mpsv->data.d.hi++; \
|
|
} \
|
|
dictitem_T *const di = HI2DI(cur_mpsv->data.d.hi); \
|
|
cur_mpsv->data.d.todo--; \
|
|
cur_mpsv->data.d.hi++; \
|
|
CONV_STR_STRING(&di->di_key[0], STRLEN(&di->di_key[0])); \
|
|
CONV_DICT_AFTER_KEY(); \
|
|
cur_tv = &di->di_tv; \
|
|
break; \
|
|
} \
|
|
case kMPConvList: { \
|
|
if (cur_mpsv->data.l.li == NULL) { \
|
|
(void) kv_pop(mpstack); \
|
|
cur_mpsv->data.l.list->lv_copyID = copyID - 1; \
|
|
CONV_LIST_END(cur_mpsv->data.l.list); \
|
|
continue; \
|
|
} else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { \
|
|
CONV_LIST_BETWEEN_ITEMS(); \
|
|
} \
|
|
cur_tv = &cur_mpsv->data.l.li->li_tv; \
|
|
cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; \
|
|
break; \
|
|
} \
|
|
case kMPConvPairs: { \
|
|
if (cur_mpsv->data.l.li == NULL) { \
|
|
(void) kv_pop(mpstack); \
|
|
cur_mpsv->data.l.list->lv_copyID = copyID - 1; \
|
|
CONV_DICT_END(); \
|
|
continue; \
|
|
} else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { \
|
|
CONV_DICT_BETWEEN_ITEMS(); \
|
|
} \
|
|
const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list; \
|
|
CONV_SPECIAL_DICT_KEY_CHECK(kv_pair); \
|
|
if (name##_convert_one_value(firstargname, &mpstack, \
|
|
&kv_pair->lv_first->li_tv, copyID, \
|
|
objname) == FAIL) { \
|
|
goto encode_vim_to_##name##_error_ret; \
|
|
} \
|
|
CONV_DICT_AFTER_KEY(); \
|
|
cur_tv = &kv_pair->lv_last->li_tv; \
|
|
cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; \
|
|
break; \
|
|
} \
|
|
} \
|
|
assert(cur_tv != NULL); \
|
|
if (name##_convert_one_value(firstargname, &mpstack, cur_tv, copyID, \
|
|
objname) == FAIL) { \
|
|
goto encode_vim_to_##name##_error_ret; \
|
|
} \
|
|
} \
|
|
kv_destroy(mpstack); \
|
|
return OK; \
|
|
encode_vim_to_##name##_error_ret: \
|
|
kv_destroy(mpstack); \
|
|
return FAIL; \
|
|
}
|
|
|
|
#define CONV_STRING(buf, len) \
|
|
do { \
|
|
const char *const buf_ = (const char *) buf; \
|
|
if (buf == NULL) { \
|
|
ga_concat(gap, "''"); \
|
|
} else { \
|
|
const size_t len_ = (len); \
|
|
size_t num_quotes = 0; \
|
|
for (size_t i = 0; i < len_; i++) { \
|
|
if (buf_[i] == '\'') { \
|
|
num_quotes++; \
|
|
} \
|
|
} \
|
|
ga_grow(gap, (int) (2 + len_ + num_quotes)); \
|
|
ga_append(gap, '\''); \
|
|
for (size_t i = 0; i < len_; i++) { \
|
|
if (buf_[i] == '\'') { \
|
|
num_quotes++; \
|
|
ga_append(gap, '\''); \
|
|
} \
|
|
ga_append(gap, buf_[i]); \
|
|
} \
|
|
ga_append(gap, '\''); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define CONV_STR_STRING(buf, len) \
|
|
CONV_STRING(buf, len)
|
|
|
|
#define CONV_EXT_STRING(buf, len, type)
|
|
|
|
#define CONV_NUMBER(num) \
|
|
do { \
|
|
char numbuf[NUMBUFLEN]; \
|
|
vim_snprintf(numbuf, NUMBUFLEN - 1, "%" PRId64, (int64_t) (num)); \
|
|
ga_concat(gap, numbuf); \
|
|
} while (0)
|
|
|
|
#define CONV_FLOAT(flt) \
|
|
do { \
|
|
const float_T flt_ = (flt); \
|
|
switch (fpclassify(flt_)) { \
|
|
case FP_NAN: { \
|
|
ga_concat(gap, (char_u *) "str2float('nan')"); \
|
|
break; \
|
|
} \
|
|
case FP_INFINITE: { \
|
|
if (flt_ < 0) { \
|
|
ga_append(gap, '-'); \
|
|
} \
|
|
ga_concat(gap, (char_u *) "str2float('inf')"); \
|
|
break; \
|
|
} \
|
|
default: { \
|
|
char numbuf[NUMBUFLEN]; \
|
|
vim_snprintf(numbuf, NUMBUFLEN - 1, "%g", flt_); \
|
|
ga_concat(gap, (char_u *) numbuf); \
|
|
} \
|
|
} \
|
|
} while (0)
|
|
|
|
#define CONV_FUNC(fun) \
|
|
do { \
|
|
ga_concat(gap, "function("); \
|
|
CONV_STRING(fun, STRLEN(fun)); \
|
|
ga_append(gap, ')'); \
|
|
} while (0)
|
|
|
|
#define CONV_EMPTY_LIST() \
|
|
ga_concat(gap, "[]")
|
|
|
|
#define CONV_LIST_START(lst) \
|
|
ga_append(gap, '[')
|
|
|
|
#define CONV_EMPTY_DICT() \
|
|
ga_concat(gap, "{}")
|
|
|
|
#define CONV_NIL() \
|
|
ga_concat(gap, "v:null")
|
|
|
|
#define CONV_BOOL(num) \
|
|
ga_concat(gap, ((num)? "v:true": "v:false"))
|
|
|
|
#define CONV_NONE_VAL() \
|
|
ga_concat(gap, "v:none")
|
|
|
|
#define CONV_UNSIGNED_NUMBER(num)
|
|
|
|
#define CONV_DICT_START(len) \
|
|
ga_append(gap, '{')
|
|
|
|
#define CONV_DICT_END() \
|
|
ga_append(gap, '}')
|
|
|
|
#define CONV_DICT_AFTER_KEY() \
|
|
ga_concat(gap, ": ")
|
|
|
|
#define CONV_DICT_BETWEEN_ITEMS() \
|
|
ga_concat(gap, ", ")
|
|
|
|
#define CONV_SPECIAL_DICT_KEY_CHECK(kv_pair)
|
|
|
|
#define CONV_LIST_END(lst) \
|
|
ga_append(gap, ']')
|
|
|
|
#define CONV_LIST_BETWEEN_ITEMS() \
|
|
CONV_DICT_BETWEEN_ITEMS()
|
|
|
|
#define CONV_RECURSE(val, conv_type) \
|
|
do { \
|
|
if (!did_echo_string_emsg) { \
|
|
/* Only give this message once for a recursive call to avoid */ \
|
|
/* flooding the user with errors. */ \
|
|
did_echo_string_emsg = true; \
|
|
EMSG(_("E724: unable to correctly dump variable " \
|
|
"with self-referencing container")); \
|
|
} \
|
|
char ebuf[NUMBUFLEN + 7]; \
|
|
size_t backref = 0; \
|
|
for (; backref < kv_size(*mpstack); backref++) { \
|
|
const MPConvStackVal mpval = kv_a(MPConvStackVal, *mpstack, backref); \
|
|
if (mpval.type == conv_type) { \
|
|
if (conv_type == kMPConvDict) { \
|
|
if ((void *) mpval.data.d.dict == (void *) (val)) { \
|
|
break; \
|
|
} \
|
|
} else if (conv_type == kMPConvList) { \
|
|
if ((void *) mpval.data.l.list == (void *) (val)) { \
|
|
break; \
|
|
} \
|
|
} \
|
|
} \
|
|
} \
|
|
vim_snprintf(ebuf, NUMBUFLEN + 6, "{E724@%zu}", backref); \
|
|
ga_concat(gap, &ebuf[0]); \
|
|
return OK; \
|
|
} while (0)
|
|
|
|
#define CONV_ALLOW_SPECIAL false
|
|
|
|
DEFINE_VIML_CONV_FUNCTIONS(static, string, garray_T *const, gap)
|
|
|
|
#undef CONV_RECURSE
|
|
#define CONV_RECURSE(val, conv_type) \
|
|
do { \
|
|
char ebuf[NUMBUFLEN + 7]; \
|
|
size_t backref = 0; \
|
|
for (; backref < kv_size(*mpstack); backref++) { \
|
|
const MPConvStackVal mpval = kv_a(MPConvStackVal, *mpstack, backref); \
|
|
if (mpval.type == conv_type) { \
|
|
if (conv_type == kMPConvDict) { \
|
|
if ((void *) mpval.data.d.dict == (void *) val) { \
|
|
break; \
|
|
} \
|
|
} else if (conv_type == kMPConvList) { \
|
|
if ((void *) mpval.data.l.list == (void *) val) { \
|
|
break; \
|
|
} \
|
|
} \
|
|
} \
|
|
} \
|
|
if (conv_type == kMPConvDict) { \
|
|
vim_snprintf(ebuf, NUMBUFLEN + 6, "{...@%zu}", backref); \
|
|
} else { \
|
|
vim_snprintf(ebuf, NUMBUFLEN + 6, "[...@%zu]", backref); \
|
|
} \
|
|
ga_concat(gap, &ebuf[0]); \
|
|
return OK; \
|
|
} while (0)
|
|
|
|
DEFINE_VIML_CONV_FUNCTIONS(, echo, garray_T *const, gap)
|
|
|
|
#undef CONV_RECURSE
|
|
#define CONV_RECURSE(val, conv_type) \
|
|
do { \
|
|
if (!did_echo_string_emsg) { \
|
|
/* Only give this message once for a recursive call to avoid */ \
|
|
/* flooding the user with errors. */ \
|
|
did_echo_string_emsg = true; \
|
|
EMSG(_("E724: unable to correctly dump variable " \
|
|
"with self-referencing container")); \
|
|
} \
|
|
return OK; \
|
|
} while (0)
|
|
|
|
#undef CONV_ALLOW_SPECIAL
|
|
#define CONV_ALLOW_SPECIAL true
|
|
|
|
#undef CONV_NIL
|
|
#define CONV_NIL() \
|
|
ga_concat(gap, "null")
|
|
|
|
#undef CONV_BOOL
|
|
#define CONV_BOOL(num) \
|
|
ga_concat(gap, ((num)? "true": "false"))
|
|
|
|
#undef CONV_UNSIGNED_NUMBER
|
|
#define CONV_UNSIGNED_NUMBER(num) \
|
|
do { \
|
|
char numbuf[NUMBUFLEN]; \
|
|
vim_snprintf(numbuf, sizeof(numbuf), "%" PRIu64, (num)); \
|
|
ga_concat(gap, numbuf); \
|
|
} while (0)
|
|
|
|
#undef CONV_FLOAT
|
|
#define CONV_FLOAT(flt) \
|
|
do { \
|
|
char numbuf[NUMBUFLEN]; \
|
|
vim_snprintf(numbuf, NUMBUFLEN - 1, "%g", (flt)); \
|
|
ga_concat(gap, numbuf); \
|
|
} while (0)
|
|
|
|
/// Last used p_enc value
|
|
///
|
|
/// Generic pointer: it is not used as a string, only pointer comparisons are
|
|
/// performed. Must not be freed.
|
|
static const void *last_p_enc = NULL;
|
|
|
|
/// Conversion setup for converting from last_p_enc to UTF-8
|
|
static vimconv_T p_enc_conv = {
|
|
.vc_type = CONV_NONE,
|
|
};
|
|
|
|
/// Escape sequences used in JSON
|
|
static const char escapes[][3] = {
|
|
[BS] = "\\b",
|
|
[TAB] = "\\t",
|
|
[NL] = "\\n",
|
|
[CAR] = "\\r",
|
|
['"'] = "\\\"",
|
|
['\\'] = "\\\\",
|
|
};
|
|
|
|
static const char xdigits[] = "0123456789ABCDEF";
|
|
|
|
/// Convert given string to JSON string
|
|
///
|
|
/// @param[out] gap Garray where result will be saved.
|
|
/// @param[in] buf Converted string.
|
|
/// @param[in] len Converted string length.
|
|
///
|
|
/// @return OK in case of success, FAIL otherwise.
|
|
static inline int convert_to_json_string(garray_T *const gap,
|
|
const char *const buf,
|
|
const size_t len)
|
|
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_ALWAYS_INLINE
|
|
{
|
|
const char *buf_ = buf;
|
|
if (buf_ == NULL) {
|
|
ga_concat(gap, "\"\"");
|
|
} else {
|
|
size_t len_ = len;
|
|
char *tofree = NULL;
|
|
if (last_p_enc != (const void *) p_enc) {
|
|
convert_setup(&p_enc_conv, p_enc, "utf-8");
|
|
p_enc_conv.vc_fail = true;
|
|
last_p_enc = p_enc;
|
|
}
|
|
if (p_enc_conv.vc_type != CONV_NONE) {
|
|
tofree = string_convert(&p_enc_conv, buf_, &len_);
|
|
if (tofree == NULL) {
|
|
EMSG2(_("E474: Failed to convert string \"%s\" to UTF-8"), buf_);
|
|
return FAIL;
|
|
}
|
|
buf_ = tofree;
|
|
}
|
|
size_t str_len = 0;
|
|
for (size_t i = 0; i < len_;) {
|
|
const int ch = utf_ptr2char(buf + i);
|
|
const size_t shift = (ch == 0? 1: utf_ptr2len(buf + i));
|
|
assert(shift > 0);
|
|
i += shift;
|
|
switch (ch) {
|
|
case BS:
|
|
case TAB:
|
|
case NL:
|
|
case FF:
|
|
case CAR:
|
|
case '"':
|
|
case '\\': {
|
|
str_len += 2;
|
|
break;
|
|
}
|
|
default: {
|
|
if (ch > 0x7F && shift == 1) {
|
|
EMSG2(_("E474: String \"%s\" contains byte that does not start any "
|
|
"UTF-8 character"), buf_);
|
|
return FAIL;
|
|
} else if ((0xD800 <= ch && ch <= 0xDB7F)
|
|
|| (0xDC00 <= ch && ch <= 0xDFFF)) {
|
|
EMSG2(_("E474: UTF-8 string contains code point which belongs "
|
|
"to surrogate pairs"), buf_);
|
|
return FAIL;
|
|
} else if (vim_isprintc(ch)) {
|
|
str_len += shift;
|
|
} else {
|
|
str_len += ((sizeof("\\u1234") - 1) * (1 + (ch > 0xFFFF)));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
ga_append(gap, '"');
|
|
ga_grow(gap, (int) str_len);
|
|
for (size_t i = 0; i < len_;) {
|
|
const int ch = utf_ptr2char(buf + i);
|
|
const size_t shift = (ch == 0? 1: utf_char2len(ch));
|
|
assert(shift > 0);
|
|
// Is false on invalid unicode, but this should already be handled.
|
|
assert(ch == 0 || shift == utf_ptr2len(buf + i));
|
|
switch (ch) {
|
|
case BS:
|
|
case TAB:
|
|
case NL:
|
|
case FF:
|
|
case CAR:
|
|
case '"':
|
|
case '\\': {
|
|
ga_concat_len(gap, escapes[ch], 2);
|
|
break;
|
|
}
|
|
default: {
|
|
if (vim_isprintc(ch)) {
|
|
ga_concat_len(gap, buf + i, shift);
|
|
} else if (ch < SURROGATE_FIRST_CHAR) {
|
|
ga_concat_len(gap, ((const char []) {
|
|
'\\', 'u',
|
|
xdigits[(ch >> (4 * 3)) & 0xF],
|
|
xdigits[(ch >> (4 * 2)) & 0xF],
|
|
xdigits[(ch >> (4 * 1)) & 0xF],
|
|
xdigits[(ch >> (4 * 0)) & 0xF],
|
|
}), sizeof("\\u1234") - 1);
|
|
} else {
|
|
uint32_t tmp = (uint32_t) ch - SURROGATE_FIRST_CHAR;
|
|
uint16_t hi = SURROGATE_HI_START + ((tmp >> 10) & ((1 << 10) - 1));
|
|
uint16_t lo = SURROGATE_LO_END + ((tmp >> 0) & ((1 << 10) - 1));
|
|
ga_concat_len(gap, ((const char []) {
|
|
'\\', 'u',
|
|
xdigits[(hi >> (4 * 3)) & 0xF],
|
|
xdigits[(hi >> (4 * 2)) & 0xF],
|
|
xdigits[(hi >> (4 * 1)) & 0xF],
|
|
xdigits[(hi >> (4 * 0)) & 0xF],
|
|
'\\', 'u',
|
|
xdigits[(lo >> (4 * 3)) & 0xF],
|
|
xdigits[(lo >> (4 * 2)) & 0xF],
|
|
xdigits[(lo >> (4 * 1)) & 0xF],
|
|
xdigits[(lo >> (4 * 0)) & 0xF],
|
|
}), (sizeof("\\u1234") - 1) * 2);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
i += shift;
|
|
}
|
|
ga_append(gap, '"');
|
|
xfree(tofree);
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
#undef CONV_STRING
|
|
#define CONV_STRING(buf, len) \
|
|
do { \
|
|
if (convert_to_json_string(gap, (const char *) (buf), (len)) != OK) { \
|
|
return FAIL; \
|
|
} \
|
|
} while (0)
|
|
|
|
#undef CONV_EXT_STRING
|
|
#define CONV_EXT_STRING(buf, len, type) \
|
|
do { \
|
|
xfree(buf); \
|
|
EMSG(_("E474: Unable to convert EXT string to JSON")); \
|
|
return FAIL; \
|
|
} while (0)
|
|
|
|
#undef CONV_FUNC
|
|
#define CONV_FUNC(fun) \
|
|
return conv_error(_("E474: Error while dumping %s, %s: " \
|
|
"attempt to dump function reference"), \
|
|
mpstack, objname)
|
|
|
|
/// Check whether given key can be used in jsonencode()
|
|
///
|
|
/// @param[in] tv Key to check.
|
|
static inline bool check_json_key(const typval_T *const tv)
|
|
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
|
|
FUNC_ATTR_ALWAYS_INLINE
|
|
{
|
|
if (tv->v_type == VAR_STRING) {
|
|
return true;
|
|
}
|
|
if (tv->v_type != VAR_DICT) {
|
|
return false;
|
|
}
|
|
const dict_T *const spdict = tv->vval.v_dict;
|
|
if (spdict->dv_hashtab.ht_used != 2) {
|
|
return false;
|
|
}
|
|
const dictitem_T *type_di;
|
|
const dictitem_T *val_di;
|
|
if ((type_di = dict_find((dict_T *) spdict, (char_u *) "_TYPE", -1)) == NULL
|
|
|| type_di->di_tv.v_type != VAR_LIST
|
|
|| (type_di->di_tv.vval.v_list != eval_msgpack_type_lists[kMPString]
|
|
&& type_di->di_tv.vval.v_list != eval_msgpack_type_lists[kMPBinary])
|
|
|| (val_di = dict_find((dict_T *) spdict, (char_u *) "_VAL", -1)) == NULL
|
|
|| val_di->di_tv.v_type != VAR_LIST) {
|
|
return false;
|
|
}
|
|
if (val_di->di_tv.vval.v_list == NULL) {
|
|
return true;
|
|
}
|
|
for (const listitem_T *li = val_di->di_tv.vval.v_list->lv_first;
|
|
li != NULL; li = li->li_next) {
|
|
if (li->li_tv.v_type != VAR_STRING) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#undef CONV_SPECIAL_DICT_KEY_CHECK
|
|
#define CONV_SPECIAL_DICT_KEY_CHECK(kv_pair) \
|
|
do { \
|
|
if (!check_json_key(&kv_pair->lv_first->li_tv)) { \
|
|
EMSG(_("E474: Invalid key in special dictionary")); \
|
|
return FAIL; \
|
|
} \
|
|
} while (0)
|
|
|
|
#undef CONV_NONE_VAL
|
|
#define CONV_NONE_VAL()
|
|
|
|
DEFINE_VIML_CONV_FUNCTIONS(static, json, garray_T *const, gap)
|
|
|
|
#undef CONV_STRING
|
|
#undef CONV_STR_STRING
|
|
#undef CONV_EXT_STRING
|
|
#undef CONV_NUMBER
|
|
#undef CONV_FLOAT
|
|
#undef CONV_FUNC
|
|
#undef CONV_EMPTY_LIST
|
|
#undef CONV_LIST_START
|
|
#undef CONV_EMPTY_DICT
|
|
#undef CONV_NIL
|
|
#undef CONV_BOOL
|
|
#undef CONV_NONE_VAL
|
|
#undef CONV_UNSIGNED_NUMBER
|
|
#undef CONV_DICT_START
|
|
#undef CONV_DICT_END
|
|
#undef CONV_DICT_AFTER_KEY
|
|
#undef CONV_DICT_BETWEEN_ITEMS
|
|
#undef CONV_SPECIAL_DICT_KEY_CHECK
|
|
#undef CONV_LIST_END
|
|
#undef CONV_LIST_BETWEEN_ITEMS
|
|
#undef CONV_RECURSE
|
|
#undef CONV_ALLOW_SPECIAL
|
|
|
|
/// Return a string with the string representation of a variable.
|
|
/// Puts quotes around strings, so that they can be parsed back by eval().
|
|
///
|
|
/// @param[in] tv typval_T to convert.
|
|
/// @param[out] len Location where length of the result will be saved.
|
|
///
|
|
/// @return String representation of the variable or NULL.
|
|
char *encode_tv2string(typval_T *tv, size_t *len)
|
|
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_MALLOC
|
|
{
|
|
garray_T ga;
|
|
ga_init(&ga, (int)sizeof(char), 80);
|
|
encode_vim_to_string(&ga, tv, "encode_tv2string() argument");
|
|
did_echo_string_emsg = false;
|
|
if (len != NULL) {
|
|
*len = (size_t) ga.ga_len;
|
|
}
|
|
ga_append(&ga, '\0');
|
|
return (char *) ga.ga_data;
|
|
}
|
|
|
|
/// Return a string with the string representation of a variable.
|
|
/// Does not put quotes around strings, as ":echo" displays values.
|
|
///
|
|
/// @param[in] tv typval_T to convert.
|
|
/// @param[out] len Location where length of the result will be saved.
|
|
///
|
|
/// @return String representation of the variable or NULL.
|
|
char *encode_tv2echo(typval_T *tv, size_t *len)
|
|
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_MALLOC
|
|
{
|
|
garray_T ga;
|
|
ga_init(&ga, (int)sizeof(char), 80);
|
|
if (tv->v_type == VAR_STRING || tv->v_type == VAR_FUNC) {
|
|
if (tv->vval.v_string != NULL) {
|
|
ga_concat(&ga, tv->vval.v_string);
|
|
}
|
|
} else {
|
|
encode_vim_to_echo(&ga, tv, ":echo argument");
|
|
}
|
|
if (len != NULL) {
|
|
*len = (size_t) ga.ga_len;
|
|
}
|
|
ga_append(&ga, '\0');
|
|
return (char *) ga.ga_data;
|
|
}
|
|
|
|
/// Return a string with the string representation of a variable.
|
|
/// Puts quotes around strings, so that they can be parsed back by eval().
|
|
///
|
|
/// @param[in] tv typval_T to convert.
|
|
/// @param[out] len Location where length of the result will be saved.
|
|
///
|
|
/// @return String representation of the variable or NULL.
|
|
char *encode_tv2json(typval_T *tv, size_t *len)
|
|
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_MALLOC
|
|
{
|
|
garray_T ga;
|
|
ga_init(&ga, (int)sizeof(char), 80);
|
|
encode_vim_to_json(&ga, tv, "encode_tv2json() argument");
|
|
did_echo_string_emsg = false;
|
|
if (len != NULL) {
|
|
*len = (size_t) ga.ga_len;
|
|
}
|
|
ga_append(&ga, '\0');
|
|
return (char *) ga.ga_data;
|
|
}
|
|
|
|
#define CONV_STRING(buf, len) \
|
|
do { \
|
|
if (buf == NULL) { \
|
|
msgpack_pack_bin(packer, 0); \
|
|
} else { \
|
|
const size_t len_ = (len); \
|
|
msgpack_pack_bin(packer, len_); \
|
|
msgpack_pack_bin_body(packer, buf, len_); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define CONV_STR_STRING(buf, len) \
|
|
do { \
|
|
if (buf == NULL) { \
|
|
msgpack_pack_str(packer, 0); \
|
|
} else { \
|
|
const size_t len_ = (len); \
|
|
msgpack_pack_str(packer, len_); \
|
|
msgpack_pack_str_body(packer, buf, len_); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define CONV_EXT_STRING(buf, len, type) \
|
|
do { \
|
|
if (buf == NULL) { \
|
|
msgpack_pack_ext(packer, 0, (int8_t) type); \
|
|
} else { \
|
|
const size_t len_ = (len); \
|
|
msgpack_pack_ext(packer, len_, (int8_t) type); \
|
|
msgpack_pack_ext_body(packer, buf, len_); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define CONV_NUMBER(num) \
|
|
msgpack_pack_int64(packer, (int64_t) (num))
|
|
|
|
#define CONV_FLOAT(flt) \
|
|
msgpack_pack_double(packer, (double) (flt))
|
|
|
|
#define CONV_FUNC(fun) \
|
|
return conv_error(_("E951: Error while dumping %s, %s: " \
|
|
"attempt to dump function reference"), \
|
|
mpstack, objname)
|
|
|
|
#define CONV_EMPTY_LIST() \
|
|
msgpack_pack_array(packer, 0)
|
|
|
|
#define CONV_LIST_START(lst) \
|
|
msgpack_pack_array(packer, (size_t) (lst)->lv_len)
|
|
|
|
#define CONV_EMPTY_DICT() \
|
|
msgpack_pack_map(packer, 0)
|
|
|
|
#define CONV_NIL() \
|
|
msgpack_pack_nil(packer)
|
|
|
|
#define CONV_NONE_VAL() \
|
|
return conv_error(_("E953: Attempt to convert v:none in %s, %s"), \
|
|
mpstack, objname)
|
|
|
|
#define CONV_BOOL(num) \
|
|
do { \
|
|
if ((num)) { \
|
|
msgpack_pack_true(packer); \
|
|
} else { \
|
|
msgpack_pack_false(packer); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define CONV_UNSIGNED_NUMBER(num) \
|
|
msgpack_pack_uint64(packer, (num))
|
|
|
|
#define CONV_DICT_START(len) \
|
|
msgpack_pack_map(packer, (size_t) (len))
|
|
|
|
#define CONV_DICT_END()
|
|
|
|
#define CONV_DICT_AFTER_KEY()
|
|
|
|
#define CONV_DICT_BETWEEN_ITEMS()
|
|
|
|
#define CONV_SPECIAL_DICT_KEY_CHECK(kv_pair)
|
|
|
|
#define CONV_LIST_END(lst)
|
|
|
|
#define CONV_LIST_BETWEEN_ITEMS()
|
|
|
|
#define CONV_RECURSE(val, conv_type) \
|
|
return conv_error(_("E952: Unable to dump %s: " \
|
|
"container references itself in %s"), \
|
|
mpstack, objname)
|
|
|
|
#define CONV_ALLOW_SPECIAL true
|
|
|
|
DEFINE_VIML_CONV_FUNCTIONS(, msgpack, msgpack_packer *const, packer)
|
|
|
|
#undef CONV_STRING
|
|
#undef CONV_STR_STRING
|
|
#undef CONV_EXT_STRING
|
|
#undef CONV_NUMBER
|
|
#undef CONV_FLOAT
|
|
#undef CONV_FUNC
|
|
#undef CONV_EMPTY_LIST
|
|
#undef CONV_LIST_START
|
|
#undef CONV_EMPTY_DICT
|
|
#undef CONV_NIL
|
|
#undef CONV_BOOL
|
|
#undef CONV_NONE_VAL
|
|
#undef CONV_UNSIGNED_NUMBER
|
|
#undef CONV_DICT_START
|
|
#undef CONV_DICT_END
|
|
#undef CONV_DICT_AFTER_KEY
|
|
#undef CONV_DICT_BETWEEN_ITEMS
|
|
#undef CONV_SPECIAL_DICT_KEY_CHECK
|
|
#undef CONV_LIST_END
|
|
#undef CONV_LIST_BETWEEN_ITEMS
|
|
#undef CONV_RECURSE
|
|
#undef CONV_ALLOW_SPECIAL
|