neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

list.c (23782B)


      1 // eval/list.c: List support and container (List, Dict, Blob) functions.
      2 
      3 #include "nvim/errors.h"
      4 #include "nvim/eval.h"
      5 #include "nvim/eval/list.h"
      6 #include "nvim/eval/typval.h"
      7 #include "nvim/eval/vars.h"
      8 #include "nvim/ex_docmd.h"
      9 #include "nvim/garray.h"
     10 #include "nvim/globals.h"
     11 #include "nvim/mbyte.h"
     12 #include "nvim/strings.h"
     13 #include "nvim/vim_defs.h"
     14 
     15 /// Enum used by filter(), map(), mapnew() and foreach()
     16 typedef enum {
     17  FILTERMAP_FILTER,
     18  FILTERMAP_MAP,
     19  FILTERMAP_MAPNEW,
     20  FILTERMAP_FOREACH,
     21 } filtermap_T;
     22 
     23 #include "eval/list.c.generated.h"
     24 
     25 static const char e_argument_of_str_must_be_list_string_or_dictionary[]
     26  = N_("E706: Argument of %s must be a List, String or Dictionary");
     27 static const char e_argument_of_str_must_be_list_string_dictionary_or_blob[]
     28  = N_("E1250: Argument of %s must be a List, String, Dictionary or Blob");
     29 
     30 /// Handle one item for map(), filter(), foreach().
     31 /// Sets v:val to "tv".  Caller must set v:key.
     32 ///
     33 /// @param tv     original value
     34 /// @param expr   callback
     35 /// @param newtv  for map() an mapnew(): new value
     36 /// @param remp   for filter(): remove flag
     37 static int filter_map_one(typval_T *tv, typval_T *expr, const filtermap_T filtermap,
     38                          typval_T *newtv, bool *remp)
     39  FUNC_ATTR_NONNULL_ALL
     40 {
     41  typval_T argv[3];
     42  int retval = FAIL;
     43 
     44  tv_copy(tv, get_vim_var_tv(VV_VAL));
     45 
     46  newtv->v_type = VAR_UNKNOWN;
     47  if (filtermap == FILTERMAP_FOREACH && expr->v_type == VAR_STRING) {
     48    // foreach() is not limited to an expression
     49    do_cmdline_cmd(expr->vval.v_string);
     50    if (!did_emsg) {
     51      retval = OK;
     52    }
     53    goto theend;
     54  }
     55 
     56  argv[0] = *get_vim_var_tv(VV_KEY);
     57  argv[1] = *get_vim_var_tv(VV_VAL);
     58  if (eval_expr_typval(expr, false, argv, 2, newtv) == FAIL) {
     59    goto theend;
     60  }
     61  if (filtermap == FILTERMAP_FILTER) {
     62    bool error = false;
     63 
     64    // filter(): when expr is zero remove the item
     65    *remp = (tv_get_number_chk(newtv, &error) == 0);
     66    tv_clear(newtv);
     67    // On type error, nothing has been removed; return FAIL to stop the
     68    // loop.  The error message was given by tv_get_number_chk().
     69    if (error) {
     70      goto theend;
     71    }
     72  } else if (filtermap == FILTERMAP_FOREACH) {
     73    tv_clear(newtv);
     74  }
     75  retval = OK;
     76 theend:
     77  tv_clear(get_vim_var_tv(VV_VAL));
     78  return retval;
     79 }
     80 
     81 /// Implementation of map(), filter(), foreach() for a Dict.  Apply "expr" to
     82 /// every item in Dict "d" and return the result in "rettv".
     83 static void filter_map_dict(dict_T *d, filtermap_T filtermap, const char *func_name,
     84                            const char *arg_errmsg, typval_T *expr, typval_T *rettv)
     85 {
     86  if (filtermap == FILTERMAP_MAPNEW) {
     87    rettv->v_type = VAR_DICT;
     88    rettv->vval.v_dict = NULL;
     89  }
     90  if (d == NULL
     91      || (filtermap == FILTERMAP_FILTER
     92          && value_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) {
     93    return;
     94  }
     95 
     96  dict_T *d_ret = NULL;
     97 
     98  if (filtermap == FILTERMAP_MAPNEW) {
     99    tv_dict_alloc_ret(rettv);
    100    d_ret = rettv->vval.v_dict;
    101  }
    102 
    103  const VarLockStatus prev_lock = d->dv_lock;
    104  if (d->dv_lock == VAR_UNLOCKED) {
    105    d->dv_lock = VAR_LOCKED;
    106  }
    107  hash_lock(&d->dv_hashtab);
    108  TV_DICT_ITER(d, di, {
    109    if (filtermap == FILTERMAP_MAP
    110        && (value_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE)
    111            || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) {
    112      break;
    113    }
    114    set_vim_var_string(VV_KEY, di->di_key, -1);
    115    typval_T newtv;
    116    bool rem;
    117    int r = filter_map_one(&di->di_tv, expr, filtermap, &newtv, &rem);
    118    tv_clear(get_vim_var_tv(VV_KEY));
    119    if (r == FAIL || did_emsg) {
    120      tv_clear(&newtv);
    121      break;
    122    }
    123    if (filtermap == FILTERMAP_MAP) {
    124      // map(): replace the dict item value
    125      tv_clear(&di->di_tv);
    126      newtv.v_lock = VAR_UNLOCKED;
    127      di->di_tv = newtv;
    128    } else if (filtermap == FILTERMAP_MAPNEW) {
    129      // mapnew(): add the item value to the new dict
    130      r = tv_dict_add_tv(d_ret, di->di_key, strlen(di->di_key), &newtv);
    131      tv_clear(&newtv);
    132      if (r == FAIL) {
    133        break;
    134      }
    135    } else if (filtermap == FILTERMAP_FILTER && rem) {
    136      // filter(false): remove the item from the dict
    137      if (var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE)
    138          || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) {
    139        break;
    140      }
    141      tv_dict_item_remove(d, di);
    142    }
    143  });
    144  hash_unlock(&d->dv_hashtab);
    145  d->dv_lock = prev_lock;
    146 }
    147 
    148 /// Implementation of map(), filter(), foreach() for a Blob.
    149 static void filter_map_blob(blob_T *blob_arg, filtermap_T filtermap, typval_T *expr,
    150                            const char *arg_errmsg, typval_T *rettv)
    151 {
    152  if (filtermap == FILTERMAP_MAPNEW) {
    153    rettv->v_type = VAR_BLOB;
    154    rettv->vval.v_blob = NULL;
    155  }
    156  blob_T *b = blob_arg;
    157  if (b == NULL
    158      || (filtermap == FILTERMAP_FILTER
    159          && value_check_lock(b->bv_lock, arg_errmsg, TV_TRANSLATE))) {
    160    return;
    161  }
    162 
    163  blob_T *b_ret = b;
    164 
    165  if (filtermap == FILTERMAP_MAPNEW) {
    166    tv_blob_copy(b, rettv);
    167    b_ret = rettv->vval.v_blob;
    168  }
    169 
    170  // set_vim_var_nr() doesn't set the type
    171  set_vim_var_type(VV_KEY, VAR_NUMBER);
    172 
    173  const VarLockStatus prev_lock = b->bv_lock;
    174  if (b->bv_lock == 0) {
    175    b->bv_lock = VAR_LOCKED;
    176  }
    177 
    178  for (int i = 0, idx = 0; i < b->bv_ga.ga_len; i++) {
    179    const varnumber_T val = tv_blob_get(b, i);
    180    typval_T tv = {
    181      .v_type = VAR_NUMBER,
    182      .v_lock = VAR_UNLOCKED,
    183      .vval.v_number = val,
    184    };
    185    set_vim_var_nr(VV_KEY, idx);
    186    typval_T newtv;
    187    bool rem;
    188    if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
    189        || did_emsg) {
    190      break;
    191    }
    192    if (filtermap != FILTERMAP_FOREACH) {
    193      if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL) {
    194        tv_clear(&newtv);
    195        emsg(_(e_invalblob));
    196        break;
    197      }
    198      if (filtermap != FILTERMAP_FILTER) {
    199        if (newtv.vval.v_number != val) {
    200          tv_blob_set(b_ret, i, (uint8_t)newtv.vval.v_number);
    201        }
    202      } else if (rem) {
    203        char *const p = (char *)blob_arg->bv_ga.ga_data;
    204        memmove(p + i, p + i + 1, (size_t)(b->bv_ga.ga_len - i - 1));
    205        b->bv_ga.ga_len--;
    206        i--;
    207      }
    208    }
    209    idx++;
    210  }
    211 
    212  b->bv_lock = prev_lock;
    213 }
    214 
    215 /// Implementation of map(), filter(), foreach() for a String.
    216 static void filter_map_string(const char *str, filtermap_T filtermap, typval_T *expr,
    217                              typval_T *rettv)
    218 {
    219  rettv->v_type = VAR_STRING;
    220  rettv->vval.v_string = NULL;
    221 
    222  // set_vim_var_nr() doesn't set the type
    223  set_vim_var_type(VV_KEY, VAR_NUMBER);
    224 
    225  garray_T ga;
    226  ga_init(&ga, (int)sizeof(char), 80);
    227  int len = 0;
    228  int idx = 0;
    229  for (const char *p = str; *p != NUL; p += len) {
    230    len = utfc_ptr2len(p);
    231    typval_T tv = {
    232      .v_type = VAR_STRING,
    233      .v_lock = VAR_UNLOCKED,
    234      .vval.v_string = xmemdupz(p, (size_t)len),
    235    };
    236 
    237    set_vim_var_nr(VV_KEY, idx);
    238    typval_T newtv = {
    239      .v_type = VAR_UNKNOWN,
    240    };
    241    bool rem;
    242    if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
    243        || did_emsg) {
    244      tv_clear(&newtv);
    245      tv_clear(&tv);
    246      break;
    247    }
    248    if (filtermap == FILTERMAP_MAP || filtermap == FILTERMAP_MAPNEW) {
    249      if (newtv.v_type != VAR_STRING) {
    250        tv_clear(&newtv);
    251        tv_clear(&tv);
    252        emsg(_(e_string_required));
    253        break;
    254      } else {
    255        ga_concat(&ga, newtv.vval.v_string);
    256      }
    257    } else if (filtermap == FILTERMAP_FOREACH || !rem) {
    258      ga_concat(&ga, tv.vval.v_string);
    259    }
    260 
    261    tv_clear(&newtv);
    262    tv_clear(&tv);
    263 
    264    idx++;
    265  }
    266  ga_append(&ga, NUL);
    267  rettv->vval.v_string = ga.ga_data;
    268 }
    269 
    270 /// Implementation of map(), filter(), foreach() for a List.  Apply "expr" to
    271 /// every item in List "l" and return the result in "rettv".
    272 static void filter_map_list(list_T *l, filtermap_T filtermap, const char *func_name,
    273                            const char *arg_errmsg, typval_T *expr, typval_T *rettv)
    274 {
    275  if (filtermap == FILTERMAP_MAPNEW) {
    276    rettv->v_type = VAR_LIST;
    277    rettv->vval.v_list = NULL;
    278  }
    279  if (l == NULL
    280      || (filtermap == FILTERMAP_FILTER
    281          && value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE))) {
    282    return;
    283  }
    284 
    285  list_T *l_ret = NULL;
    286 
    287  if (filtermap == FILTERMAP_MAPNEW) {
    288    tv_list_alloc_ret(rettv, kListLenUnknown);
    289    l_ret = rettv->vval.v_list;
    290  }
    291  // set_vim_var_nr() doesn't set the type
    292  set_vim_var_type(VV_KEY, VAR_NUMBER);
    293 
    294  const VarLockStatus prev_lock = tv_list_locked(l);
    295  if (tv_list_locked(l) == VAR_UNLOCKED) {
    296    tv_list_set_lock(l, VAR_LOCKED);
    297  }
    298 
    299  int idx = 0;
    300  for (listitem_T *li = tv_list_first(l); li != NULL;) {
    301    if (filtermap == FILTERMAP_MAP
    302        && value_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, TV_TRANSLATE)) {
    303      break;
    304    }
    305    set_vim_var_nr(VV_KEY, idx);
    306    typval_T newtv;
    307    bool rem;
    308    if (filter_map_one(TV_LIST_ITEM_TV(li), expr, filtermap, &newtv, &rem) == FAIL) {
    309      break;
    310    }
    311    if (did_emsg) {
    312      tv_clear(&newtv);
    313      break;
    314    }
    315    if (filtermap == FILTERMAP_MAP) {
    316      // map(): replace the list item value
    317      tv_clear(TV_LIST_ITEM_TV(li));
    318      newtv.v_lock = VAR_UNLOCKED;
    319      *TV_LIST_ITEM_TV(li) = newtv;
    320    } else if (filtermap == FILTERMAP_MAPNEW) {
    321      // mapnew(): append the list item value
    322      tv_list_append_owned_tv(l_ret, newtv);
    323    }
    324    if (filtermap == FILTERMAP_FILTER && rem) {
    325      li = tv_list_item_remove(l, li);
    326    } else {
    327      li = TV_LIST_ITEM_NEXT(l, li);
    328    }
    329    idx++;
    330  }
    331 
    332  tv_list_set_lock(l, prev_lock);
    333 }
    334 
    335 /// Implementation of map(), filter() and foreach().
    336 static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
    337 {
    338  const char *const func_name = (filtermap == FILTERMAP_MAP
    339                                 ? "map()"
    340                                 : (filtermap == FILTERMAP_MAPNEW
    341                                    ? "mapnew()"
    342                                    : (filtermap == FILTERMAP_FILTER
    343                                       ? "filter()"
    344                                       : "foreach()")));
    345  const char *const arg_errmsg = (filtermap == FILTERMAP_MAP
    346                                  ? N_("map() argument")
    347                                  : (filtermap == FILTERMAP_MAPNEW
    348                                     ? N_("mapnew() argument")
    349                                     : (filtermap == FILTERMAP_FILTER
    350                                        ? N_("filter() argument")
    351                                        : N_("foreach() argument"))));
    352 
    353  // map(), filter(), foreach() return the first argument, also on failure.
    354  if (filtermap != FILTERMAP_MAPNEW && argvars[0].v_type != VAR_STRING) {
    355    tv_copy(&argvars[0], rettv);
    356  }
    357 
    358  if (argvars[0].v_type != VAR_BLOB
    359      && argvars[0].v_type != VAR_LIST
    360      && argvars[0].v_type != VAR_DICT
    361      && argvars[0].v_type != VAR_STRING) {
    362    semsg(_(e_argument_of_str_must_be_list_string_dictionary_or_blob), func_name);
    363    return;
    364  }
    365 
    366  typval_T *expr = &argvars[1];
    367  // On type errors, the preceding call has already displayed an error
    368  // message.  Avoid a misleading error message for an empty string that
    369  // was not passed as argument.
    370  if (expr->v_type == VAR_UNKNOWN) {
    371    return;
    372  }
    373 
    374  typval_T save_val;
    375  typval_T save_key;
    376 
    377  prepare_vimvar(VV_VAL, &save_val);
    378  prepare_vimvar(VV_KEY, &save_key);
    379 
    380  // We reset "did_emsg" to be able to detect whether an error
    381  // occurred during evaluation of the expression.
    382  int save_did_emsg = did_emsg;
    383  did_emsg = false;
    384 
    385  if (argvars[0].v_type == VAR_DICT) {
    386    filter_map_dict(argvars[0].vval.v_dict, filtermap, func_name,
    387                    arg_errmsg, expr, rettv);
    388  } else if (argvars[0].v_type == VAR_BLOB) {
    389    filter_map_blob(argvars[0].vval.v_blob, filtermap, expr, arg_errmsg, rettv);
    390  } else if (argvars[0].v_type == VAR_STRING) {
    391    filter_map_string(tv_get_string(&argvars[0]), filtermap, expr, rettv);
    392  } else {
    393    assert(argvars[0].v_type == VAR_LIST);
    394    filter_map_list(argvars[0].vval.v_list, filtermap, func_name,
    395                    arg_errmsg, expr, rettv);
    396  }
    397 
    398  restore_vimvar(VV_KEY, &save_key);
    399  restore_vimvar(VV_VAL, &save_val);
    400 
    401  did_emsg |= save_did_emsg;
    402 }
    403 
    404 /// "filter()" function
    405 void f_filter(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    406 {
    407  filter_map(argvars, rettv, FILTERMAP_FILTER);
    408 }
    409 
    410 /// "map()" function
    411 void f_map(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    412 {
    413  filter_map(argvars, rettv, FILTERMAP_MAP);
    414 }
    415 
    416 /// "mapnew()" function
    417 void f_mapnew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    418 {
    419  filter_map(argvars, rettv, FILTERMAP_MAPNEW);
    420 }
    421 
    422 /// "foreach()" function
    423 void f_foreach(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    424 {
    425  filter_map(argvars, rettv, FILTERMAP_FOREACH);
    426 }
    427 
    428 /// "add(list, item)" function
    429 void f_add(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    430 {
    431  rettv->vval.v_number = 1;  // Default: failed.
    432  if (argvars[0].v_type == VAR_LIST) {
    433    list_T *const l = argvars[0].vval.v_list;
    434    if (!value_check_lock(tv_list_locked(l), N_("add() argument"),
    435                          TV_TRANSLATE)) {
    436      tv_list_append_tv(l, &argvars[1]);
    437      tv_copy(&argvars[0], rettv);
    438    }
    439  } else if (argvars[0].v_type == VAR_BLOB) {
    440    blob_T *const b = argvars[0].vval.v_blob;
    441    if (b != NULL
    442        && !value_check_lock(b->bv_lock, N_("add() argument"), TV_TRANSLATE)) {
    443      bool error = false;
    444      const varnumber_T n = tv_get_number_chk(&argvars[1], &error);
    445 
    446      if (!error) {
    447        ga_append(&b->bv_ga, (uint8_t)n);
    448        tv_copy(&argvars[0], rettv);
    449      }
    450    }
    451  } else {
    452    emsg(_(e_listblobreq));
    453  }
    454 }
    455 
    456 /// Count the number of times "needle" occurs in string "haystack".
    457 ///
    458 /// @param ic  ignore case
    459 static varnumber_T count_string(const char *haystack, const char *needle, bool ic)
    460 {
    461  varnumber_T n = 0;
    462  const char *p = haystack;
    463 
    464  if (p == NULL || needle == NULL || *needle == NUL) {
    465    return 0;
    466  }
    467 
    468  size_t needlelen = strlen(needle);
    469  if (ic) {
    470    while (*p != NUL) {
    471      if (mb_strnicmp(p, needle, needlelen) == 0) {
    472        n++;
    473        p += needlelen;
    474      } else {
    475        MB_PTR_ADV(p);
    476      }
    477    }
    478  } else {
    479    const char *next;
    480    while ((next = strstr(p, needle)) != NULL) {
    481      n++;
    482      p = next + needlelen;
    483    }
    484  }
    485 
    486  return n;
    487 }
    488 
    489 /// Count the number of times item "needle" occurs in List "l" starting at index "idx".
    490 ///
    491 /// @param ic  ignore case
    492 static varnumber_T count_list(list_T *l, typval_T *needle, int64_t idx, bool ic)
    493 {
    494  if (tv_list_len(l) == 0) {
    495    return 0;
    496  }
    497 
    498  listitem_T *li = tv_list_find(l, (int)idx);
    499  if (li == NULL) {
    500    semsg(_(e_list_index_out_of_range_nr), idx);
    501    return 0;
    502  }
    503 
    504  varnumber_T n = 0;
    505 
    506  for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
    507    if (tv_equal(TV_LIST_ITEM_TV(li), needle, ic)) {
    508      n++;
    509    }
    510  }
    511 
    512  return n;
    513 }
    514 
    515 /// Count the number of times item "needle" occurs in Dict "d".
    516 ///
    517 /// @param ic  ignore case
    518 static varnumber_T count_dict(dict_T *d, typval_T *needle, bool ic)
    519 {
    520  if (d == NULL) {
    521    return 0;
    522  }
    523 
    524  varnumber_T n = 0;
    525 
    526  TV_DICT_ITER(d, di, {
    527    if (tv_equal(&di->di_tv, needle, ic)) {
    528      n++;
    529    }
    530  });
    531 
    532  return n;
    533 }
    534 
    535 /// "count()" function
    536 void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    537 {
    538  varnumber_T n = 0;
    539  int ic = 0;
    540  bool error = false;
    541 
    542  if (argvars[2].v_type != VAR_UNKNOWN) {
    543    ic = (int)tv_get_number_chk(&argvars[2], &error);
    544  }
    545 
    546  if (!error && argvars[0].v_type == VAR_STRING) {
    547    n = count_string(argvars[0].vval.v_string, tv_get_string_chk(&argvars[1]), ic);
    548  } else if (!error && argvars[0].v_type == VAR_LIST) {
    549    int64_t idx = 0;
    550    if (argvars[2].v_type != VAR_UNKNOWN
    551        && argvars[3].v_type != VAR_UNKNOWN) {
    552      idx = (int64_t)tv_get_number_chk(&argvars[3], &error);
    553    }
    554    if (!error) {
    555      n = count_list(argvars[0].vval.v_list, &argvars[1], idx, ic);
    556    }
    557  } else if (!error && argvars[0].v_type == VAR_DICT) {
    558    dict_T *d = argvars[0].vval.v_dict;
    559 
    560    if (d != NULL) {
    561      if (argvars[2].v_type != VAR_UNKNOWN
    562          && argvars[3].v_type != VAR_UNKNOWN) {
    563        emsg(_(e_invarg));
    564      } else {
    565        n = count_dict(argvars[0].vval.v_dict, &argvars[1], ic);
    566      }
    567    }
    568  } else if (!error) {
    569    semsg(_(e_argument_of_str_must_be_list_string_or_dictionary), "count()");
    570  }
    571  rettv->vval.v_number = n;
    572 }
    573 
    574 /// extend() a Dict. Append Dict argvars[1] to Dict argvars[0] and return the
    575 /// resulting Dict in "rettv".
    576 ///
    577 /// @param is_new  true for extendnew()
    578 static void extend_dict(typval_T *argvars, const char *arg_errmsg, bool is_new, typval_T *rettv)
    579 {
    580  dict_T *d1 = argvars[0].vval.v_dict;
    581  if (d1 == NULL) {
    582    const bool locked = value_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE);
    583    (void)locked;
    584    assert(locked == true);
    585    return;
    586  }
    587  dict_T *const d2 = argvars[1].vval.v_dict;
    588  if (d2 == NULL) {
    589    // Do nothing
    590    tv_copy(&argvars[0], rettv);
    591    return;
    592  }
    593 
    594  if (!is_new && value_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) {
    595    return;
    596  }
    597 
    598  if (is_new) {
    599    d1 = tv_dict_copy(NULL, d1, false, get_copyID());
    600    if (d1 == NULL) {
    601      return;
    602    }
    603  }
    604 
    605  const char *action = "force";
    606  // Check the third argument.
    607  if (argvars[2].v_type != VAR_UNKNOWN) {
    608    const char *const av[] = { "keep", "force", "error" };
    609 
    610    action = tv_get_string_chk(&argvars[2]);
    611    if (action == NULL) {
    612      if (is_new) {
    613        tv_dict_unref(d1);
    614      }
    615      return;  // Type error; error message already given.
    616    }
    617    size_t i;
    618    for (i = 0; i < ARRAY_SIZE(av); i++) {
    619      if (strcmp(action, av[i]) == 0) {
    620        break;
    621      }
    622    }
    623    if (i == 3) {
    624      if (is_new) {
    625        tv_dict_unref(d1);
    626      }
    627      semsg(_(e_invarg2), action);
    628      return;
    629    }
    630  }
    631 
    632  tv_dict_extend(d1, d2, action);
    633 
    634  if (is_new) {
    635    *rettv = (typval_T){
    636      .v_type = VAR_DICT,
    637      .v_lock = VAR_UNLOCKED,
    638      .vval.v_dict = d1,
    639    };
    640  } else {
    641    tv_copy(&argvars[0], rettv);
    642  }
    643 }
    644 
    645 /// extend() a List. Append List argvars[1] to List argvars[0] before index
    646 /// argvars[3] and return the resulting list in "rettv".
    647 ///
    648 /// @param is_new  true for extendnew()
    649 static void extend_list(typval_T *argvars, const char *arg_errmsg, bool is_new, typval_T *rettv)
    650 {
    651  bool error = false;
    652 
    653  list_T *l1 = argvars[0].vval.v_list;
    654  list_T *const l2 = argvars[1].vval.v_list;
    655 
    656  if (!is_new && value_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) {
    657    return;
    658  }
    659 
    660  if (is_new) {
    661    l1 = tv_list_copy(NULL, l1, false, get_copyID());
    662    if (l1 == NULL) {
    663      return;
    664    }
    665  }
    666 
    667  listitem_T *item;
    668  if (argvars[2].v_type != VAR_UNKNOWN) {
    669    int before = (int)tv_get_number_chk(&argvars[2], &error);
    670    if (error) {
    671      return;  // Type error; errmsg already given.
    672    }
    673 
    674    if (before == tv_list_len(l1)) {
    675      item = NULL;
    676    } else {
    677      item = tv_list_find(l1, before);
    678      if (item == NULL) {
    679        semsg(_(e_list_index_out_of_range_nr), (int64_t)before);
    680        return;
    681      }
    682    }
    683  } else {
    684    item = NULL;
    685  }
    686  tv_list_extend(l1, l2, item);
    687 
    688  if (is_new) {
    689    *rettv = (typval_T){
    690      .v_type = VAR_LIST,
    691      .v_lock = VAR_UNLOCKED,
    692      .vval.v_list = l1,
    693    };
    694  } else {
    695    tv_copy(&argvars[0], rettv);
    696  }
    697 }
    698 
    699 /// "extend()" or "extendnew()" function.
    700 ///
    701 /// @param is_new  true for extendnew()
    702 static void extend(typval_T *argvars, typval_T *rettv, char *arg_errmsg, bool is_new)
    703 {
    704  if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) {
    705    extend_list(argvars, arg_errmsg, is_new, rettv);
    706  } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == VAR_DICT) {
    707    extend_dict(argvars, arg_errmsg, is_new, rettv);
    708  } else {
    709    semsg(_(e_listdictarg), is_new ? "extendnew()" : "extend()");
    710  }
    711 }
    712 
    713 /// "extend(list, list [, idx])" function
    714 /// "extend(dict, dict [, action])" function
    715 void f_extend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    716 {
    717  char *errmsg = N_("extend() argument");
    718  extend(argvars, rettv, errmsg, false);
    719 }
    720 
    721 /// "extendnew(list, list [, idx])" function
    722 /// "extendnew(dict, dict [, action])" function
    723 void f_extendnew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    724 {
    725  char *errmsg = N_("extendnew() argument");
    726  extend(argvars, rettv, errmsg, true);
    727 }
    728 
    729 /// "insert()" function
    730 void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    731 {
    732  bool error = false;
    733 
    734  if (argvars[0].v_type == VAR_BLOB) {
    735    blob_T *const b = argvars[0].vval.v_blob;
    736 
    737    if (b == NULL
    738        || value_check_lock(b->bv_lock, N_("insert() argument"),
    739                            TV_TRANSLATE)) {
    740      return;
    741    }
    742 
    743    int before = 0;
    744    const int len = tv_blob_len(b);
    745 
    746    if (argvars[2].v_type != VAR_UNKNOWN) {
    747      before = (int)tv_get_number_chk(&argvars[2], &error);
    748      if (error) {
    749        return;  // type error; errmsg already given
    750      }
    751      if (before < 0 || before > len) {
    752        semsg(_(e_invarg2), tv_get_string(&argvars[2]));
    753        return;
    754      }
    755    }
    756    const int val = (int)tv_get_number_chk(&argvars[1], &error);
    757    if (error) {
    758      return;
    759    }
    760    if (val < 0 || val > 255) {
    761      semsg(_(e_invarg2), tv_get_string(&argvars[1]));
    762      return;
    763    }
    764 
    765    ga_grow(&b->bv_ga, 1);
    766    uint8_t *const p = (uint8_t *)b->bv_ga.ga_data;
    767    memmove(p + before + 1, p + before, (size_t)(len - before));
    768    *(p + before) = (uint8_t)val;
    769    b->bv_ga.ga_len++;
    770 
    771    tv_copy(&argvars[0], rettv);
    772  } else if (argvars[0].v_type != VAR_LIST) {
    773    semsg(_(e_listblobarg), "insert()");
    774  } else {
    775    list_T *l = argvars[0].vval.v_list;
    776    if (value_check_lock(tv_list_locked(l), N_("insert() argument"), TV_TRANSLATE)) {
    777      return;
    778    }
    779 
    780    int64_t before = 0;
    781    if (argvars[2].v_type != VAR_UNKNOWN) {
    782      before = tv_get_number_chk(&argvars[2], &error);
    783    }
    784    if (error) {
    785      // type error; errmsg already given
    786      return;
    787    }
    788 
    789    listitem_T *item = NULL;
    790    if (before != tv_list_len(l)) {
    791      item = tv_list_find(l, (int)before);
    792      if (item == NULL) {
    793        semsg(_(e_list_index_out_of_range_nr), before);
    794        l = NULL;
    795      }
    796    }
    797    if (l != NULL) {
    798      tv_list_insert_tv(l, &argvars[1], item);
    799      tv_copy(&argvars[0], rettv);
    800    }
    801  }
    802 }
    803 
    804 /// "remove()" function
    805 void f_remove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    806 {
    807  const char *const arg_errmsg = N_("remove() argument");
    808 
    809  if (argvars[0].v_type == VAR_DICT) {
    810    tv_dict_remove(argvars, rettv, arg_errmsg);
    811  } else if (argvars[0].v_type == VAR_BLOB) {
    812    tv_blob_remove(argvars, rettv, arg_errmsg);
    813  } else if (argvars[0].v_type == VAR_LIST) {
    814    tv_list_remove(argvars, rettv, arg_errmsg);
    815  } else {
    816    semsg(_(e_listdictblobarg), "remove()");
    817  }
    818 }
    819 
    820 /// "reverse({list})" function
    821 void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    822 {
    823  if (tv_check_for_string_or_list_or_blob_arg(argvars, 0) == FAIL) {
    824    return;
    825  }
    826 
    827  if (argvars[0].v_type == VAR_BLOB) {
    828    blob_T *const b = argvars[0].vval.v_blob;
    829    const int len = tv_blob_len(b);
    830 
    831    for (int i = 0; i < len / 2; i++) {
    832      const uint8_t tmp = tv_blob_get(b, i);
    833      tv_blob_set(b, i, tv_blob_get(b, len - i - 1));
    834      tv_blob_set(b, len - i - 1, tmp);
    835    }
    836    tv_blob_set_ret(rettv, b);
    837  } else if (argvars[0].v_type == VAR_STRING) {
    838    rettv->v_type = VAR_STRING;
    839    if (argvars[0].vval.v_string != NULL) {
    840      rettv->vval.v_string = reverse_text(argvars[0].vval.v_string);
    841    } else {
    842      rettv->vval.v_string = NULL;
    843    }
    844  } else if (argvars[0].v_type == VAR_LIST) {
    845    list_T *const l = argvars[0].vval.v_list;
    846    if (!value_check_lock(tv_list_locked(l), N_("reverse() argument"),
    847                          TV_TRANSLATE)) {
    848      tv_list_reverse(l);
    849      tv_list_set_ret(rettv, l);
    850    }
    851  }
    852 }