diff options
Diffstat (limited to 'src/nvim/eval')
-rw-r--r-- | src/nvim/eval/decode.c | 42 | ||||
-rw-r--r-- | src/nvim/eval/encode.c | 65 | ||||
-rw-r--r-- | src/nvim/eval/executor.c | 14 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 414 | ||||
-rw-r--r-- | src/nvim/eval/typval.c | 173 | ||||
-rw-r--r-- | src/nvim/eval/typval.h | 69 | ||||
-rw-r--r-- | src/nvim/eval/typval_encode.c.h | 12 | ||||
-rw-r--r-- | src/nvim/eval/userfunc.c | 1 |
8 files changed, 691 insertions, 99 deletions
diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index 89e1f04bfd..5c03f55621 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -246,14 +246,15 @@ list_T *decode_create_map_special_dict(typval_T *const ret_tv, /// Convert char* string to typval_T /// /// Depending on whether string has (no) NUL bytes, it may use a special -/// dictionary or decode string to VAR_STRING. +/// dictionary, VAR_BLOB, or decode string to VAR_STRING. /// /// @param[in] s String to decode. /// @param[in] len String length. /// @param[in] hasnul Whether string has NUL byte, not or it was not yet /// determined. -/// @param[in] binary If true, save special string type as kMPBinary, -/// otherwise kMPString. +/// @param[in] binary Determines decode type if string has NUL bytes. +/// If true convert string to VAR_BLOB, otherwise to the +/// kMPString special type. /// @param[in] s_allocated If true, then `s` was allocated and can be saved in /// a returned structure. If it is not saved there, it /// will be freed. @@ -269,21 +270,28 @@ typval_T decode_string(const char *const s, const size_t len, ? ((s != NULL) && (memchr(s, NUL, len) != NULL)) : (bool)hasnul); if (really_hasnul) { - list_T *const list = tv_list_alloc(kListLenMayKnow); - tv_list_ref(list); typval_T tv; - create_special_dict(&tv, binary ? kMPBinary : kMPString, ((typval_T) { - .v_type = VAR_LIST, - .v_lock = VAR_UNLOCKED, - .vval = { .v_list = list }, - })); - const int elw_ret = encode_list_write((void *)list, s, len); - if (s_allocated) { - xfree((void *)s); - } - if (elw_ret == -1) { - tv_clear(&tv); - return (typval_T) { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED }; + tv.v_lock = VAR_UNLOCKED; + if (binary) { + tv_blob_alloc_ret(&tv); + ga_concat_len(&tv.vval.v_blob->bv_ga, s, len); + } else { + list_T *const list = tv_list_alloc(kListLenMayKnow); + tv_list_ref(list); + create_special_dict(&tv, kMPString, + ((typval_T){ + .v_type = VAR_LIST, + .v_lock = VAR_UNLOCKED, + .vval = { .v_list = list }, + })); + const int elw_ret = encode_list_write((void *)list, s, len); + if (s_allocated) { + xfree((void *)s); + } + if (elw_ret == -1) { + tv_clear(&tv); + return (typval_T) { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED }; + } } return tv; } else { diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index a4d7af7971..b5e50e7ef5 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -47,6 +47,14 @@ const char *const encode_special_var_names[] = { # include "eval/encode.c.generated.h" #endif +/// Msgpack callback for writing to a Blob +int encode_blob_write(void *const data, const char *const buf, const size_t len) + FUNC_ATTR_NONNULL_ARG(1) +{ + ga_concat_len(&((blob_T *)data)->bv_ga, buf, len); + return (int)len; +} + /// Msgpack callback for writing to readfile()-style list int encode_list_write(void *const data, const char *const buf, const size_t len) FUNC_ATTR_NONNULL_ARG(1) @@ -319,6 +327,30 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, #define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type) +#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \ + do { \ + const blob_T *const blob_ = (blob); \ + const int len_ = (len); \ + if (len_ == 0) { \ + ga_concat(gap, "0z"); \ + } else { \ + /* Allocate space for "0z", the two hex chars per byte, and a */ \ + /* "." separator after every eight hex chars. */ \ + /* Example: "0z00112233.44556677.8899" */ \ + ga_grow(gap, 2 + 2 * len_ + (len_ - 1) / 4); \ + ga_concat(gap, "0z"); \ + char numbuf[NUMBUFLEN]; \ + for (int i_ = 0; i_ < len_; i_++) { \ + if (i_ > 0 && (i_ & 3) == 0) { \ + ga_append(gap, '.'); \ + } \ + vim_snprintf((char *)numbuf, ARRAY_SIZE(numbuf), "%02X", \ + (int)tv_blob_get(blob_, i_)); \ + ga_concat(gap, numbuf); \ + } \ + } \ + } while (0) + #define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ do { \ char numbuf[NUMBUFLEN]; \ @@ -705,6 +737,28 @@ static inline int convert_to_json_string(garray_T *const gap, return FAIL; \ } while (0) +#undef TYPVAL_ENCODE_CONV_BLOB +#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \ + do { \ + const blob_T *const blob_ = (blob); \ + const int len_ = (len); \ + if (len_ == 0) { \ + ga_concat(gap, "[]"); \ + } else { \ + ga_append(gap, '['); \ + char numbuf[NUMBUFLEN]; \ + for (int i_ = 0; i_ < len_; i_++) { \ + if (i_ > 0) { \ + ga_concat(gap, ", "); \ + } \ + vim_snprintf((char *)numbuf, ARRAY_SIZE(numbuf), "%d", \ + (int)tv_blob_get(blob_, i_)); \ + ga_concat(gap, numbuf); \ + } \ + ga_append(gap, ']'); \ + } \ + } while (0) + #undef TYPVAL_ENCODE_CONV_FUNC_START #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ return conv_error(_("E474: Error while dumping %s, %s: " \ @@ -770,6 +824,7 @@ bool encode_check_json_key(const typval_T *const tv) #undef TYPVAL_ENCODE_CONV_STRING #undef TYPVAL_ENCODE_CONV_STR_STRING #undef TYPVAL_ENCODE_CONV_EXT_STRING +#undef TYPVAL_ENCODE_CONV_BLOB #undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_FLOAT #undef TYPVAL_ENCODE_CONV_FUNC_START @@ -904,6 +959,15 @@ char *encode_tv2json(typval_T *tv, size_t *len) } \ } while (0) +#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \ + do { \ + const size_t len_ = (size_t)(len); \ + msgpack_pack_bin(packer, len_); \ + if (len_ > 0) { \ + msgpack_pack_bin_body(packer, (blob)->bv_ga.ga_data, len_); \ + } \ + } while (0) + #define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ msgpack_pack_int64(packer, (int64_t)(num)) @@ -982,6 +1046,7 @@ char *encode_tv2json(typval_T *tv, size_t *len) #undef TYPVAL_ENCODE_CONV_STRING #undef TYPVAL_ENCODE_CONV_STR_STRING #undef TYPVAL_ENCODE_CONV_EXT_STRING +#undef TYPVAL_ENCODE_CONV_BLOB #undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_FLOAT #undef TYPVAL_ENCODE_CONV_FUNC_START diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index 8ac2c3b8eb..5ced2a0535 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -38,6 +38,20 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, case VAR_SPECIAL: { break; } + case VAR_BLOB: { + if (*op != '+' || tv2->v_type != VAR_BLOB) { + break; + } + // Blob += Blob + if (tv1->vval.v_blob != NULL && tv2->vval.v_blob != NULL) { + blob_T *const b1 = tv1->vval.v_blob; + blob_T *const b2 = tv2->vval.v_blob; + for (int i = 0; i < tv_blob_len(b2); i++) { + ga_append(&b1->bv_ga, (char)tv_blob_get(b2, i)); + } + } + return OK; + } case VAR_LIST: { if (*op != '+' || tv2->v_type != VAR_LIST) { break; diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 801b0f9d1c..bcbd2266d3 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -96,6 +96,7 @@ PRAGMA_DIAG_POP static char *e_listarg = N_("E686: Argument of %s must be a List"); +static char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob"); static char *e_invalwindow = N_("E957: Invalid window number"); /// Dummy va_list for passing to vim_snprintf @@ -321,8 +322,20 @@ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_append_tv(l, &argvars[1]); tv_copy(&argvars[0], rettv); } + } else if (argvars[0].v_type == VAR_BLOB) { + blob_T *const b = argvars[0].vval.v_blob; + if (b != NULL + && !var_check_lock(b->bv_lock, N_("add() argument"), TV_TRANSLATE)) { + bool error = false; + const varnumber_T n = tv_get_number_chk(&argvars[1], &error); + + if (!error) { + ga_append(&b->bv_ga, (int)n); + tv_copy(&argvars[0], rettv); + } + } } else { - EMSG(_(e_listreq)); + EMSG(_(e_listblobreq)); } } @@ -959,7 +972,17 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) } ptrdiff_t input_len = 0; - char *input = save_tv_as_string(&argvars[1], &input_len, false); + char *input = NULL; + if (argvars[1].v_type == VAR_BLOB) { + const blob_T *const b = argvars[1].vval.v_blob; + input_len = tv_blob_len(b); + if (input_len > 0) { + input = xmemdup(b->bv_ga.ga_data, input_len); + } + } else { + input = save_tv_as_string(&argvars[1], &input_len, false); + } + if (!input) { // Either the error has been handled by save_tv_as_string(), // or there is no input to send. @@ -1874,6 +1897,10 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) n = argvars[0].vval.v_special == kSpecialVarNull; break; } + case VAR_BLOB: { + n = (tv_blob_len(argvars[0].vval.v_blob) == 0); + break; + } case VAR_UNKNOWN: { internal_error("f_empty(UNKNOWN)"); break; @@ -2791,7 +2818,23 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) typval_T *tv = NULL; bool what_is_dict = false; - if (argvars[0].v_type == VAR_LIST) { + if (argvars[0].v_type == VAR_BLOB) { + bool error = false; + int idx = tv_get_number_chk(&argvars[1], &error); + + if (!error) { + rettv->v_type = VAR_NUMBER; + if (idx < 0) { + idx = tv_blob_len(argvars[0].vval.v_blob) + idx; + } + if (idx < 0 || idx >= tv_blob_len(argvars[0].vval.v_blob)) { + rettv->vval.v_number = -1; + } else { + rettv->vval.v_number = tv_blob_get(argvars[0].vval.v_blob, idx); + tv = rettv; + } + } + } else if (argvars[0].v_type == VAR_LIST) { if ((l = argvars[0].vval.v_list) != NULL) { bool error = false; @@ -2852,7 +2895,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } else { - EMSG2(_(e_listdictarg), "get()"); + EMSG2(_(e_listdictblobarg), "get()"); } if (tv == NULL) { @@ -4403,6 +4446,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "user_commands", "vartabs", "vertsplit", + "vimscript-1", "virtualedit", "visual", "visualextra", @@ -4790,8 +4834,38 @@ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool ic = false; rettv->vval.v_number = -1; - if (argvars[0].v_type != VAR_LIST) { - EMSG(_(e_listreq)); + if (argvars[0].v_type == VAR_BLOB) { + bool error = false; + int start = 0; + + if (argvars[2].v_type != VAR_UNKNOWN) { + start = tv_get_number_chk(&argvars[2], &error); + if (error) { + return; + } + } + blob_T *const b = argvars[0].vval.v_blob; + if (b == NULL) { + return; + } + if (start < 0) { + start = tv_blob_len(b) + start; + if (start < 0) { + start = 0; + } + } + for (idx = start; idx < tv_blob_len(b); idx++) { + typval_T tv; + tv.v_type = VAR_NUMBER; + tv.vval.v_number = tv_blob_get(b, idx); + if (tv_equal(&tv, &argvars[1], ic, false)) { + rettv->vval.v_number = idx; + return; + } + } + return; + } else if (argvars[0].v_type != VAR_LIST) { + EMSG(_(e_listblobreq)); return; } list_T *const l = argvars[0].vval.v_list; @@ -4920,8 +4994,46 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *l; bool error = false; - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "insert()"); + if (argvars[0].v_type == VAR_BLOB) { + blob_T *const b = argvars[0].vval.v_blob; + + if (b == NULL + || var_check_lock(b->bv_lock, N_("insert() argument"), + TV_TRANSLATE)) { + return; + } + + long before = 0; + const int len = tv_blob_len(b); + + if (argvars[2].v_type != VAR_UNKNOWN) { + before = (long)tv_get_number_chk(&argvars[2], &error); + if (error) { + return; // type error; errmsg already given + } + if (before < 0 || before > len) { + EMSG2(_(e_invarg2), tv_get_string(&argvars[2])); + return; + } + } + const int val = tv_get_number_chk(&argvars[1], &error); + if (error) { + return; + } + if (val < 0 || val > 255) { + EMSG2(_(e_invarg2), tv_get_string(&argvars[1])); + return; + } + + ga_grow(&b->bv_ga, 1); + char_u *const p = (char_u *)b->bv_ga.ga_data; + memmove(p + before + 1, p + before, (size_t)len - before); + *(p + before) = val; + b->bv_ga.ga_len++; + + tv_copy(&argvars[0], rettv); + } else if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listblobarg), "insert()"); } else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), N_("insert() argument"), TV_TRANSLATE)) { long before = 0; @@ -5581,6 +5693,10 @@ static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_get_string(&argvars[0])); break; } + case VAR_BLOB: { + rettv->vval.v_number = tv_blob_len(argvars[0].vval.v_blob); + break; + } case VAR_LIST: { rettv->vval.v_number = tv_list_len(argvars[0].vval.v_list); break; @@ -6391,9 +6507,16 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_listarg), "msgpackdump()"); return; } - list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); list_T *const list = argvars[0].vval.v_list; - msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write); + msgpack_packer *packer; + if (argvars[1].v_type != VAR_UNKNOWN + && strequal(tv_get_string(&argvars[1]), "B")) { + tv_blob_alloc_ret(rettv); + packer = msgpack_packer_new(rettv->vval.v_blob, &encode_blob_write); + } else { + packer = msgpack_packer_new(tv_list_alloc_ret(rettv, kListLenMayKnow), + &encode_list_write); + } const char *const msg = _("msgpackdump() argument, index %i"); // Assume that translation will not take more then 4 times more space char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN]; @@ -6401,23 +6524,50 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr) TV_LIST_ITER(list, li, { vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx); idx++; - if (encode_vim_to_msgpack(lpacker, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) { + if (encode_vim_to_msgpack(packer, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) { break; } }); - msgpack_packer_free(lpacker); + msgpack_packer_free(packer); } -/// "msgpackparse" function -static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static int msgpackparse_convert_item(const msgpack_object data, + const msgpack_unpack_return result, + list_T *const ret_list, + const bool fail_if_incomplete) FUNC_ATTR_NONNULL_ALL { - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "msgpackparse()"); - return; + switch (result) { + case MSGPACK_UNPACK_PARSE_ERROR: + EMSG2(_(e_invarg2), "Failed to parse msgpack string"); + return FAIL; + case MSGPACK_UNPACK_NOMEM_ERROR: + EMSG(_(e_outofmem)); + return FAIL; + case MSGPACK_UNPACK_CONTINUE: + if (fail_if_incomplete) { + EMSG2(_(e_invarg2), "Incomplete msgpack string"); + return FAIL; + } + return NOTDONE; + case MSGPACK_UNPACK_SUCCESS: { + typval_T tv = { .v_type = VAR_UNKNOWN }; + if (msgpack_to_vim(data, &tv) == FAIL) { + EMSG2(_(e_invarg2), "Failed to convert msgpack string"); + return FAIL; + } + tv_list_append_owned_tv(ret_list, tv); + return OK; + } + default: + abort(); } - list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); - const list_T *const list = argvars[0].vval.v_list; +} + +static void msgpackparse_unpack_list(const list_T *const list, + list_T *const ret_list) + FUNC_ATTR_NONNULL_ARG(2) +{ if (tv_list_len(list) == 0) { return; } @@ -6436,43 +6586,28 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) do { if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) { EMSG(_(e_outofmem)); - goto f_msgpackparse_exit; + goto end; } size_t read_bytes; const int rlret = encode_read_from_list( &lrstate, msgpack_unpacker_buffer(unpacker), IOSIZE, &read_bytes); if (rlret == FAIL) { EMSG2(_(e_invarg2), "List item is not a string"); - goto f_msgpackparse_exit; + goto end; } msgpack_unpacker_buffer_consumed(unpacker, read_bytes); if (read_bytes == 0) { break; } while (unpacker->off < unpacker->used) { - const msgpack_unpack_return result = msgpack_unpacker_next(unpacker, - &unpacked); - if (result == MSGPACK_UNPACK_PARSE_ERROR) { - EMSG2(_(e_invarg2), "Failed to parse msgpack string"); - goto f_msgpackparse_exit; - } - if (result == MSGPACK_UNPACK_NOMEM_ERROR) { - EMSG(_(e_outofmem)); - goto f_msgpackparse_exit; - } - if (result == MSGPACK_UNPACK_SUCCESS) { - typval_T tv = { .v_type = VAR_UNKNOWN }; - if (msgpack_to_vim(unpacked.data, &tv) == FAIL) { - EMSG2(_(e_invarg2), "Failed to convert msgpack string"); - goto f_msgpackparse_exit; - } - tv_list_append_owned_tv(ret_list, tv); - } - if (result == MSGPACK_UNPACK_CONTINUE) { - if (rlret == OK) { - EMSG2(_(e_invarg2), "Incomplete msgpack string"); - } + const msgpack_unpack_return result + = msgpack_unpacker_next(unpacker, &unpacked); + const int conv_result = msgpackparse_convert_item(unpacked.data, result, + ret_list, rlret == OK); + if (conv_result == NOTDONE) { break; + } else if (conv_result == FAIL) { + goto end; } } if (rlret == OK) { @@ -6480,10 +6615,47 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } while (true); -f_msgpackparse_exit: - msgpack_unpacked_destroy(&unpacked); +end: msgpack_unpacker_free(unpacker); - return; + msgpack_unpacked_destroy(&unpacked); +} + +static void msgpackparse_unpack_blob(const blob_T *const blob, + list_T *const ret_list) + FUNC_ATTR_NONNULL_ARG(2) +{ + const int len = tv_blob_len(blob); + if (len == 0) { + return; + } + msgpack_unpacked unpacked; + msgpack_unpacked_init(&unpacked); + for (size_t offset = 0; offset < (size_t)len;) { + const msgpack_unpack_return result + = msgpack_unpack_next(&unpacked, blob->bv_ga.ga_data, len, &offset); + if (msgpackparse_convert_item(unpacked.data, result, ret_list, true) + != OK) { + break; + } + } + + msgpack_unpacked_destroy(&unpacked); +} + +/// "msgpackparse" function +static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) + FUNC_ATTR_NONNULL_ALL +{ + if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) { + EMSG2(_(e_listblobarg), "msgpackparse()"); + return; + } + list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); + if (argvars[0].v_type == VAR_LIST) { + msgpackparse_unpack_list(argvars[0].vval.v_list, ret_list); + } else { + msgpackparse_unpack_blob(argvars[0].vval.v_blob, ret_list); + } } /* @@ -6894,6 +7066,7 @@ static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool binary = false; + bool blob = false; FILE *fd; char_u buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1 int io_size = sizeof(buf); @@ -6906,22 +7079,41 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].v_type != VAR_UNKNOWN) { if (strcmp(tv_get_string(&argvars[1]), "b") == 0) { binary = true; + } else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) { + blob = true; } if (argvars[2].v_type != VAR_UNKNOWN) { maxline = tv_get_number(&argvars[2]); } } - list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown); - // Always open the file in binary mode, library functions have a mind of // their own about CR-LF conversion. const char *const fname = tv_get_string(&argvars[0]); + + if (os_isdir((const char_u *)fname)) { + EMSG2(_(e_isadir2), fname); + return; + } if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) { EMSG2(_(e_notopen), *fname == NUL ? _("<empty>") : fname); return; } + if (blob) { + tv_blob_alloc_ret(rettv); + if (!read_blob(fd, rettv->vval.v_blob)) { + EMSG2(_(e_notread), fname); + // An empty blob is returned on error. + tv_blob_free(rettv->vval.v_blob); + rettv->vval.v_blob = NULL; + } + fclose(fd); + return; + } + + list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown); + while (maxline < 0 || tv_list_len(l) < maxline) { readlen = (int)fread(buf, 1, io_size, fd); @@ -7190,8 +7382,64 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } + } else if (argvars[0].v_type == VAR_BLOB) { + blob_T *const b = argvars[0].vval.v_blob; + + if (b != NULL && var_check_lock(b->bv_lock, arg_errmsg, TV_TRANSLATE)) { + return; + } + + bool error = false; + idx = (long)tv_get_number_chk(&argvars[1], &error); + + if (!error) { + const int len = tv_blob_len(b); + + if (idx < 0) { + // count from the end + idx = len + idx; + } + if (idx < 0 || idx >= len) { + EMSGN(_(e_blobidx), idx); + return; + } + if (argvars[2].v_type == VAR_UNKNOWN) { + // Remove one item, return its value. + char_u *const p = (char_u *)b->bv_ga.ga_data; + rettv->vval.v_number = (varnumber_T)(*(p + idx)); + memmove(p + idx, p + idx + 1, (size_t)len - idx - 1); + b->bv_ga.ga_len--; + } else { + // Remove range of items, return blob with values. + end = (long)tv_get_number_chk(&argvars[2], &error); + if (error) { + return; + } + if (end < 0) { + // count from the end + end = len + end; + } + if (end >= len || idx > end) { + EMSGN(_(e_blobidx), end); + return; + } + blob_T *const blob = tv_blob_alloc(); + blob->bv_ga.ga_len = end - idx + 1; + ga_grow(&blob->bv_ga, end - idx + 1); + + char_u *const p = (char_u *)b->bv_ga.ga_data; + memmove((char_u *)blob->bv_ga.ga_data, p + idx, + (size_t)(end - idx + 1)); + tv_blob_set_ret(rettv, blob); + + if (len - end - 1 > 0) { + memmove(p + idx, p + end + 1, (size_t)(len - end - 1)); + } + b->bv_ga.ga_len -= end - idx + 1; + } + } } else if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listdictarg), "remove()"); + EMSG2(_(e_listdictblobarg), "remove()"); } else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), arg_errmsg, TV_TRANSLATE)) { bool error = false; @@ -7465,13 +7713,25 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *l; - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "reverse()"); - } else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - N_("reverse() argument"), TV_TRANSLATE)) { - tv_list_reverse(l); - tv_list_set_ret(rettv, l); + if (argvars[0].v_type == VAR_BLOB) { + blob_T *const b = argvars[0].vval.v_blob; + const int len = tv_blob_len(b); + + for (int i = 0; i < len / 2; i++) { + const char_u tmp = tv_blob_get(b, i); + tv_blob_set(b, i, tv_blob_get(b, len - i - 1)); + tv_blob_set(b, len - i - 1, tmp); + } + tv_blob_set_ret(rettv, b); + } else if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listblobarg), "reverse()"); + } else { + list_T *const l = argvars[0].vval.v_list; + if (!var_check_lock(tv_list_locked(l), N_("reverse() argument"), + TV_TRANSLATE)) { + tv_list_reverse(l); + tv_list_set_ret(rettv, l); + } } } @@ -11291,15 +11551,16 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) int n = -1; switch (argvars[0].v_type) { - case VAR_NUMBER: n = VAR_TYPE_NUMBER; break; - case VAR_STRING: n = VAR_TYPE_STRING; break; + case VAR_NUMBER: n = VAR_TYPE_NUMBER; break; + case VAR_STRING: n = VAR_TYPE_STRING; break; case VAR_PARTIAL: - case VAR_FUNC: n = VAR_TYPE_FUNC; break; - case VAR_LIST: n = VAR_TYPE_LIST; break; - case VAR_DICT: n = VAR_TYPE_DICT; break; - case VAR_FLOAT: n = VAR_TYPE_FLOAT; break; - case VAR_BOOL: n = VAR_TYPE_BOOL; break; - case VAR_SPECIAL:n = VAR_TYPE_SPECIAL; break; + case VAR_FUNC: n = VAR_TYPE_FUNC; break; + case VAR_LIST: n = VAR_TYPE_LIST; break; + case VAR_DICT: n = VAR_TYPE_DICT; break; + case VAR_FLOAT: n = VAR_TYPE_FLOAT; break; + case VAR_BOOL: n = VAR_TYPE_BOOL; break; + case VAR_SPECIAL: n = VAR_TYPE_SPECIAL; break; + case VAR_BLOB: n = VAR_TYPE_BLOB; break; case VAR_UNKNOWN: { internal_error("f_type(UNKNOWN)"); break; @@ -11678,16 +11939,17 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "writefile()"); + if (argvars[0].v_type == VAR_LIST) { + TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { + if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) { + return; + } + }); + } else if (argvars[0].v_type != VAR_BLOB) { + EMSG2(_(e_invarg2), + _("writefile() first argument must be a List or a Blob")); return; } - const list_T *const list = argvars[0].vval.v_list; - TV_LIST_ITER_CONST(list, li, { - if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) { - return; - } - }); bool binary = false; bool append = false; @@ -11727,7 +11989,13 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) emsgf(_("E482: Can't open file %s for writing: %s"), fname, os_strerror(error)); } else { - if (write_list(&fp, list, binary)) { + bool write_ok; + if (argvars[0].v_type == VAR_BLOB) { + write_ok = write_blob(&fp, argvars[0].vval.v_blob); + } else { + write_ok = write_list(&fp, argvars[0].vval.v_list, binary); + } + if (write_ok) { rettv->vval.v_number = 0; } if ((error = file_close(&fp, do_fsync)) != 0) { diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 22b3bf026b..381d70ea1b 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2125,6 +2125,77 @@ void tv_dict_set_keys_readonly(dict_T *const dict) }); } +//{{{1 Blobs +//{{{2 Alloc/free + +/// Allocate an empty blob. +/// +/// Caller should take care of the reference count. +/// +/// @return [allocated] new blob. +blob_T *tv_blob_alloc(void) + FUNC_ATTR_NONNULL_RET +{ + blob_T *const blob = xcalloc(1, sizeof(blob_T)); + ga_init(&blob->bv_ga, 1, 100); + return blob; +} + +/// Free a blob. Ignores the reference count. +/// +/// @param[in,out] b Blob to free. +void tv_blob_free(blob_T *const b) + FUNC_ATTR_NONNULL_ALL +{ + ga_clear(&b->bv_ga); + xfree(b); +} + +/// Unreference a blob. +/// +/// Decrements the reference count and frees blob when it becomes zero. +/// +/// @param[in,out] b Blob to operate on. +void tv_blob_unref(blob_T *const b) +{ + if (b != NULL && --b->bv_refcount <= 0) { + tv_blob_free(b); + } +} + +//{{{2 Operations on the whole blob + +/// Check whether two blobs are equal. +/// +/// @param[in] b1 First blob. +/// @param[in] b2 Second blob. +/// +/// @return true if blobs are equal, false otherwise. +bool tv_blob_equal(const blob_T *const b1, const blob_T *const b2) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + const int len1 = tv_blob_len(b1); + const int len2 = tv_blob_len(b2); + + // empty and NULL are considered the same + if (len1 == 0 && len2 == 0) { + return true; + } + if (b1 == b2) { + return true; + } + if (len1 != len2) { + return false; + } + + for (int i = 0; i < b1->bv_ga.ga_len; i++) { + if (tv_blob_get(b1, i) != tv_blob_get(b2, i)) { + return false; + } + } + return true; +} + //{{{1 Generic typval operations //{{{2 Init/alloc/clear //{{{3 Alloc @@ -2169,6 +2240,44 @@ void tv_dict_alloc_ret(typval_T *const ret_tv) tv_dict_set_ret(ret_tv, d); } +/// Allocate an empty blob for a return value. +/// +/// Also sets reference count. +/// +/// @param[out] ret_tv Structure where blob is saved. +void tv_blob_alloc_ret(typval_T *const ret_tv) + FUNC_ATTR_NONNULL_ALL +{ + blob_T *const b = tv_blob_alloc(); + tv_blob_set_ret(ret_tv, b); +} + +/// Copy a blob typval to a different typval. +/// +/// @param[in] from Blob object to copy from. +/// @param[out] to Blob object to copy to. +void tv_blob_copy(typval_T *const from, typval_T *const to) + FUNC_ATTR_NONNULL_ALL +{ + assert(from->v_type == VAR_BLOB); + + to->v_type = VAR_BLOB; + to->v_lock = VAR_UNLOCKED; + if (from->vval.v_blob == NULL) { + to->vval.v_blob = NULL; + } else { + tv_blob_alloc_ret(to); + int len = from->vval.v_blob->bv_ga.ga_len; + + if (len > 0) { + to->vval.v_blob->bv_ga.ga_data + = xmemdup(from->vval.v_blob->bv_ga.ga_data, (size_t)len); + } + to->vval.v_blob->bv_ga.ga_len = len; + to->vval.v_blob->bv_ga.ga_maxlen = len; + } +} + //{{{3 Clear #define TYPVAL_ENCODE_ALLOW_SPECIALS false @@ -2210,6 +2319,13 @@ void tv_dict_alloc_ret(typval_T *const ret_tv) #define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type) +#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \ + do { \ + tv_blob_unref(tv->vval.v_blob); \ + tv->vval.v_blob = NULL; \ + tv->v_lock = VAR_UNLOCKED; \ + } while (0) + static inline int _nothing_conv_func_start(typval_T *const tv, char_u *const fun) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(1) @@ -2392,6 +2508,7 @@ static inline void _nothing_conv_dict_end(typval_T *const tv, #undef TYPVAL_ENCODE_CONV_STRING #undef TYPVAL_ENCODE_CONV_STR_STRING #undef TYPVAL_ENCODE_CONV_EXT_STRING +#undef TYPVAL_ENCODE_CONV_BLOB #undef TYPVAL_ENCODE_CONV_FUNC_START #undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS #undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF @@ -2449,6 +2566,10 @@ void tv_free(typval_T *tv) xfree(tv->vval.v_string); break; } + case VAR_BLOB: { + tv_blob_unref(tv->vval.v_blob); + break; + } case VAR_LIST: { tv_list_unref(tv->vval.v_list); break; @@ -2509,6 +2630,12 @@ void tv_copy(const typval_T *const from, typval_T *const to) } break; } + case VAR_BLOB: { + if (from->vval.v_blob != NULL) { + to->vval.v_blob->bv_refcount++; + } + break; + } case VAR_LIST: { tv_list_ref(to->vval.v_list); break; @@ -2533,7 +2660,10 @@ void tv_copy(const typval_T *const from, typval_T *const to) /// @param[out] tv Item to (un)lock. /// @param[in] deep Levels to (un)lock, -1 to (un)lock everything. /// @param[in] lock True if it is needed to lock an item, false to unlock. -void tv_item_lock(typval_T *const tv, const int deep, const bool lock) +/// @param[in] check_refcount If true, do not lock a list or dict with a +/// reference count larger than 1. +void tv_item_lock(typval_T *const tv, const int deep, const bool lock, + const bool check_refcount) FUNC_ATTR_NONNULL_ALL { // TODO(ZyX-I): Make this not recursive @@ -2560,14 +2690,21 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock) CHANGE_LOCK(lock, tv->v_lock); switch (tv->v_type) { + case VAR_BLOB: { + blob_T *const b = tv->vval.v_blob; + if (b != NULL && !(check_refcount && b->bv_refcount > 1)) { + CHANGE_LOCK(lock, b->bv_lock); + } + break; + } case VAR_LIST: { list_T *const l = tv->vval.v_list; - if (l != NULL) { + if (l != NULL && !(check_refcount && l->lv_refcount > 1)) { CHANGE_LOCK(lock, l->lv_lock); if (deep < 0 || deep > 1) { // Recursive: lock/unlock the items the List contains. TV_LIST_ITER(l, li, { - tv_item_lock(TV_LIST_ITEM_TV(li), deep - 1, lock); + tv_item_lock(TV_LIST_ITEM_TV(li), deep - 1, lock, check_refcount); }); } } @@ -2575,12 +2712,12 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock) } case VAR_DICT: { dict_T *const d = tv->vval.v_dict; - if (d != NULL) { + if (d != NULL && !(check_refcount && d->dv_refcount > 1)) { CHANGE_LOCK(lock, d->dv_lock); if (deep < 0 || deep > 1) { // recursive: lock/unlock the items the List contains TV_DICT_ITER(d, di, { - tv_item_lock(&di->di_tv, deep - 1, lock); + tv_item_lock(&di->di_tv, deep - 1, lock, check_refcount); }); } } @@ -2646,10 +2783,11 @@ bool tv_check_lock(const typval_T *tv, const char *name, VarLockStatus lock = VAR_UNLOCKED; switch (tv->v_type) { - // case VAR_BLOB: - // if (tv->vval.v_blob != NULL) - // lock = tv->vval.v_blob->bv_lock; - // break; + case VAR_BLOB: + if (tv->vval.v_blob != NULL) { + lock = tv->vval.v_blob->bv_lock; + } + break; case VAR_LIST: if (tv->vval.v_list != NULL) { lock = tv->vval.v_list->lv_lock; @@ -2769,6 +2907,9 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic, recursive_cnt--; return r; } + case VAR_BLOB: { + return tv_blob_equal(tv1->vval.v_blob, tv2->vval.v_blob); + } case VAR_NUMBER: { return tv1->vval.v_number == tv2->vval.v_number; } @@ -2835,6 +2976,10 @@ bool tv_check_str_or_nr(const typval_T *const tv) EMSG(_("E728: Expected a Number or a String, Dictionary found")); return false; } + case VAR_BLOB: { + EMSG(_("E974: Expected a Number or a String, Blob found")); + return false; + } case VAR_BOOL: { EMSG(_("E5299: Expected a Number or a String, Boolean found")); return false; @@ -2860,6 +3005,7 @@ static const char *const num_errors[] = { [VAR_LIST]=N_("E745: Using a List as a Number"), [VAR_DICT]=N_("E728: Using a Dictionary as a Number"), [VAR_FLOAT]=N_("E805: Using a Float as a Number"), + [VAR_BLOB]=N_("E974: Using a Blob as a Number"), [VAR_UNKNOWN]=N_("E685: using an invalid value as a Number"), }; @@ -2888,6 +3034,7 @@ bool tv_check_num(const typval_T *const tv) case VAR_LIST: case VAR_DICT: case VAR_FLOAT: + case VAR_BLOB: case VAR_UNKNOWN: { EMSG(_(num_errors[tv->v_type])); return false; @@ -2905,6 +3052,7 @@ static const char *const str_errors[] = { [VAR_LIST]=N_("E730: using List as a String"), [VAR_DICT]=N_("E731: using Dictionary as a String"), [VAR_FLOAT]=((const char *)e_float_as_string), + [VAR_BLOB]=N_("E976: using Blob as a String"), [VAR_UNKNOWN]=N_("E908: using an invalid value as a String"), }; @@ -2933,6 +3081,7 @@ bool tv_check_str(const typval_T *const tv) case VAR_LIST: case VAR_DICT: case VAR_FLOAT: + case VAR_BLOB: case VAR_UNKNOWN: { EMSG(_(str_errors[tv->v_type])); return false; @@ -2980,6 +3129,7 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error) case VAR_PARTIAL: case VAR_LIST: case VAR_DICT: + case VAR_BLOB: case VAR_FLOAT: { EMSG(_(num_errors[tv->v_type])); break; @@ -3075,6 +3225,10 @@ float_T tv_get_float(const typval_T *const tv) EMSG(_("E907: Using a special value as a Float")); break; } + case VAR_BLOB: { + EMSG(_("E975: Using a Blob as a Float")); + break; + } case VAR_UNKNOWN: { emsgf(_(e_intern2), "tv_get_float(UNKNOWN)"); break; @@ -3134,6 +3288,7 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf) case VAR_LIST: case VAR_DICT: case VAR_FLOAT: + case VAR_BLOB: case VAR_UNKNOWN: { EMSG(_(str_errors[tv->v_type])); return false; diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index ef49fa1de6..5aecaccee9 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -64,6 +64,7 @@ enum ListLenSpecials { typedef struct listvar_S list_T; typedef struct dictvar_S dict_T; typedef struct partial_S partial_T; +typedef struct blobvar_S blob_T; typedef struct ufunc ufunc_T; @@ -123,6 +124,7 @@ typedef enum { VAR_SPECIAL, ///< Special value (null), .v_special ///< is used. VAR_PARTIAL, ///< Partial, .v_partial is used. + VAR_BLOB, ///< Blob, .v_blob is used. } VarType; /// Structure that holds an internal variable value @@ -138,6 +140,7 @@ typedef struct { list_T *v_list; ///< List for VAR_LIST, can be NULL. dict_T *v_dict; ///< Dictionary for VAR_DICT, can be NULL. partial_T *v_partial; ///< Closure: function with args. + blob_T *v_blob; ///< Blob for VAR_BLOB, can be NULL. } vval; ///< Actual value. } typval_T; @@ -252,6 +255,13 @@ struct dictvar_S { LuaRef lua_table_ref; }; +/// Structure to hold info about a Blob +struct blobvar_S { + garray_T bv_ga; ///< Growarray with the data. + int bv_refcount; ///< Reference count. + VarLockStatus bv_lock; ///< VAR_UNLOCKED, VAR_LOCKED, VAR_FIXED. +}; + /// Type used for script ID typedef int scid_T; /// Format argument for scid_T @@ -711,6 +721,65 @@ static inline bool tv_dict_is_watched(const dict_T *const d) return d && !QUEUE_EMPTY(&d->watchers); } +static inline void tv_blob_set_ret(typval_T *const tv, blob_T *const b) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1); + +/// Set a blob as the return value. +/// +/// Increments the reference count. +/// +/// @param[out] tv Object to receive the blob. +/// @param[in,out] b Blob to pass to the object. +static inline void tv_blob_set_ret(typval_T *const tv, blob_T *const b) +{ + tv->v_type = VAR_BLOB; + tv->vval.v_blob = b; + if (b != NULL) { + b->bv_refcount++; + } +} + +static inline int tv_blob_len(const blob_T *const b) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Get the length of the data in the blob, in bytes. +/// +/// @param[in] b Blob to check. +static inline int tv_blob_len(const blob_T *const b) +{ + if (b == NULL) { + return 0; + } + return b->bv_ga.ga_len; +} + +static inline char_u tv_blob_get(const blob_T *const b, int idx) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT; + +/// Get the byte at index `idx` in the blob. +/// +/// @param[in] b Blob to index. Cannot be NULL. +/// @param[in] idx Index in a blob. Must be valid. +/// +/// @return Byte value at the given index. +static inline char_u tv_blob_get(const blob_T *const b, int idx) +{ + return ((char_u *)b->bv_ga.ga_data)[idx]; +} + +static inline void tv_blob_set(blob_T *const b, int idx, char_u c) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; + +/// Store the byte `c` at index `idx` in the blob. +/// +/// @param[in] b Blob to index. Cannot be NULL. +/// @param[in] idx Index in a blob. Must be valid. +/// @param[in] c Value to store. +static inline void tv_blob_set(blob_T *const b, int idx, char_u c) +{ + ((char_u *)b->bv_ga.ga_data)[idx] = c; +} + /// Initialize VimL object /// /// Initializes to unlocked VAR_UNKNOWN object. diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 91c948ce7e..cd1be1eecc 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -83,6 +83,13 @@ /// @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 /// @@ -330,6 +337,11 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( 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); diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 19535c2868..350c5ee1c2 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -5,6 +5,7 @@ #include "nvim/ascii.h" #include "nvim/charset.h" +#include "nvim/debugger.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" |