Mercurial > vim
view src/list.c @ 23156:6aa8ddf7a3fa v8.2.2124
patch 8.2.2124: Vim9: a range cannot be computed at runtime
Commit: https://github.com/vim/vim/commit/08597875b2a1e7d118b0346c652a96e7527e7d8b
Author: Bram Moolenaar <Bram@vim.org>
Date: Thu Dec 10 19:43:40 2020 +0100
patch 8.2.2124: Vim9: a range cannot be computed at runtime
Problem: Vim9: a range cannot be computed at runtime.
Solution: Add the ISN_RANGE instruction.
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Thu, 10 Dec 2020 19:45:04 +0100 |
parents | 77f181975381 |
children | b545334ae654 |
line wrap: on
line source
/* 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) static char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob"); // 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. * 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) { list_init(l); if (count > 0) { 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; 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) { 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 int recursive) // TRUE when used recursively { 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, recursive)) 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); // 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_listidx), idx); return NULL; } return tv_get_string(&li->li_tv); } /* * 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; l->lv_u.mat.lv_last = 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. */ int list_append_tv(list_T *l, typval_T *tv) { listitem_T *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. */ 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. */ int list_insert_tv(list_T *l, typval_T *tv, listitem_T *item) { listitem_T *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; } } /* * Flatten "list" to depth "maxdepth". * It does nothing if "maxdepth" is 0. * Returns FAIL when out of memory. */ static int list_flatten(list_T *list, long maxdepth) { listitem_T *item; listitem_T *tofree; int n; if (maxdepth == 0) return OK; CHECK_LIST_MATERIALIZE(list); n = 0; item = list->lv_first; while (item != NULL) { fast_breakcheck(); if (got_int) return FAIL; if (item->li_tv.v_type == VAR_LIST) { listitem_T *next = item->li_next; vimlist_remove(list, item, item); if (list_extend(list, item->li_tv.vval.v_list, next) == FAIL) return FAIL; clear_tv(&item->li_tv); tofree = item; if (item->li_prev == NULL) item = list->lv_first; else item = item->li_prev->li_next; list_free_item(list, tofree); if (++n >= maxdepth) { n = 0; item = next; } } else { n = 0; item = item->li_next; } } return OK; } /* * "flatten(list[, {maxdepth}])" function */ void f_flatten(typval_T *argvars, typval_T *rettv) { list_T *l; long maxdepth; int error = FALSE; if (argvars[0].v_type != VAR_LIST) { semsg(_(e_listarg), "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(_("E900: maxdepth must be non-negative number")); return; } } l = argvars[0].vval.v_list; if (l != NULL && !value_check_lock(l->lv_lock, (char_u *)N_("flatten() argument"), TRUE) && list_flatten(l, maxdepth) == OK) copy_tv(&argvars[0], rettv); } /* * 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; // 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); // 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->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, 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, long n1_arg, long n2_arg, typval_T *rettv, int verbose) { long len = list_len(list); long n1 = n1_arg; long 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 return an empty // list. A list index out of range is an error. if (!range) { if (verbose) semsg(_(e_listidx), n1); return FAIL; } n1 = n1 < 0 ? 0 : len; } if (range) { list_T *l; if (n2 < 0) n2 = len + n2; else if (n2 >= len) n2 = len - 1; 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 "copyID". * Returns NULL when out of memory. */ list_T * list_copy(list_T *orig, int deep, int copyID) { list_T *copy; listitem_T *item; listitem_T *ni; if (orig == NULL) return NULL; copy = list_alloc(); if (copy != NULL) { 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, 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, (int)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, (int)sizeof(join_T), l->lv_len); retval = list_join_inner(gap, l, sep, echo_style, restore_copyID, copyID, &join_ga); // Dispose each item in join_ga. if (join_ga.ga_data != NULL) { 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; if (argvars[0].v_type != VAR_LIST) { emsg(_(e_listreq)); 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]); rettv->v_type = VAR_STRING; if (sep != NULL) { ga_init2(&ga, (int)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 (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_OR_NUL((*arg)[1]) && (*arg)[1] != ']') { semsg(_(e_white_space_required_after_str), ","); 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), ","); else semsg(_("E696: Missing comma in List: %s"), *arg); } goto failret; } } if (**arg != ']') { if (do_error) semsg(_(e_list_end), *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_write)); 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; memset(sl, 0, sizeof(staticList10_T)); 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 (argvars[0].v_type != VAR_LIST) { emsg(_(e_invarg)); 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; } 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; int idx; 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) ; // type error: do nothing, errmsg already given else if ((item = list_find(l, idx)) == NULL) semsg(_(e_listidx), idx); 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. int 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_invrange)); else { vimlist_remove(l, item, item2); if (rettv_list_alloc(rettv) == OK) { l = rettv->vval.v_list; l->lv_first = item; l->lv_u.mat.lv_last = item2; item->li_prev = NULL; item2->li_next = NULL; l->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; #ifdef FEAT_FLOAT int item_compare_float; #endif 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_get_number(tv1); varnumber_T v2 = tv_get_number(tv2); return v1 == v2 ? 0 : v1 > v2 ? 1 : -1; } #ifdef FEAT_FLOAT 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; } #endif // 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; // 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.evaluate = TRUE; funcexe.partial = partial; funcexe.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) res = ITEM_COMPARE_FAIL; else res = (int)tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err); 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()" or "uniq()" function */ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, int sort) { list_T *l; listitem_T *li; sortItem_T *ptrs; sortinfo_T *old_sortinfo; sortinfo_T info; long len; long i; // Pointer to current info struct used in compare function. Save and // restore the current one for nested calls. old_sortinfo = sortinfo; sortinfo = &info; if (argvars[0].v_type != VAR_LIST) semsg(_(e_listarg), sort ? "sort()" : "uniq()"); else { 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); CHECK_LIST_MATERIALIZE(l); len = list_len(l); if (len <= 1) goto theend; // short list sorts pretty quickly info.item_compare_ic = FALSE; info.item_compare_lc = FALSE; info.item_compare_numeric = FALSE; info.item_compare_numbers = FALSE; #ifdef FEAT_FLOAT info.item_compare_float = FALSE; #endif info.item_compare_func = NULL; info.item_compare_partial = NULL; info.item_compare_selfdict = NULL; if (argvars[1].v_type != VAR_UNKNOWN) { // 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) goto theend; // type error; errmsg already given 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_invarg)); goto theend; } } 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; } #ifdef FEAT_FLOAT else if (STRCMP(info.item_compare_func, "f") == 0) { info.item_compare_func = NULL; info.item_compare_float = TRUE; } #endif 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 (argvars[2].v_type != VAR_DICT) { emsg(_(e_dictreq)); goto theend; } info.item_compare_selfdict = argvars[2].vval.v_dict; } } // Make an array with each entry pointing to an item in the List. ptrs = ALLOC_MULT(sortItem_T, len); if (ptrs == NULL) goto theend; i = 0; if (sort) { // 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(_("E702: 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); } } } else { int (*item_compare_func_ptr)(const void *, const void *); // 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(_("E882: 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); } 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); } 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 filter_map_one( typval_T *tv, // original value typval_T *expr, // callback filtermap_T filtermap, 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)); argv[0] = *get_vim_var_tv(VV_KEY); argv[1] = *get_vim_var_tv(VV_VAL); if (eval_expr_typval(expr, argv, 2, newtv) == FAIL) goto theend; if (filtermap == FILTERMAP_FILTER) { int error = FALSE; // filter(): when expr is zero remove the item if (in_vim9script()) *remp = !tv2bool(newtv); 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; } retval = OK; theend: clear_tv(get_vim_var_tv(VV_VAL)); return retval; } /* * Implementation of map() and filter(). */ static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap) { typval_T *expr; listitem_T *li, *nli; list_T *l = NULL; dictitem_T *di; hashtab_T *ht; hashitem_T *hi; dict_T *d = NULL; blob_T *b = NULL; int rem; int todo; char_u *ermsg = (char_u *)(filtermap == FILTERMAP_MAP ? "map()" : filtermap == FILTERMAP_MAPNEW ? "mapnew()" : "filter()"); char_u *arg_errmsg = (char_u *)(filtermap == FILTERMAP_MAP ? N_("map() argument") : filtermap == FILTERMAP_MAPNEW ? N_("mapnew() argument") : N_("filter() argument")); int save_did_emsg; int idx = 0; // map() and filter() return the first argument, also on failure. if (filtermap != FILTERMAP_MAPNEW) copy_tv(&argvars[0], rettv); if (argvars[0].v_type == VAR_BLOB) { if (filtermap == FILTERMAP_MAPNEW) { rettv->v_type = VAR_BLOB; rettv->vval.v_blob = NULL; } if ((b = argvars[0].vval.v_blob) == NULL) return; } else if (argvars[0].v_type == VAR_LIST) { if (filtermap == FILTERMAP_MAPNEW) { rettv->v_type = VAR_LIST; rettv->vval.v_list = NULL; } if ((l = argvars[0].vval.v_list) == NULL || (filtermap == FILTERMAP_FILTER && value_check_lock(l->lv_lock, arg_errmsg, TRUE))) return; } else if (argvars[0].v_type == VAR_DICT) { if (filtermap == FILTERMAP_MAPNEW) { rettv->v_type = VAR_DICT; rettv->vval.v_dict = NULL; } if ((d = argvars[0].vval.v_dict) == NULL || (filtermap == FILTERMAP_FILTER && value_check_lock(d->dv_lock, arg_errmsg, TRUE))) return; } else { semsg(_(e_listdictblobarg), ermsg); return; } expr = &argvars[1]; // 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. if (expr->v_type != VAR_UNKNOWN) { 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) { int prev_lock = d->dv_lock; dict_T *d_ret = NULL; 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); 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) { // 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; } else if (argvars[0].v_type == VAR_BLOB) { int i; typval_T tv; varnumber_T val; blob_T *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) { 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 *)argvars[0].vval.v_blob->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; } } else // argvars[0].v_type == VAR_LIST { int prev_lock = l->lv_lock; list_T *l_ret = NULL; 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 (filtermap != FILTERMAP_FILTER && l->lv_lock == 0) l->lv_lock = VAR_LOCKED; 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 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; typval_T newtv; 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, &newtv, &rem) == FAIL) break; if (did_emsg) { clear_tv(&newtv); break; } if (filtermap != FILTERMAP_FILTER) { // 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) { typval_T newtv; 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, &newtv, &rem) == FAIL) break; if (did_emsg) { clear_tv(&newtv); break; } if (filtermap == FILTERMAP_MAP) { // 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; } 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); } /* * "add(list, item)" function */ void f_add(typval_T *argvars, typval_T *rettv) { list_T *l; blob_T *b; rettv->vval.v_number = 1; // Default: Failed if (argvars[0].v_type == VAR_LIST) { if ((l = argvars[0].vval.v_list) != NULL && !value_check_lock(l->lv_lock, (char_u *)N_("add() argument"), TRUE) && list_append_tv(l, &argvars[1]) == OK) copy_tv(&argvars[0], rettv); } else if (argvars[0].v_type == VAR_BLOB) { if ((b = argvars[0].vval.v_blob) != NULL && !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); } } } else emsg(_(e_listblobreq)); } /* * "count()" function */ void f_count(typval_T *argvars, typval_T *rettv) { long n = 0; int ic = FALSE; int error = FALSE; if (argvars[2].v_type != VAR_UNKNOWN) ic = (int)tv_get_bool_chk(&argvars[2], &error); if (argvars[0].v_type == VAR_STRING) { char_u *expr = tv_get_string_chk(&argvars[1]); char_u *p = argvars[0].vval.v_string; char_u *next; if (!error && expr != NULL && *expr != NUL && p != NULL) { if (ic) { size_t len = STRLEN(expr); while (*p != NUL) { if (MB_STRNICMP(p, expr, len) == 0) { ++n; p += len; } else MB_PTR_ADV(p); } } else while ((next = (char_u *)strstr((char *)p, (char *)expr)) != NULL) { ++n; p = next + STRLEN(expr); } } } else if (argvars[0].v_type == VAR_LIST) { listitem_T *li; list_T *l; long idx; if ((l = argvars[0].vval.v_list) != NULL) { CHECK_LIST_MATERIALIZE(l); li = l->lv_first; if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[3].v_type != VAR_UNKNOWN) { idx = (long)tv_get_number_chk(&argvars[3], &error); if (!error) { li = list_find(l, idx); if (li == NULL) semsg(_(e_listidx), idx); } } if (error) li = NULL; } for ( ; li != NULL; li = li->li_next) if (tv_equal(&li->li_tv, &argvars[1], ic, FALSE)) ++n; } } else if (argvars[0].v_type == VAR_DICT) { int todo; dict_T *d; hashitem_T *hi; if ((d = argvars[0].vval.v_dict) != NULL) { if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[3].v_type != VAR_UNKNOWN) emsg(_(e_invarg)); } todo = error ? 0 : (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, &argvars[1], ic, FALSE)) ++n; } } } } else semsg(_(e_listdictarg), "count()"); rettv->vval.v_number = n; } /* * "extend(list, list [, idx])" function * "extend(dict, dict [, action])" function */ void f_extend(typval_T *argvars, typval_T *rettv) { char_u *arg_errmsg = (char_u *)N_("extend() argument"); if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) { 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 (!value_check_lock(l1->lv_lock, arg_errmsg, TRUE) && l2 != NULL) { 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_listidx), before); return; } } } else item = NULL; list_extend(l1, l2, item); copy_tv(&argvars[0], rettv); } } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == VAR_DICT) { 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 (!value_check_lock(d1->dv_lock, arg_errmsg, TRUE) && d2 != NULL) { // 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; // type error; errmsg already given 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"; dict_extend(d1, d2, action); copy_tv(&argvars[0], rettv); } } else semsg(_(e_listdictarg), "extend()"); } /* * "insert()" function */ void f_insert(typval_T *argvars, typval_T *rettv) { long before = 0; listitem_T *item; list_T *l; int error = FALSE; if (argvars[0].v_type == VAR_BLOB) { int val, len; char_u *p; if (argvars[0].vval.v_blob == NULL) return; len = blob_len(argvars[0].vval.v_blob); 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(&argvars[0].vval.v_blob->bv_ga, 1) == FAIL) return; p = (char_u *)argvars[0].vval.v_blob->bv_ga.ga_data; mch_memmove(p + before + 1, p + before, (size_t)len - before); *(p + before) = val; ++argvars[0].vval.v_blob->bv_ga.ga_len; copy_tv(&argvars[0], rettv); } else if (argvars[0].v_type != VAR_LIST) semsg(_(e_listblobarg), "insert()"); else if ((l = argvars[0].vval.v_list) != NULL && !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); } } } /* * "remove()" function */ void f_remove(typval_T *argvars, typval_T *rettv) { char_u *arg_errmsg = (char_u *)N_("remove() argument"); 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); else if (argvars[0].v_type == VAR_LIST) list_remove(argvars, rettv, arg_errmsg); else semsg(_(e_listdictblobarg), "remove()"); } /* * "reverse({list})" function */ void f_reverse(typval_T *argvars, typval_T *rettv) { list_T *l; listitem_T *li, *ni; 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) semsg(_(e_listblobarg), "reverse()"); else if ((l = argvars[0].vval.v_list) != 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; rettv_list_set(rettv, l); 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; } rettv_list_set(rettv, l); l->lv_u.mat.lv_idx = l->lv_len - l->lv_u.mat.lv_idx - 1; } } /* * "reduce(list, { accumlator, element -> value } [, initial])" function */ void f_reduce(typval_T *argvars, typval_T *rettv) { typval_T initial; char_u *func_name; partial_T *partial = NULL; funcexe_T funcexe; typval_T argv[3]; if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) { emsg(_(e_listblobreq)); return; } if (argvars[1].v_type == VAR_FUNC) func_name = argvars[1].vval.v_string; else if (argvars[1].v_type == VAR_PARTIAL) { partial = argvars[1].vval.v_partial; func_name = partial_name(partial); } else func_name = tv_get_string(&argvars[1]); if (func_name == NULL || *func_name == NUL) { emsg(_(e_missing_function_argument)); return; } vim_memset(&funcexe, 0, sizeof(funcexe)); funcexe.evaluate = TRUE; funcexe.partial = partial; if (argvars[0].v_type == VAR_LIST) { list_T *l = argvars[0].vval.v_list; listitem_T *li = NULL; int r; int called_emsg_start = called_emsg; if (l != NULL) CHECK_LIST_MATERIALIZE(l); if (argvars[2].v_type == VAR_UNKNOWN) { if (l == NULL || l->lv_first == NULL) { semsg(_(e_reduceempty), "List"); return; } initial = l->lv_first->li_tv; li = l->lv_first->li_next; } else { initial = argvars[2]; if (l != NULL) li = l->lv_first; } copy_tv(&initial, rettv); if (l != NULL) { int prev_locked = l->lv_lock; l->lv_lock = VAR_FIXED; // disallow the list changing here for ( ; li != NULL; li = li->li_next) { argv[0] = *rettv; argv[1] = li->li_tv; rettv->v_type = VAR_UNKNOWN; r = call_func(func_name, -1, rettv, 2, argv, &funcexe); clear_tv(&argv[0]); if (r == FAIL || called_emsg != called_emsg_start) break; } l->lv_lock = prev_locked; } } else { blob_T *b = argvars[0].vval.v_blob; 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_exp)); return; } else { initial = argvars[2]; i = 0; } copy_tv(&initial, rettv); if (b != NULL) { 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); if (call_func(func_name, -1, rettv, 2, argv, &funcexe) == FAIL) return; } } } } #endif // defined(FEAT_EVAL)