/* vi:set ts=8 sts=4 sw=4 noet: * * VIM - Vi IMproved by Bram Moolenaar * * Do ":help uganda" in Vim to read copying and usage conditions. * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ /* * list.c: List support and container (List, Dict, Blob) functions. */ #include "vim.h" #if defined(FEAT_EVAL) || defined(PROTO) // List heads for garbage collection. static list_T *first_list = NULL; // list of all lists #define FOR_ALL_WATCHERS(l, lw) \ for ((lw) = (l)->lv_watch; (lw) != NULL; (lw) = (lw)->lw_next) static void list_free_item(list_T *l, listitem_T *item); /* * Add a watcher to a list. */ void list_add_watch(list_T *l, listwatch_T *lw) { lw->lw_next = l->lv_watch; l->lv_watch = lw; } /* * Remove a watcher from a list. * No warning when it isn't found... */ void list_rem_watch(list_T *l, listwatch_T *lwrem) { listwatch_T *lw, **lwp; lwp = &l->lv_watch; FOR_ALL_WATCHERS(l, lw) { if (lw == lwrem) { *lwp = lw->lw_next; break; } lwp = &lw->lw_next; } } /* * Just before removing an item from a list: advance watchers to the next * item. */ static void list_fix_watch(list_T *l, listitem_T *item) { listwatch_T *lw; FOR_ALL_WATCHERS(l, lw) if (lw->lw_item == item) lw->lw_item = item->li_next; } static void list_init(list_T *l) { // Prepend the list to the list of lists for garbage collection. if (first_list != NULL) first_list->lv_used_prev = l; l->lv_used_prev = NULL; l->lv_used_next = first_list; first_list = l; } /* * Allocate an empty header for a list. * Caller should take care of the reference count. */ list_T * list_alloc(void) { list_T *l; l = ALLOC_CLEAR_ONE(list_T); if (l != NULL) list_init(l); return l; } /* * list_alloc() with an ID for alloc_fail(). */ list_T * list_alloc_id(alloc_id_T id UNUSED) { #ifdef FEAT_EVAL if (alloc_fail_id == id && alloc_does_fail(sizeof(list_T))) return NULL; #endif return (list_alloc()); } /* * Allocate space for a list, plus "count" items. * This uses one allocation for efficiency. * The reference count is not set. * Next list_set_item() must be called for each item. */ list_T * list_alloc_with_items(int count) { list_T *l; l = (list_T *)alloc_clear(sizeof(list_T) + count * sizeof(listitem_T)); if (l == NULL) return NULL; list_init(l); if (count <= 0) return l; listitem_T *li = (listitem_T *)(l + 1); int i; l->lv_len = count; l->lv_with_items = count; l->lv_first = li; l->lv_u.mat.lv_last = li + count - 1; for (i = 0; i < count; ++i) { if (i == 0) li->li_prev = NULL; else li->li_prev = li - 1; if (i == count - 1) li->li_next = NULL; else li->li_next = li + 1; ++li; } return l; } /* * Set item "idx" for a list previously allocated with list_alloc_with_items(). * The contents of "tv" is moved into the list item. * Each item must be set exactly once. */ void list_set_item(list_T *l, int idx, typval_T *tv) { listitem_T *li = (listitem_T *)(l + 1) + idx; li->li_tv = *tv; } /* * Allocate an empty list for a return value, with reference count set. * Returns OK or FAIL. */ int rettv_list_alloc(typval_T *rettv) { list_T *l = list_alloc(); if (l == NULL) return FAIL; rettv->v_lock = 0; rettv_list_set(rettv, l); return OK; } /* * Same as rettv_list_alloc() but uses an allocation id for testing. */ int rettv_list_alloc_id(typval_T *rettv, alloc_id_T id UNUSED) { #ifdef FEAT_EVAL if (alloc_fail_id == id && alloc_does_fail(sizeof(list_T))) return FAIL; #endif return rettv_list_alloc(rettv); } /* * Set a list as the return value. Increments the reference count. */ void rettv_list_set(typval_T *rettv, list_T *l) { rettv->v_type = VAR_LIST; rettv->vval.v_list = l; if (l != NULL) ++l->lv_refcount; } /* * Unreference a list: decrement the reference count and free it when it * becomes zero. */ void list_unref(list_T *l) { if (l != NULL && --l->lv_refcount <= 0) list_free(l); } /* * Free a list, including all non-container items it points to. * Ignores the reference count. */ static void list_free_contents(list_T *l) { listitem_T *item; if (l->lv_first != &range_list_item) for (item = l->lv_first; item != NULL; item = l->lv_first) { // Remove the item before deleting it. l->lv_first = item->li_next; clear_tv(&item->li_tv); list_free_item(l, item); } } /* * Go through the list of lists and free items without the copyID. * But don't free a list that has a watcher (used in a for loop), these * are not referenced anywhere. */ int list_free_nonref(int copyID) { list_T *ll; int did_free = FALSE; for (ll = first_list; ll != NULL; ll = ll->lv_used_next) if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) && ll->lv_watch == NULL) { // Free the List and ordinary items it contains, but don't recurse // into Lists and Dictionaries, they will be in the list of dicts // or list of lists. list_free_contents(ll); did_free = TRUE; } return did_free; } static void list_free_list(list_T *l) { // Remove the list from the list of lists for garbage collection. if (l->lv_used_prev == NULL) first_list = l->lv_used_next; else l->lv_used_prev->lv_used_next = l->lv_used_next; if (l->lv_used_next != NULL) l->lv_used_next->lv_used_prev = l->lv_used_prev; free_type(l->lv_type); vim_free(l); } void list_free_items(int copyID) { list_T *ll, *ll_next; for (ll = first_list; ll != NULL; ll = ll_next) { ll_next = ll->lv_used_next; if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) && ll->lv_watch == NULL) { // Free the List and ordinary items it contains, but don't recurse // into Lists and Dictionaries, they will be in the list of dicts // or list of lists. list_free_list(ll); } } } void list_free(list_T *l) { if (in_free_unref_items) return; list_free_contents(l); list_free_list(l); } /* * Allocate a list item. * It is not initialized, don't forget to set v_lock. */ listitem_T * listitem_alloc(void) { return ALLOC_ONE(listitem_T); } /* * Free a list item, unless it was allocated together with the list itself. * Does not clear the value. Does not notify watchers. */ static void list_free_item(list_T *l, listitem_T *item) { if (l->lv_with_items == 0 || item < (listitem_T *)l || item >= (listitem_T *)(l + 1) + l->lv_with_items) vim_free(item); } /* * Free a list item, unless it was allocated together with the list itself. * Also clears the value. Does not notify watchers. */ void listitem_free(list_T *l, listitem_T *item) { clear_tv(&item->li_tv); list_free_item(l, item); } /* * Remove a list item from a List and free it. Also clears the value. */ void listitem_remove(list_T *l, listitem_T *item) { vimlist_remove(l, item, item); listitem_free(l, item); } /* * Get the number of items in a list. */ long list_len(list_T *l) { if (l == NULL) return 0L; return l->lv_len; } /* * Return TRUE when two lists have exactly the same values. */ int list_equal( list_T *l1, list_T *l2, int ic) // ignore case for strings { listitem_T *item1, *item2; if (l1 == l2) return TRUE; if (list_len(l1) != list_len(l2)) return FALSE; if (list_len(l1) == 0) // empty and NULL list are considered equal return TRUE; if (l1 == NULL || l2 == NULL) return FALSE; CHECK_LIST_MATERIALIZE(l1); CHECK_LIST_MATERIALIZE(l2); for (item1 = l1->lv_first, item2 = l2->lv_first; item1 != NULL && item2 != NULL; item1 = item1->li_next, item2 = item2->li_next) if (!tv_equal(&item1->li_tv, &item2->li_tv, ic)) return FALSE; return item1 == NULL && item2 == NULL; } /* * Locate item with index "n" in list "l" and return it. * A negative index is counted from the end; -1 is the last item. * Returns NULL when "n" is out of range. */ listitem_T * list_find(list_T *l, long n) { listitem_T *item; long idx; if (l == NULL) return NULL; // Negative index is relative to the end. if (n < 0) n = l->lv_len + n; // Check for index out of range. if (n < 0 || n >= l->lv_len) return NULL; CHECK_LIST_MATERIALIZE(l); // range_list_materialize may reset l->lv_len if (n >= l->lv_len) return NULL; // When there is a cached index may start search from there. if (l->lv_u.mat.lv_idx_item != NULL) { if (n < l->lv_u.mat.lv_idx / 2) { // closest to the start of the list item = l->lv_first; idx = 0; } else if (n > (l->lv_u.mat.lv_idx + l->lv_len) / 2) { // closest to the end of the list item = l->lv_u.mat.lv_last; idx = l->lv_len - 1; } else { // closest to the cached index item = l->lv_u.mat.lv_idx_item; idx = l->lv_u.mat.lv_idx; } } else { if (n < l->lv_len / 2) { // closest to the start of the list item = l->lv_first; idx = 0; } else { // closest to the end of the list item = l->lv_u.mat.lv_last; idx = l->lv_len - 1; } } while (n > idx) { // search forward item = item->li_next; ++idx; } while (n < idx) { // search backward item = item->li_prev; --idx; } // cache the used index l->lv_u.mat.lv_idx = idx; l->lv_u.mat.lv_idx_item = item; return item; } /* * Get list item "l[idx]" as a number. */ long list_find_nr( list_T *l, long idx, int *errorp) // set to TRUE when something wrong { listitem_T *li; if (l != NULL && l->lv_first == &range_list_item) { long n = idx; // not materialized range() list: compute the value. // Negative index is relative to the end. if (n < 0) n = l->lv_len + n; // Check for index out of range. if (n < 0 || n >= l->lv_len) { if (errorp != NULL) *errorp = TRUE; return -1L; } return l->lv_u.nonmat.lv_start + n * l->lv_u.nonmat.lv_stride; } li = list_find(l, idx); if (li == NULL) { if (errorp != NULL) *errorp = TRUE; return -1L; } return (long)tv_get_number_chk(&li->li_tv, errorp); } /* * Get list item "l[idx - 1]" as a string. Returns NULL for failure. */ char_u * list_find_str(list_T *l, long idx) { listitem_T *li; li = list_find(l, idx - 1); if (li == NULL) { semsg(_(e_list_index_out_of_range_nr), idx); return NULL; } return tv_get_string(&li->li_tv); } /* * Like list_find() but when a negative index is used that is not found use * zero and set "idx" to zero. Used for first index of a range. */ listitem_T * list_find_index(list_T *l, long *idx) { listitem_T *li = list_find(l, *idx); if (li != NULL) return li; if (*idx < 0) { *idx = 0; li = list_find(l, *idx); } return li; } /* * Locate "item" list "l" and return its index. * Returns -1 when "item" is not in the list. */ long list_idx_of_item(list_T *l, listitem_T *item) { long idx = 0; listitem_T *li; if (l == NULL) return -1; CHECK_LIST_MATERIALIZE(l); idx = 0; for (li = l->lv_first; li != NULL && li != item; li = li->li_next) ++idx; if (li == NULL) return -1; return idx; } /* * Append item "item" to the end of list "l". */ void list_append(list_T *l, listitem_T *item) { CHECK_LIST_MATERIALIZE(l); if (l->lv_u.mat.lv_last == NULL) { // empty list l->lv_first = item; item->li_prev = NULL; } else { l->lv_u.mat.lv_last->li_next = item; item->li_prev = l->lv_u.mat.lv_last; } l->lv_u.mat.lv_last = item; ++l->lv_len; item->li_next = NULL; } /* * Append typval_T "tv" to the end of list "l". "tv" is copied. * Return FAIL when out of memory or the type is wrong. */ int list_append_tv(list_T *l, typval_T *tv) { listitem_T *li; if (l->lv_type != NULL && l->lv_type->tt_member != NULL && check_typval_arg_type(l->lv_type->tt_member, tv, NULL, 0) == FAIL) return FAIL; li = listitem_alloc(); if (li == NULL) return FAIL; copy_tv(tv, &li->li_tv); list_append(l, li); return OK; } /* * As list_append_tv() but move the value instead of copying it. * Return FAIL when out of memory. */ static int list_append_tv_move(list_T *l, typval_T *tv) { listitem_T *li = listitem_alloc(); if (li == NULL) return FAIL; li->li_tv = *tv; list_append(l, li); return OK; } /* * Add a dictionary to a list. Used by getqflist(). * Return FAIL when out of memory. */ int list_append_dict(list_T *list, dict_T *dict) { listitem_T *li = listitem_alloc(); if (li == NULL) return FAIL; li->li_tv.v_type = VAR_DICT; li->li_tv.v_lock = 0; li->li_tv.vval.v_dict = dict; list_append(list, li); ++dict->dv_refcount; return OK; } /* * Append list2 to list1. * Return FAIL when out of memory. */ int list_append_list(list_T *list1, list_T *list2) { listitem_T *li = listitem_alloc(); if (li == NULL) return FAIL; li->li_tv.v_type = VAR_LIST; li->li_tv.v_lock = 0; li->li_tv.vval.v_list = list2; list_append(list1, li); ++list2->lv_refcount; return OK; } /* * Make a copy of "str" and append it as an item to list "l". * When "len" >= 0 use "str[len]". * Returns FAIL when out of memory. */ int list_append_string(list_T *l, char_u *str, int len) { listitem_T *li = listitem_alloc(); if (li == NULL) return FAIL; list_append(l, li); li->li_tv.v_type = VAR_STRING; li->li_tv.v_lock = 0; if (str == NULL) li->li_tv.vval.v_string = NULL; else if ((li->li_tv.vval.v_string = (len >= 0 ? vim_strnsave(str, len) : vim_strsave(str))) == NULL) return FAIL; return OK; } /* * Append "n" to list "l". * Returns FAIL when out of memory. */ int list_append_number(list_T *l, varnumber_T n) { listitem_T *li; li = listitem_alloc(); if (li == NULL) return FAIL; li->li_tv.v_type = VAR_NUMBER; li->li_tv.v_lock = 0; li->li_tv.vval.v_number = n; list_append(l, li); return OK; } /* * Insert typval_T "tv" in list "l" before "item". * If "item" is NULL append at the end. * Return FAIL when out of memory or the type is wrong. */ int list_insert_tv(list_T *l, typval_T *tv, listitem_T *item) { listitem_T *ni; if (l->lv_type != NULL && l->lv_type->tt_member != NULL && check_typval_arg_type(l->lv_type->tt_member, tv, NULL, 0) == FAIL) return FAIL; ni = listitem_alloc(); if (ni == NULL) return FAIL; copy_tv(tv, &ni->li_tv); list_insert(l, ni, item); return OK; } void list_insert(list_T *l, listitem_T *ni, listitem_T *item) { CHECK_LIST_MATERIALIZE(l); if (item == NULL) // Append new item at end of list. list_append(l, ni); else { // Insert new item before existing item. ni->li_prev = item->li_prev; ni->li_next = item; if (item->li_prev == NULL) { l->lv_first = ni; ++l->lv_u.mat.lv_idx; } else { item->li_prev->li_next = ni; l->lv_u.mat.lv_idx_item = NULL; } item->li_prev = ni; ++l->lv_len; } } /* * Get the list item in "l" with index "n1". "n1" is adjusted if needed. * In Vim9, it is at the end of the list, add an item if "can_append" is TRUE. * Return NULL if there is no such item. */ listitem_T * check_range_index_one(list_T *l, long *n1, int can_append, int quiet) { long orig_n1 = *n1; listitem_T *li = list_find_index(l, n1); if (li != NULL) return li; // Vim9: Allow for adding an item at the end. if (can_append && in_vim9script() && *n1 == l->lv_len && l->lv_lock == 0) { list_append_number(l, 0); li = list_find_index(l, n1); } if (li == NULL) { if (!quiet) semsg(_(e_list_index_out_of_range_nr), orig_n1); return NULL; } return li; } /* * Check that "n2" can be used as the second index in a range of list "l". * If "n1" or "n2" is negative it is changed to the positive index. * "li1" is the item for item "n1". * Return OK or FAIL. */ int check_range_index_two( list_T *l, long *n1, listitem_T *li1, long *n2, int quiet) { if (*n2 < 0) { listitem_T *ni = list_find(l, *n2); if (ni == NULL) { if (!quiet) semsg(_(e_list_index_out_of_range_nr), *n2); return FAIL; } *n2 = list_idx_of_item(l, ni); } // Check that n2 isn't before n1. if (*n1 < 0) *n1 = list_idx_of_item(l, li1); if (*n2 < *n1) { if (!quiet) semsg(_(e_list_index_out_of_range_nr), *n2); return FAIL; } return OK; } /* * Assign values from list "src" into a range of "dest". * "idx1_arg" is the index of the first item in "dest" to be replaced. * "idx2" is the index of last item to be replaced, but when "empty_idx2" is * TRUE then replace all items after "idx1". * "op" is the operator, normally "=" but can be "+=" and the like. * "varname" is used for error messages. * Returns OK or FAIL. */ int list_assign_range( list_T *dest, list_T *src, long idx1_arg, long idx2, int empty_idx2, char_u *op, char_u *varname) { listitem_T *src_li; listitem_T *dest_li; long idx1 = idx1_arg; listitem_T *first_li = list_find_index(dest, &idx1); long idx; type_T *member_type = NULL; // 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; ) { if (value_check_lock(dest_li->li_tv.v_lock, varname, FALSE)) return FAIL; src_li = src_li->li_next; if (src_li == NULL || (!empty_idx2 && idx2 == idx)) break; dest_li = dest_li->li_next; ++idx; } if (in_vim9script() && dest->lv_type != NULL && dest->lv_type->tt_member != NULL) member_type = dest->lv_type->tt_member; // Assign the List values to the list items. idx = idx1; dest_li = first_li; for (src_li = src->lv_first; src_li != NULL; ) { if (op != NULL && *op != '=') tv_op(&dest_li->li_tv, &src_li->li_tv, op); else { if (member_type != NULL && check_typval_arg_type(member_type, &src_li->li_tv, NULL, 0) == FAIL) return FAIL; clear_tv(&dest_li->li_tv); copy_tv(&src_li->li_tv, &dest_li->li_tv); } src_li = src_li->li_next; if (src_li == NULL || (!empty_idx2 && idx2 == idx)) break; if (dest_li->li_next == NULL) { // Need to add an empty item. if (list_append_number(dest, 0) == FAIL) { src_li = NULL; break; } } dest_li = dest_li->li_next; ++idx; } if (src_li != NULL) { emsg(_(e_list_value_has_more_items_than_targets)); return FAIL; } if (empty_idx2 ? (dest_li != NULL && dest_li->li_next != NULL) : idx != idx2) { emsg(_(e_list_value_does_not_have_enough_items)); return FAIL; } return OK; } /* * Flatten up to "maxitems" in "list", starting at "first" to depth "maxdepth". * When "first" is NULL use the first item. * It does nothing if "maxdepth" is 0. * Returns FAIL when out of memory. */ static void list_flatten(list_T *list, listitem_T *first, long maxitems, long maxdepth) { listitem_T *item; int done = 0; if (maxdepth == 0) return; CHECK_LIST_MATERIALIZE(list); if (first == NULL) item = list->lv_first; else item = first; while (item != NULL && done < maxitems) { listitem_T *next = item->li_next; fast_breakcheck(); if (got_int) return; if (item->li_tv.v_type == VAR_LIST) { list_T *itemlist = item->li_tv.vval.v_list; vimlist_remove(list, item, item); if (list_extend(list, itemlist, next) == FAIL) { list_free_item(list, item); return; } if (maxdepth > 0) list_flatten(list, item->li_prev == NULL ? list->lv_first : item->li_prev->li_next, itemlist->lv_len, maxdepth - 1); clear_tv(&item->li_tv); list_free_item(list, item); } ++done; item = next; } } /* * "flatten()" and "flattennew()" functions */ static void flatten_common(typval_T *argvars, typval_T *rettv, int make_copy) { list_T *l; long maxdepth; int error = FALSE; if (in_vim9script() && (check_for_list_arg(argvars, 0) == FAIL || check_for_opt_number_arg(argvars, 1) == FAIL)) return; if (argvars[0].v_type != VAR_LIST) { semsg(_(e_argument_of_str_must_be_list), "flatten()"); return; } if (argvars[1].v_type == VAR_UNKNOWN) maxdepth = 999999; else { maxdepth = (long)tv_get_number_chk(&argvars[1], &error); if (error) return; if (maxdepth < 0) { emsg(_(e_maxdepth_must_be_non_negative_number)); return; } } l = argvars[0].vval.v_list; rettv->v_type = VAR_LIST; rettv->vval.v_list = l; if (l == NULL) return; if (make_copy) { l = list_copy(l, FALSE, TRUE, get_copyID()); rettv->vval.v_list = l; if (l == NULL) return; // The type will change. free_type(l->lv_type); l->lv_type = NULL; } else { if (value_check_lock(l->lv_lock, (char_u *)N_("flatten() argument"), TRUE)) return; ++l->lv_refcount; } list_flatten(l, NULL, l->lv_len, maxdepth); } /* * "flatten(list[, {maxdepth}])" function */ void f_flatten(typval_T *argvars, typval_T *rettv) { if (in_vim9script()) emsg(_(e_cannot_use_flatten_in_vim9_script)); else flatten_common(argvars, rettv, FALSE); } /* * "flattennew(list[, {maxdepth}])" function */ void f_flattennew(typval_T *argvars, typval_T *rettv) { flatten_common(argvars, rettv, TRUE); } /* * "items(list)" function * Caller must have already checked that argvars[0] is a List. */ void list2items(typval_T *argvars, typval_T *rettv) { list_T *l = argvars[0].vval.v_list; listitem_T *li; varnumber_T idx; if (rettv_list_alloc(rettv) == FAIL) return; if (l == NULL) return; // null list behaves like an empty list // TODO: would be more efficient to not materialize the argument CHECK_LIST_MATERIALIZE(l); for (idx = 0, li = l->lv_first; li != NULL; li = li->li_next, ++idx) { list_T *l2 = list_alloc(); if (l2 == NULL) break; if (list_append_list(rettv->vval.v_list, l2) == FAIL) { vim_free(l2); break; } if (list_append_number(l2, idx) == FAIL || list_append_tv(l2, &li->li_tv) == FAIL) break; } } /* * "items(string)" function * Caller must have already checked that argvars[0] is a String. */ void string2items(typval_T *argvars, typval_T *rettv) { char_u *p = argvars[0].vval.v_string; varnumber_T idx; if (rettv_list_alloc(rettv) == FAIL) return; if (p == NULL) return; // null string behaves like an empty string for (idx = 0; *p != NUL; ++idx) { int len = mb_ptr2len(p); list_T *l2; if (len == 0) break; l2 = list_alloc(); if (l2 == NULL) break; if (list_append_list(rettv->vval.v_list, l2) == FAIL) { vim_free(l2); break; } if (list_append_number(l2, idx) == FAIL || list_append_string(l2, p, len) == FAIL) break; p += len; } } /* * Extend "l1" with "l2". "l1" must not be NULL. * If "bef" is NULL append at the end, otherwise insert before this item. * Returns FAIL when out of memory. */ int list_extend(list_T *l1, list_T *l2, listitem_T *bef) { listitem_T *item; int todo; listitem_T *bef_prev; // NULL list is equivalent to an empty list: nothing to do. if (l2 == NULL || l2->lv_len == 0) return OK; todo = l2->lv_len; CHECK_LIST_MATERIALIZE(l1); CHECK_LIST_MATERIALIZE(l2); // When exending a list with itself, at some point we run into the item // that was before "bef" and need to skip over the already inserted items // to "bef". bef_prev = bef == NULL ? NULL : bef->li_prev; // We also quit the loop when we have inserted the original item count of // the list, avoid a hang when we extend a list with itself. for (item = l2->lv_first; item != NULL && --todo >= 0; item = item == bef_prev ? bef : item->li_next) if (list_insert_tv(l1, &item->li_tv, bef) == FAIL) return FAIL; return OK; } /* * Concatenate lists "l1" and "l2" into a new list, stored in "tv". * Return FAIL when out of memory. */ int list_concat(list_T *l1, list_T *l2, typval_T *tv) { list_T *l; // make a copy of the first list. if (l1 == NULL) l = list_alloc(); else l = list_copy(l1, FALSE, TRUE, 0); if (l == NULL) return FAIL; tv->v_type = VAR_LIST; tv->v_lock = 0; tv->vval.v_list = l; if (l1 == NULL) ++l->lv_refcount; // append all items from the second list return list_extend(l, l2, NULL); } list_T * list_slice(list_T *ol, long n1, long n2) { listitem_T *item; list_T *l = list_alloc(); if (l == NULL) return NULL; for (item = list_find(ol, n1); n1 <= n2; ++n1) { if (list_append_tv(l, &item->li_tv) == FAIL) { list_free(l); return NULL; } item = item->li_next; } return l; } int list_slice_or_index( list_T *list, int range, varnumber_T n1_arg, varnumber_T n2_arg, int exclusive, typval_T *rettv, int verbose) { long len = list_len(list); varnumber_T n1 = n1_arg; varnumber_T n2 = n2_arg; typval_T var1; if (n1 < 0) n1 = len + n1; if (n1 < 0 || n1 >= len) { // For a range we allow invalid values and for legacy script return an // empty list, for Vim9 script start at the first item. // A list index out of range is an error. if (!range) { if (verbose) semsg(_(e_list_index_out_of_range_nr), (long)n1_arg); return FAIL; } if (in_vim9script()) n1 = n1 < 0 ? 0 : len; else n1 = len; } if (range) { list_T *l; if (n2 < 0) n2 = len + n2; else if (n2 >= len) n2 = len - (exclusive ? 0 : 1); if (exclusive) --n2; if (n2 < 0 || n2 + 1 < n1) n2 = -1; l = list_slice(list, n1, n2); if (l == NULL) return FAIL; clear_tv(rettv); rettv_list_set(rettv, l); } else { // copy the item to "var1" to avoid that freeing the list makes it // invalid. copy_tv(&list_find(list, n1)->li_tv, &var1); clear_tv(rettv); *rettv = var1; } return OK; } /* * Make a copy of list "orig". Shallow if "deep" is FALSE. * The refcount of the new list is set to 1. * See item_copy() for "top" and "copyID". * Returns NULL when out of memory. */ list_T * list_copy(list_T *orig, int deep, int top, int copyID) { list_T *copy; listitem_T *item; listitem_T *ni; if (orig == NULL) return NULL; copy = list_alloc(); if (copy == NULL) return NULL; if (orig->lv_type == NULL || top || deep) copy->lv_type = NULL; else copy->lv_type = alloc_type(orig->lv_type); if (copyID != 0) { // Do this before adding the items, because one of the items may // refer back to this list. orig->lv_copyID = copyID; orig->lv_copylist = copy; } CHECK_LIST_MATERIALIZE(orig); for (item = orig->lv_first; item != NULL && !got_int; item = item->li_next) { ni = listitem_alloc(); if (ni == NULL) break; if (deep) { if (item_copy(&item->li_tv, &ni->li_tv, deep, FALSE, copyID) == FAIL) { vim_free(ni); break; } } else copy_tv(&item->li_tv, &ni->li_tv); list_append(copy, ni); } ++copy->lv_refcount; if (item != NULL) { list_unref(copy); copy = NULL; } return copy; } /* * Remove items "item" to "item2" from list "l". * Does not free the listitem or the value! * This used to be called list_remove, but that conflicts with a Sun header * file. */ void vimlist_remove(list_T *l, listitem_T *item, listitem_T *item2) { listitem_T *ip; CHECK_LIST_MATERIALIZE(l); // notify watchers for (ip = item; ip != NULL; ip = ip->li_next) { --l->lv_len; list_fix_watch(l, ip); if (ip == item2) break; } if (item2->li_next == NULL) l->lv_u.mat.lv_last = item->li_prev; else item2->li_next->li_prev = item->li_prev; if (item->li_prev == NULL) l->lv_first = item2->li_next; else item->li_prev->li_next = item2->li_next; l->lv_u.mat.lv_idx_item = NULL; } /* * Return an allocated string with the string representation of a list. * May return NULL. */ char_u * list2string(typval_T *tv, int copyID, int restore_copyID) { garray_T ga; if (tv->vval.v_list == NULL) return NULL; ga_init2(&ga, sizeof(char), 80); ga_append(&ga, '['); CHECK_LIST_MATERIALIZE(tv->vval.v_list); if (list_join(&ga, tv->vval.v_list, (char_u *)", ", FALSE, restore_copyID, copyID) == FAIL) { vim_free(ga.ga_data); return NULL; } ga_append(&ga, ']'); ga_append(&ga, NUL); return (char_u *)ga.ga_data; } typedef struct join_S { char_u *s; char_u *tofree; } join_T; static int list_join_inner( garray_T *gap, // to store the result in list_T *l, char_u *sep, int echo_style, int restore_copyID, int copyID, garray_T *join_gap) // to keep each list item string { int i; join_T *p; int len; int sumlen = 0; int first = TRUE; char_u *tofree; char_u numbuf[NUMBUFLEN]; listitem_T *item; char_u *s; // Stringify each item in the list. CHECK_LIST_MATERIALIZE(l); for (item = l->lv_first; item != NULL && !got_int; item = item->li_next) { s = echo_string_core(&item->li_tv, &tofree, numbuf, copyID, echo_style, restore_copyID, !echo_style); if (s == NULL) return FAIL; len = (int)STRLEN(s); sumlen += len; (void)ga_grow(join_gap, 1); p = ((join_T *)join_gap->ga_data) + (join_gap->ga_len++); if (tofree != NULL || s != numbuf) { p->s = s; p->tofree = tofree; } else { p->s = vim_strnsave(s, len); p->tofree = p->s; } line_breakcheck(); if (did_echo_string_emsg) // recursion error, bail out break; } // Allocate result buffer with its total size, avoid re-allocation and // multiple copy operations. Add 2 for a tailing ']' and NUL. if (join_gap->ga_len >= 2) sumlen += (int)STRLEN(sep) * (join_gap->ga_len - 1); if (ga_grow(gap, sumlen + 2) == FAIL) return FAIL; for (i = 0; i < join_gap->ga_len && !got_int; ++i) { if (first) first = FALSE; else ga_concat(gap, sep); p = ((join_T *)join_gap->ga_data) + i; if (p->s != NULL) ga_concat(gap, p->s); line_breakcheck(); } return OK; } /* * Join list "l" into a string in "*gap", using separator "sep". * When "echo_style" is TRUE use String as echoed, otherwise as inside a List. * Return FAIL or OK. */ int list_join( garray_T *gap, list_T *l, char_u *sep, int echo_style, int restore_copyID, int copyID) { garray_T join_ga; int retval; join_T *p; int i; if (l->lv_len < 1) return OK; // nothing to do ga_init2(&join_ga, sizeof(join_T), l->lv_len); retval = list_join_inner(gap, l, sep, echo_style, restore_copyID, copyID, &join_ga); if (join_ga.ga_data == NULL) return retval; // Dispose each item in join_ga. p = (join_T *)join_ga.ga_data; for (i = 0; i < join_ga.ga_len; ++i) { vim_free(p->tofree); ++p; } ga_clear(&join_ga); return retval; } /* * "join()" function */ void f_join(typval_T *argvars, typval_T *rettv) { garray_T ga; char_u *sep; rettv->v_type = VAR_STRING; if (in_vim9script() && (check_for_list_arg(argvars, 0) == FAIL || check_for_opt_string_arg(argvars, 1) == FAIL)) return; if (check_for_list_arg(argvars, 0) == FAIL) return; if (argvars[0].vval.v_list == NULL) return; if (argvars[1].v_type == VAR_UNKNOWN) sep = (char_u *)" "; else sep = tv_get_string_chk(&argvars[1]); if (sep != NULL) { ga_init2(&ga, sizeof(char), 80); list_join(&ga, argvars[0].vval.v_list, sep, TRUE, FALSE, 0); ga_append(&ga, NUL); rettv->vval.v_string = (char_u *)ga.ga_data; } else rettv->vval.v_string = NULL; } /* * Allocate a variable for a List and fill it from "*arg". * "*arg" points to the "[". * Return OK or FAIL. */ int eval_list(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int do_error) { int evaluate = evalarg == NULL ? FALSE : evalarg->eval_flags & EVAL_EVALUATE; list_T *l = NULL; typval_T tv; listitem_T *item; int vim9script = in_vim9script(); int had_comma; if (evaluate) { l = list_alloc(); if (l == NULL) return FAIL; } *arg = skipwhite_and_linebreak(*arg + 1, evalarg); while (**arg != ']' && **arg != NUL) { if (eval1(arg, &tv, evalarg) == FAIL) // recursive! goto failret; if (check_typval_is_value(&tv) == FAIL) { if (evaluate) clear_tv(&tv); goto failret; } if (evaluate) { item = listitem_alloc(); if (item != NULL) { item->li_tv = tv; item->li_tv.v_lock = 0; list_append(l, item); } else clear_tv(&tv); } // Legacy Vim script allowed a space before the comma. if (!vim9script) *arg = skipwhite(*arg); // the comma must come after the value had_comma = **arg == ','; if (had_comma) { if (vim9script && !IS_WHITE_NL_OR_NUL((*arg)[1]) && (*arg)[1] != ']') { semsg(_(e_white_space_required_after_str_str), ",", *arg); goto failret; } *arg = skipwhite(*arg + 1); } // The "]" can be on the next line. But a double quoted string may // follow, not a comment. *arg = skipwhite_and_linebreak(*arg, evalarg); if (**arg == ']') break; if (!had_comma) { if (do_error) { if (**arg == ',') semsg(_(e_no_white_space_allowed_before_str_str), ",", *arg); else semsg(_(e_missing_comma_in_list_str), *arg); } goto failret; } } if (**arg != ']') { if (do_error) semsg(_(e_missing_end_of_list_rsb_str), *arg); failret: if (evaluate) list_free(l); return FAIL; } *arg += 1; if (evaluate) rettv_list_set(rettv, l); return OK; } /* * Write "list" of strings to file "fd". */ int write_list(FILE *fd, list_T *list, int binary) { listitem_T *li; int c; int ret = OK; char_u *s; CHECK_LIST_MATERIALIZE(list); FOR_ALL_LIST_ITEMS(list, li) { for (s = tv_get_string(&li->li_tv); *s != NUL; ++s) { if (*s == '\n') c = putc(NUL, fd); else c = putc(*s, fd); if (c == EOF) { ret = FAIL; break; } } if (!binary || li->li_next != NULL) if (putc('\n', fd) == EOF) { ret = FAIL; break; } if (ret == FAIL) { emsg(_(e_error_while_writing)); break; } } return ret; } /* * Initialize a static list with 10 items. */ void init_static_list(staticList10_T *sl) { list_T *l = &sl->sl_list; int i; CLEAR_POINTER(sl); l->lv_first = &sl->sl_items[0]; l->lv_u.mat.lv_last = &sl->sl_items[9]; l->lv_refcount = DO_NOT_FREE_CNT; l->lv_lock = VAR_FIXED; sl->sl_list.lv_len = 10; for (i = 0; i < 10; ++i) { listitem_T *li = &sl->sl_items[i]; if (i == 0) li->li_prev = NULL; else li->li_prev = li - 1; if (i == 9) li->li_next = NULL; else li->li_next = li + 1; } } /* * "list2str()" function */ void f_list2str(typval_T *argvars, typval_T *rettv) { list_T *l; listitem_T *li; garray_T ga; int utf8 = FALSE; rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; if (in_vim9script() && (check_for_list_arg(argvars, 0) == FAIL || check_for_opt_bool_arg(argvars, 1) == FAIL)) return; if (check_for_list_arg(argvars, 0) == FAIL) return; l = argvars[0].vval.v_list; if (l == NULL) return; // empty list results in empty string if (argvars[1].v_type != VAR_UNKNOWN) utf8 = (int)tv_get_bool_chk(&argvars[1], NULL); CHECK_LIST_MATERIALIZE(l); ga_init2(&ga, 1, 80); if (has_mbyte || utf8) { char_u buf[MB_MAXBYTES + 1]; int (*char2bytes)(int, char_u *); if (utf8 || enc_utf8) char2bytes = utf_char2bytes; else char2bytes = mb_char2bytes; FOR_ALL_LIST_ITEMS(l, li) { buf[(*char2bytes)(tv_get_number(&li->li_tv), buf)] = NUL; ga_concat(&ga, buf); } ga_append(&ga, NUL); } else if (ga_grow(&ga, list_len(l) + 1) == OK) { FOR_ALL_LIST_ITEMS(l, li) ga_append(&ga, tv_get_number(&li->li_tv)); ga_append(&ga, NUL); } rettv->v_type = VAR_STRING; 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) { list_T *l; listitem_T *item, *item2; 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)) return; idx = (long)tv_get_number_chk(&argvars[1], &error); if (error) return; // type error: do nothing, errmsg already given if ((item = list_find(l, idx)) == NULL) { semsg(_(e_list_index_out_of_range_nr), 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_list_index_out_of_range_nr), 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) == FAIL) 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 { rl->lv_first = item; rl->lv_u.mat.lv_last = item2; item->li_prev = NULL; item2->li_next = NULL; rl->lv_len = cnt; } } static int item_compare(const void *s1, const void *s2); static int item_compare2(const void *s1, const void *s2); // struct used in the array that's given to qsort() typedef struct { listitem_T *item; int idx; } sortItem_T; // struct storing information about current sort typedef struct { int item_compare_ic; int item_compare_lc; int item_compare_numeric; int item_compare_numbers; int item_compare_float; char_u *item_compare_func; partial_T *item_compare_partial; dict_T *item_compare_selfdict; int item_compare_func_err; int item_compare_keep_zero; } sortinfo_T; static sortinfo_T *sortinfo = NULL; #define ITEM_COMPARE_FAIL 999 /* * Compare functions for f_sort() and f_uniq() below. */ static int item_compare(const void *s1, const void *s2) { sortItem_T *si1, *si2; typval_T *tv1, *tv2; char_u *p1, *p2; char_u *tofree1 = NULL, *tofree2 = NULL; int res; char_u numbuf1[NUMBUFLEN]; char_u numbuf2[NUMBUFLEN]; si1 = (sortItem_T *)s1; si2 = (sortItem_T *)s2; tv1 = &si1->item->li_tv; tv2 = &si2->item->li_tv; if (sortinfo->item_compare_numbers) { varnumber_T v1 = tv_to_number(tv1); varnumber_T v2 = tv_to_number(tv2); return v1 == v2 ? 0 : v1 > v2 ? 1 : -1; } if (sortinfo->item_compare_float) { float_T v1 = tv_get_float(tv1); float_T v2 = tv_get_float(tv2); return v1 == v2 ? 0 : v1 > v2 ? 1 : -1; } // tv2string() puts quotes around a string and allocates memory. Don't do // that for string variables. Use a single quote when comparing with a // non-string to do what the docs promise. if (tv1->v_type == VAR_STRING) { if (tv2->v_type != VAR_STRING || sortinfo->item_compare_numeric) p1 = (char_u *)"'"; else p1 = tv1->vval.v_string; } else p1 = tv2string(tv1, &tofree1, numbuf1, 0); if (tv2->v_type == VAR_STRING) { if (tv1->v_type != VAR_STRING || sortinfo->item_compare_numeric) p2 = (char_u *)"'"; else p2 = tv2->vval.v_string; } else p2 = tv2string(tv2, &tofree2, numbuf2, 0); if (p1 == NULL) p1 = (char_u *)""; if (p2 == NULL) p2 = (char_u *)""; if (!sortinfo->item_compare_numeric) { if (sortinfo->item_compare_lc) res = strcoll((char *)p1, (char *)p2); else res = sortinfo->item_compare_ic ? STRICMP(p1, p2): STRCMP(p1, p2); } else { double n1, n2; n1 = strtod((char *)p1, (char **)&p1); n2 = strtod((char *)p2, (char **)&p2); res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1; } // When the result would be zero, compare the item indexes. Makes the // sort stable. if (res == 0 && !sortinfo->item_compare_keep_zero) res = si1->idx > si2->idx ? 1 : -1; vim_free(tofree1); vim_free(tofree2); return res; } static int item_compare2(const void *s1, const void *s2) { sortItem_T *si1, *si2; int res; typval_T rettv; typval_T argv[3]; char_u *func_name; partial_T *partial = sortinfo->item_compare_partial; funcexe_T funcexe; int did_emsg_before = did_emsg; // shortcut after failure in previous call; compare all items equal if (sortinfo->item_compare_func_err) return 0; si1 = (sortItem_T *)s1; si2 = (sortItem_T *)s2; if (partial == NULL) func_name = sortinfo->item_compare_func; else func_name = partial_name(partial); // Copy the values. This is needed to be able to set v_lock to VAR_FIXED // in the copy without changing the original list items. copy_tv(&si1->item->li_tv, &argv[0]); copy_tv(&si2->item->li_tv, &argv[1]); rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this CLEAR_FIELD(funcexe); funcexe.fe_evaluate = TRUE; funcexe.fe_partial = partial; funcexe.fe_selfdict = sortinfo->item_compare_selfdict; res = call_func(func_name, -1, &rettv, 2, argv, &funcexe); clear_tv(&argv[0]); clear_tv(&argv[1]); if (res == FAIL || did_emsg > did_emsg_before) res = ITEM_COMPARE_FAIL; else { res = (int)tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err); if (res > 0) res = 1; else if (res < 0) res = -1; } if (sortinfo->item_compare_func_err) res = ITEM_COMPARE_FAIL; // return value has wrong type clear_tv(&rettv); // When the result would be zero, compare the pointers themselves. Makes // the sort stable. if (res == 0 && !sortinfo->item_compare_keep_zero) res = si1->idx > si2->idx ? 1 : -1; return res; } /* * sort() List "l" */ static void do_sort(list_T *l, sortinfo_T *info) { long len; sortItem_T *ptrs; long i = 0; listitem_T *li; len = list_len(l); // Make an array with each entry pointing to an item in the List. ptrs = ALLOC_MULT(sortItem_T, len); if (ptrs == NULL) return; // sort(): ptrs will be the list to sort FOR_ALL_LIST_ITEMS(l, li) { ptrs[i].item = li; ptrs[i].idx = i; ++i; } info->item_compare_func_err = FALSE; info->item_compare_keep_zero = FALSE; // test the compare function if ((info->item_compare_func != NULL || info->item_compare_partial != NULL) && item_compare2((void *)&ptrs[0], (void *)&ptrs[1]) == ITEM_COMPARE_FAIL) emsg(_(e_sort_compare_function_failed)); else { // Sort the array with item pointers. qsort((void *)ptrs, (size_t)len, sizeof(sortItem_T), info->item_compare_func == NULL && info->item_compare_partial == NULL ? item_compare : item_compare2); if (!info->item_compare_func_err) { // Clear the List and append the items in sorted order. l->lv_first = l->lv_u.mat.lv_last = l->lv_u.mat.lv_idx_item = NULL; l->lv_len = 0; for (i = 0; i < len; ++i) list_append(l, ptrs[i].item); } } vim_free(ptrs); } /* * uniq() List "l" */ static void do_uniq(list_T *l, sortinfo_T *info) { long len; sortItem_T *ptrs; long i = 0; listitem_T *li; int (*item_compare_func_ptr)(const void *, const void *); len = list_len(l); // Make an array with each entry pointing to an item in the List. ptrs = ALLOC_MULT(sortItem_T, len); if (ptrs == NULL) return; // f_uniq(): ptrs will be a stack of items to remove info->item_compare_func_err = FALSE; info->item_compare_keep_zero = TRUE; item_compare_func_ptr = info->item_compare_func != NULL || info->item_compare_partial != NULL ? item_compare2 : item_compare; for (li = l->lv_first; li != NULL && li->li_next != NULL; li = li->li_next) { if (item_compare_func_ptr((void *)&li, (void *)&li->li_next) == 0) ptrs[i++].item = li; if (info->item_compare_func_err) { emsg(_(e_uniq_compare_function_failed)); break; } } if (!info->item_compare_func_err) { while (--i >= 0) { li = ptrs[i].item->li_next; ptrs[i].item->li_next = li->li_next; if (li->li_next != NULL) li->li_next->li_prev = ptrs[i].item; else l->lv_u.mat.lv_last = ptrs[i].item; list_fix_watch(l, li); listitem_free(l, li); l->lv_len--; } } vim_free(ptrs); } /* * 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) { info->item_compare_ic = FALSE; info->item_compare_lc = FALSE; info->item_compare_numeric = FALSE; info->item_compare_numbers = FALSE; info->item_compare_float = FALSE; info->item_compare_func = NULL; info->item_compare_partial = NULL; info->item_compare_selfdict = NULL; if (argvars[1].v_type == VAR_UNKNOWN) return OK; // optional second argument: {func} if (argvars[1].v_type == VAR_FUNC) info->item_compare_func = argvars[1].vval.v_string; else if (argvars[1].v_type == VAR_PARTIAL) info->item_compare_partial = argvars[1].vval.v_partial; else { int error = FALSE; int nr = 0; if (argvars[1].v_type == VAR_NUMBER) { nr = tv_get_number_chk(&argvars[1], &error); if (error) return FAIL; if (nr == 1) info->item_compare_ic = TRUE; } if (nr != 1) { if (argvars[1].v_type != VAR_NUMBER) info->item_compare_func = tv_get_string(&argvars[1]); else if (nr != 0) { emsg(_(e_invalid_argument)); return FAIL; } } if (info->item_compare_func != NULL) { if (*info->item_compare_func == NUL) { // empty string means default sort info->item_compare_func = NULL; } else if (STRCMP(info->item_compare_func, "n") == 0) { info->item_compare_func = NULL; info->item_compare_numeric = TRUE; } else if (STRCMP(info->item_compare_func, "N") == 0) { info->item_compare_func = NULL; info->item_compare_numbers = TRUE; } else if (STRCMP(info->item_compare_func, "f") == 0) { info->item_compare_func = NULL; info->item_compare_float = TRUE; } else if (STRCMP(info->item_compare_func, "i") == 0) { info->item_compare_func = NULL; info->item_compare_ic = TRUE; } else if (STRCMP(info->item_compare_func, "l") == 0) { info->item_compare_func = NULL; info->item_compare_lc = TRUE; } } } if (argvars[2].v_type != VAR_UNKNOWN) { // optional third argument: {dict} if (check_for_dict_arg(argvars, 2) == FAIL) return FAIL; info->item_compare_selfdict = argvars[2].vval.v_dict; } return OK; } /* * "sort()" or "uniq()" function */ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, int sort) { list_T *l; sortinfo_T *old_sortinfo; sortinfo_T info; long len; if (in_vim9script() && (check_for_list_arg(argvars, 0) == FAIL || (argvars[1].v_type != VAR_UNKNOWN && (check_for_string_or_func_arg(argvars, 1) == FAIL || check_for_opt_dict_arg(argvars, 2) == FAIL)))) return; if (argvars[0].v_type != VAR_LIST) { semsg(_(e_argument_of_str_must_be_list), sort ? "sort()" : "uniq()"); return; } // Pointer to current info struct used in compare function. Save and // restore the current one for nested calls. old_sortinfo = sortinfo; sortinfo = &info; l = argvars[0].vval.v_list; if (l != NULL && value_check_lock(l->lv_lock, (char_u *)(sort ? N_("sort() argument") : N_("uniq() argument")), TRUE)) goto theend; rettv_list_set(rettv, l); if (l == NULL) goto theend; CHECK_LIST_MATERIALIZE(l); len = list_len(l); if (len <= 1) goto theend; // short list sorts pretty quickly if (parse_sort_uniq_args(argvars, &info) == FAIL) goto theend; if (sort) do_sort(l, &info); else do_uniq(l, &info); theend: sortinfo = old_sortinfo; } /* * "sort({list})" function */ void f_sort(typval_T *argvars, typval_T *rettv) { do_sort_uniq(argvars, rettv, TRUE); } /* * "uniq({list})" function */ void f_uniq(typval_T *argvars, typval_T *rettv) { do_sort_uniq(argvars, rettv, FALSE); } /* * Handle one item for map(), filter(), foreach(). * Sets v:val to "tv". Caller must set v:key. */ int filter_map_one( typval_T *tv, // original value typval_T *expr, // callback filtermap_T filtermap, funccall_T *fc, // from eval_expr_get_funccal() typval_T *newtv, // for map() and mapnew(): new value int *remp) // for filter(): remove flag { typval_T argv[3]; int retval = FAIL; copy_tv(tv, get_vim_var_tv(VV_VAL)); newtv->v_type = VAR_UNKNOWN; if (filtermap == FILTERMAP_FOREACH && expr->v_type == VAR_STRING) { // foreach() is not limited to an expression do_cmdline_cmd(expr->vval.v_string); if (!did_emsg) retval = OK; goto theend; } argv[0] = *get_vim_var_tv(VV_KEY); argv[1] = *get_vim_var_tv(VV_VAL); if (eval_expr_typval(expr, FALSE, argv, 2, fc, newtv) == FAIL) goto theend; if (filtermap == FILTERMAP_FILTER) { int error = FALSE; // filter(): when expr is zero remove the item if (in_vim9script()) *remp = !tv_get_bool_chk(newtv, &error); else *remp = (tv_get_number_chk(newtv, &error) == 0); clear_tv(newtv); // On type error, nothing has been removed; return FAIL to stop the // loop. The error message was given by tv_get_number_chk(). if (error) goto theend; } else if (filtermap == FILTERMAP_FOREACH) clear_tv(newtv); retval = OK; theend: clear_tv(get_vim_var_tv(VV_VAL)); return retval; } /* * Implementation of map(), filter(), foreach() for a List. Apply "expr" to * every item in List "l" and return the result in "rettv". */ static void list_filter_map( list_T *l, filtermap_T filtermap, type_T *argtype, char *func_name, char_u *arg_errmsg, typval_T *expr, typval_T *rettv) { int prev_lock; list_T *l_ret = NULL; int idx = 0; int rem; listitem_T *li, *nli; typval_T newtv; funccall_T *fc; if (filtermap == FILTERMAP_MAPNEW) { rettv->v_type = VAR_LIST; rettv->vval.v_list = NULL; } if (l == NULL || (filtermap == FILTERMAP_FILTER && value_check_lock(l->lv_lock, arg_errmsg, TRUE))) return; prev_lock = l->lv_lock; if (filtermap == FILTERMAP_MAPNEW) { if (rettv_list_alloc(rettv) == FAIL) return; l_ret = rettv->vval.v_list; } // set_vim_var_nr() doesn't set the type set_vim_var_type(VV_KEY, VAR_NUMBER); if (l->lv_lock == 0) l->lv_lock = VAR_LOCKED; // Create one funccall_T for all eval_expr_typval() calls. fc = eval_expr_get_funccal(expr, &newtv); if (l->lv_first == &range_list_item) { varnumber_T val = l->lv_u.nonmat.lv_start; int len = l->lv_len; int stride = l->lv_u.nonmat.lv_stride; // List from range(): loop over the numbers // NOTE: foreach() returns the range_list_item if (filtermap != FILTERMAP_MAPNEW && filtermap != FILTERMAP_FOREACH) { l->lv_first = NULL; l->lv_u.mat.lv_last = NULL; l->lv_len = 0; l->lv_u.mat.lv_idx_item = NULL; } for (idx = 0; idx < len; ++idx) { typval_T tv; tv.v_type = VAR_NUMBER; tv.v_lock = 0; tv.vval.v_number = val; set_vim_var_nr(VV_KEY, idx); if (filter_map_one(&tv, expr, filtermap, fc, &newtv, &rem) == FAIL) break; if (did_emsg) { clear_tv(&newtv); break; } if (filtermap != FILTERMAP_FOREACH) { if (filtermap != FILTERMAP_FILTER) { if (filtermap == FILTERMAP_MAP && argtype != NULL && check_typval_arg_type( argtype->tt_member, &newtv, func_name, 0) == FAIL) { clear_tv(&newtv); break; } // map(), mapnew(): always append the new value to the // list if (list_append_tv_move(filtermap == FILTERMAP_MAP ? l : l_ret, &newtv) == FAIL) break; } else if (!rem) { // filter(): append the list item value when not rem if (list_append_tv_move(l, &tv) == FAIL) break; } } val += stride; } } else { // Materialized list: loop over the items for (li = l->lv_first; li != NULL; li = nli) { if (filtermap == FILTERMAP_MAP && value_check_lock( li->li_tv.v_lock, arg_errmsg, TRUE)) break; nli = li->li_next; set_vim_var_nr(VV_KEY, idx); if (filter_map_one(&li->li_tv, expr, filtermap, fc, &newtv, &rem) == FAIL) break; if (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 list item value clear_tv(&li->li_tv); newtv.v_lock = 0; li->li_tv = newtv; } else if (filtermap == FILTERMAP_MAPNEW) { // mapnew(): append the list item value if (list_append_tv_move(l_ret, &newtv) == FAIL) break; } else if (filtermap == FILTERMAP_FILTER && rem) listitem_remove(l, li); ++idx; } } l->lv_lock = prev_lock; if (fc != NULL) remove_funccal(); } /* * Implementation of map(), filter() and foreach(). */ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap) { typval_T *expr; char *func_name = filtermap == FILTERMAP_MAP ? "map()" : filtermap == FILTERMAP_MAPNEW ? "mapnew()" : filtermap == FILTERMAP_FILTER ? "filter()" : "foreach()"; char_u *arg_errmsg = (char_u *)(filtermap == FILTERMAP_MAP ? N_("map() argument") : filtermap == FILTERMAP_MAPNEW ? N_("mapnew() argument") : filtermap == FILTERMAP_FILTER ? N_("filter() argument") : N_("foreach() argument")); int save_did_emsg; type_T *type = NULL; // map(), filter(), foreach() return the first argument, also on failure. if (filtermap != FILTERMAP_MAPNEW && argvars[0].v_type != VAR_STRING) copy_tv(&argvars[0], rettv); if (in_vim9script() && (check_for_list_or_dict_or_blob_or_string_arg(argvars, 0) == FAIL)) return; if (filtermap == FILTERMAP_MAP && in_vim9script()) { // Check that map() does not change the declared type of the list or // dict. if (argvars[0].v_type == VAR_DICT && argvars[0].vval.v_dict != NULL) type = argvars[0].vval.v_dict->dv_type; else if (argvars[0].v_type == VAR_LIST && argvars[0].vval.v_list != NULL) type = argvars[0].vval.v_list->lv_type; } if (argvars[0].v_type != VAR_BLOB && argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_DICT && argvars[0].v_type != VAR_STRING) { semsg(_(e_argument_of_str_must_be_list_string_dictionary_or_blob), func_name); return; } // On type errors, the preceding call has already displayed an error // message. Avoid a misleading error message for an empty string that // was not passed as argument. expr = &argvars[1]; if (expr->v_type == VAR_UNKNOWN) return; typval_T save_val; typval_T save_key; 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_DICT) dict_filter_map(argvars[0].vval.v_dict, filtermap, type, func_name, arg_errmsg, expr, rettv); else if (argvars[0].v_type == VAR_BLOB) blob_filter_map(argvars[0].vval.v_blob, filtermap, expr, arg_errmsg, rettv); else if (argvars[0].v_type == VAR_STRING) string_filter_map(tv_get_string(&argvars[0]), filtermap, expr, rettv); else // argvars[0].v_type == VAR_LIST list_filter_map(argvars[0].vval.v_list, filtermap, type, func_name, arg_errmsg, expr, rettv); restore_vimvar(VV_KEY, &save_key); restore_vimvar(VV_VAL, &save_val); did_emsg |= save_did_emsg; } /* * "filter()" function */ void f_filter(typval_T *argvars, typval_T *rettv) { filter_map(argvars, rettv, FILTERMAP_FILTER); } /* * "map()" function */ void f_map(typval_T *argvars, typval_T *rettv) { filter_map(argvars, rettv, FILTERMAP_MAP); } /* * "mapnew()" function */ void f_mapnew(typval_T *argvars, typval_T *rettv) { filter_map(argvars, rettv, FILTERMAP_MAPNEW); } /* * "foreach()" function */ void f_foreach(typval_T *argvars, typval_T *rettv) { filter_map(argvars, rettv, FILTERMAP_FOREACH); } /* * "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) { rettv->vval.v_number = 1; // Default: Failed if (in_vim9script() && (check_for_list_or_blob_arg(argvars, 0) == FAIL || (argvars[0].v_type == VAR_BLOB && check_for_number_arg(argvars, 1) == FAIL))) return; if (argvars[0].v_type == VAR_LIST) list_add(argvars, rettv); else if (argvars[0].v_type == VAR_BLOB) blob_add(argvars, rettv); else emsg(_(e_list_or_blob_required)); } /* * Count the number of times item "needle" occurs in List "l" starting at index * "idx". Case is ignored if "ic" is TRUE. */ static long list_count(list_T *l, typval_T *needle, long idx, int ic) { long n = 0; listitem_T *li; if (l == NULL) return 0; CHECK_LIST_MATERIALIZE(l); if (list_len(l) == 0) return 0; li = list_find(l, idx); if (li == NULL) { semsg(_(e_list_index_out_of_range_nr), idx); return 0; } for ( ; li != NULL; li = li->li_next) if (tv_equal(&li->li_tv, needle, ic)) ++n; return n; } /* * "count()" function */ void f_count(typval_T *argvars, typval_T *rettv) { long n = 0; int ic = FALSE; int error = FALSE; if (in_vim9script() && (check_for_string_or_list_or_dict_arg(argvars, 0) == FAIL || check_for_opt_bool_arg(argvars, 2) == FAIL || (argvars[2].v_type != VAR_UNKNOWN && check_for_opt_number_arg(argvars, 3) == FAIL))) return; if (argvars[2].v_type != VAR_UNKNOWN) ic = (int)tv_get_bool_chk(&argvars[2], &error); if (!error && argvars[0].v_type == VAR_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) { long idx = 0; if (argvars[2].v_type != VAR_UNKNOWN && argvars[3].v_type != VAR_UNKNOWN) idx = (long)tv_get_number_chk(&argvars[3], &error); if (!error) n = list_count(argvars[0].vval.v_list, &argvars[1], idx, ic); } else if (!error && argvars[0].v_type == VAR_DICT) { if (argvars[2].v_type != VAR_UNKNOWN && argvars[3].v_type != VAR_UNKNOWN) emsg(_(e_invalid_argument)); else n = dict_count(argvars[0].vval.v_dict, &argvars[1], ic); } else if (!error) semsg(_(e_argument_of_str_must_be_list_string_or_dictionary), "count()"); rettv->vval.v_number = n; } /* * extend() a List. Append List argvars[1] to List argvars[0] before index * argvars[3] and return the resulting list in "rettv". "is_new" is TRUE for * extendnew(). */ static void list_extend_func( typval_T *argvars, type_T *type, char *func_name, char_u *arg_errmsg, int is_new, typval_T *rettv) { list_T *l1, *l2; listitem_T *item; long before; int error = FALSE; l1 = argvars[0].vval.v_list; if (l1 == NULL) { emsg(_(e_cannot_extend_null_list)); return; } l2 = argvars[1].vval.v_list; if ((is_new || !value_check_lock(l1->lv_lock, arg_errmsg, TRUE)) && l2 != NULL) { if (is_new) { l1 = list_copy(l1, FALSE, TRUE, get_copyID()); if (l1 == NULL) 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 == l1->lv_len) item = NULL; else { item = list_find(l1, before); if (item == NULL) { semsg(_(e_list_index_out_of_range_nr), before); return; } } } else item = NULL; if (type != NULL && check_typval_arg_type( type, &argvars[1], func_name, 2) == FAIL) return; list_extend(l1, l2, item); if (is_new) { rettv->v_type = VAR_LIST; rettv->vval.v_list = l1; rettv->v_lock = FALSE; } else copy_tv(&argvars[0], rettv); } } /* * "extend()" or "extendnew()" function. "is_new" is TRUE for extendnew(). */ static void extend(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg, int is_new) { type_T *type = NULL; char *func_name = is_new ? "extendnew()" : "extend()"; if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) { // Check that extend() does not change the type of the list if it was // declared. if (!is_new && in_vim9script() && argvars[0].vval.v_list != NULL) type = argvars[0].vval.v_list->lv_type; 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) { // Check that extend() does not change the type of the dict if it was // declared. if (!is_new && in_vim9script() && argvars[0].vval.v_dict != NULL) type = argvars[0].vval.v_dict->dv_type; dict_extend_func(argvars, type, func_name, arg_errmsg, is_new, rettv); } else semsg(_(e_argument_of_str_must_be_list_or_dictionary), func_name); } /* * "extend(list, list [, idx])" function * "extend(dict, dict [, action])" function */ void f_extend(typval_T *argvars, typval_T *rettv) { char_u *errmsg = (char_u *)N_("extend() argument"); extend(argvars, rettv, errmsg, FALSE); } /* * "extendnew(list, list [, idx])" function * "extendnew(dict, dict [, action])" function */ void f_extendnew(typval_T *argvars, typval_T *rettv) { char_u *errmsg = (char_u *)N_("extendnew() argument"); 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_list_index_out_of_range_nr), 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) { if (in_vim9script() && (check_for_list_or_blob_arg(argvars, 0) == FAIL || (argvars[0].v_type == VAR_BLOB && check_for_number_arg(argvars, 1) == FAIL) || check_for_opt_number_arg(argvars, 2) == FAIL)) return; if (argvars[0].v_type == VAR_BLOB) blob_insert_func(argvars, rettv); else if (argvars[0].v_type != VAR_LIST) semsg(_(e_argument_of_str_must_be_list_or_blob), "insert()"); else list_insert_func(argvars, rettv); } /* * "remove()" function */ void f_remove(typval_T *argvars, typval_T *rettv) { char_u *arg_errmsg = (char_u *)N_("remove() argument"); if (in_vim9script() && (check_for_list_or_dict_or_blob_arg(argvars, 0) == FAIL || ((argvars[0].v_type == VAR_LIST || argvars[0].v_type == VAR_BLOB) && (check_for_number_arg(argvars, 1) == FAIL || check_for_opt_number_arg(argvars, 2) == FAIL)) || (argvars[0].v_type == VAR_DICT && check_for_string_or_number_arg(argvars, 1) == FAIL))) return; if (argvars[0].v_type == VAR_DICT) dict_remove(argvars, rettv, arg_errmsg); else if (argvars[0].v_type == VAR_BLOB) blob_remove(argvars, rettv, arg_errmsg); else if (argvars[0].v_type == VAR_LIST) list_remove(argvars, rettv, arg_errmsg); else semsg(_(e_argument_of_str_must_be_list_dictionary_or_blob), "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 + ((varnumber_T)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) { if (check_for_string_or_list_or_blob_arg(argvars, 0) == FAIL) return; if (argvars[0].v_type == VAR_BLOB) blob_reverse(argvars[0].vval.v_blob, rettv); else if (argvars[0].v_type == VAR_STRING) { rettv->v_type = VAR_STRING; if (argvars[0].vval.v_string != NULL) rettv->vval.v_string = reverse_text(argvars[0].vval.v_string); else rettv->vval.v_string = NULL; } else if (argvars[0].v_type == VAR_LIST) list_reverse(argvars[0].vval.v_list, rettv); } /* * Implementation of reduce() for list "argvars[0]", using the function "expr" * starting with the optional initial value argvars[2] and return the result in * "rettv". */ static void list_reduce( typval_T *argvars, typval_T *expr, typval_T *rettv) { list_T *l = argvars[0].vval.v_list; listitem_T *li = NULL; int range_list; int range_idx = 0; varnumber_T range_val = 0; typval_T initial; typval_T argv[3]; int r; int called_emsg_start = called_emsg; int prev_locked; funccall_T *fc; // Using reduce on a range() uses "range_idx" and "range_val". range_list = l != NULL && l->lv_first == &range_list_item; if (range_list) range_val = l->lv_u.nonmat.lv_start; if (argvars[2].v_type == VAR_UNKNOWN) { if (l == NULL || l->lv_len == 0) { semsg(_(e_reduce_of_an_empty_str_with_no_initial_value), "List"); return; } if (range_list) { initial.v_type = VAR_NUMBER; initial.vval.v_number = range_val; range_val += l->lv_u.nonmat.lv_stride; range_idx = 1; } else { initial = l->lv_first->li_tv; li = l->lv_first->li_next; } } else { initial = argvars[2]; if (l != NULL && !range_list) li = l->lv_first; } copy_tv(&initial, rettv); if (l == NULL) return; // Create one funccall_T for all eval_expr_typval() calls. fc = eval_expr_get_funccal(expr, rettv); prev_locked = l->lv_lock; l->lv_lock = VAR_FIXED; // disallow the list changing here while (range_list ? range_idx < l->lv_len : li != NULL) { argv[0] = *rettv; rettv->v_type = VAR_UNKNOWN; if (range_list) { argv[1].v_type = VAR_NUMBER; argv[1].vval.v_number = range_val; } else argv[1] = li->li_tv; r = eval_expr_typval(expr, TRUE, argv, 2, fc, rettv); if (argv[0].v_type != VAR_NUMBER && argv[0].v_type != VAR_UNKNOWN) clear_tv(&argv[0]); if (r == FAIL || called_emsg != called_emsg_start) break; // advance to the next item if (range_list) { range_val += l->lv_u.nonmat.lv_stride; ++range_idx; } else li = li->li_next; } if (fc != NULL) remove_funccal(); l->lv_lock = prev_locked; } /* * "reduce(list, { accumulator, element -> value } [, initial])" function * "reduce(blob, { accumulator, element -> value } [, initial])" * "reduce(string, { accumulator, element -> value } [, initial])" */ void f_reduce(typval_T *argvars, typval_T *rettv) { char_u *func_name; if (in_vim9script() && check_for_string_or_list_or_blob_arg(argvars, 0) == FAIL) return; if (argvars[0].v_type != VAR_STRING && argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) { emsg(_(e_string_list_or_blob_required)); return; } if (argvars[1].v_type == VAR_FUNC) func_name = argvars[1].vval.v_string; else if (argvars[1].v_type == VAR_PARTIAL) func_name = partial_name(argvars[1].vval.v_partial); else func_name = tv_get_string(&argvars[1]); if (func_name == NULL || *func_name == NUL) { emsg(_(e_missing_function_argument)); return; } if (argvars[0].v_type == VAR_LIST) list_reduce(argvars, &argvars[1], rettv); else if (argvars[0].v_type == VAR_STRING) string_reduce(argvars, &argvars[1], rettv); else blob_reduce(argvars, &argvars[1], rettv); } /* * slice() function */ void f_slice(typval_T *argvars, typval_T *rettv) { if (in_vim9script() && ((argvars[0].v_type != VAR_STRING && argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB && check_for_list_arg(argvars, 0) == FAIL) || check_for_number_arg(argvars, 1) == FAIL || check_for_opt_number_arg(argvars, 2) == FAIL)) return; if (check_can_index(&argvars[0], TRUE, FALSE) != OK) return; copy_tv(argvars, rettv); eval_index_inner(rettv, TRUE, argvars + 1, argvars[2].v_type == VAR_UNKNOWN ? NULL : argvars + 2, TRUE, NULL, 0, FALSE); } #endif // defined(FEAT_EVAL)