# HG changeset patch # User Bram Moolenaar # Date 1631635208 -7200 # Node ID 8d55e978f95bb57f5a9c48f6f1cfc5f3d3b98c78 # Parent 6f144509b673738ee0ec48d10ce7f8298c9744b6 patch 8.2.3438: cannot manipulate blobs Commit: https://github.com/vim/vim/commit/5dfe467432638fac2e0156a2f9cd0d9eb569fb39 Author: Yegappan Lakshmanan Date: Tue Sep 14 17:54:30 2021 +0200 patch 8.2.3438: cannot manipulate blobs Problem: Cannot manipulate blobs. Solution: Add blob2list() and list2blob(). (Yegappan Lakshmanan, closes #8868) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2469,6 +2469,7 @@ atan2({expr1}, {expr2}) Float arc tange balloon_gettext() String current text in the balloon balloon_show({expr}) none show {expr} inside the balloon balloon_split({msg}) List split {msg} as used for a balloon +blob2list({blob}) List convert {blob} into a list of numbers browse({save}, {title}, {initdir}, {default}) String put up a file requester browsedir({title}, {initdir}) String put up a directory requester @@ -2721,7 +2722,8 @@ libcallnr({lib}, {func}, {arg}) Number i line({expr} [, {winid}]) Number line nr of cursor, last line or mark line2byte({lnum}) Number byte count of line {lnum} lispindent({lnum}) Number Lisp indent for line {lnum} -list2str({list} [, {utf8}]) String turn numbers in {list} into a String +list2blob({list}) Blob turn {list} of numbers into a Blob +list2str({list} [, {utf8}]) String turn {list} of numbers into a String listener_add({callback} [, {buf}]) Number add a callback to listen to changes listener_flush([{buf}]) none invoke listener callbacks @@ -3355,6 +3357,17 @@ balloon_split({msg}) *balloon_split( < {only available when compiled with the |+balloon_eval_term| feature} +blob2list({blob}) *blob2list()* + Return a List containing the number value of each byte in Blob + {blob}. Examples: > + blob2list(0z0102.0304) returns [1, 2, 3, 4] + blob2list(0z) returns [] +< Returns an empty List on error. |list2blob()| does the + opposite. + + Can also be used as a |method|: > + GetBlob()->blob2list() + *browse()* browse({save}, {title}, {initdir}, {default}) Put up a file requester. This only works when "has("browse")" @@ -7208,6 +7221,19 @@ lispindent({lnum}) *lispindent()* Can also be used as a |method|: > GetLnum()->lispindent() +list2blob({list}) *list2blob()* + Return a Blob concatenating all the number values in {list}. + Examples: > + list2blob([1, 2, 3, 4]) returns 0z01020304 + list2blob([]) returns 0z +< Returns an empty Blob on error. If one of the numbers is + negative or more than 255 error *E1239* is given. + + |blob2list()| does the opposite. + + Can also be used as a |method|: > + GetList()->list2blob() + list2str({list} [, {utf8}]) *list2str()* Convert each number in {list} to a character string can concatenate them all. Examples: > diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -723,6 +723,10 @@ Floating point computation: *float-fu isinf() check for infinity isnan() check for not a number +Blob manipulation: *blob-functions* + blob2list() get a list of numbers from a blob + list2blob() get a blob from a list of numbers + Other computation: *bitwise-function* and() bitwise AND invert() bitwise invert @@ -1449,6 +1453,8 @@ is a List with arguments. Function references are most useful in combination with a Dictionary, as is explained in the next section. +More information about defining your own functions here: |user-functions|. + ============================================================================== *41.8* Lists and Dictionaries diff --git a/src/blob.c b/src/blob.c --- a/src/blob.c +++ b/src/blob.c @@ -483,4 +483,65 @@ blob_remove(typval_T *argvars, typval_T } } +/* + * blob2list() function + */ + void +f_blob2list(typval_T *argvars, typval_T *rettv) +{ + blob_T *blob; + list_T *l; + int i; + + if (rettv_list_alloc(rettv) == FAIL) + return; + + if (check_for_blob_arg(argvars, 0) == FAIL) + return; + + blob = argvars->vval.v_blob; + l = rettv->vval.v_list; + for (i = 0; i < blob_len(blob); i++) + list_append_number(l, blob_get(blob, i)); +} + +/* + * list2blob() function + */ + void +f_list2blob(typval_T *argvars, typval_T *rettv) +{ + list_T *l; + listitem_T *li; + blob_T *blob; + + if (rettv_blob_alloc(rettv) == FAIL) + return; + blob = rettv->vval.v_blob; + + if (check_for_list_arg(argvars, 0) == FAIL) + return; + + l = argvars->vval.v_list; + if (l == NULL) + return; + + FOR_ALL_LIST_ITEMS(l, li) + { + int error; + varnumber_T n; + + error = FALSE; + n = tv_get_number_chk(&li->li_tv, &error); + if (error == TRUE || n < 0 || n > 255) + { + if (!error) + semsg(_(e_invalid_value_for_blob_nr), n); + ga_clear(&blob->bv_ga); + return; + } + ga_append(&blob->bv_ga, n); + } +} + #endif // defined(FEAT_EVAL) diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -660,3 +660,7 @@ EXTERN char e_cannot_use_str_itself_it_i INIT(= N_("E1236: Cannot use %s itself, it is imported with '*'")); EXTERN char e_no_such_user_defined_command_in_current_buffer_str[] INIT(= N_("E1237: No such user-defined command in current buffer: %s")); +EXTERN char e_blob_required_for_argument_nr[] + INIT(= N_("E1238: Blob required for argument %d")); +EXTERN char e_invalid_value_for_blob_nr[] + INIT(= N_("E1239: Invalid value for blob: %d")); diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -289,6 +289,15 @@ arg_string(type_T *type, argcontext_T *c } /* + * Check "type" is a blob + */ + static int +arg_blob(type_T *type, argcontext_T *context) +{ + return check_arg_type(&t_blob, type, context); +} + +/* * Check "type" is a bool or number 0 or 1. */ static int @@ -680,6 +689,7 @@ arg_cursor1(type_T *type, argcontext_T * /* * Lists of functions that check the argument types of a builtin function. */ +static argcheck_T arg1_blob[] = {arg_blob}; static argcheck_T arg1_bool[] = {arg_bool}; static argcheck_T arg1_buffer[] = {arg_buffer}; static argcheck_T arg1_buffer_or_dict_any[] = {arg_buffer_or_dict_any}; @@ -1169,6 +1179,8 @@ static funcentry_T global_functions[] = NULL #endif }, + {"blob2list", 1, 1, FEARG_1, arg1_blob, + ret_list_number, f_blob2list}, {"browse", 4, 4, 0, arg4_browse, ret_string, f_browse}, {"browsedir", 2, 2, 0, arg2_string, @@ -1589,6 +1601,8 @@ static funcentry_T global_functions[] = ret_number, f_line2byte}, {"lispindent", 1, 1, FEARG_1, arg1_lnum, ret_number, f_lispindent}, + {"list2blob", 1, 1, FEARG_1, arg1_list_number, + ret_blob, f_list2blob}, {"list2str", 1, 2, FEARG_1, arg2_list_number_bool, ret_string, f_list2str}, {"listener_add", 1, 2, FEARG_2, arg2_any_buffer, diff --git a/src/proto/blob.pro b/src/proto/blob.pro --- a/src/proto/blob.pro +++ b/src/proto/blob.pro @@ -19,4 +19,6 @@ int check_blob_index(long bloblen, varnu int check_blob_range(long bloblen, varnumber_T n1, varnumber_T n2, int quiet); int blob_set_range(blob_T *dest, long n1, long n2, typval_T *src); void blob_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg); +void f_blob2list(typval_T *argvars, typval_T *rettv); +void f_list2blob(typval_T *argvars, typval_T *rettv); /* vim: set ft=c : */ diff --git a/src/proto/typval.pro b/src/proto/typval.pro --- a/src/proto/typval.pro +++ b/src/proto/typval.pro @@ -17,6 +17,7 @@ int check_for_opt_number_arg(typval_T *a int check_for_float_or_nr_arg(typval_T *args, int idx); int check_for_bool_arg(typval_T *args, int idx); int check_for_opt_bool_arg(typval_T *args, int idx); +int check_for_blob_arg(typval_T *args, int idx); int check_for_list_arg(typval_T *args, int idx); int check_for_opt_list_arg(typval_T *args, int idx); int check_for_dict_arg(typval_T *args, int idx); diff --git a/src/testdir/test_blob.vim b/src/testdir/test_blob.vim --- a/src/testdir/test_blob.vim +++ b/src/testdir/test_blob.vim @@ -638,4 +638,43 @@ func Test_blob_sort() call CheckLegacyAndVim9Failure(['call sort([11, 0z11], "N")'], 'E974:') endfunc +" Tests for the blob2list() function +func Test_blob2list() + call assert_fails('let v = blob2list(10)', 'E1238: Blob required for argument 1') + eval 0zFFFF->blob2list()->assert_equal([255, 255]) + let tests = [[0z0102, [1, 2]], + \ [0z00, [0]], + \ [0z, []], + \ [0z00000000, [0, 0, 0, 0]], + \ [0zAABB.CCDD, [170, 187, 204, 221]]] + for t in tests + call assert_equal(t[0]->blob2list(), t[1]) + endfor + exe 'let v = 0z' .. repeat('000102030405060708090A0B0C0D0E0F', 64) + call assert_equal(1024, blob2list(v)->len()) + call assert_equal([4, 8, 15], [v[100], v[1000], v[1023]]) + call assert_equal([], blob2list(test_null_blob())) +endfunc + +" Tests for the list2blob() function +func Test_list2blob() + call assert_fails('let b = list2blob(0z10)', 'E1211: List required for argument 1') + let tests = [[[1, 2], 0z0102], + \ [[0], 0z00], + \ [[], 0z], + \ [[0, 0, 0, 0], 0z00000000], + \ [[170, 187, 204, 221], 0zAABB.CCDD], + \ ] + for t in tests + call assert_equal(t[0]->list2blob(), t[1]) + endfor + call assert_fails('let b = list2blob([1, []])', 'E745:') + call assert_fails('let b = list2blob([-1])', 'E1239:') + call assert_fails('let b = list2blob([256])', 'E1239:') + let b = range(16)->repeat(64)->list2blob() + call assert_equal(1024, b->len()) + call assert_equal([4, 8, 15], [b[100], b[1000], b[1023]]) + call assert_equal(0z, list2blob(test_null_list())) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -287,6 +287,10 @@ def Test_balloon_split() assert_fails('balloon_split(true)', 'E1174:') enddef +def Test_blob2list() + CheckDefAndScriptFailure2(['blob2list(10)'], 'E1013: Argument 1: type mismatch, expected blob but got number', 'E1238: Blob required for argument 1') +enddef + def Test_browse() CheckFeature browse @@ -572,6 +576,7 @@ def Test_char2nr() assert_equal(97, char2nr('a', 0)) assert_equal(97, char2nr('a', true)) assert_equal(97, char2nr('a', false)) + char2nr('')->assert_equal(0) enddef def Test_charclass() @@ -786,6 +791,8 @@ def Test_escape() CheckDefAndScriptFailure2(['escape(true, false)'], 'E1013: Argument 1: type mismatch, expected string but got bool', 'E1174: String required for argument 1') CheckDefAndScriptFailure2(['escape("a", 10)'], 'E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2') assert_equal('a\:b', escape("a:b", ":")) + escape('abc', '')->assert_equal('abc') + escape('', ':')->assert_equal('') enddef def Test_eval() @@ -1921,6 +1928,11 @@ def Test_lispindent() assert_equal(0, lispindent(1)) enddef +def Test_list2blob() + CheckDefAndScriptFailure2(['list2blob(10)'], 'E1013: Argument 1: type mismatch, expected list but got number', 'E1211: List required for argument 1') + CheckDefFailure(['list2blob([0z10, 0z02])'], 'E1013: Argument 1: type mismatch, expected list but got list') +enddef + def Test_list2str_str2list_utf8() var s = "\u3042\u3044" var l = [0x3042, 0x3044] diff --git a/src/typval.c b/src/typval.c --- a/src/typval.c +++ b/src/typval.c @@ -471,6 +471,23 @@ check_for_opt_bool_arg(typval_T *args, i } /* + * Give an error and return FAIL unless "args[idx]" is a blob. + */ + int +check_for_blob_arg(typval_T *args, int idx) +{ + if (args[idx].v_type != VAR_BLOB) + { + if (idx >= 0) + semsg(_(e_blob_required_for_argument_nr), idx + 1); + else + emsg(_(e_blob_required)); + return FAIL; + } + return OK; +} + +/* * Give an error and return FAIL unless "args[idx]" is a list. */ int diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -756,6 +756,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 3438, +/**/ 3437, /**/ 3436,