diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 562198e79c1f9..a969104e94315 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2469,6 +2469,7 @@ atan2({expr1}, {expr2}) Float arc tangent of {expr1} / {expr2} 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 idem, but return a Number 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 index a5dde18c38f05..1e1a23d6735b8 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -723,6 +723,10 @@ Floating point computation: *float-functions* 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 index 2138ac07403f0..04319f8b1ed6c 100644 --- a/src/blob.c +++ b/src/blob.c @@ -483,4 +483,65 @@ blob_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg) } } +/* + * 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 index d4ba7f0ea58f8..a8ea33d1701d1 100644 --- a/src/errors.h +++ b/src/errors.h @@ -660,3 +660,7 @@ EXTERN char e_cannot_use_str_itself_it_is_imported_with_star[] 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 index 47290ddeed7c7..16d9be3321155 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -288,6 +288,15 @@ arg_string(type_T *type, argcontext_T *context) return check_arg_type(&t_string, type, context); } +/* + * 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. */ @@ -680,6 +689,7 @@ arg_cursor1(type_T *type, argcontext_T *context) /* * 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 index 5d80463e3d39c..dbc8d1490c4fd 100644 --- a/src/proto/blob.pro +++ b/src/proto/blob.pro @@ -19,4 +19,6 @@ int check_blob_index(long bloblen, varnumber_T n1, int quiet); 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 index e509e4d9515d1..d13a1ec71126e 100644 --- a/src/proto/typval.pro +++ b/src/proto/typval.pro @@ -17,6 +17,7 @@ int check_for_opt_number_arg(typval_T *args, int idx); 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 index d56c4f748a7a7..e722dab559e3e 100644 --- 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 index 5f22b333d6691..746d3f8833cda 100644 --- 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 index 3a0e2e5d99ee1..c859122ee7933 100644 --- a/src/typval.c +++ b/src/typval.c @@ -470,6 +470,23 @@ check_for_opt_bool_arg(typval_T *args, int idx) return check_for_bool_arg(args, idx); } +/* + * 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. */ diff --git a/src/version.c b/src/version.c index 11a976c4bc957..c803e5bb573bf 100644 --- a/src/version.c +++ b/src/version.c @@ -755,6 +755,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 3438, /**/ 3437, /**/