]> granicus.if.org Git - vim/commitdiff
patch 9.0.0196: finding value in list may require a for loop v9.0.0196
authorYegappan Lakshmanan <yegappan@yahoo.com>
Sat, 13 Aug 2022 12:09:20 +0000 (13:09 +0100)
committerBram Moolenaar <Bram@vim.org>
Sat, 13 Aug 2022 12:09:20 +0000 (13:09 +0100)
Problem:    Finding value in list may require a for loop.
Solution:   Add indexof(). (Yegappan Lakshmanan, closes #10903)

runtime/doc/builtin.txt
runtime/doc/usr_41.txt
src/evalfunc.c
src/testdir/test_blob.vim
src/testdir/test_listdict.vim
src/testdir/test_vim9_builtin.vim
src/version.c

index a65f60d413619e51859d87f93c77ff125bb9953a..3c0f143c5179fd71151956d598df6c46100fb26f 100644 (file)
@@ -291,6 +291,8 @@ iconv({expr}, {from}, {to}) String  convert encoding of {expr}
 indent({lnum})                 Number  indent of line {lnum}
 index({object}, {expr} [, {start} [, {ic}]])
                                Number  index in {object} where {expr} appears
+indexof({object}, {expr} [, {opts}]])
+                               Number  index in {object} where {expr} is true
 input({prompt} [, {text} [, {completion}]])
                                String  get input from the user
 inputdialog({prompt} [, {text} [, {cancelreturn}]])
@@ -4730,19 +4732,25 @@ indent({lnum})  The result is a Number, which is indent of line {lnum} in the
                        GetLnum()->indent()
 
 index({object}, {expr} [, {start} [, {ic}]])                   *index()*
+               Find {expr} in {object} and return its index.  See
+               |filterof()| for using a lambda to select the item.
+
                If {object} is a |List| return the lowest index where the item
                has a value equal to {expr}.  There is no automatic
                conversion, so the String "4" is different from the Number 4.
                And the number 4 is different from the Float 4.0.  The value
-               of 'ignorecase' is not used here, case always matters.
+               of 'ignorecase' is not used here, case matters as indicated by
+               the {ic} argument.
 
                If {object} is |Blob| return the lowest index where the byte
                value is equal to {expr}.
 
                If {start} is given then start looking at the item with index
                {start} (may be negative for an item relative to the end).
+
                When {ic} is given and it is |TRUE|, ignore case.  Otherwise
                case must match.
+
                -1 is returned when {expr} is not found in {object}.
                Example: >
                        :let idx = index(words, "the")
@@ -4751,6 +4759,44 @@ index({object}, {expr} [, {start} [, {ic}]])                     *index()*
 <              Can also be used as a |method|: >
                        GetObject()->index(what)
 
