changeset 25806:8d55e978f95b v8.2.3438

patch 8.2.3438: cannot manipulate blobs Commit: https://github.com/vim/vim/commit/5dfe467432638fac2e0156a2f9cd0d9eb569fb39 Author: Yegappan Lakshmanan <yegappan@yahoo.com> 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)
author Bram Moolenaar <Bram@vim.org>
date Tue, 14 Sep 2021 18:00:08 +0200
parents 6f144509b673
children 703ae6848d39
files runtime/doc/eval.txt runtime/doc/usr_41.txt src/blob.c src/errors.h src/evalfunc.c src/proto/blob.pro src/proto/typval.pro src/testdir/test_blob.vim src/testdir/test_vim9_builtin.vim src/typval.c src/version.c
diffstat 11 files changed, 185 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- 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: >
--- 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
 
--- 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)
--- 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"));
--- 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,
--- 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 : */
--- 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);
--- 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
--- 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<number> but got number', 'E1211: List required for argument 1')
+  CheckDefFailure(['list2blob([0z10, 0z02])'], 'E1013: Argument 1: type mismatch, expected list<number> but got list<blob>')
+enddef
+
 def Test_list2str_str2list_utf8()
   var s = "\u3042\u3044"
   var l = [0x3042, 0x3044]
--- 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
--- 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,