# HG changeset patch # User Bram Moolenaar # Date 1640197804 -3600 # Node ID 2126feddeda68dc3a0b6feb51d6c0c1796227eab # Parent d267ab922b5741246e43f8df6f954706940468c0 patch 8.2.3871: list.c contains code for dict and blob Commit: https://github.com/vim/vim/commit/f973eeb4911de09258e84cb2248dc0f9392824b4 Author: Yegappan Lakshmanan Date: Wed Dec 22 18:19:26 2021 +0000 patch 8.2.3871: list.c contains code for dict and blob Problem: List.c contains code for dict and blob. Solution: Refactor to put code where it belongs. (Yegappan Lakshmanan, closes #9386) diff --git a/src/blob.c b/src/blob.c --- a/src/blob.c +++ b/src/blob.c @@ -410,81 +410,309 @@ blob_set_range(blob_T *dest, long n1, lo } /* - * "remove({blob})" function + * "add(blob, item)" function + */ + void +blob_add(typval_T *argvars, typval_T *rettv) +{ + blob_T *b = argvars[0].vval.v_blob; + int error = FALSE; + varnumber_T n; + + if (b == NULL) + { + if (in_vim9script()) + emsg(_(e_cannot_add_to_null_blob)); + return; + } + + if (value_check_lock(b->bv_lock, (char_u *)N_("add() argument"), TRUE)) + return; + + n = tv_get_number_chk(&argvars[1], &error); + if (error) + return; + + ga_append(&b->bv_ga, (int)n); + copy_tv(&argvars[0], rettv); +} + +/* + * "remove({blob}, {idx} [, {end}])" function */ void blob_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg) { blob_T *b = argvars[0].vval.v_blob; + blob_T *newblob; int error = FALSE; long idx; long end; + int len; + char_u *p; if (b != NULL && value_check_lock(b->bv_lock, arg_errmsg, TRUE)) return; idx = (long)tv_get_number_chk(&argvars[1], &error); - if (!error) + if (error) + return; + + len = blob_len(b); + + if (idx < 0) + // count from the end + idx = len + idx; + if (idx < 0 || idx >= len) + { + semsg(_(e_blobidx), idx); + return; + } + if (argvars[2].v_type == VAR_UNKNOWN) + { + // Remove one item, return its value. + p = (char_u *)b->bv_ga.ga_data; + rettv->vval.v_number = (varnumber_T) *(p + idx); + mch_memmove(p + idx, p + idx + 1, (size_t)len - idx - 1); + --b->bv_ga.ga_len; + return; + } + + // Remove range of items, return blob with values. + end = (long)tv_get_number_chk(&argvars[2], &error); + if (error) + return; + if (end < 0) + // count from the end + end = len + end; + if (end >= len || idx > end) { - int len = blob_len(b); - char_u *p; + semsg(_(e_blobidx), end); + return; + } + newblob = blob_alloc(); + if (newblob == NULL) + return; + newblob->bv_ga.ga_len = end - idx + 1; + if (ga_grow(&newblob->bv_ga, end - idx + 1) == FAIL) + { + vim_free(newblob); + return; + } + p = (char_u *)b->bv_ga.ga_data; + mch_memmove((char_u *)newblob->bv_ga.ga_data, p + idx, + (size_t)(end - idx + 1)); + ++newblob->bv_refcount; + rettv->v_type = VAR_BLOB; + rettv->vval.v_blob = newblob; + + if (len - end - 1 > 0) + mch_memmove(p + idx, p + end + 1, (size_t)(len - end - 1)); + b->bv_ga.ga_len -= end - idx + 1; +} + +/* + * Implementation of map() and filter() for a Blob. Apply "expr" to every + * number in Blob "blob_arg" and return the result in "rettv". + */ + void +blob_filter_map( + blob_T *blob_arg, + filtermap_T filtermap, + typval_T *expr, + typval_T *rettv) +{ + blob_T *b; + int i; + typval_T tv; + varnumber_T val; + blob_T *b_ret; + int idx = 0; + int rem; - if (idx < 0) - // count from the end - idx = len + idx; - if (idx < 0 || idx >= len) + if (filtermap == FILTERMAP_MAPNEW) + { + rettv->v_type = VAR_BLOB; + rettv->vval.v_blob = NULL; + } + if ((b = blob_arg) == NULL) + return; + + b_ret = b; + if (filtermap == FILTERMAP_MAPNEW) + { + if (blob_copy(b, rettv) == FAIL) + return; + b_ret = rettv->vval.v_blob; + } + + // set_vim_var_nr() doesn't set the type + set_vim_var_type(VV_KEY, VAR_NUMBER); + + for (i = 0; i < b->bv_ga.ga_len; i++) + { + typval_T newtv; + + tv.v_type = VAR_NUMBER; + val = blob_get(b, i); + tv.vval.v_number = val; + set_vim_var_nr(VV_KEY, idx); + if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL + || did_emsg) + break; + if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL) + { + clear_tv(&newtv); + emsg(_(e_invalblob)); + break; + } + if (filtermap != FILTERMAP_FILTER) + { + if (newtv.vval.v_number != val) + blob_set(b_ret, i, newtv.vval.v_number); + } + else if (rem) { - semsg(_(e_blobidx), idx); + char_u *p = (char_u *)blob_arg->bv_ga.ga_data; + + mch_memmove(p + i, p + i + 1, + (size_t)b->bv_ga.ga_len - i - 1); + --b->bv_ga.ga_len; + --i; + } + ++idx; + } +} + +/* + * "insert(blob, {item} [, {idx}])" function + */ + void +blob_insert_func(typval_T *argvars, typval_T *rettv) +{ + blob_T *b = argvars[0].vval.v_blob; + long before = 0; + int error = FALSE; + int val, len; + char_u *p; + + if (b == NULL) + { + if (in_vim9script()) + emsg(_(e_cannot_add_to_null_blob)); + return; + } + + if (value_check_lock(b->bv_lock, (char_u *)N_("insert() argument"), TRUE)) + return; + + len = blob_len(b); + if (argvars[2].v_type != VAR_UNKNOWN) + { + before = (long)tv_get_number_chk(&argvars[2], &error); + if (error) + return; // type error; errmsg already given + if (before < 0 || before > len) + { + semsg(_(e_invarg2), tv_get_string(&argvars[2])); return; } - if (argvars[2].v_type == VAR_UNKNOWN) - { - // Remove one item, return its value. - p = (char_u *)b->bv_ga.ga_data; - rettv->vval.v_number = (varnumber_T) *(p + idx); - mch_memmove(p + idx, p + idx + 1, (size_t)len - idx - 1); - --b->bv_ga.ga_len; - } - else - { - blob_T *blob; + } + val = tv_get_number_chk(&argvars[1], &error); + if (error) + return; + if (val < 0 || val > 255) + { + semsg(_(e_invarg2), tv_get_string(&argvars[1])); + return; + } + + if (ga_grow(&b->bv_ga, 1) == FAIL) + return; + p = (char_u *)b->bv_ga.ga_data; + mch_memmove(p + before + 1, p + before, (size_t)len - before); + *(p + before) = val; + ++b->bv_ga.ga_len; + + copy_tv(&argvars[0], rettv); +} + +/* + * reduce() Blob argvars[0] using the function 'funcname' with arguments in + * 'funcexe' starting with the initial value argvars[2] and return the result + * in 'rettv'. + */ + void +blob_reduce( + typval_T *argvars, + char_u *func_name, + funcexe_T *funcexe, + typval_T *rettv) +{ + blob_T *b = argvars[0].vval.v_blob; + int called_emsg_start = called_emsg; + int r; + typval_T initial; + typval_T argv[3]; + int i; - // Remove range of items, return blob with values. - end = (long)tv_get_number_chk(&argvars[2], &error); - if (error) - return; - if (end < 0) - // count from the end - end = len + end; - if (end >= len || idx > end) - { - semsg(_(e_blobidx), end); - return; - } - blob = blob_alloc(); - if (blob == NULL) - return; - blob->bv_ga.ga_len = end - idx + 1; - if (ga_grow(&blob->bv_ga, end - idx + 1) == FAIL) - { - vim_free(blob); - return; - } - p = (char_u *)b->bv_ga.ga_data; - mch_memmove((char_u *)blob->bv_ga.ga_data, p + idx, - (size_t)(end - idx + 1)); - ++blob->bv_refcount; - rettv->v_type = VAR_BLOB; - rettv->vval.v_blob = blob; + if (argvars[2].v_type == VAR_UNKNOWN) + { + if (b == NULL || b->bv_ga.ga_len == 0) + { + semsg(_(e_reduceempty), "Blob"); + return; + } + initial.v_type = VAR_NUMBER; + initial.vval.v_number = blob_get(b, 0); + i = 1; + } + else if (argvars[2].v_type != VAR_NUMBER) + { + emsg(_(e_number_expected)); + return; + } + else + { + initial = argvars[2]; + i = 0; + } + + copy_tv(&initial, rettv); + if (b == NULL) + return; - if (len - end - 1 > 0) - mch_memmove(p + idx, p + end + 1, (size_t)(len - end - 1)); - b->bv_ga.ga_len -= end - idx + 1; - } + for ( ; i < b->bv_ga.ga_len; i++) + { + argv[0] = *rettv; + argv[1].v_type = VAR_NUMBER; + argv[1].vval.v_number = blob_get(b, i); + r = call_func(func_name, -1, rettv, 2, argv, funcexe); + clear_tv(&argv[0]); + if (r == FAIL || called_emsg != called_emsg_start) + return; } } /* + * "reverse({blob})" function + */ + void +blob_reverse(blob_T *b, typval_T *rettv) +{ + int i, len = blob_len(b); + + for (i = 0; i < len / 2; i++) + { + int tmp = blob_get(b, i); + + blob_set(b, i, blob_get(b, len - i - 1)); + blob_set(b, len - i - 1, tmp); + } + rettv_blob_set(rettv, b); +} + +/* * blob2list() function */ void diff --git a/src/dict.c b/src/dict.c --- a/src/dict.c +++ b/src/dict.c @@ -896,13 +896,11 @@ eval_dict(char_u **arg, typval_T *rettv, int vim9script = in_vim9script(); int had_comma; - /* - * First check if it's not a curly-braces thing: {expr}. - * Must do this without evaluating, otherwise a function may be called - * twice. Unfortunately this means we need to call eval1() twice for the - * first item. - * But {} is an empty Dictionary. - */ + // First check if it's not a curly-braces thing: {expr}. + // Must do this without evaluating, otherwise a function may be called + // twice. Unfortunately this means we need to call eval1() twice for the + // first item. + // But {} is an empty Dictionary. if (!vim9script && *curly_expr != '}' && eval1(&curly_expr, &tv, NULL) == OK @@ -1184,6 +1182,251 @@ dict_equal( } /* + * Count the number of times item "needle" occurs in Dict "d". Case is ignored + * if "ic" is TRUE. + */ + long +dict_count(dict_T *d, typval_T *needle, int ic) +{ + int todo; + hashitem_T *hi; + long n = 0; + + if (d == NULL) + return 0; + + todo = (int)d->dv_hashtab.ht_used; + for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) + { + if (!HASHITEM_EMPTY(hi)) + { + --todo; + if (tv_equal(&HI2DI(hi)->di_tv, needle, ic, FALSE)) + ++n; + } + } + + return n; +} + +/* + * extend() a Dict. Append Dict argvars[1] to Dict argvars[0] and return the + * resulting Dict in "rettv". "is_new" is TRUE for extendnew(). + */ + void +dict_extend_func( + typval_T *argvars, + type_T *type, + char *func_name, + char_u *arg_errmsg, + int is_new, + typval_T *rettv) +{ + dict_T *d1, *d2; + char_u *action; + int i; + + d1 = argvars[0].vval.v_dict; + if (d1 == NULL) + { + emsg(_(e_cannot_extend_null_dict)); + return; + } + d2 = argvars[1].vval.v_dict; + if ((is_new || !value_check_lock(d1->dv_lock, arg_errmsg, TRUE)) + && d2 != NULL) + { + if (is_new) + { + d1 = dict_copy(d1, FALSE, get_copyID()); + if (d1 == NULL) + return; + } + + // Check the third argument. + if (argvars[2].v_type != VAR_UNKNOWN) + { + static char *(av[]) = {"keep", "force", "error"}; + + action = tv_get_string_chk(&argvars[2]); + if (action == NULL) + return; + for (i = 0; i < 3; ++i) + if (STRCMP(action, av[i]) == 0) + break; + if (i == 3) + { + semsg(_(e_invarg2), action); + return; + } + } + else + action = (char_u *)"force"; + + if (type != NULL && check_typval_arg_type(type, &argvars[1], + func_name, 2) == FAIL) + return; + dict_extend(d1, d2, action, func_name); + + if (is_new) + { + rettv->v_type = VAR_DICT; + rettv->vval.v_dict = d1; + rettv->v_lock = FALSE; + } + else + copy_tv(&argvars[0], rettv); + } +} + +/* + * Implementation of map() and filter() for a Dict. Apply "expr" to every + * item in Dict "d" and return the result in "rettv". + */ + void +dict_filter_map( + dict_T *d, + filtermap_T filtermap, + type_T *argtype, + char *func_name, + char_u *arg_errmsg, + typval_T *expr, + typval_T *rettv) +{ + int prev_lock; + dict_T *d_ret = NULL; + hashtab_T *ht; + hashitem_T *hi; + dictitem_T *di; + int todo; + int rem; + + if (filtermap == FILTERMAP_MAPNEW) + { + rettv->v_type = VAR_DICT; + rettv->vval.v_dict = NULL; + } + if (d == NULL + || (filtermap == FILTERMAP_FILTER + && value_check_lock(d->dv_lock, arg_errmsg, TRUE))) + return; + + prev_lock = d->dv_lock; + + if (filtermap == FILTERMAP_MAPNEW) + { + if (rettv_dict_alloc(rettv) == FAIL) + return; + d_ret = rettv->vval.v_dict; + } + + if (filtermap != FILTERMAP_FILTER && d->dv_lock == 0) + d->dv_lock = VAR_LOCKED; + ht = &d->dv_hashtab; + hash_lock(ht); + todo = (int)ht->ht_used; + for (hi = ht->ht_array; todo > 0; ++hi) + { + if (!HASHITEM_EMPTY(hi)) + { + int r; + typval_T newtv; + + --todo; + di = HI2DI(hi); + if (filtermap == FILTERMAP_MAP + && (value_check_lock(di->di_tv.v_lock, + arg_errmsg, TRUE) + || var_check_ro(di->di_flags, + arg_errmsg, TRUE))) + break; + set_vim_var_string(VV_KEY, di->di_key, -1); + newtv.v_type = VAR_UNKNOWN; + r = filter_map_one(&di->di_tv, expr, filtermap, + &newtv, &rem); + clear_tv(get_vim_var_tv(VV_KEY)); + if (r == FAIL || did_emsg) + { + clear_tv(&newtv); + break; + } + if (filtermap == FILTERMAP_MAP) + { + if (argtype != NULL && check_typval_arg_type( + argtype->tt_member, &newtv, + func_name, 0) == FAIL) + { + clear_tv(&newtv); + break; + } + // map(): replace the dict item value + clear_tv(&di->di_tv); + newtv.v_lock = 0; + di->di_tv = newtv; + } + else if (filtermap == FILTERMAP_MAPNEW) + { + // mapnew(): add the item value to the new dict + r = dict_add_tv(d_ret, (char *)di->di_key, &newtv); + clear_tv(&newtv); + if (r == FAIL) + break; + } + else if (filtermap == FILTERMAP_FILTER && rem) + { + // filter(false): remove the item from the dict + if (var_check_fixed(di->di_flags, arg_errmsg, TRUE) + || var_check_ro(di->di_flags, arg_errmsg, TRUE)) + break; + dictitem_remove(d, di); + } + } + } + hash_unlock(ht); + d->dv_lock = prev_lock; +} + +/* + * "remove({dict})" function + */ + void +dict_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg) +{ + dict_T *d; + char_u *key; + dictitem_T *di; + + if (argvars[2].v_type != VAR_UNKNOWN) + { + semsg(_(e_too_many_arguments_for_function_str), "remove()"); + return; + } + + d = argvars[0].vval.v_dict; + if (d == NULL || value_check_lock(d->dv_lock, arg_errmsg, TRUE)) + return; + + key = tv_get_string_chk(&argvars[1]); + if (key == NULL) + return; + + di = dict_find(d, key, -1); + if (di == NULL) + { + semsg(_(e_dictkey), key); + return; + } + + if (var_check_fixed(di->di_flags, arg_errmsg, TRUE) + || var_check_ro(di->di_flags, arg_errmsg, TRUE)) + return; + + *rettv = di->di_tv; + init_tv(&di->di_tv); + dictitem_remove(d, di); +} + +/* * Turn a dict into a list: * "what" == 0: list of keys * "what" == 1: list of values @@ -1338,36 +1581,4 @@ f_has_key(typval_T *argvars, typval_T *r tv_get_string(&argvars[1]), -1) != NULL; } -/* - * "remove({dict})" function - */ - void -dict_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg) -{ - dict_T *d; - char_u *key; - dictitem_T *di; - - if (argvars[2].v_type != VAR_UNKNOWN) - semsg(_(e_too_many_arguments_for_function_str), "remove()"); - else if ((d = argvars[0].vval.v_dict) != NULL - && !value_check_lock(d->dv_lock, arg_errmsg, TRUE)) - { - key = tv_get_string_chk(&argvars[1]); - if (key != NULL) - { - di = dict_find(d, key, -1); - if (di == NULL) - semsg(_(e_dictkey), key); - else if (!var_check_fixed(di->di_flags, arg_errmsg, TRUE) - && !var_check_ro(di->di_flags, arg_errmsg, TRUE)) - { - *rettv = di->di_tv; - init_tv(&di->di_tv); - dictitem_remove(d, di); - } - } - } -} - #endif // defined(FEAT_EVAL) diff --git a/src/list.c b/src/list.c --- a/src/list.c +++ b/src/list.c @@ -314,28 +314,6 @@ listitem_alloc(void) } /* - * Make a typval_T of the first character of "input" and store it in "output". - * Return OK or FAIL. - */ - static int -tv_get_first_char(char_u *input, typval_T *output) -{ - char_u buf[MB_MAXBYTES + 1]; - int len; - - if (input == NULL || output == NULL) - return FAIL; - - len = has_mbyte ? mb_ptr2len(input) : 1; - STRNCPY(buf, input, len); - buf[len] = NUL; - output->v_type = VAR_STRING; - output->vval.v_string = vim_strsave(buf); - - return output->vval.v_string == NULL ? FAIL : OK; -} - -/* * Free a list item, unless it was allocated together with the list itself. * Does not clear the value. Does not notify watchers. */ @@ -876,9 +854,7 @@ list_assign_range( long idx; type_T *member_type = NULL; - /* - * Check whether any of the list items is locked before making any changes. - */ + // Check whether any of the list items is locked before making any changes. idx = idx1; dest_li = first_li; for (src_li = src->lv_first; src_li != NULL && dest_li != NULL; ) @@ -896,9 +872,7 @@ list_assign_range( && dest->lv_type->tt_member != NULL) member_type = dest->lv_type->tt_member; - /* - * Assign the List values to the list items. - */ + // Assign the List values to the list items. idx = idx1; dest_li = first_li; for (src_li = src->lv_first; src_li != NULL; ) @@ -1725,6 +1699,10 @@ f_list2str(typval_T *argvars, typval_T * rettv->vval.v_string = ga.ga_data; } +/* + * Remove item argvars[1] from List argvars[0]. If argvars[2] is supplied, then + * remove the range of items from argvars[1] to argvars[2] (inclusive). + */ static void list_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg) { @@ -1733,6 +1711,9 @@ list_remove(typval_T *argvars, typval_T listitem_T *li; int error = FALSE; long idx; + long end; + int cnt = 0; + list_T *rl; if ((l = argvars[0].vval.v_list) == NULL || value_check_lock(l->lv_lock, arg_errmsg, TRUE)) @@ -1740,74 +1721,75 @@ list_remove(typval_T *argvars, typval_T idx = (long)tv_get_number_chk(&argvars[1], &error); if (error) - ; // type error: do nothing, errmsg already given - else if ((item = list_find(l, idx)) == NULL) + return; // type error: do nothing, errmsg already given + + if ((item = list_find(l, idx)) == NULL) + { semsg(_(e_listidx), idx); + return; + } + + if (argvars[2].v_type == VAR_UNKNOWN) + { + // Remove one item, return its value. + vimlist_remove(l, item, item); + *rettv = item->li_tv; + list_free_item(l, item); + return; + } + + // Remove range of items, return list with values. + end = (long)tv_get_number_chk(&argvars[2], &error); + if (error) + return; // type error: do nothing + + if ((item2 = list_find(l, end)) == NULL) + { + semsg(_(e_listidx), end); + return; + } + + for (li = item; li != NULL; li = li->li_next) + { + ++cnt; + if (li == item2) + break; + } + if (li == NULL) // didn't find "item2" after "item" + { + emsg(_(e_invalid_range)); + return; + } + + vimlist_remove(l, item, item2); + if (rettv_list_alloc(rettv) != OK) + return; + + rl = rettv->vval.v_list; + + if (l->lv_with_items > 0) + { + // need to copy the list items and move the value + while (item != NULL) + { + li = listitem_alloc(); + if (li == NULL) + return; + li->li_tv = item->li_tv; + init_tv(&item->li_tv); + list_append(rl, li); + if (item == item2) + break; + item = item->li_next; + } + } else { - if (argvars[2].v_type == VAR_UNKNOWN) - { - // Remove one item, return its value. - vimlist_remove(l, item, item); - *rettv = item->li_tv; - list_free_item(l, item); - } - else - { - // Remove range of items, return list with values. - long end = (long)tv_get_number_chk(&argvars[2], &error); - - if (error) - ; // type error: do nothing - else if ((item2 = list_find(l, end)) == NULL) - semsg(_(e_listidx), end); - else - { - int cnt = 0; - - for (li = item; li != NULL; li = li->li_next) - { - ++cnt; - if (li == item2) - break; - } - if (li == NULL) // didn't find "item2" after "item" - emsg(_(e_invalid_range)); - else - { - vimlist_remove(l, item, item2); - if (rettv_list_alloc(rettv) == OK) - { - list_T *rl = rettv->vval.v_list; - - if (l->lv_with_items > 0) - { - // need to copy the list items and move the value - while (item != NULL) - { - li = listitem_alloc(); - if (li == NULL) - return; - li->li_tv = item->li_tv; - init_tv(&item->li_tv); - list_append(rl, li); - if (item == item2) - break; - item = item->li_next; - } - } - else - { - rl->lv_first = item; - rl->lv_u.mat.lv_last = item2; - item->li_prev = NULL; - item2->li_next = NULL; - rl->lv_len = cnt; - } - } - } - } - } + rl->lv_first = item; + rl->lv_u.mat.lv_last = item2; + item->li_prev = NULL; + item2->li_next = NULL; + rl->lv_len = cnt; } } @@ -2101,8 +2083,8 @@ do_uniq(list_T *l, sortinfo_T *info) } /* - * Parse the optional arguments to sort() and uniq() and return the values in - * 'info'. + * Parse the optional arguments supplied to the sort() or uniq() function and + * return the values in "info". */ static int parse_sort_uniq_args(typval_T *argvars, sortinfo_T *info) @@ -2272,17 +2254,11 @@ f_uniq(typval_T *argvars, typval_T *rett do_sort_uniq(argvars, rettv, FALSE); } -typedef enum { - FILTERMAP_FILTER, - FILTERMAP_MAP, - FILTERMAP_MAPNEW -} filtermap_T; - /* * Handle one item for map() and filter(). * Sets v:val to "tv". Caller must set v:key. */ - static int + int filter_map_one( typval_T *tv, // original value typval_T *expr, // callback @@ -2320,254 +2296,11 @@ theend: } /* - * Implementation of map() and filter() for a Dict. - */ - static void -filter_map_dict( - dict_T *d, - filtermap_T filtermap, - type_T *argtype, - char *func_name, - char_u *arg_errmsg, - typval_T *expr, - typval_T *rettv) -{ - int prev_lock; - dict_T *d_ret = NULL; - hashtab_T *ht; - hashitem_T *hi; - dictitem_T *di; - int todo; - int rem; - - if (filtermap == FILTERMAP_MAPNEW) - { - rettv->v_type = VAR_DICT; - rettv->vval.v_dict = NULL; - } - if (d == NULL - || (filtermap == FILTERMAP_FILTER - && value_check_lock(d->dv_lock, arg_errmsg, TRUE))) - return; - - prev_lock = d->dv_lock; - - if (filtermap == FILTERMAP_MAPNEW) - { - if (rettv_dict_alloc(rettv) == FAIL) - return; - d_ret = rettv->vval.v_dict; - } - - if (filtermap != FILTERMAP_FILTER && d->dv_lock == 0) - d->dv_lock = VAR_LOCKED; - ht = &d->dv_hashtab; - hash_lock(ht); - todo = (int)ht->ht_used; - for (hi = ht->ht_array; todo > 0; ++hi) - { - if (!HASHITEM_EMPTY(hi)) - { - int r; - typval_T newtv; - - --todo; - di = HI2DI(hi); - if (filtermap == FILTERMAP_MAP - && (value_check_lock(di->di_tv.v_lock, - arg_errmsg, TRUE) - || var_check_ro(di->di_flags, - arg_errmsg, TRUE))) - break; - set_vim_var_string(VV_KEY, di->di_key, -1); - newtv.v_type = VAR_UNKNOWN; - r = filter_map_one(&di->di_tv, expr, filtermap, - &newtv, &rem); - clear_tv(get_vim_var_tv(VV_KEY)); - if (r == FAIL || did_emsg) - { - clear_tv(&newtv); - break; - } - if (filtermap == FILTERMAP_MAP) - { - if (argtype != NULL && check_typval_arg_type( - argtype->tt_member, &newtv, - func_name, 0) == FAIL) - { - clear_tv(&newtv); - break; - } - // map(): replace the dict item value - clear_tv(&di->di_tv); - newtv.v_lock = 0; - di->di_tv = newtv; - } - else if (filtermap == FILTERMAP_MAPNEW) - { - // mapnew(): add the item value to the new dict - r = dict_add_tv(d_ret, (char *)di->di_key, &newtv); - clear_tv(&newtv); - if (r == FAIL) - break; - } - else if (filtermap == FILTERMAP_FILTER && rem) - { - // filter(false): remove the item from the dict - if (var_check_fixed(di->di_flags, arg_errmsg, TRUE) - || var_check_ro(di->di_flags, arg_errmsg, TRUE)) - break; - dictitem_remove(d, di); - } - } - } - hash_unlock(ht); - d->dv_lock = prev_lock; -} - -/* - * Implementation of map() and filter() for a Blob. + * Implementation of map() and filter() for a List. Apply "expr" to every item + * in List "l" and return the result in "rettv". */ static void -filter_map_blob( - blob_T *blob_arg, - filtermap_T filtermap, - typval_T *expr, - typval_T *rettv) -{ - blob_T *b; - int i; - typval_T tv; - varnumber_T val; - blob_T *b_ret; - int idx = 0; - int rem; - - if (filtermap == FILTERMAP_MAPNEW) - { - rettv->v_type = VAR_BLOB; - rettv->vval.v_blob = NULL; - } - if ((b = blob_arg) == NULL) - return; - - b_ret = b; - if (filtermap == FILTERMAP_MAPNEW) - { - if (blob_copy(b, rettv) == FAIL) - return; - b_ret = rettv->vval.v_blob; - } - - // set_vim_var_nr() doesn't set the type - set_vim_var_type(VV_KEY, VAR_NUMBER); - - for (i = 0; i < b->bv_ga.ga_len; i++) - { - typval_T newtv; - - tv.v_type = VAR_NUMBER; - val = blob_get(b, i); - tv.vval.v_number = val; - set_vim_var_nr(VV_KEY, idx); - if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL - || did_emsg) - break; - if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL) - { - clear_tv(&newtv); - emsg(_(e_invalblob)); - break; - } - if (filtermap != FILTERMAP_FILTER) - { - if (newtv.vval.v_number != val) - blob_set(b_ret, i, newtv.vval.v_number); - } - else if (rem) - { - char_u *p = (char_u *)blob_arg->bv_ga.ga_data; - - mch_memmove(p + i, p + i + 1, - (size_t)b->bv_ga.ga_len - i - 1); - --b->bv_ga.ga_len; - --i; - } - ++idx; - } -} - -/* - * Implementation of map() and filter() for a String. - */ - static void -filter_map_string( - char_u *str, - filtermap_T filtermap, - typval_T *expr, - typval_T *rettv) -{ - char_u *p; - typval_T tv; - garray_T ga; - int len = 0; - int idx = 0; - int rem; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - // set_vim_var_nr() doesn't set the type - set_vim_var_type(VV_KEY, VAR_NUMBER); - - ga_init2(&ga, (int)sizeof(char), 80); - for (p = str; *p != NUL; p += len) - { - typval_T newtv; - - if (tv_get_first_char(p, &tv) == FAIL) - break; - len = (int)STRLEN(tv.vval.v_string); - - set_vim_var_nr(VV_KEY, idx); - if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL - || did_emsg) - break; - if (did_emsg) - { - clear_tv(&newtv); - clear_tv(&tv); - break; - } - else if (filtermap != FILTERMAP_FILTER) - { - if (newtv.v_type != VAR_STRING) - { - clear_tv(&newtv); - clear_tv(&tv); - emsg(_(e_stringreq)); - break; - } - else - ga_concat(&ga, newtv.vval.v_string); - } - else if (!rem) - ga_concat(&ga, tv.vval.v_string); - - clear_tv(&newtv); - clear_tv(&tv); - - ++idx; - } - ga_append(&ga, NUL); - rettv->vval.v_string = ga.ga_data; -} - -/* - * Implementation of map() and filter() for a List. - */ - static void -filter_map_list( +list_filter_map( list_T *l, filtermap_T filtermap, type_T *argtype, @@ -2775,15 +2508,15 @@ filter_map(typval_T *argvars, typval_T * did_emsg = FALSE; if (argvars[0].v_type == VAR_DICT) - filter_map_dict(argvars[0].vval.v_dict, filtermap, type, func_name, + dict_filter_map(argvars[0].vval.v_dict, filtermap, type, func_name, arg_errmsg, expr, rettv); else if (argvars[0].v_type == VAR_BLOB) - filter_map_blob(argvars[0].vval.v_blob, filtermap, expr, rettv); + blob_filter_map(argvars[0].vval.v_blob, filtermap, expr, rettv); else if (argvars[0].v_type == VAR_STRING) - filter_map_string(tv_get_string(&argvars[0]), filtermap, expr, + string_filter_map(tv_get_string(&argvars[0]), filtermap, expr, rettv); else // argvars[0].v_type == VAR_LIST - filter_map_list(argvars[0].vval.v_list, filtermap, type, func_name, + list_filter_map(argvars[0].vval.v_list, filtermap, type, func_name, arg_errmsg, expr, rettv); restore_vimvar(VV_KEY, &save_key); @@ -2827,6 +2560,27 @@ f_mapnew(typval_T *argvars, typval_T *re /* * "add(list, item)" function */ + static void +list_add(typval_T *argvars, typval_T *rettv) +{ + list_T *l = argvars[0].vval.v_list; + + if (l == NULL) + { + if (in_vim9script()) + emsg(_(e_cannot_add_to_null_list)); + } + else if (!value_check_lock(l->lv_lock, + (char_u *)N_("add() argument"), TRUE) + && list_append_tv(l, &argvars[1]) == OK) + { + copy_tv(&argvars[0], rettv); + } +} + +/* + * "add(object, item)" function + */ void f_add(typval_T *argvars, typval_T *rettv) { @@ -2839,92 +2593,19 @@ f_add(typval_T *argvars, typval_T *rettv return; if (argvars[0].v_type == VAR_LIST) - { - list_T *l = argvars[0].vval.v_list; - - if (l == NULL) - { - if (in_vim9script()) - emsg(_(e_cannot_add_to_null_list)); - } - else if (!value_check_lock(l->lv_lock, - (char_u *)N_("add() argument"), TRUE) - && list_append_tv(l, &argvars[1]) == OK) - { - copy_tv(&argvars[0], rettv); - } - } + list_add(argvars, rettv); else if (argvars[0].v_type == VAR_BLOB) - { - blob_T *b = argvars[0].vval.v_blob; - - if (b == NULL) - { - if (in_vim9script()) - emsg(_(e_cannot_add_to_null_blob)); - } - else if (!value_check_lock(b->bv_lock, - (char_u *)N_("add() argument"), TRUE)) - { - int error = FALSE; - varnumber_T n = tv_get_number_chk(&argvars[1], &error); - - if (!error) - { - ga_append(&b->bv_ga, (int)n); - copy_tv(&argvars[0], rettv); - } - } - } + blob_add(argvars, rettv); else emsg(_(e_listblobreq)); } /* - * Count the number of times "needle" occurs in string "haystack". Case is - * ignored if "ic" is TRUE. - */ - static long -count_string(char_u *haystack, char_u *needle, int ic) -{ - long n = 0; - char_u *p = haystack; - char_u *next; - - if (p == NULL || needle == NULL || *needle == NUL) - return 0; - - if (ic) - { - size_t len = STRLEN(needle); - - while (*p != NUL) - { - if (MB_STRNICMP(p, needle, len) == 0) - { - ++n; - p += len; - } - else - MB_PTR_ADV(p); - } - } - else - while ((next = (char_u *)strstr((char *)p, (char *)needle)) != NULL) - { - ++n; - p = next + STRLEN(needle); - } - - return n; -} - -/* * Count the number of times item "needle" occurs in List "l" starting at index * "idx". Case is ignored if "ic" is TRUE. */ static long -count_list(list_T *l, typval_T *needle, long idx, int ic) +list_count(list_T *l, typval_T *needle, long idx, int ic) { long n = 0; listitem_T *li; @@ -2952,34 +2633,6 @@ count_list(list_T *l, typval_T *needle, } /* - * Count the number of times item "needle" occurs in Dict "d". Case is ignored - * if "ic" is TRUE. - */ - static long -count_dict(dict_T *d, typval_T *needle, int ic) -{ - int todo; - hashitem_T *hi; - long n = 0; - - if (d == NULL) - return 0; - - todo = (int)d->dv_hashtab.ht_used; - for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) - { - if (!HASHITEM_EMPTY(hi)) - { - --todo; - if (tv_equal(&HI2DI(hi)->di_tv, needle, ic, FALSE)) - ++n; - } - } - - return n; -} - -/* * "count()" function */ void @@ -3000,7 +2653,7 @@ f_count(typval_T *argvars, typval_T *ret ic = (int)tv_get_bool_chk(&argvars[2], &error); if (!error && argvars[0].v_type == VAR_STRING) - n = count_string(argvars[0].vval.v_string, + n = string_count(argvars[0].vval.v_string, tv_get_string_chk(&argvars[1]), ic); else if (!error && argvars[0].v_type == VAR_LIST) { @@ -3010,7 +2663,7 @@ f_count(typval_T *argvars, typval_T *ret && argvars[3].v_type != VAR_UNKNOWN) idx = (long)tv_get_number_chk(&argvars[3], &error); if (!error) - n = count_list(argvars[0].vval.v_list, &argvars[1], idx, ic); + n = list_count(argvars[0].vval.v_list, &argvars[1], idx, ic); } else if (!error && argvars[0].v_type == VAR_DICT) { @@ -3018,7 +2671,7 @@ f_count(typval_T *argvars, typval_T *ret && argvars[3].v_type != VAR_UNKNOWN) emsg(_(e_invarg)); else - n = count_dict(argvars[0].vval.v_dict, &argvars[1], ic); + n = dict_count(argvars[0].vval.v_dict, &argvars[1], ic); } else semsg(_(e_listdictarg), "count()"); @@ -3031,7 +2684,7 @@ f_count(typval_T *argvars, typval_T *ret * extendnew(). */ static void -extend_list( +list_extend_func( typval_T *argvars, type_T *type, char *func_name, @@ -3098,76 +2751,6 @@ extend_list( } /* - * extend() a Dict. Append Dict argvars[1] to Dict argvars[0] and return the - * resulting Dict in "rettv". "is_new" is TRUE for extendnew(). - */ - static void -extend_dict( - typval_T *argvars, - type_T *type, - char *func_name, - char_u *arg_errmsg, - int is_new, - typval_T *rettv) -{ - dict_T *d1, *d2; - char_u *action; - int i; - - d1 = argvars[0].vval.v_dict; - if (d1 == NULL) - { - emsg(_(e_cannot_extend_null_dict)); - return; - } - d2 = argvars[1].vval.v_dict; - if ((is_new || !value_check_lock(d1->dv_lock, arg_errmsg, TRUE)) - && d2 != NULL) - { - if (is_new) - { - d1 = dict_copy(d1, FALSE, get_copyID()); - if (d1 == NULL) - return; - } - - // Check the third argument. - if (argvars[2].v_type != VAR_UNKNOWN) - { - static char *(av[]) = {"keep", "force", "error"}; - - action = tv_get_string_chk(&argvars[2]); - if (action == NULL) - return; - for (i = 0; i < 3; ++i) - if (STRCMP(action, av[i]) == 0) - break; - if (i == 3) - { - semsg(_(e_invarg2), action); - return; - } - } - else - action = (char_u *)"force"; - - if (type != NULL && check_typval_arg_type(type, &argvars[1], - func_name, 2) == FAIL) - return; - dict_extend(d1, d2, action, func_name); - - if (is_new) - { - rettv->v_type = VAR_DICT; - rettv->vval.v_dict = d1; - rettv->v_lock = FALSE; - } - else - copy_tv(&argvars[0], rettv); - } -} - -/* * "extend()" or "extendnew()" function. "is_new" is TRUE for extendnew(). */ static void @@ -3185,9 +2768,9 @@ extend(typval_T *argvars, typval_T *rett } if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) - extend_list(argvars, type, func_name, arg_errmsg, is_new, rettv); + list_extend_func(argvars, type, func_name, arg_errmsg, is_new, rettv); else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == VAR_DICT) - extend_dict(argvars, type, func_name, arg_errmsg, is_new, rettv); + dict_extend_func(argvars, type, func_name, arg_errmsg, is_new, rettv); else semsg(_(e_listdictarg), func_name); @@ -3219,16 +2802,53 @@ f_extendnew(typval_T *argvars, typval_T extend(argvars, rettv, errmsg, TRUE); } + static void +list_insert_func(typval_T *argvars, typval_T *rettv) +{ + list_T *l = argvars[0].vval.v_list; + long before = 0; + listitem_T *item; + int error = FALSE; + + if (l == NULL) + { + if (in_vim9script()) + emsg(_(e_cannot_add_to_null_list)); + return; + } + + if (value_check_lock(l->lv_lock, (char_u *)N_("insert() argument"), TRUE)) + return; + + if (argvars[2].v_type != VAR_UNKNOWN) + before = (long)tv_get_number_chk(&argvars[2], &error); + if (error) + return; // type error; errmsg already given + + if (before == l->lv_len) + item = NULL; + else + { + item = list_find(l, before); + if (item == NULL) + { + semsg(_(e_listidx), before); + l = NULL; + } + } + if (l != NULL) + { + (void)list_insert_tv(l, &argvars[1], item); + copy_tv(&argvars[0], rettv); + } +} + /* * "insert()" function */ void f_insert(typval_T *argvars, typval_T *rettv) { - long before = 0; - listitem_T *item; - int error = FALSE; - if (in_vim9script() && (check_for_list_or_blob_arg(argvars, 0) == FAIL || (argvars[0].v_type == VAR_BLOB @@ -3237,88 +2857,11 @@ f_insert(typval_T *argvars, typval_T *re return; if (argvars[0].v_type == VAR_BLOB) - { - blob_T *b = argvars[0].vval.v_blob; - - if (b == NULL) - { - if (in_vim9script()) - emsg(_(e_cannot_add_to_null_blob)); - } - else if (!value_check_lock(b->bv_lock, - (char_u *)N_("insert() argument"), TRUE)) - { - int val, len; - char_u *p; - - len = blob_len(b); - if (argvars[2].v_type != VAR_UNKNOWN) - { - before = (long)tv_get_number_chk(&argvars[2], &error); - if (error) - return; // type error; errmsg already given - if (before < 0 || before > len) - { - semsg(_(e_invarg2), tv_get_string(&argvars[2])); - return; - } - } - val = tv_get_number_chk(&argvars[1], &error); - if (error) - return; - if (val < 0 || val > 255) - { - semsg(_(e_invarg2), tv_get_string(&argvars[1])); - return; - } - - if (ga_grow(&b->bv_ga, 1) == FAIL) - return; - p = (char_u *)b->bv_ga.ga_data; - mch_memmove(p + before + 1, p + before, (size_t)len - before); - *(p + before) = val; - ++b->bv_ga.ga_len; - - copy_tv(&argvars[0], rettv); - } - } + blob_insert_func(argvars, rettv); else if (argvars[0].v_type != VAR_LIST) semsg(_(e_listblobarg), "insert()"); else - { - list_T *l = argvars[0].vval.v_list; - - if (l == NULL) - { - if (in_vim9script()) - emsg(_(e_cannot_add_to_null_list)); - } - else if (!value_check_lock(l->lv_lock, - (char_u *)N_("insert() argument"), TRUE)) - { - if (argvars[2].v_type != VAR_UNKNOWN) - before = (long)tv_get_number_chk(&argvars[2], &error); - if (error) - return; // type error; errmsg already given - - if (before == l->lv_len) - item = NULL; - else - { - item = list_find(l, before); - if (item == NULL) - { - semsg(_(e_listidx), before); - l = NULL; - } - } - if (l != NULL) - { - (void)list_insert_tv(l, &argvars[1], item); - copy_tv(&argvars[0], rettv); - } - } - } + list_insert_func(argvars, rettv); } /* @@ -3349,66 +2892,54 @@ f_remove(typval_T *argvars, typval_T *re semsg(_(e_listdictblobarg), "remove()"); } + static void +list_reverse(list_T *l, typval_T *rettv) +{ + listitem_T *li, *ni; + + rettv_list_set(rettv, l); + if (l != NULL + && !value_check_lock(l->lv_lock, + (char_u *)N_("reverse() argument"), TRUE)) + { + if (l->lv_first == &range_list_item) + { + varnumber_T new_start = l->lv_u.nonmat.lv_start + + (l->lv_len - 1) * l->lv_u.nonmat.lv_stride; + l->lv_u.nonmat.lv_end = new_start + - (l->lv_u.nonmat.lv_end - l->lv_u.nonmat.lv_start); + l->lv_u.nonmat.lv_start = new_start; + l->lv_u.nonmat.lv_stride = -l->lv_u.nonmat.lv_stride; + return; + } + li = l->lv_u.mat.lv_last; + l->lv_first = l->lv_u.mat.lv_last = NULL; + l->lv_len = 0; + while (li != NULL) + { + ni = li->li_prev; + list_append(l, li); + li = ni; + } + l->lv_u.mat.lv_idx = l->lv_len - l->lv_u.mat.lv_idx - 1; + } +} + /* * "reverse({list})" function */ void f_reverse(typval_T *argvars, typval_T *rettv) { - list_T *l; - listitem_T *li, *ni; - if (in_vim9script() && check_for_list_or_blob_arg(argvars, 0) == FAIL) return; if (argvars[0].v_type == VAR_BLOB) - { - blob_T *b = argvars[0].vval.v_blob; - int i, len = blob_len(b); - - for (i = 0; i < len / 2; i++) - { - int tmp = blob_get(b, i); - - blob_set(b, i, blob_get(b, len - i - 1)); - blob_set(b, len - i - 1, tmp); - } - rettv_blob_set(rettv, b); - return; - } - - if (argvars[0].v_type != VAR_LIST) + blob_reverse(argvars[0].vval.v_blob, rettv); + else if (argvars[0].v_type != VAR_LIST) semsg(_(e_listblobarg), "reverse()"); else - { - l = argvars[0].vval.v_list; - rettv_list_set(rettv, l); - if (l != NULL - && !value_check_lock(l->lv_lock, - (char_u *)N_("reverse() argument"), TRUE)) - { - if (l->lv_first == &range_list_item) - { - varnumber_T new_start = l->lv_u.nonmat.lv_start - + (l->lv_len - 1) * l->lv_u.nonmat.lv_stride; - l->lv_u.nonmat.lv_end = new_start - - (l->lv_u.nonmat.lv_end - l->lv_u.nonmat.lv_start); - l->lv_u.nonmat.lv_start = new_start; - l->lv_u.nonmat.lv_stride = -l->lv_u.nonmat.lv_stride; - return; - } - li = l->lv_u.mat.lv_last; - l->lv_first = l->lv_u.mat.lv_last = NULL; - l->lv_len = 0; - while (li != NULL) - { - ni = li->li_prev; - list_append(l, li); - li = ni; - } - l->lv_u.mat.lv_idx = l->lv_len - l->lv_u.mat.lv_idx - 1; - } - } + list_reverse(argvars[0].vval.v_list, rettv); } /* @@ -3417,7 +2948,7 @@ f_reverse(typval_T *argvars, typval_T *r * in 'rettv'. */ static void -reduce_list( +list_reduce( typval_T *argvars, char_u *func_name, funcexe_T *funcexe, @@ -3471,114 +3002,6 @@ reduce_list( } /* - * reduce() String argvars[0] using the function 'funcname' with arguments in - * 'funcexe' starting with the initial value argvars[2] and return the result - * in 'rettv'. - */ - static void -reduce_string( - typval_T *argvars, - char_u *func_name, - funcexe_T *funcexe, - typval_T *rettv) -{ - char_u *p = tv_get_string(&argvars[0]); - int len; - typval_T argv[3]; - int r; - int called_emsg_start = called_emsg; - - if (argvars[2].v_type == VAR_UNKNOWN) - { - if (*p == NUL) - { - semsg(_(e_reduceempty), "String"); - return; - } - if (tv_get_first_char(p, rettv) == FAIL) - return; - p += STRLEN(rettv->vval.v_string); - } - else if (argvars[2].v_type != VAR_STRING) - { - semsg(_(e_string_expected_for_argument_nr), 3); - return; - } - else - copy_tv(&argvars[2], rettv); - - for ( ; *p != NUL; p += len) - { - argv[0] = *rettv; - if (tv_get_first_char(p, &argv[1]) == FAIL) - break; - len = (int)STRLEN(argv[1].vval.v_string); - r = call_func(func_name, -1, rettv, 2, argv, funcexe); - clear_tv(&argv[0]); - clear_tv(&argv[1]); - if (r == FAIL || called_emsg != called_emsg_start) - return; - } -} - -/* - * reduce() Blob argvars[0] using the function 'funcname' with arguments in - * 'funcexe' starting with the initial value argvars[2] and return the result - * in 'rettv'. - */ - static void -reduce_blob( - typval_T *argvars, - char_u *func_name, - funcexe_T *funcexe, - typval_T *rettv) -{ - blob_T *b = argvars[0].vval.v_blob; - int called_emsg_start = called_emsg; - int r; - typval_T initial; - typval_T argv[3]; - int i; - - if (argvars[2].v_type == VAR_UNKNOWN) - { - if (b == NULL || b->bv_ga.ga_len == 0) - { - semsg(_(e_reduceempty), "Blob"); - return; - } - initial.v_type = VAR_NUMBER; - initial.vval.v_number = blob_get(b, 0); - i = 1; - } - else if (argvars[2].v_type != VAR_NUMBER) - { - emsg(_(e_number_expected)); - return; - } - else - { - initial = argvars[2]; - i = 0; - } - - copy_tv(&initial, rettv); - if (b == NULL) - return; - - for ( ; i < b->bv_ga.ga_len; i++) - { - argv[0] = *rettv; - argv[1].v_type = VAR_NUMBER; - argv[1].vval.v_number = blob_get(b, i); - r = call_func(func_name, -1, rettv, 2, argv, funcexe); - clear_tv(&argv[0]); - if (r == FAIL || called_emsg != called_emsg_start) - return; - } -} - -/* * "reduce(list, { accumulator, element -> value } [, initial])" function * "reduce(blob, { accumulator, element -> value } [, initial])" * "reduce(string, { accumulator, element -> value } [, initial])" @@ -3622,11 +3045,11 @@ f_reduce(typval_T *argvars, typval_T *re funcexe.fe_partial = partial; if (argvars[0].v_type == VAR_LIST) - reduce_list(argvars, func_name, &funcexe, rettv); + list_reduce(argvars, func_name, &funcexe, rettv); else if (argvars[0].v_type == VAR_STRING) - reduce_string(argvars, func_name, &funcexe, rettv); + string_reduce(argvars, func_name, &funcexe, rettv); else - reduce_blob(argvars, func_name, &funcexe, rettv); + blob_reduce(argvars, func_name, &funcexe, rettv); } #endif // defined(FEAT_EVAL) diff --git a/src/proto/blob.pro b/src/proto/blob.pro --- a/src/proto/blob.pro +++ b/src/proto/blob.pro @@ -18,7 +18,12 @@ int blob_slice_or_index(blob_T *blob, in 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_add(typval_T *argvars, typval_T *rettv); void blob_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg); +void blob_filter_map(blob_T *blob_arg, filtermap_T filtermap, typval_T *expr, typval_T *rettv); +void blob_insert_func(typval_T *argvars, typval_T *rettv); +void blob_reduce(typval_T *argvars, char_u *func_name, funcexe_T *funcexe, typval_T *rettv); +void blob_reverse(blob_T *b, typval_T *rettv); 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/dict.pro b/src/proto/dict.pro --- a/src/proto/dict.pro +++ b/src/proto/dict.pro @@ -39,10 +39,13 @@ int eval_dict(char_u **arg, typval_T *re void dict_extend(dict_T *d1, dict_T *d2, char_u *action, char *func_name); dictitem_T *dict_lookup(hashitem_T *hi); int dict_equal(dict_T *d1, dict_T *d2, int ic, int recursive); +long dict_count(dict_T *d, typval_T *needle, int ic); +void dict_extend_func(typval_T *argvars, type_T *type, char *func_name, char_u *arg_errmsg, int is_new, typval_T *rettv); +void dict_filter_map(dict_T *d, filtermap_T filtermap, type_T *argtype, char *func_name, char_u *arg_errmsg, typval_T *expr, typval_T *rettv); +void dict_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg); void f_items(typval_T *argvars, typval_T *rettv); void f_keys(typval_T *argvars, typval_T *rettv); void f_values(typval_T *argvars, typval_T *rettv); void dict_set_items_ro(dict_T *di); void f_has_key(typval_T *argvars, typval_T *rettv); -void dict_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg); /* vim: set ft=c : */ diff --git a/src/proto/list.pro b/src/proto/list.pro --- a/src/proto/list.pro +++ b/src/proto/list.pro @@ -50,6 +50,7 @@ void init_static_list(staticList10_T *sl void f_list2str(typval_T *argvars, typval_T *rettv); void f_sort(typval_T *argvars, typval_T *rettv); void f_uniq(typval_T *argvars, typval_T *rettv); +int filter_map_one(typval_T *tv, typval_T *expr, filtermap_T filtermap, typval_T *newtv, int *remp); void f_filter(typval_T *argvars, typval_T *rettv); void f_map(typval_T *argvars, typval_T *rettv); void f_mapnew(typval_T *argvars, typval_T *rettv); diff --git a/src/proto/strings.pro b/src/proto/strings.pro --- a/src/proto/strings.pro +++ b/src/proto/strings.pro @@ -21,6 +21,9 @@ void sort_strings(char_u **files, int co int has_non_ascii(char_u *s); char_u *concat_str(char_u *str1, char_u *str2); char_u *string_quote(char_u *str, int function); +long string_count(char_u *haystack, char_u *needle, int ic); +void string_filter_map(char_u *str, filtermap_T filtermap, typval_T *expr, typval_T *rettv); +void string_reduce(typval_T *argvars, char_u *func_name, funcexe_T *funcexe, typval_T *rettv); void f_byteidx(typval_T *argvars, typval_T *rettv); void f_byteidxcomp(typval_T *argvars, typval_T *rettv); void f_charidx(typval_T *argvars, typval_T *rettv); diff --git a/src/strings.c b/src/strings.c --- a/src/strings.c +++ b/src/strings.c @@ -764,7 +764,6 @@ concat_str(char_u *str1, char_u *str2) } #if defined(FEAT_EVAL) || defined(PROTO) - /* * Return string "str" in ' quotes, doubling ' characters. * If "str" is NULL an empty string is assumed. @@ -809,6 +808,185 @@ string_quote(char_u *str, int function) return s; } +/* + * Count the number of times "needle" occurs in string "haystack". Case is + * ignored if "ic" is TRUE. + */ + long +string_count(char_u *haystack, char_u *needle, int ic) +{ + long n = 0; + char_u *p = haystack; + char_u *next; + + if (p == NULL || needle == NULL || *needle == NUL) + return 0; + + if (ic) + { + size_t len = STRLEN(needle); + + while (*p != NUL) + { + if (MB_STRNICMP(p, needle, len) == 0) + { + ++n; + p += len; + } + else + MB_PTR_ADV(p); + } + } + else + while ((next = (char_u *)strstr((char *)p, (char *)needle)) != NULL) + { + ++n; + p = next + STRLEN(needle); + } + + return n; +} + +/* + * Make a typval_T of the first character of "input" and store it in "output". + * Return OK or FAIL. + */ + static int +copy_first_char_to_tv(char_u *input, typval_T *output) +{ + char_u buf[MB_MAXBYTES + 1]; + int len; + + if (input == NULL || output == NULL) + return FAIL; + + len = has_mbyte ? mb_ptr2len(input) : 1; + STRNCPY(buf, input, len); + buf[len] = NUL; + output->v_type = VAR_STRING; + output->vval.v_string = vim_strsave(buf); + + return output->vval.v_string == NULL ? FAIL : OK; +} + +/* + * Implementation of map() and filter() for a String. Apply "expr" to every + * character in string "str" and return the result in "rettv". + */ + void +string_filter_map( + char_u *str, + filtermap_T filtermap, + typval_T *expr, + typval_T *rettv) +{ + char_u *p; + typval_T tv; + garray_T ga; + int len = 0; + int idx = 0; + int rem; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + // set_vim_var_nr() doesn't set the type + set_vim_var_type(VV_KEY, VAR_NUMBER); + + ga_init2(&ga, (int)sizeof(char), 80); + for (p = str; *p != NUL; p += len) + { + typval_T newtv; + + if (copy_first_char_to_tv(p, &tv) == FAIL) + break; + len = (int)STRLEN(tv.vval.v_string); + + set_vim_var_nr(VV_KEY, idx); + if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL + || did_emsg) + break; + if (did_emsg) + { + clear_tv(&newtv); + clear_tv(&tv); + break; + } + else if (filtermap != FILTERMAP_FILTER) + { + if (newtv.v_type != VAR_STRING) + { + clear_tv(&newtv); + clear_tv(&tv); + emsg(_(e_stringreq)); + break; + } + else + ga_concat(&ga, newtv.vval.v_string); + } + else if (!rem) + ga_concat(&ga, tv.vval.v_string); + + clear_tv(&newtv); + clear_tv(&tv); + + ++idx; + } + ga_append(&ga, NUL); + rettv->vval.v_string = ga.ga_data; +} + +/* + * reduce() String argvars[0] using the function 'funcname' with arguments in + * 'funcexe' starting with the initial value argvars[2] and return the result + * in 'rettv'. + */ + void +string_reduce( + typval_T *argvars, + char_u *func_name, + funcexe_T *funcexe, + typval_T *rettv) +{ + char_u *p = tv_get_string(&argvars[0]); + int len; + typval_T argv[3]; + int r; + int called_emsg_start = called_emsg; + + if (argvars[2].v_type == VAR_UNKNOWN) + { + if (*p == NUL) + { + semsg(_(e_reduceempty), "String"); + return; + } + if (copy_first_char_to_tv(p, rettv) == FAIL) + return; + p += STRLEN(rettv->vval.v_string); + } + else if (argvars[2].v_type != VAR_STRING) + { + semsg(_(e_string_expected_for_argument_nr), 3); + return; + } + else + copy_tv(&argvars[2], rettv); + + for ( ; *p != NUL; p += len) + { + argv[0] = *rettv; + if (copy_first_char_to_tv(p, &argv[1]) == FAIL) + break; + len = (int)STRLEN(argv[1].vval.v_string); + r = call_func(func_name, -1, rettv, 2, argv, funcexe); + clear_tv(&argv[0]); + clear_tv(&argv[1]); + if (r == FAIL || called_emsg != called_emsg_start) + return; + } +} + static void byteidx(typval_T *argvars, typval_T *rettv, int comp UNUSED) { @@ -1686,9 +1864,6 @@ f_trim(typval_T *argvars, typval_T *rett rettv->vval.v_string = vim_strnsave(head, tail - head); } -#endif - -#if defined(FEAT_EVAL) static char *e_printf = N_("E766: Insufficient arguments for printf()"); /* @@ -1766,6 +1941,7 @@ tv_float(typval_T *tvs, int *idxp) return f; } # endif + #endif #ifdef FEAT_FLOAT diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -4492,3 +4492,11 @@ typedef struct { int sve_did_save; hashtab_T sve_hashtab; } save_v_event_T; + +// Enum used by filter(), map() and mapnew() +typedef enum { + FILTERMAP_FILTER, + FILTERMAP_MAP, + FILTERMAP_MAPNEW +} filtermap_T; + diff --git a/src/testdir/test_filter_map.vim b/src/testdir/test_filter_map.vim --- a/src/testdir/test_filter_map.vim +++ b/src/testdir/test_filter_map.vim @@ -182,6 +182,7 @@ func Test_filter_map_string() call assert_equal('', map('abc', LSTART i, x LMIDDLE '' LEND)) call assert_equal('', map('', "v:val == 'a'")) call assert_equal('', map(test_null_string(), "v:val == 'a'")) + call assert_fails('echo map("abc", "10")', 'E928:') END call CheckLegacyAndVim9Success(lines) diff --git a/src/testdir/test_listdict.vim b/src/testdir/test_listdict.vim --- a/src/testdir/test_listdict.vim +++ b/src/testdir/test_listdict.vim @@ -783,6 +783,32 @@ func Test_dict_lock_map() call CheckScriptFailure(lines, 'E741:') endfunc +" Lock one item in a list +func Test_list_item_lock_map() + let lines =<< trim END + VAR l = [99, 100, 101] + lockvar l[1] + call assert_fails("echo map(l, 'v:val + 200')", 'E741:') + call assert_equal([299, 100, 101], l) + END + " This won't work in a :def function + call CheckTransLegacySuccess(lines) + call CheckTransVim9Success(lines) +endfunc + +" Lock one item in a dict +func Test_dict_item_lock_map() + let lines =<< trim END + VAR d = {'a': 99, 'b': 100, 'c': 101} + lockvar d.b + call assert_fails("echo map(d, 'v:val + 200')", 'E741:') + call assert_equal({'a': 299, 'b': 100, 'c': 101}, d) + END + " This won't work in a :def function + call CheckTransLegacySuccess(lines) + call CheckTransVim9Success(lines) +endfunc + " No extend() after lock on dict item func Test_dict_lock_extend() let d = {'a': 99, 'b': 100} diff --git a/src/testdir/test_sort.vim b/src/testdir/test_sort.vim --- a/src/testdir/test_sort.vim +++ b/src/testdir/test_sort.vim @@ -1548,4 +1548,20 @@ func Test_sort_with_marks() close! endfunc +" Test for sort() using a dict function +func Test_sort_using_dict_func() + func DictSort(a, b) dict + if self.order == 'reverse' + return a:b - a:a + else + return a:a - a:b + endif + endfunc + let d = #{order: ''} + call assert_equal([1, 2, 3], sort([2, 1, 3], 'DictSort', d)) + let d = #{order: 'reverse'} + call assert_equal([3, 2, 1], sort([2, 1, 3], 'DictSort', d)) + delfunc DictSort +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 3871, +/**/ 3870, /**/ 3869,