mirror of
https://github.com/neovim/neovim.git
synced 2026-08-22 07:11:12 +00:00
This is a breaking change which will make refactor of typval and shada code a lot easier. In particular, code that would use or check for v:msgpack_types.binary in the wild would be broken. This appears to be rarely used in existing plugins. Also some cases where v:msgpack_type.string would be used to represent a binary string of "string" type, we use a BLOB instead, which is vimscripts native type for binary blobs, and already was used for BIN formats when necessary. msgpackdump(msgpackparse(data)) no longer preserves the distinction of BIN and STR strings. This is very common behavior for language-specific msgpack bindings. Nvim uses msgpack as a tool to serialize its data. Nvim is not a tool to bit-perfectly manipulate arbitrary msgpack data out in the wild. The changed tests should indicate how behavior changes in various edge cases.
839 lines
33 KiB
C
839 lines
33 KiB
C
// uncrustify:off
|
|
|
|
/// @file eval/typval_encode.c.h
|
|
///
|
|
/// Contains set of macros used to convert (possibly recursive) typval_T into
|
|
/// something else. For these macros to work the following macros must be
|
|
/// defined:
|
|
|
|
/// @def TYPVAL_ENCODE_CHECK_BEFORE
|
|
/// @brief Macro used before any specific CONV function
|
|
///
|
|
/// can be used for a common check, like flushing a buffer if necessary
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_NIL
|
|
/// @brief Macros used to convert NIL value
|
|
///
|
|
/// Is called both for special dictionary (unless #TYPVAL_ENCODE_ALLOW_SPECIALS
|
|
/// is false) and `v:null`.
|
|
///
|
|
/// @param tv Pointer to typval where value is stored. May not be NULL. May
|
|
/// point to special dictionary.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_BOOL
|
|
/// @brief Macros used to convert boolean value
|
|
///
|
|
/// Is called both for special dictionary (unless #TYPVAL_ENCODE_ALLOW_SPECIALS
|
|
/// is false) and `v:true`/`v:false`.
|
|
///
|
|
/// @param tv Pointer to typval where value is stored. May not be NULL. May
|
|
/// point to a special dictionary.
|
|
/// @param num Boolean value to convert. Value is an expression which
|
|
/// evaluates to some integer.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_NUMBER
|
|
/// @brief Macros used to convert integer
|
|
///
|
|
/// @param tv Pointer to typval where value is stored. May not be NULL. May
|
|
/// point to a special dictionary.
|
|
/// @param num Integer to convert, must accept both varnumber_T and int64_t.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER
|
|
/// @brief Macros used to convert unsigned integer
|
|
///
|
|
/// Not used if #TYPVAL_ENCODE_ALLOW_SPECIALS is false, but still must be
|
|
/// defined.
|
|
///
|
|
/// @param tv Pointer to typval where value is stored. May not be NULL. Points
|
|
/// to a special dictionary.
|
|
/// @param num Integer to convert, must accept uint64_t.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_FLOAT
|
|
/// @brief Macros used to convert floating-point number
|
|
///
|
|
/// @param tv Pointer to typval where value is stored. May not be NULL. May
|
|
/// point to a special dictionary.
|
|
/// @param flt Number to convert, must accept float_T.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_STRING
|
|
/// @brief Macros used to convert plain string
|
|
///
|
|
/// Is used to convert VAR_STRING objects as well as BIN strings represented as
|
|
/// special dictionary.
|
|
///
|
|
/// @param tv Pointer to typval where value is stored. May not be NULL. May
|
|
/// point to a special dictionary.
|
|
/// @param buf String to convert. Is a char[] buffer, not NUL-terminated.
|
|
/// @param len String length.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_STR_STRING
|
|
/// @brief Like #TYPVAL_ENCODE_CONV_STRING, but for STR strings
|
|
///
|
|
/// Is used to convert dictionary keys and STR strings represented as special
|
|
/// dictionaries.
|
|
///
|
|
/// @param tv Pointer to typval where value is stored. May be NULL. May
|
|
/// point to a special dictionary.
|
|
/// @param buf String to convert. Is a char[] buffer, not NUL-terminated.
|
|
/// @param len String length.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_EXT_STRING
|
|
/// @brief Macros used to convert EXT string
|
|
///
|
|
/// Is used to convert EXT strings represented as special dictionaries. Never
|
|
/// actually used if #TYPVAL_ENCODE_ALLOW_SPECIALS is false, but still must be
|
|
/// defined.
|
|
///
|
|
/// @param tv Pointer to typval where value is stored. May not be NULL. Points
|
|
/// to a special dictionary.
|
|
/// @param buf String to convert. Is a char[] buffer, not NUL-terminated.
|
|
/// @param len String length.
|
|
/// @param type EXT type.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_BLOB
|
|
/// @brief Macros used to convert a blob
|
|
///
|
|
/// @param tv Pointer to typval where value is stored. May not be NULL.
|
|
/// @param blob Pointer to the blob to convert.
|
|
/// @param len Blob length.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_FUNC_START
|
|
/// @brief Macros used when starting to convert a funcref or a partial
|
|
///
|
|
/// @param tv Pointer to typval where value is stored. May not be NULL.
|
|
/// @param fun Function name. May be NULL.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS
|
|
/// @brief Macros used before starting to convert partial arguments
|
|
///
|
|
/// @param tv Pointer to typval where value is stored. May not be NULL.
|
|
/// @param len Number of arguments. Zero for absent arguments or when
|
|
/// converting a funcref.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF
|
|
/// @brief Macros used before starting to convert self dictionary
|
|
///
|
|
/// @param tv Pointer to typval where value is stored. May not be NULL.
|
|
/// @param len Number of arguments. May be zero for empty dictionary or -1 for
|
|
/// missing self dictionary, also when converting function
|
|
/// reference.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_FUNC_END
|
|
/// @brief Macros used after converting a funcref or a partial
|
|
///
|
|
/// @param tv Pointer to typval where value is stored. May not be NULL.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_EMPTY_LIST
|
|
/// @brief Macros used to convert an empty list
|
|
///
|
|
/// @param tv Pointer to typval where value is stored. May not be NULL.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_EMPTY_DICT
|
|
/// @brief Macros used to convert an empty dictionary
|
|
///
|
|
/// @param tv Pointer to typval where value is stored. May be NULL. May
|
|
/// point to a special dictionary.
|
|
/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR
|
|
/// (for dictionaries represented as special lists).
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_LIST_START
|
|
/// @brief Macros used before starting to convert non-empty list
|
|
///
|
|
/// @param tv Pointer to typval where value is stored. May be NULL. May
|
|
/// point to a special dictionary.
|
|
/// @param len List length. Is an expression which evaluates to an integer.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START
|
|
/// @brief Macros used after pushing list onto the stack
|
|
///
|
|
/// Only used for real list_T* lists, not for special dictionaries or partial
|
|
/// arguments.
|
|
///
|
|
/// @param tv Pointer to typval where value is stored. May be NULL. May
|
|
/// point to a special dictionary.
|
|
/// @param mpsv Pushed MPConvStackVal value.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS
|
|
/// @brief Macros used after finishing converting non-last list item
|
|
///
|
|
/// @param tv Pointer to typval where list is stored. May be NULL.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_LIST_END
|
|
/// @brief Macros used after converting non-empty list
|
|
///
|
|
/// @param tv Pointer to typval where list is stored. May be NULL.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_DICT_START
|
|
/// @brief Macros used before starting to convert non-empty dictionary
|
|
///
|
|
/// Only used for real dict_T* dictionaries, not for special dictionaries. Also
|
|
/// used for partial self dictionary.
|
|
///
|
|
/// @param tv Pointer to typval where dictionary is stored. May be NULL. May
|
|
/// point to a special dictionary.
|
|
/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR
|
|
/// (for dictionaries represented as special lists).
|
|
/// @param len Dictionary length. Is an expression which evaluates to an
|
|
/// integer.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START
|
|
/// @brief Macros used after pushing dictionary onto the stack
|
|
///
|
|
/// @param tv Pointer to typval where dictionary is stored. May be NULL.
|
|
/// May not point to a special dictionary.
|
|
/// @param dict Converted dictionary, lvalue.
|
|
/// @param mpsv Pushed MPConvStackVal value.
|
|
|
|
/// @def TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK
|
|
/// @brief Macros used to check special dictionary key
|
|
///
|
|
/// @param label Label for goto in case check was not successful.
|
|
/// @param key typval_T key to check.
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_DICT_AFTER_KEY
|
|
/// @brief Macros used after finishing converting dictionary key
|
|
///
|
|
/// @param tv Pointer to typval where dictionary is stored. May be NULL. May
|
|
/// point to a special dictionary.
|
|
/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR
|
|
/// (for dictionaries represented as special lists).
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS
|
|
/// @brief Macros used after finishing converting non-last dictionary value
|
|
///
|
|
/// @param tv Pointer to typval where dictionary is stored. May be NULL. May
|
|
/// point to a special dictionary.
|
|
/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR
|
|
/// (for dictionaries represented as special lists).
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_DICT_END
|
|
/// @brief Macros used after converting non-empty dictionary
|
|
///
|
|
/// @param tv Pointer to typval where dictionary is stored. May be NULL. May
|
|
/// point to a special dictionary.
|
|
/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR
|
|
/// (for dictionaries represented as special lists).
|
|
|
|
/// @def TYPVAL_ENCODE_CONV_RECURSE
|
|
/// @brief Macros used when self-containing container is detected
|
|
///
|
|
/// @param val Container for which this situation was detected.
|
|
/// @param conv_type Type of the stack entry, @see MPConvStackValType.
|
|
|
|
/// @def TYPVAL_ENCODE_ALLOW_SPECIALS
|
|
/// @brief Macros that specifies whether special dictionaries are special
|
|
///
|
|
/// Must be something that evaluates to boolean, most likely `true` or `false`.
|
|
/// If it is false then special dictionaries are not treated specially.
|
|
|
|
/// @def TYPVAL_ENCODE_SCOPE
|
|
/// @brief Scope of the main function: either nothing or `static`
|
|
|
|
/// @def TYPVAL_ENCODE_NAME
|
|
/// @brief Name of the target converter
|
|
///
|
|
/// After including this file it will define function
|
|
/// `encode_vim_to_{TYPVAL_ENCODE_NAME}` with scope #TYPVAL_ENCODE_SCOPE and
|
|
/// static functions `_typval_encode_{TYPVAL_ENCODE_NAME}_convert_one_value` and
|
|
/// `_typval_encode_{TYPVAL_ENCODE_NAME}_check_self_reference`.
|
|
|
|
/// @def TYPVAL_ENCODE_FIRST_ARG_TYPE
|
|
/// @brief Type of the first argument, which will be used to return the results
|
|
///
|
|
/// Is expected to be a pointer type.
|
|
|
|
/// @def TYPVAL_ENCODE_FIRST_ARG_NAME
|
|
/// @brief Name of the first argument
|
|
///
|
|
/// This name will only be used by one of the above macros which are defined by
|
|
/// the caller. Functions defined here do not use first argument directly.
|
|
#include <assert.h>
|
|
#include <inttypes.h>
|
|
#include <stddef.h>
|
|
|
|
#include "klib/kvec.h"
|
|
#include "nvim/eval.h"
|
|
#include "nvim/eval/encode.h"
|
|
#include "nvim/eval/typval.h"
|
|
#include "nvim/eval/typval_encode.h"
|
|
#include "nvim/func_attr.h"
|
|
|
|
/// Dummy variable used because some macros need lvalue
|
|
///
|
|
/// Must not be written to, if needed one must check that address of the
|
|
/// macros argument is (not) equal to `&TYPVAL_ENCODE_NODICT_VAR`.
|
|
const dict_T *const TYPVAL_ENCODE_NODICT_VAR = NULL;
|
|
|
|
static inline int TYPVAL_ENCODE_CHECK_SELF_REFERENCE(
|
|
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME,
|
|
void *const val, int *const val_copyID,
|
|
const MPConvStack *const mpstack, const int copyID,
|
|
const MPConvStackValType conv_type,
|
|
const char *const objname)
|
|
REAL_FATTR_NONNULL_ARG(2, 3, 4, 7) REAL_FATTR_WARN_UNUSED_RESULT
|
|
REAL_FATTR_ALWAYS_INLINE;
|
|
|
|
/// Function for checking whether container references itself
|
|
///
|
|
/// @param TYPVAL_ENCODE_FIRST_ARG_NAME First argument.
|
|
/// @param[in,out] val Container to check.
|
|
/// @param val_copyID Pointer to the container attribute that holds copyID.
|
|
/// After checking whether value of this attribute is
|
|
/// copyID (variable) it is set to copyID.
|
|
/// @param[in] mpstack Stack with values to convert. Read-only, used for error
|
|
/// reporting.
|
|
/// @param[in] copyID CopyID used by the caller.
|
|
/// @param[in] conv_type Type of the conversion, @see MPConvStackValType.
|
|
/// @param[in] objname Object name, used for error reporting.
|
|
///
|
|
/// @return NOTDONE in case of success, what to return in case of failure.
|
|
static inline int TYPVAL_ENCODE_CHECK_SELF_REFERENCE(
|
|
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, void *const val, int *const val_copyID,
|
|
const MPConvStack *const mpstack, const int copyID, const MPConvStackValType conv_type,
|
|
const char *const objname)
|
|
{
|
|
if (*val_copyID == copyID) {
|
|
TYPVAL_ENCODE_CONV_RECURSE(val, conv_type);
|
|
return OK;
|
|
}
|
|
*val_copyID = copyID;
|
|
return NOTDONE;
|
|
}
|
|
|
|
static int TYPVAL_ENCODE_CONVERT_ONE_VALUE(
|
|
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME,
|
|
MPConvStack *const mpstack, MPConvStackVal *const cur_mpsv,
|
|
typval_T *const tv, const int copyID,
|
|
const char *const objname)
|
|
REAL_FATTR_NONNULL_ARG(2, 4, 6) REAL_FATTR_WARN_UNUSED_RESULT;
|
|
|
|
/// Convert single value
|
|
///
|
|
/// Only scalar values are converted immediately, everything else is pushed onto
|
|
/// the stack.
|
|
///
|
|
/// @param TYPVAL_ENCODE_FIRST_ARG_NAME First argument, defined by the
|
|
/// includer. Only meaningful to macros
|
|
/// defined by the includer.
|
|
/// @param mpstack Stack with values to convert. Values which are not
|
|
/// converted completely by this function (i.e.
|
|
/// non-scalars) are pushed here.
|
|
/// @param cur_mpsv Currently converted value from stack.
|
|
/// @param tv Converted value.
|
|
/// @param[in] copyID CopyID.
|
|
/// @param[in] objname Object name, used for error reporting.
|
|
///
|
|
/// @return OK in case of success, FAIL in case of failure.
|
|
static int TYPVAL_ENCODE_CONVERT_ONE_VALUE(
|
|
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, MPConvStack *const mpstack,
|
|
MPConvStackVal *const cur_mpsv, typval_T *const tv, const int copyID, const char *const objname)
|
|
{
|
|
TYPVAL_ENCODE_CHECK_BEFORE;
|
|
switch (tv->v_type) {
|
|
case VAR_STRING:
|
|
TYPVAL_ENCODE_CONV_STRING(tv, tv->vval.v_string, tv_strlen(tv));
|
|
break;
|
|
case VAR_NUMBER:
|
|
TYPVAL_ENCODE_CONV_NUMBER(tv, tv->vval.v_number);
|
|
break;
|
|
case VAR_FLOAT:
|
|
TYPVAL_ENCODE_CONV_FLOAT(tv, tv->vval.v_float);
|
|
break;
|
|
case VAR_BLOB:
|
|
TYPVAL_ENCODE_CONV_BLOB(tv, tv->vval.v_blob,
|
|
tv_blob_len(tv->vval.v_blob));
|
|
break;
|
|
case VAR_FUNC:
|
|
TYPVAL_ENCODE_CONV_FUNC_START(tv, tv->vval.v_string);
|
|
TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, 0);
|
|
TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, -1);
|
|
TYPVAL_ENCODE_CONV_FUNC_END(tv);
|
|
break;
|
|
case VAR_PARTIAL: {
|
|
partial_T *const pt = tv->vval.v_partial;
|
|
(void)pt;
|
|
TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : partial_name(pt)));
|
|
kvi_push(*mpstack, ((MPConvStackVal) {
|
|
.type = kMPConvPartial,
|
|
.tv = tv,
|
|
.saved_copyID = copyID - 1,
|
|
.data = {
|
|
.p = {
|
|
.stage = kMPConvPartialArgs,
|
|
.pt = tv->vval.v_partial,
|
|
},
|
|
},
|
|
}));
|
|
break;
|
|
}
|
|
case VAR_LIST: {
|
|
if (tv->vval.v_list == NULL || tv_list_len(tv->vval.v_list) == 0) {
|
|
TYPVAL_ENCODE_CONV_EMPTY_LIST(tv);
|
|
break;
|
|
}
|
|
const int saved_copyID = tv_list_copyid(tv->vval.v_list);
|
|
TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID,
|
|
kMPConvList);
|
|
TYPVAL_ENCODE_CONV_LIST_START(tv, tv_list_len(tv->vval.v_list));
|
|
assert(saved_copyID != copyID);
|
|
kvi_push(*mpstack, ((MPConvStackVal) {
|
|
.type = kMPConvList,
|
|
.tv = tv,
|
|
.saved_copyID = saved_copyID,
|
|
.data = {
|
|
.l = {
|
|
.list = tv->vval.v_list,
|
|
.li = tv_list_first(tv->vval.v_list),
|
|
},
|
|
},
|
|
}));
|
|
TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, kv_last(*mpstack));
|
|
break;
|
|
}
|
|
case VAR_BOOL:
|
|
switch (tv->vval.v_bool) {
|
|
case kBoolVarTrue:
|
|
case kBoolVarFalse:
|
|
TYPVAL_ENCODE_CONV_BOOL(tv, tv->vval.v_bool == kBoolVarTrue);
|
|
break;
|
|
}
|
|
break;
|
|
case VAR_SPECIAL:
|
|
switch (tv->vval.v_special) {
|
|
case kSpecialVarNull:
|
|
TYPVAL_ENCODE_CONV_NIL(tv);
|
|
break;
|
|
}
|
|
break;
|
|
case VAR_DICT: {
|
|
if (tv->vval.v_dict == NULL
|
|
|| tv->vval.v_dict->dv_hashtab.ht_used == 0) {
|
|
TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, tv->vval.v_dict);
|
|
break;
|
|
}
|
|
const dictitem_T *type_di;
|
|
const dictitem_T *val_di;
|
|
if (TYPVAL_ENCODE_ALLOW_SPECIALS
|
|
&& tv->vval.v_dict->dv_hashtab.ht_used == 2
|
|
&& (type_di = tv_dict_find((dict_T *)tv->vval.v_dict,
|
|
S_LEN("_TYPE"))) != NULL
|
|
&& type_di->di_tv.v_type == VAR_LIST
|
|
&& (val_di = tv_dict_find((dict_T *)tv->vval.v_dict,
|
|
S_LEN("_VAL"))) != 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;
|
|
}
|
|
}
|
|
TYPVAL_ENCODE_CHECK_BEFORE;
|
|
if (i == ARRAY_SIZE(eval_msgpack_type_lists)) {
|
|
goto _convert_one_value_regular_dict;
|
|
}
|
|
switch ((MessagePackType)i) {
|
|
case kMPNil:
|
|
TYPVAL_ENCODE_CONV_NIL(tv);
|
|
break;
|
|
case kMPBoolean:
|
|
if (val_di->di_tv.v_type != VAR_NUMBER) {
|
|
goto _convert_one_value_regular_dict;
|
|
}
|
|
TYPVAL_ENCODE_CONV_BOOL(tv, 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
|
|
|| tv_list_len(val_list = val_di->di_tv.vval.v_list) != 4) {
|
|
goto _convert_one_value_regular_dict;
|
|
}
|
|
|
|
const listitem_T *const sign_li = tv_list_first(val_list);
|
|
if (TV_LIST_ITEM_TV(sign_li)->v_type != VAR_NUMBER
|
|
|| (sign = TV_LIST_ITEM_TV(sign_li)->vval.v_number) == 0) {
|
|
goto _convert_one_value_regular_dict;
|
|
}
|
|
|
|
const listitem_T *const highest_bits_li = (
|
|
TV_LIST_ITEM_NEXT(val_list, sign_li));
|
|
if (TV_LIST_ITEM_TV(highest_bits_li)->v_type != VAR_NUMBER
|
|
|| ((highest_bits = TV_LIST_ITEM_TV(highest_bits_li)->vval.v_number)
|
|
< 0)) {
|
|
goto _convert_one_value_regular_dict;
|
|
}
|
|
|
|
const listitem_T *const high_bits_li = (
|
|
TV_LIST_ITEM_NEXT(val_list, highest_bits_li));
|
|
if (TV_LIST_ITEM_TV(high_bits_li)->v_type != VAR_NUMBER
|
|
|| ((high_bits = TV_LIST_ITEM_TV(high_bits_li)->vval.v_number)
|
|
< 0)) {
|
|
goto _convert_one_value_regular_dict;
|
|
}
|
|
|
|
const listitem_T *const low_bits_li = tv_list_last(val_list);
|
|
if (TV_LIST_ITEM_TV(low_bits_li)->v_type != VAR_NUMBER
|
|
|| ((low_bits = TV_LIST_ITEM_TV(low_bits_li)->vval.v_number)
|
|
< 0)) {
|
|
goto _convert_one_value_regular_dict;
|
|
}
|
|
|
|
const uint64_t number = ((uint64_t)(((uint64_t)highest_bits) << 62)
|
|
| (uint64_t)(((uint64_t)high_bits) << 31)
|
|
| (uint64_t)low_bits);
|
|
if (sign > 0) {
|
|
TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, number);
|
|
} else {
|
|
TYPVAL_ENCODE_CONV_NUMBER(tv, -number);
|
|
}
|
|
break;
|
|
}
|
|
case kMPFloat:
|
|
if (val_di->di_tv.v_type != VAR_FLOAT) {
|
|
goto _convert_one_value_regular_dict;
|
|
}
|
|
TYPVAL_ENCODE_CONV_FLOAT(tv, val_di->di_tv.vval.v_float);
|
|
break;
|
|
case kMPString: {
|
|
if (val_di->di_tv.v_type != VAR_LIST) {
|
|
goto _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 _convert_one_value_regular_dict;
|
|
}
|
|
TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len);
|
|
xfree(buf);
|
|
break;
|
|
}
|
|
case kMPArray: {
|
|
if (val_di->di_tv.v_type != VAR_LIST) {
|
|
goto _convert_one_value_regular_dict;
|
|
}
|
|
const int saved_copyID = tv_list_copyid(val_di->di_tv.vval.v_list);
|
|
TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list,
|
|
lv_copyID, copyID,
|
|
kMPConvList);
|
|
TYPVAL_ENCODE_CONV_LIST_START(tv, tv_list_len(val_di->di_tv.vval.v_list));
|
|
assert(saved_copyID != copyID && saved_copyID != copyID - 1);
|
|
kvi_push(*mpstack, ((MPConvStackVal) {
|
|
.tv = tv,
|
|
.type = kMPConvList,
|
|
.saved_copyID = saved_copyID,
|
|
.data = {
|
|
.l = {
|
|
.list = val_di->di_tv.vval.v_list,
|
|
.li = tv_list_first(val_di->di_tv.vval.v_list),
|
|
},
|
|
},
|
|
}));
|
|
break;
|
|
}
|
|
case kMPMap: {
|
|
if (val_di->di_tv.v_type != VAR_LIST) {
|
|
goto _convert_one_value_regular_dict;
|
|
}
|
|
list_T *const val_list = val_di->di_tv.vval.v_list;
|
|
if (val_list == NULL || tv_list_len(val_list) == 0) {
|
|
TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, TYPVAL_ENCODE_NODICT_VAR);
|
|
break;
|
|
}
|
|
TV_LIST_ITER_CONST(val_list, li, {
|
|
if (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST
|
|
|| tv_list_len(TV_LIST_ITEM_TV(li)->vval.v_list) != 2) {
|
|
goto _convert_one_value_regular_dict;
|
|
}
|
|
});
|
|
const int saved_copyID = tv_list_copyid(val_di->di_tv.vval.v_list);
|
|
TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID,
|
|
kMPConvPairs);
|
|
TYPVAL_ENCODE_CONV_DICT_START(tv, TYPVAL_ENCODE_NODICT_VAR,
|
|
tv_list_len(val_list));
|
|
assert(saved_copyID != copyID && saved_copyID != copyID - 1);
|
|
kvi_push(*mpstack, ((MPConvStackVal) {
|
|
.tv = tv,
|
|
.type = kMPConvPairs,
|
|
.saved_copyID = saved_copyID,
|
|
.data = {
|
|
.l = {
|
|
.list = val_list,
|
|
.li = tv_list_first(val_list),
|
|
},
|
|
},
|
|
}));
|
|
break;
|
|
}
|
|
case kMPExt: {
|
|
const list_T *val_list;
|
|
varnumber_T type;
|
|
if (val_di->di_tv.v_type != VAR_LIST
|
|
|| tv_list_len((val_list = val_di->di_tv.vval.v_list)) != 2
|
|
|| (TV_LIST_ITEM_TV(tv_list_first(val_list))->v_type
|
|
!= VAR_NUMBER)
|
|
|| ((type = TV_LIST_ITEM_TV(tv_list_first(val_list))->vval.v_number)
|
|
> INT8_MAX)
|
|
|| type < INT8_MIN
|
|
|| (TV_LIST_ITEM_TV(tv_list_last(val_list))->v_type
|
|
!= VAR_LIST)) {
|
|
goto _convert_one_value_regular_dict;
|
|
}
|
|
size_t len;
|
|
char *buf;
|
|
if (!(
|
|
encode_vim_list_to_buf(TV_LIST_ITEM_TV(tv_list_last(val_list))->vval.v_list, &len,
|
|
&buf))) {
|
|
goto _convert_one_value_regular_dict;
|
|
}
|
|
TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type);
|
|
xfree(buf);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
_convert_one_value_regular_dict: {}
|
|
const int saved_copyID = tv->vval.v_dict->dv_copyID;
|
|
TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, copyID,
|
|
kMPConvDict);
|
|
TYPVAL_ENCODE_CONV_DICT_START(tv, tv->vval.v_dict,
|
|
tv->vval.v_dict->dv_hashtab.ht_used);
|
|
assert(saved_copyID != copyID);
|
|
kvi_push(*mpstack, ((MPConvStackVal) {
|
|
.tv = tv,
|
|
.type = kMPConvDict,
|
|
.saved_copyID = saved_copyID,
|
|
.data = {
|
|
.d = {
|
|
.dict = tv->vval.v_dict,
|
|
.dictp = &tv->vval.v_dict,
|
|
.hi = tv->vval.v_dict->dv_hashtab.ht_array,
|
|
.todo = tv->vval.v_dict->dv_hashtab.ht_used,
|
|
},
|
|
},
|
|
}));
|
|
TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, tv->vval.v_dict,
|
|
kv_last(*mpstack));
|
|
break;
|
|
}
|
|
case VAR_UNKNOWN:
|
|
internal_error(STR(TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()");
|
|
return FAIL;
|
|
}
|
|
typval_encode_stop_converting_one_item:
|
|
return OK;
|
|
// Prevent “unused label” warnings.
|
|
goto typval_encode_stop_converting_one_item;
|
|
}
|
|
|
|
TYPVAL_ENCODE_SCOPE int TYPVAL_ENCODE_ENCODE(
|
|
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME,
|
|
typval_T *const tv, const char *const objname)
|
|
REAL_FATTR_NONNULL_ARG(2, 3) REAL_FATTR_WARN_UNUSED_RESULT;
|
|
|
|
/// Convert the whole typval
|
|
///
|
|
/// @param TYPVAL_ENCODE_FIRST_ARG_NAME First argument, defined by the
|
|
/// includer. Only meaningful to macros
|
|
/// defined by the includer.
|
|
/// @param top_tv Converted value.
|
|
/// @param[in] objname Object name, used for error reporting.
|
|
///
|
|
/// @return OK in case of success, FAIL in case of failure.
|
|
TYPVAL_ENCODE_SCOPE int TYPVAL_ENCODE_ENCODE(
|
|
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, typval_T *const top_tv,
|
|
const char *const objname)
|
|
{
|
|
const int copyID = get_copyID();
|
|
MPConvStack mpstack;
|
|
kvi_init(mpstack);
|
|
if (TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack,
|
|
NULL,
|
|
top_tv, copyID, objname)
|
|
== FAIL) {
|
|
goto encode_vim_to__error_ret;
|
|
}
|
|
/// Label common for this and convert_one_value functions, used for escaping
|
|
/// from macros like TYPVAL_ENCODE_CONV_DICT_START.
|
|
typval_encode_stop_converting_one_item:
|
|
while (kv_size(mpstack)) {
|
|
MPConvStackVal *cur_mpsv = &kv_last(mpstack);
|
|
typval_T *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 = cur_mpsv->saved_copyID;
|
|
TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, *cur_mpsv->data.d.dictp);
|
|
continue;
|
|
} else if (cur_mpsv->data.d.todo
|
|
!= cur_mpsv->data.d.dict->dv_hashtab.ht_used) {
|
|
TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(cur_mpsv->tv,
|
|
*cur_mpsv->data.d.dictp);
|
|
}
|
|
while (HASHITEM_EMPTY(cur_mpsv->data.d.hi)) {
|
|
cur_mpsv->data.d.hi++;
|
|
}
|
|
dictitem_T *const di = TV_DICT_HI2DI(cur_mpsv->data.d.hi);
|
|
cur_mpsv->data.d.todo--;
|
|
cur_mpsv->data.d.hi++;
|
|
TYPVAL_ENCODE_CONV_STR_STRING(NULL, &di->di_key[0],
|
|
strlen((char *)&di->di_key[0]));
|
|
TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(cur_mpsv->tv,
|
|
*cur_mpsv->data.d.dictp);
|
|
tv = &di->di_tv;
|
|
break;
|
|
}
|
|
case kMPConvList:
|
|
if (cur_mpsv->data.l.li == NULL) {
|
|
(void)kv_pop(mpstack);
|
|
tv_list_set_copyid(cur_mpsv->data.l.list, cur_mpsv->saved_copyID);
|
|
TYPVAL_ENCODE_CONV_LIST_END(cur_mpsv->tv);
|
|
continue;
|
|
} else if (cur_mpsv->data.l.li
|
|
!= tv_list_first(cur_mpsv->data.l.list)) {
|
|
TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(cur_mpsv->tv);
|
|
}
|
|
tv = TV_LIST_ITEM_TV(cur_mpsv->data.l.li);
|
|
cur_mpsv->data.l.li = TV_LIST_ITEM_NEXT(cur_mpsv->data.l.list,
|
|
cur_mpsv->data.l.li);
|
|
break;
|
|
case kMPConvPairs: {
|
|
if (cur_mpsv->data.l.li == NULL) {
|
|
(void)kv_pop(mpstack);
|
|
tv_list_set_copyid(cur_mpsv->data.l.list, cur_mpsv->saved_copyID);
|
|
TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR);
|
|
continue;
|
|
} else if (cur_mpsv->data.l.li
|
|
!= tv_list_first(cur_mpsv->data.l.list)) {
|
|
TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR);
|
|
}
|
|
const list_T *const kv_pair = (
|
|
TV_LIST_ITEM_TV(cur_mpsv->data.l.li)->vval.v_list);
|
|
TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(encode_vim_to__error_ret,
|
|
*TV_LIST_ITEM_TV(tv_list_first(kv_pair)));
|
|
if (
|
|
TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, cur_mpsv,
|
|
TV_LIST_ITEM_TV(tv_list_first(kv_pair)), copyID, objname)
|
|
== FAIL) {
|
|
goto encode_vim_to__error_ret;
|
|
}
|
|
TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(cur_mpsv->tv,
|
|
TYPVAL_ENCODE_NODICT_VAR);
|
|
tv = TV_LIST_ITEM_TV(tv_list_last(kv_pair));
|
|
cur_mpsv->data.l.li = TV_LIST_ITEM_NEXT(cur_mpsv->data.l.list,
|
|
cur_mpsv->data.l.li);
|
|
break;
|
|
}
|
|
case kMPConvPartial: {
|
|
partial_T *const pt = cur_mpsv->data.p.pt;
|
|
tv = cur_mpsv->tv;
|
|
(void)tv;
|
|
switch (cur_mpsv->data.p.stage) {
|
|
case kMPConvPartialArgs:
|
|
TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv,
|
|
pt == NULL ? 0 : pt->pt_argc);
|
|
cur_mpsv->data.p.stage = kMPConvPartialSelf;
|
|
if (pt != NULL && pt->pt_argc > 0) {
|
|
TYPVAL_ENCODE_CONV_LIST_START(NULL, pt->pt_argc);
|
|
kvi_push(mpstack, ((MPConvStackVal) {
|
|
.type = kMPConvPartialList,
|
|
.tv = NULL,
|
|
.saved_copyID = copyID - 1,
|
|
.data = {
|
|
.a = {
|
|
.arg = pt->pt_argv,
|
|
.argv = pt->pt_argv,
|
|
.todo = (size_t)pt->pt_argc,
|
|
},
|
|
},
|
|
}));
|
|
}
|
|
break;
|
|
case kMPConvPartialSelf: {
|
|
cur_mpsv->data.p.stage = kMPConvPartialEnd;
|
|
dict_T *const dict = pt == NULL ? NULL : pt->pt_dict;
|
|
if (dict != NULL) {
|
|
TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, dict->dv_hashtab.ht_used);
|
|
if (dict->dv_hashtab.ht_used == 0) {
|
|
TYPVAL_ENCODE_CONV_EMPTY_DICT(NULL, pt->pt_dict);
|
|
continue;
|
|
}
|
|
const int saved_copyID = dict->dv_copyID;
|
|
const int te_csr_ret = TYPVAL_ENCODE_CHECK_SELF_REFERENCE(TYPVAL_ENCODE_FIRST_ARG_NAME,
|
|
dict, &dict->dv_copyID,
|
|
&mpstack, copyID, kMPConvDict,
|
|
objname);
|
|
if (te_csr_ret != NOTDONE) {
|
|
if (te_csr_ret == FAIL) {
|
|
goto encode_vim_to__error_ret;
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
TYPVAL_ENCODE_CONV_DICT_START(NULL, pt->pt_dict,
|
|
dict->dv_hashtab.ht_used);
|
|
assert(saved_copyID != copyID && saved_copyID != copyID - 1);
|
|
kvi_push(mpstack, ((MPConvStackVal) {
|
|
.type = kMPConvDict,
|
|
.tv = NULL,
|
|
.saved_copyID = saved_copyID,
|
|
.data = {
|
|
.d = {
|
|
.dict = dict,
|
|
.dictp = &pt->pt_dict,
|
|
.hi = dict->dv_hashtab.ht_array,
|
|
.todo = dict->dv_hashtab.ht_used,
|
|
},
|
|
},
|
|
}));
|
|
TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(NULL, pt->pt_dict,
|
|
kv_last(mpstack));
|
|
} else {
|
|
TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, -1);
|
|
}
|
|
break;
|
|
}
|
|
case kMPConvPartialEnd:
|
|
TYPVAL_ENCODE_CONV_FUNC_END(tv);
|
|
(void)kv_pop(mpstack);
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
case kMPConvPartialList:
|
|
if (!cur_mpsv->data.a.todo) {
|
|
(void)kv_pop(mpstack);
|
|
TYPVAL_ENCODE_CONV_LIST_END(NULL);
|
|
continue;
|
|
} else if (cur_mpsv->data.a.argv != cur_mpsv->data.a.arg) {
|
|
TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(NULL);
|
|
}
|
|
tv = cur_mpsv->data.a.arg++;
|
|
cur_mpsv->data.a.todo--;
|
|
break;
|
|
}
|
|
assert(tv != NULL);
|
|
if (TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack,
|
|
cur_mpsv, tv, copyID, objname)
|
|
== FAIL) {
|
|
goto encode_vim_to__error_ret;
|
|
}
|
|
}
|
|
kvi_destroy(mpstack);
|
|
return OK;
|
|
encode_vim_to__error_ret:
|
|
kvi_destroy(mpstack);
|
|
return FAIL;
|
|
// Prevent “unused label” warnings.
|
|
goto typval_encode_stop_converting_one_item;
|
|
}
|