text: /// @file eval/typval_convert.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_PARTIAL
  /// @brief Macros used to convert a partial
  ///
  /// @param  pt Partial name.
  
  /// @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.
  #ifndef NVIM_EVAL_TYPVAL_ENCODE_H
  #define NVIM_EVAL_TYPVAL_ENCODE_H
  
  #include <stddef.h>
  #include <inttypes.h>
  #include <assert.h>
  
  #include "nvim/lib/kvec.h"
  #include "nvim/eval_defs.h"
  #include "nvim/eval/encode.h"
  #include "nvim/func_attr.h"
  
  /// Type of the stack entry
  typedef enum {
    kMPConvDict,   ///< Convert dict_T *dictionary.
    kMPConvList,   ///< Convert list_T *list.
    kMPConvPairs,  ///< Convert mapping represented as a list_T* of pairs.
  } MPConvStackValType;
  
  /// Structure representing current VimL to messagepack conversion state
  typedef struct {
    MPConvStackValType type;  ///< Type of the stack entry.
    typval_T *tv;  ///< Currently converted typval_T.
    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_withinit_t(MPConvStackVal, 8) MPConvStack;
  
  // Defines for MPConvStack
  #define _mp_size kv_size
  #define _mp_init kvi_init
  #define _mp_destroy kvi_destroy
  #define _mp_push kvi_push
  #define _mp_pop kv_pop
  #define _mp_last kv_last
  
  /// 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.
  /// @param  conv_type  Type of the conversion, @see MPConvStackValType.
  #define _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val, copyID_attr, conv_type) \
      do { \
        if ((val)->copyID_attr == copyID) { \
          TYPVAL_ENCODE_CONV_RECURSE((val), conv_type); \
          return OK; \
        } \
        (val)->copyID_attr = copyID; \
      } while (0)
  
  /// Length of the string stored in typval_T
  ///
  /// @param[in]  tv  String for which to compute length for. Must be typval_T
  ///                 with VAR_STRING.
  ///
  /// @return Length of the string stored in typval_T, including 0 for NULL
  ///         string.
  static inline size_t tv_strlen(const typval_T *const tv)
    FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
    FUNC_ATTR_NONNULL_ALL
  {
    assert(tv->v_type == VAR_STRING);
    return (tv->vval.v_string == NULL
            ? 0
            : strlen((char *) tv->vval.v_string));
  }
  
  /// Define functions to convert a VimL value:
  ///   `{name}_convert_one_value(...)`
  ///   `encode_vim_to_{name}(...)`
  ///
  /// @param  scope  Scope of the main function: either nothing or `static`.
  /// @param  name  Name of the target converter.
  /// @param  firstargtype  Type of the first argument. It will be used to return
  ///                       the results.
  /// @param  firstargname  Name of the first argument.
  #define TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(scope, name, firstargtype, \
                                              firstargname) \
  /* Returns OK or FAIL */ \
  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: { \
        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(tv->vval.v_string); \
        break; \
      } \
      case VAR_PARTIAL: { \
        TYPVAL_ENCODE_CONV_PARTIAL(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, \
                                            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 name##_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 name##_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 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) { \
                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 name##_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 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) { \
                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 name##_convert_one_value_regular_dict; \
              } \
              _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, \
                                                  lv_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 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) { \
                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 name##_convert_one_value_regular_dict; \
                } \
              } \
              _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val_list, lv_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 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; \
              } \
              TYPVAL_ENCODE_CONV_EXT_STRING(buf, len, type); \
              xfree(buf); \
              break; \
            } \
          } \
          break; \
        } \
  name##_convert_one_value_regular_dict: \
        _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_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), #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; \
    _mp_init(mpstack); \
    if (name##_convert_one_value(firstargname, &mpstack, tv, copyID, objname) \
        == FAIL) { \
      goto encode_vim_to_##name##_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_##name##_error_ret, kv_pair->lv_first->li_tv); \
          if (name##_convert_one_value(firstargname, &mpstack, \
                                       &kv_pair->lv_first->li_tv, copyID, \
                                       objname) == FAIL) { \
            goto encode_vim_to_##name##_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; \
        } \
      } \
      assert(cur_tv != NULL); \
      if (name##_convert_one_value(firstargname, &mpstack, cur_tv, copyID, \
                                   objname) == FAIL) { \
        goto encode_vim_to_##name##_error_ret; \
      } \
    } \
    _mp_destroy(mpstack); \
    return OK; \
  encode_vim_to_##name##_error_ret: \
    _mp_destroy(mpstack); \
    return FAIL; \
  }
  
  #endif  // NVIM_EVAL_TYPVAL_ENCODE_H
  