+indexof({object}, {expr} [, {opt}])                    *indexof()*
+               {object} must be a |List| or a |Blob|.
+               If {object} is a |List|, evaluate {expr} for each item in the
+               List until the expression returns v:true and return the index
+               of this item.
+
+               If {object} is a |Blob| evaluate {expr} for each byte in the
+               Blob until the expression returns v:true and return the index
+               of this byte.
+
+               {expr} must be a |string| or |Funcref|.
+
+               If {expr} is a |string|: If {object} is a |List|, inside
+               {expr} |v:key| has the index of the current List item and
+               |v:val| has the value of the item.  If {object} is a |Blob|,
+               inside {expr} |v:key| has the index of the current byte and
+               |v:val| has the byte value.
+
+               If {expr} is a |Funcref| it must take two arguments:
+                       1. the key or the index of the current item.
+                       2. the value of the current item.
+               The function must return |TRUE| if the item is found and the
+               search should stop.
+
+               The optional argument {opt} is a Dict and supports the
+               following items:
+                   start       start evaluating {expr} at the item with index
+                               {start} (may be negative for an item relative
+                               to the end).
+               Returns -1 when {expr} evaluates to v:false for all the items.
+               Example: >
+                       :let l = [#{n: 10}, #{n: 20}, #{n: 30]]
+                       :let idx = indexof(l, "v:val.n == 20")
+                       :let idx = indexof(l, {i, v -> v.n == 30})
+
+<              Can also be used as a |method|: >
+                       mylist->indexof(expr)
+
 input({prompt} [, {text} [, {completion}]])            *input()*
                The result is a String, which is whatever the user typed on
                the command-line.  The {prompt} argument is either a prompt
index c1f74d6241ac726c68284c0d60a28bdc2a3b514a..1938e3546075504ff588185660484ba3f080f361 100644 (file)
@@ -792,14 +792,16 @@ List manipulation:                                        *list-functions*
        reduce()                reduce a List to a value
        slice()                 take a slice of a List
        sort()                  sort a List
-       reverse()               reverse the order of a List
+       reverse()               reverse the order of a List or Blob
        uniq()                  remove copies of repeated adjacent items
        split()                 split a String into a List
        join()                  join List items into a String
        range()                 return a List with a sequence of numbers
        string()                String representation of a List
        call()                  call a function with List as arguments
-       index()                 index of a value in a List
+       index()                 index of a value in a List or Blob
+       indexof()               index in a List or Blob where an expression
+                               evaluates to true 
        max()                   maximum value in a List
        min()                   minimum value in a List
        count()                 count number of times a value appears in a List
index da2f35e5cf8306af474be70198817cd74ce79d6e..ce981bfe540148f3fccbced618b5c00c322f6cea 100644 (file)
@@ -79,6 +79,7 @@ static void f_hlID(typval_T *argvars, typval_T *rettv);
 static void f_hlexists(typval_T *argvars, typval_T *rettv);
 static void f_hostname(typval_T *argvars, typval_T *rettv);
 static void f_index(typval_T *argvars, typval_T *rettv);
+static void f_indexof(typval_T *argvars, typval_T *rettv);
 static void f_input(typval_T *argvars, typval_T *rettv);
 static void f_inputdialog(typval_T *argvars, typval_T *rettv);
 static void f_inputlist(typval_T *argvars, typval_T *rettv);
@@ -1037,6 +1038,7 @@ static argcheck_T arg23_get[] = {arg_get1, arg_string_or_nr, NULL};
 static argcheck_T arg14_glob[] = {arg_string, arg_bool, arg_bool, arg_bool};
 static argcheck_T arg25_globpath[] = {arg_string, arg_string, arg_bool, arg_bool, arg_bool};
 static argcheck_T arg24_index[] = {arg_list_or_blob, arg_item_of_prev, arg_number, arg_bool};
+static argcheck_T arg23_index[] = {arg_list_or_blob, arg_filter_func, arg_dict_any};
 static argcheck_T arg23_insert[] = {arg_list_or_blob, arg_item_of_prev, arg_number};
 static argcheck_T arg1_len[] = {arg_len1};
 static argcheck_T arg3_libcall[] = {arg_string, arg_string, arg_string_or_nr};
@@ -1995,6 +1997,8 @@ static funcentry_T global_functions[] =
                        ret_number,         f_indent},
     {"index",          2, 4, FEARG_1,      arg24_index,
                        ret_number,         f_index},
+    {"indexof",                2, 3, FEARG_1,      arg23_index,
+                       ret_number,         f_indexof},
     {"input",          1, 3, FEARG_1,      arg3_string,
                        ret_string,         f_input},
     {"inputdialog",    1, 3, FEARG_1,      arg3_string,
@@ -6789,6 +6793,136 @@ f_index(typval_T *argvars, typval_T *rettv)
     }
 }
 
