diff options
author | ZyX <kp-pav@yandex.ru> | 2016-01-30 22:25:21 +0300 |
---|---|---|
committer | ZyX <kp-pav@yandex.ru> | 2016-04-18 02:44:03 +0300 |
commit | 68e58444b48fc34c9a7c262883750778fbd935d7 (patch) | |
tree | e70c3cbc8ae375c34498c30620571b1deb5e3099 /src | |
parent | c3efad5398da01fcdb0fd40822bedff4957c7797 (diff) | |
download | rneovim-68e58444b48fc34c9a7c262883750778fbd935d7.tar.gz rneovim-68e58444b48fc34c9a7c262883750778fbd935d7.tar.bz2 rneovim-68e58444b48fc34c9a7c262883750778fbd935d7.zip |
eval: Add jsonencode() function
Ref #3471
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/encode.c | 370 | ||||
-rw-r--r-- | src/nvim/eval.c | 8 | ||||
-rw-r--r-- | src/nvim/garray.c | 17 |
3 files changed, 356 insertions, 39 deletions
diff --git a/src/nvim/encode.c b/src/nvim/encode.c index da93ae98ad..84aaed53cb 100644 --- a/src/nvim/encode.c +++ b/src/nvim/encode.c @@ -7,16 +7,29 @@ // TODO(ZyX-I): Move this to src/nvim/viml or src/nvim/eval #include <msgpack.h> +#include <inttypes.h> #include "nvim/encode.h" +#include "nvim/buffer_defs.h" // vimconv_T #include "nvim/eval.h" #include "nvim/garray.h" +#include "nvim/mbyte.h" #include "nvim/message.h" +#include "nvim/charset.h" // vim_isprintc() #include "nvim/macros.h" #include "nvim/ascii.h" #include "nvim/vim.h" // For _() #include "nvim/lib/kvec.h" +#define ga_concat(a, b) ga_concat(a, (char_u *)b) +#define utf_ptr2char(b) utf_ptr2char((char_u *)b) +#define utf_ptr2len(b) ((size_t)utf_ptr2len((char_u *)b)) +#define utf_char2len(b) ((size_t)utf_char2len(b)) +#define string_convert(a, b, c) \ + ((char *)string_convert((vimconv_T *)a, (char_u *)b, c)) +#define convert_setup(vcp, from, to) \ + (convert_setup(vcp, (char_u *)from, (char_u *)to)) + /// Structure representing current VimL to messagepack conversion state typedef struct { enum { @@ -120,7 +133,7 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, char *const idx_msg = _("index %i"); for (size_t i = 0; i < kv_size(*mpstack); i++) { if (i != 0) { - ga_concat(&msg_ga, (char_u *) ", "); + ga_concat(&msg_ga, ", "); } MPConvStackVal v = kv_A(*mpstack, i); switch (v.type) { @@ -450,11 +463,11 @@ static int name##_convert_one_value(firstargtype firstargname, \ if (val_di->di_tv.v_type != VAR_LIST) { \ goto name##_convert_one_value_regular_dict; \ } \ - if (val_di->di_tv.vval.v_list == NULL) { \ + 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; \ } \ - list_T *const val_list = val_di->di_tv.vval.v_list; \ for (const listitem_T *li = val_list->lv_first; li != NULL; \ li = li->li_next) { \ if (li->li_tv.v_type != VAR_LIST \ @@ -463,7 +476,7 @@ static int name##_convert_one_value(firstargtype firstargname, \ } \ } \ CHECK_SELF_REFERENCE(val_list, lv_copyID, kMPConvPairs); \ - CONV_SPECIAL_MAP_START(val_list); \ + CONV_DICT_START(val_list->lv_len); \ kv_push(MPConvStackVal, *mpstack, ((MPConvStackVal) { \ .type = kMPConvPairs, \ .data = { \ @@ -502,7 +515,7 @@ static int name##_convert_one_value(firstargtype firstargname, \ } \ name##_convert_one_value_regular_dict: \ CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, kMPConvDict); \ - CONV_DICT_START(tv->vval.v_dict); \ + CONV_DICT_START(tv->vval.v_dict->dv_hashtab.ht_used); \ kv_push(MPConvStackVal, *mpstack, ((MPConvStackVal) { \ .type = kMPConvDict, \ .data = { \ @@ -543,11 +556,11 @@ scope int encode_vim_to_##name(firstargtype firstargname, typval_T *const tv, \ if (!cur_mpsv->data.d.todo) { \ (void) kv_pop(mpstack); \ cur_mpsv->data.d.dict->dv_copyID = copyID - 1; \ - CONV_DICT_END(cur_mpsv->data.d.dict); \ + CONV_DICT_END(); \ continue; \ } else if (cur_mpsv->data.d.todo \ != cur_mpsv->data.d.dict->dv_hashtab.ht_used) { \ - CONV_DICT_BETWEEN_ITEMS(cur_mpsv->data.d.dict); \ + CONV_DICT_BETWEEN_ITEMS(); \ } \ while (HASHITEM_EMPTY(cur_mpsv->data.d.hi)) { \ cur_mpsv->data.d.hi++; \ @@ -556,7 +569,7 @@ scope int encode_vim_to_##name(firstargtype firstargname, typval_T *const tv, \ 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_mpsv->data.d.dict); \ + CONV_DICT_AFTER_KEY(); \ cur_tv = &di->di_tv; \ break; \ } \ @@ -567,7 +580,7 @@ scope int encode_vim_to_##name(firstargtype firstargname, typval_T *const tv, \ 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_mpsv->data.l.list); \ + 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; \ @@ -577,14 +590,19 @@ scope int encode_vim_to_##name(firstargtype firstargname, typval_T *const tv, \ if (cur_mpsv->data.l.li == NULL) { \ (void) kv_pop(mpstack); \ cur_mpsv->data.l.list->lv_copyID = copyID - 1; \ + CONV_DICT_END(); \ continue; \ + } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { \ + CONV_DICT_BETWEEN_ITEMS(); \ } \ const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list; \ + CONV_SPECIAL_DICT_KEY_CHECK(kv_pair); \ if (name##_convert_one_value(firstargname, &mpstack, \ &kv_pair->lv_first->li_tv, copyID, \ objname) == FAIL) { \ goto encode_vim_to_##name##_error_ret; \ } \ + CONV_DICT_AFTER_KEY(); \ cur_tv = &kv_pair->lv_last->li_tv; \ cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; \ break; \ @@ -606,7 +624,7 @@ encode_vim_to_##name##_error_ret: \ do { \ const char *const buf_ = (const char *) buf; \ if (buf == NULL) { \ - ga_concat(gap, (char_u *) "''"); \ + ga_concat(gap, "''"); \ } else { \ const size_t len_ = (len); \ size_t num_quotes = 0; \ @@ -637,7 +655,7 @@ encode_vim_to_##name##_error_ret: \ do { \ char numbuf[NUMBUFLEN]; \ vim_snprintf(numbuf, NUMBUFLEN - 1, "%" PRId64, (int64_t) (num)); \ - ga_concat(gap, (char_u *) numbuf); \ + ga_concat(gap, numbuf); \ } while (0) #define CONV_FLOAT(flt) \ @@ -665,19 +683,19 @@ encode_vim_to_##name##_error_ret: \ #define CONV_FUNC(fun) \ do { \ - ga_concat(gap, (char_u *) "function("); \ + ga_concat(gap, "function("); \ CONV_STRING(fun, STRLEN(fun)); \ ga_append(gap, ')'); \ } while (0) #define CONV_EMPTY_LIST() \ - ga_concat(gap, (char_u *) "[]") + ga_concat(gap, "[]") #define CONV_LIST_START(lst) \ ga_append(gap, '[') #define CONV_EMPTY_DICT() \ - ga_concat(gap, (char_u *) "{}") + ga_concat(gap, "{}") #define CONV_SPECIAL_NIL() @@ -685,25 +703,25 @@ encode_vim_to_##name##_error_ret: \ #define CONV_UNSIGNED_NUMBER(num) -#define CONV_SPECIAL_MAP_START(lst) - -#define CONV_DICT_START(dct) \ +#define CONV_DICT_START(len) \ ga_append(gap, '{') -#define CONV_DICT_END(dct) \ +#define CONV_DICT_END() \ ga_append(gap, '}') -#define CONV_DICT_AFTER_KEY(dct) \ - ga_concat(gap, (char_u *) ": ") +#define CONV_DICT_AFTER_KEY() \ + ga_concat(gap, ": ") + +#define CONV_DICT_BETWEEN_ITEMS() \ + ga_concat(gap, ", ") -#define CONV_DICT_BETWEEN_ITEMS(dct) \ - ga_concat(gap, (char_u *) ", ") +#define CONV_SPECIAL_DICT_KEY_CHECK(kv_pair) #define CONV_LIST_END(lst) \ ga_append(gap, ']') -#define CONV_LIST_BETWEEN_ITEMS(lst) \ - CONV_DICT_BETWEEN_ITEMS(NULL) +#define CONV_LIST_BETWEEN_ITEMS() \ + CONV_DICT_BETWEEN_ITEMS() #define CONV_RECURSE(val, conv_type) \ do { \ @@ -731,7 +749,7 @@ encode_vim_to_##name##_error_ret: \ } \ } \ vim_snprintf(ebuf, NUMBUFLEN + 6, "{E724@%zu}", backref); \ - ga_concat(gap, (char_u *) &ebuf[0]); \ + ga_concat(gap, &ebuf[0]); \ return OK; \ } while (0) @@ -763,12 +781,272 @@ DEFINE_VIML_CONV_FUNCTIONS(static, string, garray_T *const, gap) } else { \ vim_snprintf(ebuf, NUMBUFLEN + 6, "[...@%zu]", backref); \ } \ - ga_concat(gap, (char_u *) &ebuf[0]); \ + 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_SPECIAL_NIL +#define CONV_SPECIAL_NIL() \ + ga_concat(gap, "null") + +#undef CONV_SPECIAL_BOOL +#define CONV_SPECIAL_BOOL(num) \ + ga_concat(gap, ((num)? "true": "false")) + +#undef CONV_UNSIGNED_NUMBER +#define CONV_UNSIGNED_NUMBER(num) \ + do { \ + char numbuf[NUMBUFLEN]; \ + vim_snprintf(numbuf, sizeof(numbuf), "%" PRIu64, (num)); \ + ga_concat(gap, numbuf); \ + } while (0) + +#undef CONV_FLOAT +#define CONV_FLOAT(flt) \ + do { \ + char numbuf[NUMBUFLEN]; \ + vim_snprintf(numbuf, NUMBUFLEN - 1, "%g", (flt)); \ + ga_concat(gap, numbuf); \ + } while (0) + +/// Last used p_enc value +/// +/// Generic pointer: it is not used as a string, only pointer comparisons are +/// performed. Must not be freed. +static const void *last_p_enc = NULL; + +/// Conversion setup for converting from last_p_enc to UTF-8 +static vimconv_T p_enc_conv = { + .vc_type = CONV_NONE, +}; + +/// Escape sequences used in JSON +static const char escapes[][3] = { + [BS] = "\\b", + [TAB] = "\\t", + [NL] = "\\n", + [CAR] = "\\r", + ['"'] = "\\\"", + ['\\'] = "\\\\", +}; + +static const char xdigits[] = "0123456789ABCDEF"; + +/// Convert given string to JSON string +/// +/// @param[out] gap Garray where result will be saved. +/// @param[in] buf Converted string. +/// @param[in] len Converted string length. +/// +/// @return OK in case of success, FAIL otherwise. +static inline int convert_to_json_string(garray_T *const gap, + const char *const buf, + const size_t len) + FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_ALWAYS_INLINE +{ + const char *buf_ = buf; + if (buf_ == NULL) { + ga_concat(gap, "\"\""); + } else { + size_t len_ = len; + char *tofree = NULL; + if (last_p_enc != (const void *) p_enc) { + convert_setup(&p_enc_conv, p_enc, "utf-8"); + p_enc_conv.vc_fail = true; + last_p_enc = p_enc; + } + if (p_enc_conv.vc_type != CONV_NONE) { + tofree = string_convert(&p_enc_conv, buf_, &len_); + if (tofree == NULL) { + EMSG2(_("E474: Failed to convert string \"%s\" to UTF-8"), buf_); + return FAIL; + } + buf_ = tofree; + } + size_t str_len = 0; + for (size_t i = 0; i < len_;) { + const int ch = utf_ptr2char(buf + i); + const size_t shift = (ch == 0? 1: utf_ptr2len(buf + i)); + assert(shift > 0); + i += shift; + switch (ch) { + case BS: + case TAB: + case NL: + case FF: + case CAR: + case '"': + case '\\': { + str_len += 2; + break; + } + default: { + if (ch > 0x7F && shift == 1) { + EMSG2(_("E474: String \"%s\" contains byte that does not start any " + "UTF-8 character"), buf_); + return FAIL; + } else if ((0xD800 <= ch && ch <= 0xDB7F) + || (0xDC00 <= ch && ch <= 0xDFFF)) { + EMSG2(_("E474: UTF-8 string contains code point which belongs " + "to surrogate pairs"), buf_); + return FAIL; + } else if (vim_isprintc(ch)) { + str_len += shift; + } else { + str_len += ((sizeof("\\u1234") - 1) * (1 + (ch > 0xFFFF))); + } + break; + } + } + } + ga_append(gap, '"'); + ga_grow(gap, (int) str_len); + for (size_t i = 0; i < len_;) { + const int ch = utf_ptr2char(buf + i); + const size_t shift = (ch == 0? 1: utf_char2len(ch)); + assert(shift > 0); + // Is false on invalid unicode, but this should already be handled. + assert(ch == 0 || shift == utf_ptr2len(buf + i)); + switch (ch) { + case BS: + case TAB: + case NL: + case FF: + case CAR: + case '"': + case '\\': { + ga_concat_len(gap, escapes[ch], 2); + break; + } + default: { + if (vim_isprintc(ch)) { + ga_concat_len(gap, buf + i, shift); + } else if (ch <= 0xFFFF) { + ga_concat_len(gap, ((const char []) { + '\\', 'u', + xdigits[(ch >> (4 * 3)) & 0xF], + xdigits[(ch >> (4 * 2)) & 0xF], + xdigits[(ch >> (4 * 1)) & 0xF], + xdigits[(ch >> (4 * 0)) & 0xF], + }), sizeof("\\u1234") - 1); + } else { + uint32_t tmp = (uint32_t) ch - 0x010000; + uint16_t hi = 0xD800 + ((tmp >> 10) & 0x03FF); + uint16_t lo = 0xDC00 + ((tmp >> 0) & 0x03FF); + ga_concat_len(gap, ((const char []) { + '\\', 'u', + xdigits[(hi >> (4 * 3)) & 0xF], + xdigits[(hi >> (4 * 2)) & 0xF], + xdigits[(hi >> (4 * 1)) & 0xF], + xdigits[(hi >> (4 * 0)) & 0xF], + '\\', 'u', + xdigits[(lo >> (4 * 3)) & 0xF], + xdigits[(lo >> (4 * 2)) & 0xF], + xdigits[(lo >> (4 * 1)) & 0xF], + xdigits[(lo >> (4 * 0)) & 0xF], + }), (sizeof("\\u1234") - 1) * 2); + } + break; + } + } + i += shift; + } + ga_append(gap, '"'); + xfree(tofree); + } + return OK; +} + +#undef CONV_STRING +#define CONV_STRING(buf, len) \ + do { \ + if (convert_to_json_string(gap, (const char *) (buf), (len)) != OK) { \ + return FAIL; \ + } \ + } while (0) + +#undef CONV_EXT_STRING +#define CONV_EXT_STRING(buf, len, type) \ + do { \ + xfree(buf); \ + EMSG(_("E474: Unable to convert EXT string to JSON")); \ + return FAIL; \ + } while (0) + +#undef CONV_FUNC +#define CONV_FUNC(fun) \ + return conv_error(_("E474: Error while dumping %s, %s: " \ + "attempt to dump function reference"), \ + mpstack, objname) + +/// Check whether given key can be used in jsonencode() +/// +/// @param[in] tv Key to check. +static inline bool check_json_key(const typval_T *const tv) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE + FUNC_ATTR_ALWAYS_INLINE +{ + if (tv->v_type == VAR_STRING) { + return true; + } + if (tv->v_type != VAR_DICT) { + return false; + } + const dict_T *const spdict = tv->vval.v_dict; + if (spdict->dv_hashtab.ht_used != 2) { + return false; + } + const dictitem_T *type_di; + const dictitem_T *val_di; + if ((type_di = dict_find((dict_T *) spdict, (char_u *) "_TYPE", -1)) == NULL + || type_di->di_tv.v_type != VAR_LIST + || (type_di->di_tv.vval.v_list != eval_msgpack_type_lists[kMPString] + && type_di->di_tv.vval.v_list != eval_msgpack_type_lists[kMPBinary]) + || (val_di = dict_find((dict_T *) spdict, (char_u *) "_VAL", -1)) == NULL + || val_di->di_tv.v_type != VAR_LIST) { + return false; + } + if (val_di->di_tv.vval.v_list == NULL) { + return true; + } + for (const listitem_T *li = val_di->di_tv.vval.v_list->lv_first; + li != NULL; li = li->li_next) { + if (li->li_tv.v_type != VAR_STRING) { + return false; + } + } + return true; +} + +#undef CONV_SPECIAL_DICT_KEY_CHECK +#define CONV_SPECIAL_DICT_KEY_CHECK(kv_pair) \ + do { \ + if (!check_json_key(&kv_pair->lv_first->li_tv)) { \ + EMSG(_("E474: Invalid key in special dictionary")); \ + return FAIL; \ + } \ + } while (0) + +DEFINE_VIML_CONV_FUNCTIONS(static, json, garray_T *const, gap) + #undef CONV_STRING #undef CONV_STR_STRING #undef CONV_EXT_STRING @@ -781,11 +1059,11 @@ DEFINE_VIML_CONV_FUNCTIONS(, echo, garray_T *const, gap) #undef CONV_SPECIAL_NIL #undef CONV_SPECIAL_BOOL #undef CONV_UNSIGNED_NUMBER -#undef CONV_SPECIAL_MAP_START #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 @@ -838,6 +1116,27 @@ char *encode_tv2echo(typval_T *tv, size_t *len) 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) { \ @@ -906,21 +1205,20 @@ char *encode_tv2echo(typval_T *tv, size_t *len) #define CONV_UNSIGNED_NUMBER(num) \ msgpack_pack_uint64(packer, (num)) -#define CONV_SPECIAL_MAP_START(lst) \ - msgpack_pack_map(packer, (size_t) (lst)->lv_len) +#define CONV_DICT_START(len) \ + msgpack_pack_map(packer, (size_t) (len)) -#define CONV_DICT_START(dct) \ - msgpack_pack_map(packer, (dct)->dv_hashtab.ht_used) +#define CONV_DICT_END() -#define CONV_DICT_END(dct) +#define CONV_DICT_AFTER_KEY() -#define CONV_DICT_AFTER_KEY(dct) +#define CONV_DICT_BETWEEN_ITEMS() -#define CONV_DICT_BETWEEN_ITEMS(dct) +#define CONV_SPECIAL_DICT_KEY_CHECK(kv_pair) #define CONV_LIST_END(lst) -#define CONV_LIST_BETWEEN_ITEMS(lst) +#define CONV_LIST_BETWEEN_ITEMS() #define CONV_RECURSE(val, conv_type) \ return conv_error(_("E952: Unable to dump %s: " \ @@ -943,11 +1241,11 @@ DEFINE_VIML_CONV_FUNCTIONS(, msgpack, msgpack_packer *const, packer) #undef CONV_SPECIAL_NIL #undef CONV_SPECIAL_BOOL #undef CONV_UNSIGNED_NUMBER -#undef CONV_SPECIAL_MAP_START #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 diff --git a/src/nvim/eval.c b/src/nvim/eval.c index b633bfb1b1..9e0698b104 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6704,6 +6704,7 @@ static struct fst { { "jobstop", 1, 1, f_jobstop }, { "jobwait", 1, 2, f_jobwait }, { "join", 1, 2, f_join }, + { "jsonencode", 1, 1, f_jsonencode }, { "keys", 1, 1, f_keys }, { "last_buffer_nr", 0, 0, f_last_buffer_nr }, // obsolete { "len", 1, 1, f_len }, @@ -11494,6 +11495,13 @@ static void f_join(typval_T *argvars, typval_T *rettv) rettv->vval.v_string = NULL; } +/// jsonencode() function +static void f_jsonencode(typval_T *argvars, typval_T *rettv) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *) encode_tv2json(&argvars[0], NULL); +} + /* * "keys()" function */ diff --git a/src/nvim/garray.c b/src/nvim/garray.c index e6cbd9332b..47a370b18c 100644 --- a/src/nvim/garray.c +++ b/src/nvim/garray.c @@ -188,11 +188,22 @@ void ga_concat(garray_T *gap, const char_u *restrict s) return; } - int len = (int)strlen((char *) s); + ga_concat_len(gap, (const char *restrict) s, strlen((char *) s)); +} + +/// Concatenate a string to a growarray which contains characters +/// +/// @param[out] gap Growarray to modify. +/// @param[in] s String to concatenate. +/// @param[in] len String length. +void ga_concat_len(garray_T *const gap, const char *restrict s, + const size_t len) + FUNC_ATTR_NONNULL_ALL +{ if (len) { - ga_grow(gap, len); + ga_grow(gap, (int) len); char *data = gap->ga_data; - memcpy(data + gap->ga_len, s, (size_t)len); + memcpy(data + gap->ga_len, s, len); gap->ga_len += len; } } |