/// @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_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`. Accepts no arguments, but still must be /// a function-like macros. /// @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 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 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 num Integer to convert, must accept uint64_t. /// @def TYPVAL_ENCODE_CONV_FLOAT /// @brief Macros used to convert floating-point number /// /// @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 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. /// @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 buf String to convert. Is a char[] buffer, not NUL-terminated. /// @param len String length. /// @param type EXT type. /// @def TYPVAL_ENCODE_CONV_FUNC /// @brief Macros used to convert a function reference /// /// @param fun Function name. /// @def TYPVAL_ENCODE_CONV_FUNC_START /// @brief Macros used when starting to convert a funcref or a partial /// /// @param fun Function name. /// @param is_partial True if converted function is a partial. /// @param pt Pointer to partial or NULL. /// @def TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS /// @brief Macros used before starting to convert partial arguments /// /// @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 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 /// /// Accepts no arguments, but still must be a function-like macros. /// @def TYPVAL_ENCODE_CONV_EMPTY_LIST /// @brief Macros used to convert an empty list /// /// Accepts no arguments, but still must be a function-like macros. /// @def TYPVAL_ENCODE_CONV_EMPTY_DICT /// @brief Macros used to convert an empty dictionary /// /// Accepts no arguments, but still must be a function-like macros. /// @def TYPVAL_ENCODE_CONV_LIST_START /// @brief Macros used before starting to convert non-empty list /// /// @param len List length. Is an expression which evaluates to an integer. /// @def TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS /// @brief Macros used after finishing converting non-last list item /// /// Accepts no arguments, but still must be a function-like macros. /// @def TYPVAL_ENCODE_CONV_LIST_END /// @brief Macros used after converting non-empty list /// /// Accepts no arguments, but still must be a function-like macros. /// @def TYPVAL_ENCODE_CONV_DICT_START /// @brief Macros used before starting to convert non-empty dictionary /// /// @param len Dictionary length. Is an expression which evaluates to an /// integer. /// @def TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK /// @brief Macros used to check special dictionary key /// /// @param label Label for goto in case check was not successfull. /// @param key typval_T key to check. /// @def TYPVAL_ENCODE_CONV_DICT_AFTER_KEY /// @brief Macros used after finishing converting dictionary key /// /// Accepts no arguments, but still must be a function-like macros. /// @def TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS /// @brief Macros used after finishing converting non-last dictionary value /// /// Accepts no arguments, but still must be a function-like macros. /// @def TYPVAL_ENCODE_CONV_DICT_END /// @brief Macros used after converting non-empty dictionary /// /// Accepts no arguments, but still must be a function-like macros. /// @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 function `_{TYPVAL_ENCODE_NAME}_convert_one_value`. /// @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. #ifndef NVIM_EVAL_TYPVAL_ENCODE_C_H #define NVIM_EVAL_TYPVAL_ENCODE_C_H #undef NVIM_EVAL_TYPVAL_ENCODE_C_H #include #include #include #include "nvim/lib/kvec.h" #include "nvim/eval_defs.h" #include "nvim/eval/encode.h" #include "nvim/func_attr.h" #include "nvim/eval/typval_encode.h" static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, MPConvStack *const mpstack, typval_T *const tv, const int copyID, const char *const objname) REAL_FATTR_NONNULL_ALL 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[out] mpstack Stack with values to convert. Values which are not /// converted completely by this function (i.e. /// non-scalars) are pushed here. /// @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, typval_T *const tv, const int copyID, const char *const objname) { switch (tv->v_type) { case VAR_STRING: { TYPVAL_ENCODE_CONV_STRING(tv->vval.v_string, tv_strlen(tv)); break; } case VAR_NUMBER: { TYPVAL_ENCODE_CONV_NUMBER(tv->vval.v_number); break; } case VAR_FLOAT: { TYPVAL_ENCODE_CONV_FLOAT(tv->vval.v_float); break; } case VAR_FUNC: { TYPVAL_ENCODE_CONV_FUNC_START(tv->vval.v_string, false, NULL); TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(0); TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(-1); TYPVAL_ENCODE_CONV_FUNC_END(); break; } case VAR_PARTIAL: { partial_T *const pt = tv->vval.v_partial; (void)pt; TYPVAL_ENCODE_CONV_FUNC_START(pt->pt_name, true, pt); _mp_push(*mpstack, ((MPConvStackVal) { .type = kMPConvPartial, .tv = tv, .data = { .p = { .stage = kMPConvPartialArgs, .pt = tv->vval.v_partial, }, }, })); break; } case VAR_LIST: { if (tv->vval.v_list == NULL || tv->vval.v_list->lv_len == 0) { TYPVAL_ENCODE_CONV_EMPTY_LIST(); break; } _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID, kMPConvList); TYPVAL_ENCODE_CONV_LIST_START(tv->vval.v_list->lv_len); _mp_push(*mpstack, ((MPConvStackVal) { .type = kMPConvList, .tv = tv, .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: { TYPVAL_ENCODE_CONV_NIL(); break; } case kSpecialVarTrue: case kSpecialVarFalse: { TYPVAL_ENCODE_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) { TYPVAL_ENCODE_CONV_EMPTY_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 = 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 _convert_one_value_regular_dict; } switch ((MessagePackType)i) { case kMPNil: { TYPVAL_ENCODE_CONV_NIL(); break; } case kMPBoolean: { if (val_di->di_tv.v_type != VAR_NUMBER) { goto _convert_one_value_regular_dict; } TYPVAL_ENCODE_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 _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) { TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(number); } else { TYPVAL_ENCODE_CONV_NUMBER(-number); } break; } case kMPFloat: { if (val_di->di_tv.v_type != VAR_FLOAT) { goto _convert_one_value_regular_dict; } TYPVAL_ENCODE_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 _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; } if (is_string) { TYPVAL_ENCODE_CONV_STR_STRING(buf, len); } else { TYPVAL_ENCODE_CONV_STRING(buf, len); } xfree(buf); break; } case kMPArray: { if (val_di->di_tv.v_type != VAR_LIST) { goto _convert_one_value_regular_dict; } _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, lv_copyID, copyID, kMPConvList); TYPVAL_ENCODE_CONV_LIST_START(val_di->di_tv.vval.v_list->lv_len); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, .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 _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) { TYPVAL_ENCODE_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 _convert_one_value_regular_dict; } } _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID, kMPConvPairs); TYPVAL_ENCODE_CONV_DICT_START(val_list->lv_len); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, .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 _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 _convert_one_value_regular_dict; } TYPVAL_ENCODE_CONV_EXT_STRING(buf, len, type); xfree(buf); break; } } break; } _convert_one_value_regular_dict: _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, copyID, kMPConvDict); TYPVAL_ENCODE_CONV_DICT_START(tv->vval.v_dict->dv_hashtab.ht_used); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, .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), STR(_TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()"); return FAIL; } } return OK; } 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_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 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 tv, const char *const objname) { const int copyID = get_copyID(); MPConvStack mpstack; _mp_init(mpstack); if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, tv, copyID, objname) == FAIL) { goto encode_vim_to__error_ret; } while (_mp_size(mpstack)) { MPConvStackVal *cur_mpsv = &_mp_last(mpstack); typval_T *cur_tv = NULL; switch (cur_mpsv->type) { case kMPConvDict: { if (!cur_mpsv->data.d.todo) { (void)_mp_pop(mpstack); cur_mpsv->data.d.dict->dv_copyID = copyID - 1; TYPVAL_ENCODE_CONV_DICT_END(); continue; } else if (cur_mpsv->data.d.todo != cur_mpsv->data.d.dict->dv_hashtab.ht_used) { TYPVAL_ENCODE_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++; TYPVAL_ENCODE_CONV_STR_STRING(&di->di_key[0], strlen((char *)&di->di_key[0])); TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(); cur_tv = &di->di_tv; break; } case kMPConvList: { if (cur_mpsv->data.l.li == NULL) { (void)_mp_pop(mpstack); cur_mpsv->data.l.list->lv_copyID = copyID - 1; TYPVAL_ENCODE_CONV_LIST_END(); continue; } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { TYPVAL_ENCODE_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)_mp_pop(mpstack); cur_mpsv->data.l.list->lv_copyID = copyID - 1; TYPVAL_ENCODE_CONV_DICT_END(); continue; } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(); } const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list; TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK( encode_vim_to__error_ret, kv_pair->lv_first->li_tv); if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, &kv_pair->lv_first->li_tv, copyID, objname) == FAIL) { goto encode_vim_to__error_ret; } TYPVAL_ENCODE_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; } case kMPConvPartial: { partial_T *const pt = cur_mpsv->data.p.pt; switch (cur_mpsv->data.p.stage) { case kMPConvPartialArgs: { TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(pt == NULL ? 0 : pt->pt_argc); cur_mpsv->data.p.stage = kMPConvPartialSelf; if (pt != NULL && pt->pt_argc > 0) { TYPVAL_ENCODE_CONV_LIST_START(pt->pt_argc); _mp_push(mpstack, ((MPConvStackVal) { .type = kMPConvPartialList, .tv = tv, .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(dict->dv_hashtab.ht_used); TYPVAL_ENCODE_CONV_DICT_START(dict->dv_hashtab.ht_used); _mp_push(mpstack, ((MPConvStackVal) { .type = kMPConvDict, .tv = tv, .data = { .d = { .dict = dict, .hi = dict->dv_hashtab.ht_array, .todo = dict->dv_hashtab.ht_used, }, }, })); } else { TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(-1); } break; } case kMPConvPartialEnd: { TYPVAL_ENCODE_CONV_FUNC_END(); (void)_mp_pop(mpstack); break; } } continue; } case kMPConvPartialList: { if (!cur_mpsv->data.a.todo) { (void)_mp_pop(mpstack); TYPVAL_ENCODE_CONV_LIST_END(); continue; } else if (cur_mpsv->data.a.argv != cur_mpsv->data.a.arg) { TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(); } cur_tv = cur_mpsv->data.a.arg++; cur_mpsv->data.a.todo--; break; } } assert(cur_tv != NULL); if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, cur_tv, copyID, objname) == FAIL) { goto encode_vim_to__error_ret; } } _mp_destroy(mpstack); return OK; encode_vim_to__error_ret: _mp_destroy(mpstack); return FAIL; } #endif // NVIM_EVAL_TYPVAL_ENCODE_C_H