+/*
+ * Evaluate 'expr' with the v:key and v:val arguments and return the result.
+ * The expression is expected to return a boolean value.  The caller should set
+ * the VV_KEY and VV_VAL vim variables before calling this function.
+ */
+    static int
+indexof_eval_expr(typval_T *expr)
+{
+    typval_T   argv[3];
+    typval_T   newtv;
+    varnumber_T        found;
+    int                error = FALSE;
+
+    argv[0] = *get_vim_var_tv(VV_KEY);
+    argv[1] = *get_vim_var_tv(VV_VAL);
+    newtv.v_type = VAR_UNKNOWN;
+
+    if (eval_expr_typval(expr, argv, 2, &newtv) == FAIL)
+       return FALSE;
+
+    found = tv_get_bool_chk(&newtv, &error);
+
+    return error ? FALSE : found;
+}
+
+/*
+ * "indexof()" function
+ */
+    static void
+f_indexof(typval_T *argvars, typval_T *rettv)
+{
+    list_T     *l;
+    listitem_T *item;
+    blob_T     *b;
+    long       startidx = 0;
+    long       idx = 0;
+    typval_T   save_val;
+    typval_T   save_key;
+    int                save_did_emsg;
+
+    rettv->vval.v_number = -1;
+
+    if (check_for_list_or_blob_arg(argvars, 0) == FAIL
+           || check_for_string_or_func_arg(argvars, 1) == FAIL
+           || check_for_opt_dict_arg(argvars, 2) == FAIL)
+       return;
+
+    if ((argvars[1].v_type == VAR_STRING && argvars[1].vval.v_string == NULL)
+           || (argvars[1].v_type == VAR_FUNC
+               && argvars[1].vval.v_partial == NULL))
+       return;
+
+    if (argvars[2].v_type == VAR_DICT)
+       startidx = dict_get_number_def(argvars[2].vval.v_dict, "startidx", 0);
+
+    prepare_vimvar(VV_VAL, &save_val);
+    prepare_vimvar(VV_KEY, &save_key);
+
+    // We reset "did_emsg" to be able to detect whether an error occurred
+    // during evaluation of the expression.
+    save_did_emsg = did_emsg;
+    did_emsg = FALSE;
+
+    if (argvars[0].v_type == VAR_BLOB)
+    {
+       b = argvars[0].vval.v_blob;
+       if (b == NULL)
+           goto theend;
+       if (startidx < 0)
+       {
+           startidx = blob_len(b) + startidx;
+           if (startidx < 0)
+               startidx = 0;
+       }
+
+       set_vim_var_type(VV_KEY, VAR_NUMBER);
+       set_vim_var_type(VV_VAL, VAR_NUMBER);
+
+       for (idx = startidx; idx < blob_len(b); ++idx)
+       {
+           set_vim_var_nr(VV_KEY, idx);
+           set_vim_var_nr(VV_VAL, blob_get(b, idx));
+
+           if (indexof_eval_expr(&argvars[1]))
+           {
+               rettv->vval.v_number = idx;
+               break;
+           }
+       }
+    }
+    else
+    {
+       l = argvars[0].vval.v_list;
+       if (l == NULL)
+           goto theend;
+
+       CHECK_LIST_MATERIALIZE(l);
+
+       if (startidx == 0)
+           item = l->lv_first;
+       else
+       {
+           // Start at specified item.  Use the cached index that list_find()
+           // sets, so that a negative number also works.
+           item = list_find(l, startidx);
+           if (item != NULL)
+               idx = l->lv_u.mat.lv_idx;
+       }
+
+       set_vim_var_type(VV_KEY, VAR_NUMBER);
+
+       for ( ; item != NULL; item = item->li_next, ++idx)
+       {
+           set_vim_var_nr(VV_KEY, idx);
+           copy_tv(&item->li_tv, get_vim_var_tv(VV_VAL));
+
+           if (indexof_eval_expr(&argvars[1]))
+           {
+               rettv->vval.v_number = idx;
+               break;
+           }
+       }
+    }
+
+theend:
+    restore_vimvar(VV_KEY, &save_key);
+    restore_vimvar(VV_VAL, &save_val);
+    did_emsg |= save_did_emsg;
+}
+
 static int inputsecret_flag = 0;
 
 /*
index ff4186a7305055e566d0e258664bb5929bbe3a0c..46f2d61ad3aa66cf3aa9fa5afc91d23de129ee9a 100644 (file)
@@ -764,4 +764,30 @@ func Test_blob_alloc_failure()
   call assert_equal(0, x)
 endfunc
 
+" Test for the indexof() function
+func Test_indexof()
+  let b = 0zdeadbeef
+  call assert_equal(0, indexof(b, {i, v -> v == 0xde}))
+  call assert_equal(3, indexof(b, {i, v -> v == 0xef}))
+  call assert_equal(-1, indexof(b, {i, v -> v == 0x1}))
+  call assert_equal(1, indexof(b, "v:val == 0xad"))
+  call assert_equal(-1, indexof(b, "v:val == 0xff"))
+
+  call assert_equal(-1, indexof(0z, "v:val == 0x0"))
+  call assert_equal(-1, indexof(test_null_blob(), "v:val == 0xde"))
+  call assert_equal(-1, indexof(b, test_null_string()))
+  call assert_equal(-1, indexof(b, test_null_function()))
+
+  let b = 0z01020102
+  call assert_equal(1, indexof(b, "v:val == 0x02", #{startidx: 0}))
+  call assert_equal(2, indexof(b, "v:val == 0x01", #{startidx: -2}))
+  call assert_equal(-1, indexof(b, "v:val == 0x01", #{startidx: 5}))
+  call assert_equal(0, indexof(b, "v:val == 0x01", #{startidx: -5}))
+  call assert_equal(0, indexof(b, "v:val == 0x01", test_null_dict()))
+
+  " failure cases
+  call assert_fails('let i = indexof(b, "val == 0xde")', 'E121:')
+  call assert_fails('let i = indexof(b, {})', 'E1256:')
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
index 1a6a1d045bbf90706d11be7131c67dbede7cd614..2c4e5c722dc6595e6fa15c318e24e64b9b940245 100644 (file)
@@ -1446,4 +1446,49 @@ func Test_null_dict()
   unlockvar d
 endfunc
 
+" Test for the indexof() function
+func Test_indexof()
+  let l = [#{color: 'red'}, #{color: 'blue'}, #{color: 'green'}]
+  call assert_equal(0, indexof(l, {i, v -> v.color == 'red'}))
+  call assert_equal(2, indexof(l, {i, v -> v.color == 'green'}))
+  call assert_equal(-1, indexof(l, {i, v -> v.color == 'grey'}))
+  call assert_equal(1, indexof(l, "v:val.color == 'blue'"))
+  call assert_equal(-1, indexof(l, "v:val.color == 'cyan'"))
+
+  let l = [#{n: 10}, #{n: 10}, #{n: 20}]
+  call assert_equal(0, indexof(l, "v:val.n == 10", #{startidx: 0}))
+  call assert_equal(1, indexof(l, "v:val.n == 10", #{startidx: -2}))
+  call assert_equal(-1, indexof(l, "v:val.n == 10", #{startidx: 4}))
+  call assert_equal(-1, indexof(l, "v:val.n == 10", #{startidx: -4}))
+  call assert_equal(0, indexof(l, "v:val.n == 10", test_null_dict()))
+
+  call assert_equal(-1, indexof([], {i, v -> v == 'a'}))
+  call assert_equal(-1, indexof(test_null_list(), {i, v -> v == 'a'}))
+  call assert_equal(-1, indexof(l, test_null_string()))
+  call assert_equal(-1, indexof(l, test_null_function()))
+
+  " failure cases
+  call assert_fails('let i = indexof(l, "v:val == ''cyan''")', 'E735:')
+  call assert_fails('let i = indexof(l, "color == ''cyan''")', 'E121:')
+  call assert_fails('let i = indexof(l, {})', 'E1256:')
+  call assert_fails('let i = indexof({}, "v:val == 2")', 'E1226:')
+  call assert_fails('let i = indexof([], "v:val == 2", [])', 'E1206:')
+
+  func TestIdx(k, v)
+    return a:v.n == 20
+  endfunc
+  call assert_equal(2, indexof(l, function("TestIdx")))
+  delfunc TestIdx
+  func TestIdx(k, v)
+    return {}
+  endfunc
+  call assert_fails('let i = indexof(l, function("TestIdx"))', 'E728:')
+  delfunc TestIdx
+  func TestIdx(k, v)
+    throw "IdxError"
+  endfunc
+  call assert_fails('let i = indexof(l, function("TestIdx"))', 'E605:')
+  delfunc TestIdx
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
index b8e2ca7e387dc40d5865b14a1212cdcb3e88d370..212e9f11b7f853a30b6ca4a0884cd72faeafdeef 100644 (file)
@@ -2066,6 +2066,13 @@ def Test_index()
   v9.CheckDefAndScriptFailure(['index(0z1020, 10, 1, 2)'], ['E1013: Argument 4: type mismatch, expected bool but got number', 'E1212: Bool required for argument 4'])
 enddef
 
+def Test_indexof()
+  var l = [{color: 'red'}, {color: 'blue'}, {color: 'green'}]
+  indexof(l, (i, v) => v.color == 'green')->assert_equal(2)
+  var b = 0zdeadbeef
+  indexof(b, "v:val == 0xef")->assert_equal(3)
+enddef
+
 def Test_input()
   v9.CheckDefAndScriptFailure(['input(5)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
   v9.CheckDefAndScriptFailure(['input(["a"])'], ['E1013: Argument 1: type mismatch, expected string but got list<string>', 'E1174: String required for argument 1'])
index 72890e46b3c35eb4ccc7a553443ee71c8d7c726f..65bbcf7392ed000ca23fe3904cc4d85ebb8fa71b 100644 (file)
@@ -735,6 +735,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    196,
 /**/
     195,
 /**/