diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 619d8a3626..cc29839519 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -745,6 +745,7 @@ Blob manipulation: *blob-functions* reverse() reverse the order of numbers in a blob index() index of a value in a Blob indexof() index in a Blob where an expression is true + items() get List of Blob index-value pairs Other computation: *bitwise-function* and() bitwise AND diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt index d3e12ee52e..e5a31ec0cf 100644 --- a/runtime/doc/vimfn.txt +++ b/runtime/doc/vimfn.txt @@ -5415,7 +5415,7 @@ items({expr}) *items()* Return a |List| with all key/index and value pairs of {expr}. Each |List| item is a list with two items: - for a |Dict|: the key and the value - - for a |List| or |String|: the index and the value + - for a |List|, |Blob| or |String|: the index and the value The returned |List| is in arbitrary order for a |Dict|, otherwise it's in ascending order of the index. @@ -5428,6 +5428,7 @@ items({expr}) *items()* endfor echo items([1, 2, 3]) echo items("foobar") + echo items(0z0102) < Parameters: ~ diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index ed9f1f8791..2b859fc8f9 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -4901,7 +4901,7 @@ function vim.fn.isnan(expr) end --- Return a |List| with all key/index and value pairs of {expr}. --- Each |List| item is a list with two items: --- - for a |Dict|: the key and the value ---- - for a |List| or |String|: the index and the value +--- - for a |List|, |Blob| or |String|: the index and the value --- The returned |List| is in arbitrary order for a |Dict|, --- otherwise it's in ascending order of the index. --- @@ -4914,6 +4914,7 @@ function vim.fn.isnan(expr) end --- endfor --- echo items([1, 2, 3]) --- echo items("foobar") +--- echo items(0z0102) --- < --- --- @param expr table|string diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 13f9ca4fac..70f83d1444 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -6044,7 +6044,7 @@ M.funcs = { Return a |List| with all key/index and value pairs of {expr}. Each |List| item is a list with two items: - for a |Dict|: the key and the value - - for a |List| or |String|: the index and the value + - for a |List|, |Blob| or |String|: the index and the value The returned |List| is in arbitrary order for a |Dict|, otherwise it's in ascending order of the index. @@ -6057,6 +6057,7 @@ M.funcs = { endfor echo items([1, 2, 3]) echo items("foobar") + echo items(0z0102) < ]=], name = 'items', diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index d9f59e09f0..3c88f5c967 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -93,8 +93,8 @@ static const char e_string_or_number_required_for_argument_nr[] = N_("E1220: String or Number required for argument %d"); static const char e_string_or_list_required_for_argument_nr[] = N_("E1222: String or List required for argument %d"); -static const char e_string_list_or_dict_required_for_argument_nr[] - = N_("E1225: String, List or Dictionary required for argument %d"); +static const char e_list_dict_blob_or_string_required_for_argument_nr[] + = N_("E1225: List, Dictionary, Blob or String required for argument %d"); static const char e_list_or_blob_required_for_argument_nr[] = N_("E1226: List or Blob required for argument %d"); static const char e_blob_required_for_argument_nr[] @@ -793,6 +793,30 @@ void tv_list_flatten(list_T *list, listitem_T *first, int64_t maxitems, int64_t } } +/// "items(blob)" function +/// Converts a Blob into a List of [index, byte] pairs. +/// Caller must have already checked that argvars[0] is a Blob. +/// A null blob behaves like an empty blob. +static void tv_blob2items(typval_T *argvars, typval_T *rettv) +{ + blob_T *blob = argvars[0].vval.v_blob; + + tv_list_alloc_ret(rettv, tv_blob_len(blob)); + + for (int i = 0; i < tv_blob_len(blob); i++) { + list_T *l2 = tv_list_alloc(2); + tv_list_append_list(rettv->vval.v_list, l2); + tv_list_append_number(l2, i); + tv_list_append_number(l2, tv_blob_get(blob, i)); + } +} + +/// "items(dict)" function +static void tv_dict2items(typval_T *argvars, typval_T *rettv) +{ + tv_dict2list(argvars, rettv, kDict2ListItems); +} + /// "items(list)" function /// Caller must have already checked that argvars[0] is a List. static void tv_list2items(typval_T *argvars, typval_T *rettv) @@ -3226,9 +3250,7 @@ void tv_dict_alloc_ret(typval_T *const ret_tv) /// @param[in] what What to save in rettv. static void tv_dict2list(typval_T *const argvars, typval_T *const rettv, const DictListType what) { - if ((what == kDict2ListItems - ? tv_check_for_string_or_list_or_dict_arg(argvars, 0) - : tv_check_for_dict_arg(argvars, 0)) == FAIL) { + if (tv_check_for_dict_arg(argvars, 0) == FAIL) { tv_list_alloc_ret(rettv, 0); return; } @@ -3267,15 +3289,19 @@ static void tv_dict2list(typval_T *const argvars, typval_T *const rettv, const D }); } -/// "items(dict)" function +/// "items()" function void f_items(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type == VAR_STRING) { tv_string2items(argvars, rettv); } else if (argvars[0].v_type == VAR_LIST) { tv_list2items(argvars, rettv); + } else if (argvars[0].v_type == VAR_BLOB) { + tv_blob2items(argvars, rettv); + } else if (argvars[0].v_type == VAR_DICT) { + tv_dict2items(argvars, rettv); } else { - tv_dict2list(argvars, rettv, kDict2ListItems); + semsg(_(e_list_dict_blob_or_string_required_for_argument_nr), 1); } } @@ -4511,19 +4537,6 @@ int tv_check_for_opt_string_or_list_arg(const typval_T *const args, const int id || tv_check_for_string_or_list_arg(args, idx) != FAIL) ? OK : FAIL; } -/// Give an error and return FAIL unless "args[idx]" is a string or a list or a dict -int tv_check_for_string_or_list_or_dict_arg(const typval_T *const args, const int idx) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE -{ - if (args[idx].v_type != VAR_STRING - && args[idx].v_type != VAR_LIST - && args[idx].v_type != VAR_DICT) { - semsg(_(e_string_list_or_dict_required_for_argument_nr), idx + 1); - return FAIL; - } - return OK; -} - /// Give an error and return FAIL unless "args[idx]" is a string /// or a function reference. int tv_check_for_string_or_func_arg(const typval_T *const args, const int idx) diff --git a/test/old/testdir/test_blob.vim b/test/old/testdir/test_blob.vim index 0bb018f107..f4e79d6ea5 100644 --- a/test/old/testdir/test_blob.vim +++ b/test/old/testdir/test_blob.vim @@ -884,4 +884,15 @@ func Test_indexof() call assert_fails('let i = indexof(b, " ")', 'E15:') endfunc +" Test for using the items() function with a blob +func Test_blob_items() + let lines =<< trim END + call assert_equal([[0, 0xAA], [1, 0xBB], [2, 0xCC]], 0zAABBCC->items()) + call assert_equal([[0, 0]], 0z00->items()) + call assert_equal([], 0z->items()) + call assert_equal([], v:_null_blob->items()) + END + call CheckSourceLegacyAndVim9Success(lines) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/vim9.vim b/test/old/testdir/vim9.vim index 7d1eec7d4f..1a4ade3417 100644 --- a/test/old/testdir/vim9.vim +++ b/test/old/testdir/vim9.vim @@ -89,20 +89,24 @@ func CheckLegacyFailure(lines, error) endtry endfunc +" Translate "lines" to legacy Vim script +func s:LegacyTrans(lines) + return a:lines->mapnew({_, v -> + \ v->substitute('\', 'let', 'g') + \ ->substitute('\', 'let', 'g') + \ ->substitute('\', '{', 'g') + \ ->substitute('\', '->', 'g') + \ ->substitute('\', '}', 'g') + \ ->substitute('\', '1', 'g') + \ ->substitute('\', '0', 'g') + \ ->substitute('#"', ' "', 'g') + \ }) +endfunc + " Execute "lines" in a legacy function, translated as in " CheckLegacyAndVim9Success() func CheckTransLegacySuccess(lines) - let legacylines = a:lines->mapnew({_, v -> - \ v->substitute('\', 'let', 'g') - \ ->substitute('\', 'let', 'g') - \ ->substitute('\', '{', 'g') - \ ->substitute('\', '->', 'g') - \ ->substitute('\', '}', 'g') - \ ->substitute('\', '1', 'g') - \ ->substitute('\', '0', 'g') - \ ->substitute('#"', ' "', 'g') - \ }) - call CheckLegacySuccess(legacylines) + call CheckLegacySuccess(s:LegacyTrans(a:lines)) endfunc func CheckTransDefSuccess(lines) @@ -143,6 +147,65 @@ func CheckLegacyAndVim9Failure(lines, error) call CheckLegacyFailure(legacylines, legacyError) endfunc +" Check that "lines" inside a legacy function has no error. +func CheckSourceLegacySuccess(lines) + let cwd = getcwd() + new + call setline(1, ['func Func()'] + a:lines + ['endfunc', 'call Func()']) + let bnr = bufnr() + try + :source + finally + delfunc! Func + call chdir(cwd) + exe $':bw! {bnr}' + endtry +endfunc + +" Check that "lines" inside a legacy function results in the expected error +func CheckSourceLegacyFailure(lines, error) + let cwd = getcwd() + new + call setline(1, ['func Func()'] + a:lines + ['endfunc', 'call Func()']) + let bnr = bufnr() + try + call assert_fails('source', a:error) + finally + delfunc! Func + call chdir(cwd) + exe $':bw! {bnr}' + endtry +endfunc + +" Execute "lines" in a legacy function, translated as in +" CheckSourceLegacyAndVim9Success() +func CheckSourceTransLegacySuccess(lines) + call CheckSourceLegacySuccess(s:LegacyTrans(a:lines)) +endfunc + +" Execute "lines" in a :def function, translated as in +" CheckLegacyAndVim9Success() +func CheckSourceTransDefSuccess(lines) + return +endfunc + +" Execute "lines" in a Vim9 script, translated as in +" CheckLegacyAndVim9Success() +func CheckSourceTransVim9Success(lines) + return +endfunc + +" Execute "lines" in a legacy function, :def function and Vim9 script. +" Use 'VAR' for a declaration. +" Use 'LET' for an assignment +" Use ' #"' for a comment +" Use LSTART arg LMIDDLE expr LEND for lambda +" Use 'TRUE' for 1 in legacy, true in Vim9 +" Use 'FALSE' for 0 in legacy, false in Vim9 +func CheckSourceLegacyAndVim9Success(lines) + call CheckSourceTransLegacySuccess(a:lines) +endfunc + " :source a list of "lines" and check whether it fails with "error" func CheckSourceScriptFailure(lines, error, lnum = -3) if get(a:lines, 0, '') ==# 'vim9script' @@ -195,26 +258,10 @@ func CheckSourceScriptSuccess(lines) endtry endfunc -func CheckSourceSuccess(lines) - call CheckSourceScriptSuccess(a:lines) -endfunc - -func CheckSourceFailure(lines, error, lnum = -3) - call CheckSourceScriptFailure(a:lines, a:error, a:lnum) -endfunc - -func CheckSourceFailureList(lines, errors, lnum = -3) - call CheckSourceScriptFailureList(a:lines, a:errors, a:lnum) -endfunc - func CheckSourceDefSuccess(lines) return endfunc -func CheckSourceDefAndScriptSuccess(lines) - return -endfunc - func CheckSourceDefCompileSuccess(lines) return endfunc @@ -235,3 +282,28 @@ func CheckSourceDefExecAndScriptFailure(lines, error, lnum = -3) return endfunc +func CheckSourceSuccess(lines) + call CheckSourceScriptSuccess(a:lines) +endfunc + +func CheckSourceFailure(lines, error, lnum = -3) + call CheckSourceScriptFailure(a:lines, a:error, a:lnum) +endfunc + +func CheckSourceFailureList(lines, errors, lnum = -3) + call CheckSourceScriptFailureList(a:lines, a:errors, a:lnum) +endfunc + +func CheckSourceDefAndScriptSuccess(lines) + return +endfunc + +" Execute "lines" in a legacy function, :def function and Vim9 script. +" Use 'VAR' for a declaration. +" Use 'LET' for an assignment +" Use ' #"' for a comment +func CheckSourceLegacyAndVim9Failure(lines, error) + let legacyError = type(a:error) == type('string') ? a:error : a:error[0] + call CheckSourceLegacyFailure(s:LegacyTrans(a:lines), legacyError) +endfunc +