aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/eval
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/eval')
-rw-r--r--src/nvim/eval/decode.c1116
-rw-r--r--src/nvim/eval/decode.h13
-rw-r--r--src/nvim/eval/encode.c1296
-rw-r--r--src/nvim/eval/encode.h75
4 files changed, 2500 insertions, 0 deletions
diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c
new file mode 100644
index 0000000000..0774ef515f
--- /dev/null
+++ b/src/nvim/eval/decode.c
@@ -0,0 +1,1116 @@
+#include <stddef.h>
+
+#include <msgpack.h>
+
+#include "nvim/eval_defs.h"
+#include "nvim/eval.h"
+#include "nvim/eval/encode.h"
+#include "nvim/ascii.h"
+#include "nvim/message.h"
+#include "nvim/charset.h" // vim_str2nr
+#include "nvim/lib/kvec.h"
+#include "nvim/vim.h" // OK, FAIL
+
+/// Helper structure for container_struct
+typedef struct {
+ size_t stack_index; ///< Index of current container in stack.
+ list_T *special_val; ///< _VAL key contents for special maps.
+ ///< When container is not a special dictionary it is
+ ///< NULL.
+ const char *s; ///< Location where container starts.
+ typval_T container; ///< Container. Either VAR_LIST, VAR_DICT or VAR_LIST
+ ///< which is _VAL from special dictionary.
+} ContainerStackItem;
+
+/// Helper structure for values struct
+typedef struct {
+ bool is_special_string; ///< Indicates that current value is a special
+ ///< dictionary with string.
+ bool didcomma; ///< True if previous token was comma.
+ bool didcolon; ///< True if previous token was colon.
+ typval_T val; ///< Actual value.
+} ValuesStackItem;
+
+/// Vector containing values not yet saved in any container
+typedef kvec_t(ValuesStackItem) ValuesStack;
+
+/// Vector containing containers, each next container is located inside previous
+typedef kvec_t(ContainerStackItem) ContainerStack;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "eval/decode.c.generated.h"
+#endif
+
+/// Create special dictionary
+///
+/// @param[out] rettv Location where created dictionary will be saved.
+/// @param[in] type Type of the dictionary.
+/// @param[in] val Value associated with the _VAL key.
+static inline void create_special_dict(typval_T *const rettv,
+ const MessagePackType type,
+ typval_T val)
+ FUNC_ATTR_NONNULL_ALL
+{
+ dict_T *const dict = dict_alloc();
+ dictitem_T *const type_di = dictitem_alloc((char_u *) "_TYPE");
+ type_di->di_tv.v_type = VAR_LIST;
+ type_di->di_tv.v_lock = VAR_UNLOCKED;
+ type_di->di_tv.vval.v_list = (list_T *) eval_msgpack_type_lists[type];
+ type_di->di_tv.vval.v_list->lv_refcount++;
+ dict_add(dict, type_di);
+ dictitem_T *const val_di = dictitem_alloc((char_u *) "_VAL");
+ val_di->di_tv = val;
+ dict_add(dict, val_di);
+ dict->dv_refcount++;
+ *rettv = (typval_T) {
+ .v_type = VAR_DICT,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_dict = dict },
+ };
+}
+
+#define DICT_LEN(dict) (dict)->dv_hashtab.ht_used
+
+/// Helper function used for working with stack vectors used by JSON decoder
+///
+/// @param[in,out] obj New object. Will either be put into the stack (and,
+/// probably, also inside container) or freed.
+/// @param[out] stack Object stack.
+/// @param[out] container_stack Container objects stack.
+/// @param[in,out] pp Position in string which is currently being parsed. Used
+/// for error reporting and is also set when decoding is
+/// restarted due to the necessity of converting regular
+/// dictionary to a special map.
+/// @param[out] next_map_special Is set to true when dictionary needs to be
+/// converted to a special map, otherwise not
+/// touched. Indicates that decoding has been
+/// restarted.
+/// @param[out] didcomma True if previous token was comma. Is set to recorded
+/// value when decoder is restarted, otherwise unused.
+/// @param[out] didcolon True if previous token was colon. Is set to recorded
+/// value when decoder is restarted, otherwise unused.
+///
+/// @return OK in case of success, FAIL in case of error.
+static inline int json_decoder_pop(ValuesStackItem obj,
+ ValuesStack *const stack,
+ ContainerStack *const container_stack,
+ const char **const pp,
+ bool *const next_map_special,
+ bool *const didcomma,
+ bool *const didcolon)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (kv_size(*container_stack) == 0) {
+ kv_push(ValuesStackItem, *stack, obj);
+ return OK;
+ }
+ ContainerStackItem last_container = kv_last(*container_stack);
+ const char *val_location = *pp;
+ if (obj.val.v_type == last_container.container.v_type
+ // vval.v_list and vval.v_dict should have the same size and offset
+ && ((void *) obj.val.vval.v_list
+ == (void *) last_container.container.vval.v_list)) {
+ (void) kv_pop(*container_stack);
+ val_location = last_container.s;
+ last_container = kv_last(*container_stack);
+ }
+ if (last_container.container.v_type == VAR_LIST) {
+ if (last_container.container.vval.v_list->lv_len != 0
+ && !obj.didcomma) {
+ EMSG2(_("E474: Expected comma before list item: %s"), val_location);
+ clear_tv(&obj.val);
+ return FAIL;
+ }
+ assert(last_container.special_val == NULL);
+ listitem_T *obj_li = listitem_alloc();
+ obj_li->li_tv = obj.val;
+ list_append(last_container.container.vval.v_list, obj_li);
+ } else if (last_container.stack_index == kv_size(*stack) - 2) {
+ if (!obj.didcolon) {
+ EMSG2(_("E474: Expected colon before dictionary value: %s"),
+ val_location);
+ clear_tv(&obj.val);
+ return FAIL;
+ }
+ ValuesStackItem key = kv_pop(*stack);
+ if (last_container.special_val == NULL) {
+ // These cases should have already been handled.
+ assert(!(key.is_special_string
+ || key.val.vval.v_string == NULL
+ || *key.val.vval.v_string == NUL));
+ dictitem_T *obj_di = dictitem_alloc(key.val.vval.v_string);
+ clear_tv(&key.val);
+ if (dict_add(last_container.container.vval.v_dict, obj_di)
+ == FAIL) {
+ assert(false);
+ }
+ obj_di->di_tv = obj.val;
+ } else {
+ list_T *const kv_pair = list_alloc();
+ list_append_list(last_container.special_val, kv_pair);
+ listitem_T *const key_li = listitem_alloc();
+ key_li->li_tv = key.val;
+ list_append(kv_pair, key_li);
+ listitem_T *const val_li = listitem_alloc();
+ val_li->li_tv = obj.val;
+ list_append(kv_pair, val_li);
+ }
+ } else {
+ // Object with key only
+ if (!obj.is_special_string && obj.val.v_type != VAR_STRING) {
+ EMSG2(_("E474: Expected string key: %s"), *pp);
+ clear_tv(&obj.val);
+ return FAIL;
+ } else if (!obj.didcomma
+ && (last_container.special_val == NULL
+ && (DICT_LEN(last_container.container.vval.v_dict) != 0))) {
+ EMSG2(_("E474: Expected comma before dictionary key: %s"), val_location);
+ clear_tv(&obj.val);
+ return FAIL;
+ }
+ // Handle empty key and key represented as special dictionary
+ if (last_container.special_val == NULL
+ && (obj.is_special_string
+ || obj.val.vval.v_string == NULL
+ || *obj.val.vval.v_string == NUL
+ || dict_find(last_container.container.vval.v_dict,
+ obj.val.vval.v_string, -1))) {
+ clear_tv(&obj.val);
+
+ // Restart
+ (void) kv_pop(*container_stack);
+ ValuesStackItem last_container_val =
+ kv_A(*stack, last_container.stack_index);
+ while (kv_size(*stack) > last_container.stack_index) {
+ clear_tv(&(kv_pop(*stack).val));
+ }
+ *pp = last_container.s;
+ *didcomma = last_container_val.didcomma;
+ *didcolon = last_container_val.didcolon;
+ *next_map_special = true;
+ return OK;
+ }
+ kv_push(ValuesStackItem, *stack, obj);
+ }
+ return OK;
+}
+
+#define LENP(p, e) \
+ ((int) ((e) - (p))), (p)
+#define OBJ(obj_tv, is_sp_string, didcomma_, didcolon_) \
+ ((ValuesStackItem) { \
+ .is_special_string = (is_sp_string), \
+ .val = (obj_tv), \
+ .didcomma = (didcomma_), \
+ .didcolon = (didcolon_), \
+ })
+
+#define POP(obj_tv, is_sp_string) \
+ do { \
+ if (json_decoder_pop(OBJ(obj_tv, is_sp_string, *didcomma, *didcolon), \
+ stack, container_stack, \
+ &p, next_map_special, didcomma, didcolon) \
+ == FAIL) { \
+ goto parse_json_string_fail; \
+ } \
+ if (*next_map_special) { \
+ goto parse_json_string_ret; \
+ } \
+ } while (0)
+
+/// Parse JSON double-quoted string
+///
+/// @param[in] conv Defines conversion necessary to convert UTF-8 string to
+/// &encoding.
+/// @param[in] buf Buffer being converted.
+/// @param[in] buf_len Length of the buffer.
+/// @param[in,out] pp Pointer to the start of the string. Must point to '"'.
+/// Is advanced to the closing '"'. Also see
+/// json_decoder_pop(), it may set pp to another location
+/// and alter next_map_special, didcomma and didcolon.
+/// @param[out] stack Object stack.
+/// @param[out] container_stack Container objects stack.
+/// @param[out] next_map_special Is set to true when dictionary is converted
+/// to a special map, otherwise not touched.
+/// @param[out] didcomma True if previous token was comma. Is set to recorded
+/// value when decoder is restarted, otherwise unused.
+/// @param[out] didcolon True if previous token was colon. Is set to recorded
+/// value when decoder is restarted, otherwise unused.
+///
+/// @return OK in case of success, FAIL in case of error.
+static inline int parse_json_string(vimconv_T *const conv,
+ const char *const buf, const size_t buf_len,
+ const char **const pp,
+ ValuesStack *const stack,
+ ContainerStack *const container_stack,
+ bool *const next_map_special,
+ bool *const didcomma,
+ bool *const didcolon)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE
+{
+ const char *const e = buf + buf_len;
+ const char *p = *pp;
+ size_t len = 0;
+ const char *const s = ++p;
+ int ret = OK;
+ while (p < e && *p != '"') {
+ if (*p == '\\') {
+ p++;
+ if (p == e) {
+ emsgf(_("E474: Unfinished escape sequence: %.*s"),
+ (int) buf_len, buf);
+ goto parse_json_string_fail;
+ }
+ switch (*p) {
+ case 'u': {
+ if (p + 4 >= e) {
+ emsgf(_("E474: Unfinished unicode escape sequence: %.*s"),
+ (int) buf_len, buf);
+ goto parse_json_string_fail;
+ } else if (!ascii_isxdigit(p[1])
+ || !ascii_isxdigit(p[2])
+ || !ascii_isxdigit(p[3])
+ || !ascii_isxdigit(p[4])) {
+ emsgf(_("E474: Expected four hex digits after \\u: %.*s"),
+ LENP(p - 1, e));
+ goto parse_json_string_fail;
+ }
+ // One UTF-8 character below U+10000 can take up to 3 bytes,
+ // above up to 6, but they are encoded using two \u escapes.
+ len += 3;
+ p += 5;
+ break;
+ }
+ case '\\':
+ case '/':
+ case '"':
+ case 't':
+ case 'b':
+ case 'n':
+ case 'r':
+ case 'f': {
+ len++;
+ p++;
+ break;
+ }
+ default: {
+ emsgf(_("E474: Unknown escape sequence: %.*s"), LENP(p - 1, e));
+ goto parse_json_string_fail;
+ }
+ }
+ } else {
+ uint8_t p_byte = (uint8_t) *p;
+ // unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
+ if (p_byte < 0x20) {
+ emsgf(_("E474: ASCII control characters cannot be present "
+ "inside string: %.*s"), LENP(p, e));
+ goto parse_json_string_fail;
+ }
+ const int ch = utf_ptr2char((char_u *) p);
+ // All characters above U+007F are encoded using two or more bytes
+ // and thus cannot possibly be equal to *p. But utf_ptr2char({0xFF,
+ // 0}) will return 0xFF, even though 0xFF cannot start any UTF-8
+ // code point at all.
+ //
+ // The only exception is U+00C3 which is represented as 0xC3 0x83.
+ if (ch >= 0x80 && p_byte == ch
+ && !(ch == 0xC3 && p + 1 < e && (uint8_t) p[1] == 0x83)) {
+ emsgf(_("E474: Only UTF-8 strings allowed: %.*s"), LENP(p, e));
+ goto parse_json_string_fail;
+ } else if (ch > 0x10FFFF) {
+ emsgf(_("E474: Only UTF-8 code points up to U+10FFFF "
+ "are allowed to appear unescaped: %.*s"), LENP(p, e));
+ goto parse_json_string_fail;
+ }
+ const size_t ch_len = (size_t) utf_char2len(ch);
+ assert(ch_len == (size_t) (ch ? utf_ptr2len((char_u *) p) : 1));
+ len += ch_len;
+ p += ch_len;
+ }
+ }
+ if (p == e || *p != '"') {
+ emsgf(_("E474: Expected string end: %.*s"), (int) buf_len, buf);
+ goto parse_json_string_fail;
+ }
+ if (len == 0) {
+ POP(((typval_T) {
+ .v_type = VAR_STRING,
+ .vval = { .v_string = NULL },
+ }), false);
+ goto parse_json_string_ret;
+ }
+ char *str = xmalloc(len + 1);
+ int fst_in_pair = 0;
+ char *str_end = str;
+ bool hasnul = false;
+#define PUT_FST_IN_PAIR(fst_in_pair, str_end) \
+ do { \
+ if (fst_in_pair != 0) { \
+ str_end += utf_char2bytes(fst_in_pair, (char_u *) str_end); \
+ fst_in_pair = 0; \
+ } \
+ } while (0)
+ for (const char *t = s; t < p; t++) {
+ if (t[0] != '\\' || t[1] != 'u') {
+ PUT_FST_IN_PAIR(fst_in_pair, str_end);
+ }
+ if (*t == '\\') {
+ t++;
+ switch (*t) {
+ case 'u': {
+ const char ubuf[] = { t[1], t[2], t[3], t[4] };
+ t += 4;
+ unsigned long ch;
+ vim_str2nr((char_u *) ubuf, NULL, NULL,
+ STR2NR_HEX | STR2NR_FORCE, NULL, &ch, 4);
+ if (ch == 0) {
+ hasnul = true;
+ }
+ if (SURROGATE_HI_START <= ch && ch <= SURROGATE_HI_END) {
+ PUT_FST_IN_PAIR(fst_in_pair, str_end);
+ fst_in_pair = (int) ch;
+ } else if (SURROGATE_LO_START <= ch && ch <= SURROGATE_LO_END
+ && fst_in_pair != 0) {
+ const int full_char = (
+ (int) (ch - SURROGATE_LO_START)
+ + ((fst_in_pair - SURROGATE_HI_START) << 10)
+ + SURROGATE_FIRST_CHAR);
+ str_end += utf_char2bytes(full_char, (char_u *) str_end);
+ fst_in_pair = 0;
+ } else {
+ PUT_FST_IN_PAIR(fst_in_pair, str_end);
+ str_end += utf_char2bytes((int) ch, (char_u *) str_end);
+ }
+ break;
+ }
+ case '\\':
+ case '/':
+ case '"':
+ case 't':
+ case 'b':
+ case 'n':
+ case 'r':
+ case 'f': {
+ static const char escapes[] = {
+ ['\\'] = '\\',
+ ['/'] = '/',
+ ['"'] = '"',
+ ['t'] = TAB,
+ ['b'] = BS,
+ ['n'] = NL,
+ ['r'] = CAR,
+ ['f'] = FF,
+ };
+ *str_end++ = escapes[(int) *t];
+ break;
+ }
+ default: {
+ assert(false);
+ }
+ }
+ } else {
+ *str_end++ = *t;
+ }
+ }
+ PUT_FST_IN_PAIR(fst_in_pair, str_end);
+#undef PUT_FST_IN_PAIR
+ if (conv->vc_type != CONV_NONE) {
+ size_t str_len = (size_t) (str_end - str);
+ char *const new_str = (char *) string_convert(conv, (char_u *) str,
+ &str_len);
+ if (new_str == NULL) {
+ emsgf(_("E474: Failed to convert string \"%.*s\" from UTF-8"),
+ (int) str_len, str);
+ xfree(str);
+ goto parse_json_string_fail;
+ }
+ xfree(str);
+ str = new_str;
+ str_end = new_str + str_len;
+ }
+ if (hasnul) {
+ typval_T obj;
+ list_T *const list = list_alloc();
+ list->lv_refcount++;
+ create_special_dict(&obj, kMPString, ((typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_list = list },
+ }));
+ if (encode_list_write((void *) list, str, (size_t) (str_end - str))
+ == -1) {
+ clear_tv(&obj);
+ goto parse_json_string_fail;
+ }
+ xfree(str);
+ POP(obj, true);
+ } else {
+ *str_end = NUL;
+ POP(((typval_T) {
+ .v_type = VAR_STRING,
+ .vval = { .v_string = (char_u *) str },
+ }), false);
+ }
+ goto parse_json_string_ret;
+parse_json_string_fail:
+ ret = FAIL;
+parse_json_string_ret:
+ *pp = p;
+ return ret;
+}
+
+#undef POP
+
+/// Parse JSON number: both floating-point and integer
+///
+/// Number format: `-?\d+(?:.\d+)?(?:[eE][+-]?\d+)?`.
+///
+/// @param[in] buf Buffer being converted.
+/// @param[in] buf_len Length of the buffer.
+/// @param[in,out] pp Pointer to the start of the number. Must point to
+/// a digit or a minus sign. Is advanced to the last
+/// character of the number. Also see json_decoder_pop(), it
+/// may set pp to another location and alter
+/// next_map_special, didcomma and didcolon.
+/// @param[out] stack Object stack.
+/// @param[out] container_stack Container objects stack.
+/// @param[out] next_map_special Is set to true when dictionary is converted
+/// to a special map, otherwise not touched.
+/// @param[out] didcomma True if previous token was comma. Is set to recorded
+/// value when decoder is restarted, otherwise unused.
+/// @param[out] didcolon True if previous token was colon. Is set to recorded
+/// value when decoder is restarted, otherwise unused.
+///
+/// @return OK in case of success, FAIL in case of error.
+static inline int parse_json_number(const char *const buf, const size_t buf_len,
+ const char **const pp,
+ ValuesStack *const stack,
+ ContainerStack *const container_stack,
+ bool *const next_map_special,
+ bool *const didcomma,
+ bool *const didcolon)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE
+{
+ const char *const e = buf + buf_len;
+ const char *p = *pp;
+ int ret = OK;
+ const char *const s = p;
+ const char *ints = NULL;
+ const char *fracs = NULL;
+ const char *exps = NULL;
+ const char *exps_s = NULL;
+ if (*p == '-') {
+ p++;
+ }
+ ints = p;
+ if (p >= e) {
+ goto parse_json_number_check;
+ }
+ while (p < e && ascii_isdigit(*p)) {
+ p++;
+ }
+ if (p != ints + 1 && *ints == '0') {
+ emsgf(_("E474: Leading zeroes are not allowed: %.*s"), LENP(s, e));
+ goto parse_json_number_fail;
+ }
+ if (p >= e || p == ints) {
+ goto parse_json_number_check;
+ }
+ if (*p == '.') {
+ p++;
+ fracs = p;
+ while (p < e && ascii_isdigit(*p)) {
+ p++;
+ }
+ if (p >= e || p == fracs) {
+ goto parse_json_number_check;
+ }
+ }
+ if (*p == 'e' || *p == 'E') {
+ p++;
+ exps_s = p;
+ if (p < e && (*p == '-' || *p == '+')) {
+ p++;
+ }
+ exps = p;
+ while (p < e && ascii_isdigit(*p)) {
+ p++;
+ }
+ }
+parse_json_number_check:
+ if (p == ints) {
+ emsgf(_("E474: Missing number after minus sign: %.*s"), LENP(s, e));
+ goto parse_json_number_fail;
+ } else if (p == fracs || exps_s == fracs + 1) {
+ emsgf(_("E474: Missing number after decimal dot: %.*s"), LENP(s, e));
+ goto parse_json_number_fail;
+ } else if (p == exps) {
+ emsgf(_("E474: Missing exponent: %.*s"), LENP(s, e));
+ goto parse_json_number_fail;
+ }
+ typval_T tv = {
+ .v_type = VAR_NUMBER,
+ .v_lock = VAR_UNLOCKED,
+ };
+ const size_t exp_num_len = (size_t) (p - s);
+ if (fracs || exps) {
+ // Convert floating-point number
+ const size_t num_len = string2float(s, &tv.vval.v_float);
+ if (exp_num_len != num_len) {
+ emsgf(_("E685: internal error: while converting number \"%.*s\" "
+ "to float string2float consumed %zu bytes in place of %zu"),
+ (int) exp_num_len, s, num_len, exp_num_len);
+ }
+ tv.v_type = VAR_FLOAT;
+ } else {
+ // Convert integer
+ long nr;
+ int num_len;
+ vim_str2nr((char_u *) s, NULL, &num_len, 0, &nr, NULL, (int) (p - s));
+ if ((int) exp_num_len != num_len) {
+ emsgf(_("E685: internal error: while converting number \"%.*s\" "
+ "to integer vim_str2nr consumed %i bytes in place of %zu"),
+ (int) exp_num_len, s, num_len, exp_num_len);
+ }
+ tv.vval.v_number = (varnumber_T) nr;
+ }
+ if (json_decoder_pop(OBJ(tv, false, *didcomma, *didcolon),
+ stack, container_stack,
+ &p, next_map_special, didcomma, didcolon) == FAIL) {
+ goto parse_json_number_fail;
+ }
+ if (*next_map_special) {
+ goto parse_json_number_ret;
+ }
+ p--;
+ goto parse_json_number_ret;
+parse_json_number_fail:
+ ret = FAIL;
+parse_json_number_ret:
+ *pp = p;
+ return ret;
+}
+
+#define POP(obj_tv, is_sp_string) \
+ do { \
+ if (json_decoder_pop(OBJ(obj_tv, is_sp_string, didcomma, didcolon), \
+ &stack, &container_stack, \
+ &p, &next_map_special, &didcomma, &didcolon) \
+ == FAIL) { \
+ goto json_decode_string_fail; \
+ } \
+ if (next_map_special) { \
+ goto json_decode_string_cycle_start; \
+ } \
+ } while (0)
+
+/// Convert JSON string into VimL object
+///
+/// @param[in] buf String to convert. UTF-8 encoding is assumed.
+/// @param[in] buf_len Length of the string.
+/// @param[out] rettv Location where to save results.
+///
+/// @return OK in case of success, FAIL otherwise.
+int json_decode_string(const char *const buf, const size_t buf_len,
+ typval_T *const rettv)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const char *p = buf;
+ const char *const e = buf + buf_len;
+ while (p < e && (*p == ' ' || *p == TAB || *p == NL || *p == CAR)) {
+ p++;
+ }
+ if (p == e) {
+ EMSG(_("E474: Attempt to decode a blank string"));
+ return FAIL;
+ }
+ vimconv_T conv = { .vc_type = CONV_NONE };
+ convert_setup(&conv, (char_u *) "utf-8", p_enc);
+ conv.vc_fail = true;
+ int ret = OK;
+ ValuesStack stack;
+ kv_init(stack);
+ ContainerStack container_stack;
+ kv_init(container_stack);
+ rettv->v_type = VAR_UNKNOWN;
+ bool didcomma = false;
+ bool didcolon = false;
+ bool next_map_special = false;
+ for (; p < e; p++) {
+json_decode_string_cycle_start:
+ assert(*p == '{' || next_map_special == false);
+ switch (*p) {
+ case '}':
+ case ']': {
+ if (kv_size(container_stack) == 0) {
+ emsgf(_("E474: No container to close: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ }
+ ContainerStackItem last_container = kv_last(container_stack);
+ if (*p == '}' && last_container.container.v_type != VAR_DICT) {
+ emsgf(_("E474: Closing list with curly bracket: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ } else if (*p == ']' && last_container.container.v_type != VAR_LIST) {
+ emsgf(_("E474: Closing dictionary with square bracket: %.*s"),
+ LENP(p, e));
+ goto json_decode_string_fail;
+ } else if (didcomma) {
+ emsgf(_("E474: Trailing comma: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ } else if (didcolon) {
+ emsgf(_("E474: Expected value after colon: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ } else if (last_container.stack_index != kv_size(stack) - 1) {
+ assert(last_container.stack_index < kv_size(stack) - 1);
+ emsgf(_("E474: Expected value: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ }
+ if (kv_size(stack) == 1) {
+ p++;
+ (void) kv_pop(container_stack);
+ goto json_decode_string_after_cycle;
+ } else {
+ if (json_decoder_pop(kv_pop(stack), &stack, &container_stack, &p,
+ &next_map_special, &didcomma, &didcolon)
+ == FAIL) {
+ goto json_decode_string_fail;
+ }
+ assert(!next_map_special);
+ break;
+ }
+ }
+ case ',': {
+ if (kv_size(container_stack) == 0) {
+ emsgf(_("E474: Comma not inside container: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ }
+ ContainerStackItem last_container = kv_last(container_stack);
+ if (didcomma) {
+ emsgf(_("E474: Duplicate comma: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ } else if (didcolon) {
+ emsgf(_("E474: Comma after colon: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ } else if (last_container.container.v_type == VAR_DICT
+ && last_container.stack_index != kv_size(stack) - 1) {
+ emsgf(_("E474: Using comma in place of colon: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ } else if (last_container.special_val == NULL
+ ? (last_container.container.v_type == VAR_DICT
+ ? (DICT_LEN(last_container.container.vval.v_dict) == 0)
+ : (last_container.container.vval.v_list->lv_len == 0))
+ : (last_container.special_val->lv_len == 0)) {
+ emsgf(_("E474: Leading comma: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ }
+ didcomma = true;
+ continue;
+ }
+ case ':': {
+ if (kv_size(container_stack) == 0) {
+ emsgf(_("E474: Colon not inside container: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ }
+ ContainerStackItem last_container = kv_last(container_stack);
+ if (last_container.container.v_type != VAR_DICT) {
+ emsgf(_("E474: Using colon not in dictionary: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ } else if (last_container.stack_index != kv_size(stack) - 2) {
+ emsgf(_("E474: Unexpected colon: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ } else if (didcomma) {
+ emsgf(_("E474: Colon after comma: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ } else if (didcolon) {
+ emsgf(_("E474: Duplicate colon: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ }
+ didcolon = true;
+ continue;
+ }
+ case ' ':
+ case TAB:
+ case NL:
+ case CAR: {
+ continue;
+ }
+ case 'n': {
+ if ((p + 3) >= e || strncmp(p + 1, "ull", 3) != 0) {
+ emsgf(_("E474: Expected null: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ }
+ p += 3;
+ POP(((typval_T) {
+ .v_type = VAR_SPECIAL,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_special = kSpecialVarNull },
+ }), false);
+ break;
+ }
+ case 't': {
+ if ((p + 3) >= e || strncmp(p + 1, "rue", 3) != 0) {
+ emsgf(_("E474: Expected true: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ }
+ p += 3;
+ POP(((typval_T) {
+ .v_type = VAR_SPECIAL,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_special = kSpecialVarTrue },
+ }), false);
+ break;
+ }
+ case 'f': {
+ if ((p + 4) >= e || strncmp(p + 1, "alse", 4) != 0) {
+ emsgf(_("E474: Expected false: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ }
+ p += 4;
+ POP(((typval_T) {
+ .v_type = VAR_SPECIAL,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_special = kSpecialVarFalse },
+ }), false);
+ break;
+ }
+ case '"': {
+ if (parse_json_string(&conv, buf, buf_len, &p, &stack, &container_stack,
+ &next_map_special, &didcomma, &didcolon)
+ == FAIL) {
+ // Error message was already given
+ goto json_decode_string_fail;
+ }
+ if (next_map_special) {
+ goto json_decode_string_cycle_start;
+ }
+ break;
+ }
+ case '-':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9': {
+ if (parse_json_number(buf, buf_len, &p, &stack, &container_stack,
+ &next_map_special, &didcomma, &didcolon)
+ == FAIL) {
+ // Error message was already given
+ goto json_decode_string_fail;
+ }
+ if (next_map_special) {
+ goto json_decode_string_cycle_start;
+ }
+ break;
+ }
+ case '[': {
+ list_T *list = list_alloc();
+ list->lv_refcount++;
+ typval_T tv = {
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_list = list },
+ };
+ kv_push(ContainerStackItem, container_stack, ((ContainerStackItem) {
+ .stack_index = kv_size(stack),
+ .s = p,
+ .container = tv,
+ .special_val = NULL,
+ }));
+ kv_push(ValuesStackItem, stack, OBJ(tv, false, didcomma, didcolon));
+ break;
+ }
+ case '{': {
+ typval_T tv;
+ list_T *val_list = NULL;
+ if (next_map_special) {
+ next_map_special = false;
+ val_list = list_alloc();
+ val_list->lv_refcount++;
+ create_special_dict(&tv, kMPMap, ((typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_list = val_list },
+ }));
+ } else {
+ dict_T *dict = dict_alloc();
+ dict->dv_refcount++;
+ tv = (typval_T) {
+ .v_type = VAR_DICT,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_dict = dict },
+ };
+ }
+ kv_push(ContainerStackItem, container_stack, ((ContainerStackItem) {
+ .stack_index = kv_size(stack),
+ .s = p,
+ .container = tv,
+ .special_val = val_list,
+ }));
+ kv_push(ValuesStackItem, stack, OBJ(tv, false, didcomma, didcolon));
+ break;
+ }
+ default: {
+ emsgf(_("E474: Unidentified byte: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ }
+ }
+ didcomma = false;
+ didcolon = false;
+ if (kv_size(container_stack) == 0) {
+ p++;
+ break;
+ }
+ }
+json_decode_string_after_cycle:
+ for (; p < e; p++) {
+ switch (*p) {
+ case NL:
+ case ' ':
+ case TAB:
+ case CAR: {
+ break;
+ }
+ default: {
+ emsgf(_("E474: Trailing characters: %.*s"), LENP(p, e));
+ goto json_decode_string_fail;
+ }
+ }
+ }
+ if (kv_size(stack) == 1 && kv_size(container_stack) == 0) {
+ *rettv = kv_pop(stack).val;
+ goto json_decode_string_ret;
+ }
+ emsgf(_("E474: Unexpected end of input: %.*s"), (int) buf_len, buf);
+json_decode_string_fail:
+ ret = FAIL;
+ while (kv_size(stack)) {
+ clear_tv(&(kv_pop(stack).val));
+ }
+json_decode_string_ret:
+ kv_destroy(stack);
+ kv_destroy(container_stack);
+ return ret;
+}
+
+#undef LENP
+#undef POP
+
+#undef OBJ
+
+#undef DICT_LEN
+
+/// Convert msgpack object to a VimL one
+int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ switch (mobj.type) {
+ case MSGPACK_OBJECT_NIL: {
+ *rettv = (typval_T) {
+ .v_type = VAR_SPECIAL,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_special = kSpecialVarNull },
+ };
+ break;
+ }
+ case MSGPACK_OBJECT_BOOLEAN: {
+ *rettv = (typval_T) {
+ .v_type = VAR_SPECIAL,
+ .v_lock = VAR_UNLOCKED,
+ .vval = {
+ .v_special = mobj.via.boolean ? kSpecialVarTrue : kSpecialVarFalse
+ },
+ };
+ break;
+ }
+ case MSGPACK_OBJECT_POSITIVE_INTEGER: {
+ if (mobj.via.u64 <= VARNUMBER_MAX) {
+ *rettv = (typval_T) {
+ .v_type = VAR_NUMBER,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_number = (varnumber_T) mobj.via.u64 },
+ };
+ } else {
+ list_T *const list = list_alloc();
+ list->lv_refcount++;
+ create_special_dict(rettv, kMPInteger, ((typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_list = list },
+ }));
+ uint64_t n = mobj.via.u64;
+ list_append_number(list, 1);
+ list_append_number(list, (varnumber_T) ((n >> 62) & 0x3));
+ list_append_number(list, (varnumber_T) ((n >> 31) & 0x7FFFFFFF));
+ list_append_number(list, (varnumber_T) (n & 0x7FFFFFFF));
+ }
+ break;
+ }
+ case MSGPACK_OBJECT_NEGATIVE_INTEGER: {
+ if (mobj.via.i64 >= VARNUMBER_MIN) {
+ *rettv = (typval_T) {
+ .v_type = VAR_NUMBER,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_number = (varnumber_T) mobj.via.i64 },
+ };
+ } else {
+ list_T *const list = list_alloc();
+ list->lv_refcount++;
+ create_special_dict(rettv, kMPInteger, ((typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_list = list },
+ }));
+ uint64_t n = -((uint64_t) mobj.via.i64);
+ list_append_number(list, -1);
+ list_append_number(list, (varnumber_T) ((n >> 62) & 0x3));
+ list_append_number(list, (varnumber_T) ((n >> 31) & 0x7FFFFFFF));
+ list_append_number(list, (varnumber_T) (n & 0x7FFFFFFF));
+ }
+ break;
+ }
+ case MSGPACK_OBJECT_FLOAT: {
+ *rettv = (typval_T) {
+ .v_type = VAR_FLOAT,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_float = mobj.via.f64 },
+ };
+ break;
+ }
+ case MSGPACK_OBJECT_STR: {
+ list_T *const list = list_alloc();
+ list->lv_refcount++;
+ create_special_dict(rettv, kMPString, ((typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_list = list },
+ }));
+ if (encode_list_write((void *) list, mobj.via.str.ptr, mobj.via.str.size)
+ == -1) {
+ return FAIL;
+ }
+ break;
+ }
+ case MSGPACK_OBJECT_BIN: {
+ if (memchr(mobj.via.bin.ptr, NUL, mobj.via.bin.size) == NULL) {
+ *rettv = (typval_T) {
+ .v_type = VAR_STRING,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_string = xmemdupz(mobj.via.bin.ptr, mobj.via.bin.size) },
+ };
+ break;
+ }
+ list_T *const list = list_alloc();
+ list->lv_refcount++;
+ create_special_dict(rettv, kMPBinary, ((typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_list = list },
+ }));
+ if (encode_list_write((void *) list, mobj.via.bin.ptr, mobj.via.bin.size)
+ == -1) {
+ return FAIL;
+ }
+ break;
+ }
+ case MSGPACK_OBJECT_ARRAY: {
+ list_T *const list = list_alloc();
+ list->lv_refcount++;
+ *rettv = (typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_list = list },
+ };
+ for (size_t i = 0; i < mobj.via.array.size; i++) {
+ listitem_T *const li = listitem_alloc();
+ li->li_tv.v_type = VAR_UNKNOWN;
+ list_append(list, li);
+ if (msgpack_to_vim(mobj.via.array.ptr[i], &li->li_tv) == FAIL) {
+ return FAIL;
+ }
+ }
+ break;
+ }
+ case MSGPACK_OBJECT_MAP: {
+ for (size_t i = 0; i < mobj.via.map.size; i++) {
+ if (mobj.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR
+ || mobj.via.map.ptr[i].key.via.str.size == 0
+ || memchr(mobj.via.map.ptr[i].key.via.str.ptr, NUL,
+ mobj.via.map.ptr[i].key.via.str.size) != NULL) {
+ goto msgpack_to_vim_generic_map;
+ }
+ }
+ dict_T *const dict = dict_alloc();
+ dict->dv_refcount++;
+ *rettv = (typval_T) {
+ .v_type = VAR_DICT,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_dict = dict },
+ };
+ for (size_t i = 0; i < mobj.via.map.size; i++) {
+ dictitem_T *const di = xmallocz(offsetof(dictitem_T, di_key)
+ + mobj.via.map.ptr[i].key.via.str.size);
+ memcpy(&di->di_key[0], mobj.via.map.ptr[i].key.via.str.ptr,
+ mobj.via.map.ptr[i].key.via.str.size);
+ di->di_tv.v_type = VAR_UNKNOWN;
+ if (dict_add(dict, di) == FAIL) {
+ // Duplicate key: fallback to generic map
+ clear_tv(rettv);
+ xfree(di);
+ goto msgpack_to_vim_generic_map;
+ }
+ if (msgpack_to_vim(mobj.via.map.ptr[i].val, &di->di_tv) == FAIL) {
+ return FAIL;
+ }
+ }
+ break;
+msgpack_to_vim_generic_map: {}
+ list_T *const list = list_alloc();
+ list->lv_refcount++;
+ create_special_dict(rettv, kMPMap, ((typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_list = list },
+ }));
+ for (size_t i = 0; i < mobj.via.map.size; i++) {
+ list_T *const kv_pair = list_alloc();
+ list_append_list(list, kv_pair);
+ listitem_T *const key_li = listitem_alloc();
+ key_li->li_tv.v_type = VAR_UNKNOWN;
+ list_append(kv_pair, key_li);
+ listitem_T *const val_li = listitem_alloc();
+ val_li->li_tv.v_type = VAR_UNKNOWN;
+ list_append(kv_pair, val_li);
+ if (msgpack_to_vim(mobj.via.map.ptr[i].key, &key_li->li_tv) == FAIL) {
+ return FAIL;
+ }
+ if (msgpack_to_vim(mobj.via.map.ptr[i].val, &val_li->li_tv) == FAIL) {
+ return FAIL;
+ }
+ }
+ break;
+ }
+ case MSGPACK_OBJECT_EXT: {
+ list_T *const list = list_alloc();
+ list->lv_refcount++;
+ list_append_number(list, mobj.via.ext.type);
+ list_T *const ext_val_list = list_alloc();
+ list_append_list(list, ext_val_list);
+ create_special_dict(rettv, kMPExt, ((typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_list = list },
+ }));
+ if (encode_list_write((void *) ext_val_list, mobj.via.ext.ptr,
+ mobj.via.ext.size) == -1) {
+ return FAIL;
+ }
+ break;
+ }
+ }
+ return OK;
+}
diff --git a/src/nvim/eval/decode.h b/src/nvim/eval/decode.h
new file mode 100644
index 0000000000..5c25a64f7a
--- /dev/null
+++ b/src/nvim/eval/decode.h
@@ -0,0 +1,13 @@
+#ifndef NVIM_EVAL_DECODE_H
+#define NVIM_EVAL_DECODE_H
+
+#include <stddef.h>
+
+#include <msgpack.h>
+
+#include "nvim/eval_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "eval/decode.h.generated.h"
+#endif
+#endif // NVIM_EVAL_DECODE_H
diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c
new file mode 100644
index 0000000000..c651a50be9
--- /dev/null
+++ b/src/nvim/eval/encode.c
@@ -0,0 +1,1296 @@
+/// @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 <math.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/memory.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",
+ [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;
+ listitem_T *li = list->lv_last;
+
+ // Continue the last list element
+ if (li != NULL) {
+ line_end = xmemscan(buf, NL, len);
+ if (line_end != buf) {
+ const size_t line_length = (size_t)(line_end - buf);
+ char *str = (char *)li->li_tv.vval.v_string;
+ const size_t li_len = (str == NULL ? 0 : strlen(str));
+ li->li_tv.vval.v_string = xrealloc(str, li_len + line_length + 1);
+ str = (char *)li->li_tv.vval.v_string + li_len;
+ memcpy(str, buf, line_length);
+ str[line_length] = 0;
+ memchrsub(str, NUL, NL, line_length);
+ }
+ line_end++;
+ }
+
+ while (line_end < end) {
+ const char *line_start = line_end;
+ line_end = xmemscan(line_start, NL, (size_t) (end - line_start));
+ char *str = NULL;
+ if (line_end != line_start) {
+ const size_t line_length = (size_t)(line_end - line_start);
+ str = xmemdupz(line_start, line_length);
+ memchrsub(str, NUL, NL, line_length);
+ }
+ list_append_allocated_string(list, str);
+ line_end++;
+ }
+ if (line_end == end) {
+ list_append_allocated_string(list, NULL);
+ }
+ 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++ = (char) ((char) ch == (char) NL ? (char) NUL : (char) 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 TV_STRLEN(tv) \
+ (tv->vval.v_string == NULL ? 0 : STRLEN(tv->vval.v_string))
+
+/// 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, TV_STRLEN(tv)); \
+ 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; \
+ } \
+ } \
+ 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(name, 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); \
+ ga_grow(gap, (int) (2 + len_ + memcnt(buf_, '\'', len_))); \
+ ga_append(gap, '\''); \
+ for (size_t i = 0; i < len_; i++) { \
+ if (buf_[i] == '\'') { \
+ 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, ARRAY_SIZE(numbuf), "%" 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, ARRAY_SIZE(numbuf), "%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_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(name, 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(*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, ARRAY_SIZE(ebuf), "{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(*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, ARRAY_SIZE(ebuf), "{...@%zu}", backref); \
+ } else { \
+ vim_snprintf(ebuf, ARRAY_SIZE(ebuf), "[...@%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, ARRAY_SIZE(numbuf), "%" PRIu64, (num)); \
+ ga_concat(gap, numbuf); \
+ } while (0)
+
+#undef CONV_FLOAT
+#define CONV_FLOAT(flt) \
+ do { \
+ const float_T flt_ = (flt); \
+ switch (fpclassify(flt_)) { \
+ case FP_NAN: { \
+ EMSG(_("E474: Unable to represent NaN value in JSON")); \
+ return FAIL; \
+ } \
+ case FP_INFINITE: { \
+ EMSG(_("E474: Unable to represent infinity in JSON")); \
+ return FAIL; \
+ } \
+ default: { \
+ char numbuf[NUMBUFLEN]; \
+ vim_snprintf(numbuf, ARRAY_SIZE(numbuf), "%g", flt_); \
+ ga_concat(gap, (char_u *) numbuf); \
+ break; \
+ } \
+ } \
+ } 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",
+ ['"'] = "\\\"",
+ ['\\'] = "\\\\",
+ [FF] = "\\f",
+};
+
+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 *utf_buf = buf;
+ if (utf_buf == NULL) {
+ ga_concat(gap, "\"\"");
+ } else {
+ size_t utf_len = len;
+ char *tofree = NULL;
+ if (last_p_enc != (const void *) p_enc) {
+ p_enc_conv.vc_type = CONV_NONE;
+ 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, &utf_len);
+ if (tofree == NULL) {
+ emsgf(_("E474: Failed to convert string \"%.*s\" to UTF-8"),
+ utf_len, utf_buf);
+ return FAIL;
+ }
+ utf_buf = tofree;
+ }
+ size_t str_len = 0;
+ // Encode character as \u0000 if
+ // 1. It is an ASCII control character (0x0 .. 0x1F, 0x7F).
+ // 2. &encoding is not UTF-8 and code point is above 0x7F.
+ // 3. &encoding is UTF-8 and code point is not printable according to
+ // utf_printable().
+ // This is done to make it possible to :echo values when &encoding is not
+ // UTF-8.
+#define ENCODE_RAW(p_enc_conv, ch) \
+ (ch >= 0x20 && (p_enc_conv.vc_type == CONV_NONE \
+ ? utf_printable(ch) \
+ : ch < 0x7F))
+ for (size_t i = 0; i < utf_len;) {
+ const int ch = utf_ptr2char(utf_buf + i);
+ const size_t shift = (ch == 0? 1: utf_ptr2len(utf_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) {
+ emsgf(_("E474: String \"%.*s\" contains byte that does not start "
+ "any UTF-8 character"),
+ utf_len - (i - shift), utf_buf + i - shift);
+ xfree(tofree);
+ return FAIL;
+ } else if ((SURROGATE_HI_START <= ch && ch <= SURROGATE_HI_END)
+ || (SURROGATE_LO_START <= ch && ch <= SURROGATE_LO_END)) {
+ emsgf(_("E474: UTF-8 string contains code point which belongs "
+ "to a surrogate pair: %.*s"),
+ utf_len - (i - shift), utf_buf + i - shift);
+ xfree(tofree);
+ return FAIL;
+ } else if (ENCODE_RAW(p_enc_conv, ch)) {
+ str_len += shift;
+ } else {
+ str_len += ((sizeof("\\u1234") - 1)
+ * (size_t) (1 + (ch >= SURROGATE_FIRST_CHAR)));
+ }
+ break;
+ }
+ }
+ }
+ ga_append(gap, '"');
+ ga_grow(gap, (int) str_len);
+ for (size_t i = 0; i < utf_len;) {
+ const int ch = utf_ptr2char(utf_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(utf_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 (ENCODE_RAW(p_enc_conv, ch)) {
+ ga_concat_len(gap, utf_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 {
+ const int tmp = ch - SURROGATE_FIRST_CHAR;
+ const int hi = SURROGATE_HI_START + ((tmp >> 10) & ((1 << 10) - 1));
+ const int 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 json_encode()
+///
+/// @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(name, kv_pair) \
+ do { \
+ if (!check_json_key(&kv_pair->lv_first->li_tv)) { \
+ EMSG(_("E474: Invalid key in special dictionary")); \
+ goto encode_vim_to_##name##_error_ret; \
+ } \
+ } while (0)
+
+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_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_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(name, 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_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
diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h
new file mode 100644
index 0000000000..9bc665253b
--- /dev/null
+++ b/src/nvim/eval/encode.h
@@ -0,0 +1,75 @@
+#ifndef NVIM_EVAL_ENCODE_H
+#define NVIM_EVAL_ENCODE_H
+
+#include <stddef.h>
+
+#include <msgpack.h>
+
+#include "nvim/eval.h"
+#include "nvim/garray.h"
+#include "nvim/vim.h" // For STRLEN
+
+/// Convert VimL value to msgpack string
+///
+/// @param[out] packer Packer to save results in.
+/// @param[in] tv Dumped value.
+/// @param[in] objname Object name, used for error message.
+///
+/// @return OK in case of success, FAIL otherwise.
+int encode_vim_to_msgpack(msgpack_packer *const packer,
+ typval_T *const tv,
+ const char *const objname);
+
+/// Convert VimL value to :echo output
+///
+/// @param[out] packer Packer to save results in.
+/// @param[in] tv Dumped value.
+/// @param[in] objname Object name, used for error message.
+///
+/// @return OK in case of success, FAIL otherwise.
+int encode_vim_to_echo(garray_T *const packer,
+ typval_T *const tv,
+ const char *const objname);
+
+/// Structure defining state for read_from_list()
+typedef struct {
+ const listitem_T *li; ///< Item currently read.
+ size_t offset; ///< Byte offset inside the read item.
+ size_t li_length; ///< Length of the string inside the read item.
+} ListReaderState;
+
+/// Initialize ListReaderState structure
+static inline ListReaderState encode_init_lrstate(const list_T *const list)
+ FUNC_ATTR_NONNULL_ALL
+{
+ return (ListReaderState) {
+ .li = list->lv_first,
+ .offset = 0,
+ .li_length = (list->lv_first->li_tv.vval.v_string == NULL
+ ? 0
+ : STRLEN(list->lv_first->li_tv.vval.v_string)),
+ };
+}
+
+/// Array mapping values from SpecialVarValue enum to names
+extern const char *const encode_special_var_names[];
+
+/// First codepoint in high surrogates block
+#define SURROGATE_HI_START 0xD800
+
+/// Last codepoint in high surrogates block
+#define SURROGATE_HI_END 0xDBFF
+
+/// First codepoint in low surrogates block
+#define SURROGATE_LO_START 0xDC00
+
+/// Last codepoint in low surrogates block
+#define SURROGATE_LO_END 0xDFFF
+
+/// First character that needs to be encoded as surrogate pair
+#define SURROGATE_FIRST_CHAR 0x10000
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "eval/encode.h.generated.h"
+#endif
+#endif // NVIM_EVAL_ENCODE_H