| /* 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. |
| */ |
| |
| /* |
| * tuple.c: Tuple support functions. |
| */ |
| |
| #include "vim.h" |
| |
| #if defined(FEAT_EVAL) || defined(PROTO) |
| |
| // Tuple heads for garbage collection. |
| static tuple_T *first_tuple = NULL; // list of all tuples |
| |
| static void |
| tuple_init(tuple_T *tuple) |
| { |
| // Prepend the tuple to the list of tuples for garbage collection. |
| if (first_tuple != NULL) |
| first_tuple->tv_used_prev = tuple; |
| tuple->tv_used_prev = NULL; |
| tuple->tv_used_next = first_tuple; |
| first_tuple = tuple; |
| |
| ga_init2(&tuple->tv_items, sizeof(typval_T), 20); |
| } |
| |
| /* |
| * Allocate an empty header for a tuple. |
| * Caller should take care of the reference count. |
| */ |
| tuple_T * |
| tuple_alloc(void) |
| { |
| tuple_T *tuple; |
| |
| tuple = ALLOC_CLEAR_ONE(tuple_T); |
| if (tuple != NULL) |
| tuple_init(tuple); |
| return tuple; |
| } |
| |
| /* |
| * Allocate space for a tuple with "count" items. |
| * This uses one allocation for efficiency. |
| * The reference count is not set. |
| * Next tuple_set_item() must be called for each item. |
| */ |
| tuple_T * |
| tuple_alloc_with_items(int count) |
| { |
| tuple_T *tuple; |
| |
| tuple = tuple_alloc(); |
| if (tuple == NULL) |
| return NULL; |
| |
| if (count <= 0) |
| return tuple; |
| |
| if (ga_grow(&tuple->tv_items, count) == FAIL) |
| { |
| tuple_free(tuple); |
| return NULL; |
| } |
| |
| return tuple; |
| } |
| |
| /* |
| * Set item "idx" for a tuple previously allocated with |
| * tuple_alloc_with_items(). |
| * The contents of "tv" is copied into the tuple item. |
| * Each item must be set exactly once. |
| */ |
| void |
| tuple_set_item(tuple_T *tuple, int idx, typval_T *tv) |
| { |
| *TUPLE_ITEM(tuple, idx) = *tv; |
| tuple->tv_items.ga_len++; |
| } |
| |
| /* |
| * Allocate an empty tuple for a return value, with reference count set. |
| * Returns OK or FAIL. |
| */ |
| int |
| rettv_tuple_alloc(typval_T *rettv) |
| { |
| tuple_T *tuple = tuple_alloc(); |
| |
| if (tuple == NULL) |
| return FAIL; |
| |
| rettv->v_lock = 0; |
| rettv_tuple_set(rettv, tuple); |
| return OK; |
| } |
| |
| /* |
| * Set a tuple as the return value. Increments the reference count. |
| */ |
| void |
| rettv_tuple_set(typval_T *rettv, tuple_T *tuple) |
| { |
| rettv->v_type = VAR_TUPLE; |
| rettv->vval.v_tuple = tuple; |
| if (tuple != NULL) |
| ++tuple->tv_refcount; |
| } |
| |
| /* |
| * Set a new tuple with "count" items as the return value. |
| * Returns OK on success and FAIL on allocation failure. |
| */ |
| int |
| rettv_tuple_set_with_items(typval_T *rettv, int count) |
| { |
| tuple_T *new_tuple; |
| |
| new_tuple = tuple_alloc_with_items(count); |
| if (new_tuple == NULL) |
| return FAIL; |
| |
| rettv_tuple_set(rettv, new_tuple); |
| |
| return OK; |
| } |
| |
| /* |
| * Unreference a tuple: decrement the reference count and free it when it |
| * becomes zero. |
| */ |
| void |
| tuple_unref(tuple_T *tuple) |
| { |
| if (tuple != NULL && --tuple->tv_refcount <= 0) |
| tuple_free(tuple); |
| } |
| |
| /* |
| * Free a tuple, including all non-container items it points to. |
| * Ignores the reference count. |
| */ |
| static void |
| tuple_free_contents(tuple_T *tuple) |
| { |
| for (int i = 0; i < TUPLE_LEN(tuple); i++) |
| clear_tv(TUPLE_ITEM(tuple, i)); |
| |
| ga_clear(&tuple->tv_items); |
| } |
| |
| /* |
| * Go through the list of tuples and free items without the copyID. |
| * But don't free a tuple that has a watcher (used in a for loop), these |
| * are not referenced anywhere. |
| */ |
| int |
| tuple_free_nonref(int copyID) |
| { |
| tuple_T *tt; |
| int did_free = FALSE; |
| |
| for (tt = first_tuple; tt != NULL; tt = tt->tv_used_next) |
| if ((tt->tv_copyID & COPYID_MASK) != (copyID & COPYID_MASK)) |
| { |
| // Free the Tuple 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. |
| tuple_free_contents(tt); |
| did_free = TRUE; |
| } |
| return did_free; |
| } |
| |
| static void |
| tuple_free_list(tuple_T *tuple) |
| { |
| // Remove the tuple from the list of tuples for garbage collection. |
| if (tuple->tv_used_prev == NULL) |
| first_tuple = tuple->tv_used_next; |
| else |
| tuple->tv_used_prev->tv_used_next = tuple->tv_used_next; |
| if (tuple->tv_used_next != NULL) |
| tuple->tv_used_next->tv_used_prev = tuple->tv_used_prev; |
| |
| free_type(tuple->tv_type); |
| vim_free(tuple); |
| } |
| |
| void |
| tuple_free_items(int copyID) |
| { |
| tuple_T *tt, *tt_next; |
| |
| for (tt = first_tuple; tt != NULL; tt = tt_next) |
| { |
| tt_next = tt->tv_used_next; |
| if ((tt->tv_copyID & COPYID_MASK) != (copyID & COPYID_MASK)) |
| { |
| // Free the tuple 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. |
| tuple_free_list(tt); |
| } |
| } |
| } |
| |
| void |
| tuple_free(tuple_T *tuple) |
| { |
| if (in_free_unref_items) |
| return; |
| |
| tuple_free_contents(tuple); |
| tuple_free_list(tuple); |
| } |
| |
| /* |
| * Get the number of items in a tuple. |
| */ |
| long |
| tuple_len(tuple_T *tuple) |
| { |
| if (tuple == NULL) |
| return 0L; |
| return tuple->tv_items.ga_len; |
| } |
| |
| /* |
| * Return TRUE when two tuples have exactly the same values. |
| */ |
| int |
| tuple_equal( |
| tuple_T *t1, |
| tuple_T *t2, |
| int ic) // ignore case for strings |
| { |
| if (t1 == t2) |
| return TRUE; |
| |
| int t1_len = tuple_len(t1); |
| int t2_len = tuple_len(t2); |
| |
| if (t1_len != t2_len) |
| return FALSE; |
| |
| if (t1_len == 0) |
| // empty and NULL tuples are considered equal |
| return TRUE; |
| |
| // If the tuples "t1" or "t2" is NULL, then it is handled by the length |
| // checks above. |
| |
| for (int i = 0, j = 0; i < t1_len && j < t2_len; i++, j++) |
| if (!tv_equal(TUPLE_ITEM(t1, i), TUPLE_ITEM(t2, j), ic)) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| /* |
| * Locate item with index "n" in tuple "tuple" and return it. |
| * A negative index is counted from the end; -1 is the last item. |
| * Returns NULL when "n" is out of range. |
| */ |
| typval_T * |
| tuple_find(tuple_T *tuple, long n) |
| { |
| if (tuple == NULL) |
| return NULL; |
| |
| // Negative index is relative to the end. |
| if (n < 0) |
| n = TUPLE_LEN(tuple) + n; |
| |
| // Check for index out of range. |
| if (n < 0 || n >= TUPLE_LEN(tuple)) |
| return NULL; |
| |
| return TUPLE_ITEM(tuple, n); |
| } |
| |
| int |
| tuple_append_tv(tuple_T *tuple, typval_T *tv) |
| { |
| if (ga_grow(&tuple->tv_items, 1) == FAIL) |
| return FAIL; |
| |
| tuple_set_item(tuple, TUPLE_LEN(tuple), tv); |
| |
| return OK; |
| } |
| |
| /* |
| * Concatenate tuples "t1" and "t2" into a new tuple, stored in "tv". |
| * Return FAIL when out of memory. |
| */ |
| int |
| tuple_concat(tuple_T *t1, tuple_T *t2, typval_T *tv) |
| { |
| tuple_T *tuple; |
| |
| // make a copy of the first tuple. |
| if (t1 == NULL) |
| tuple = tuple_alloc(); |
| else |
| tuple = tuple_copy(t1, FALSE, TRUE, 0); |
| if (tuple == NULL) |
| return FAIL; |
| |
| tv->v_type = VAR_TUPLE; |
| tv->v_lock = 0; |
| tv->vval.v_tuple = tuple; |
| if (t1 == NULL) |
| ++tuple->tv_refcount; |
| |
| // append all the items from the second tuple |
| for (int i = 0; i < tuple_len(t2); i++) |
| { |
| typval_T new_tv; |
| |
| copy_tv(TUPLE_ITEM(t2, i), &new_tv); |
| |
| if (tuple_append_tv(tuple, &new_tv) == FAIL) |
| { |
| tuple_free(tuple); |
| return FAIL; |
| } |
| } |
| |
| return OK; |
| } |
| |
| /* |
| * Return a slice of tuple starting at index n1 and ending at index n2, |
| * inclusive (tuple[n1 : n2]) |
| */ |
| tuple_T * |
| tuple_slice(tuple_T *tuple, long n1, long n2) |
| { |
| tuple_T *new_tuple; |
| |
| new_tuple = tuple_alloc_with_items(n2 - n1 + 1); |
| if (new_tuple == NULL) |
| return NULL; |
| |
| for (int i = n1; i <= n2; i++) |
| { |
| typval_T new_tv; |
| |
| copy_tv(TUPLE_ITEM(tuple, i), &new_tv); |
| |
| if (tuple_append_tv(new_tuple, &new_tv) == FAIL) |
| { |
| tuple_free(new_tuple); |
| return NULL; |
| } |
| } |
| |
| return new_tuple; |
| } |
| |
| int |
| tuple_slice_or_index( |
| tuple_T *tuple, |
| int range, |
| varnumber_T n1_arg, |
| varnumber_T n2_arg, |
| int exclusive, |
| typval_T *rettv, |
| int verbose) |
| { |
| long len = tuple_len(tuple); |
| 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 tuple, for Vim9 script start at the first item. |
| // A tuple index out of range is an error. |
| if (!range) |
| { |
| if (verbose) |
| semsg(_(e_tuple_index_out_of_range_nr), (long)n1_arg); |
| return FAIL; |
| } |
| if (in_vim9script()) |
| n1 = n1 < 0 ? 0 : len; |
| else |
| n1 = len; |
| } |
| if (range) |
| { |
| tuple_T *new_tuple; |
| |
| 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; |
| new_tuple = tuple_slice(tuple, n1, n2); |
| if (new_tuple == NULL) |
| return FAIL; |
| clear_tv(rettv); |
| rettv_tuple_set(rettv, new_tuple); |
| } |
| else |
| { |
| // copy the item to "var1" to avoid that freeing the tuple makes it |
| // invalid. |
| copy_tv(tuple_find(tuple, n1), &var1); |
| clear_tv(rettv); |
| *rettv = var1; |
| } |
| return OK; |
| } |
| |
| /* |
| * Make a copy of tuple "orig". Shallow if "deep" is FALSE. |
| * The refcount of the new tuple is set to 1. |
| * See item_copy() for "top" and "copyID". |
| * Returns NULL when out of memory. |
| */ |
| tuple_T * |
| tuple_copy(tuple_T *orig, int deep, int top, int copyID) |
| { |
| tuple_T *copy; |
| int idx; |
| |
| if (orig == NULL) |
| return NULL; |
| |
| copy = tuple_alloc_with_items(TUPLE_LEN(orig)); |
| if (copy == NULL) |
| return NULL; |
| |
| if (orig->tv_type == NULL || top || deep) |
| copy->tv_type = NULL; |
| else |
| copy->tv_type = alloc_type(orig->tv_type); |
| if (copyID != 0) |
| { |
| // Do this before adding the items, because one of the items may |
| // refer back to this tuple. |
| orig->tv_copyID = copyID; |
| orig->tv_copytuple = copy; |
| } |
| |
| for (idx = 0; idx < TUPLE_LEN(orig) && !got_int; idx++) |
| { |
| copy->tv_items.ga_len++; |
| if (deep) |
| { |
| if (item_copy(TUPLE_ITEM(orig, idx), TUPLE_ITEM(copy, idx), |
| deep, FALSE, copyID) == FAIL) |
| break; |
| } |
| else |
| copy_tv(TUPLE_ITEM(orig, idx), TUPLE_ITEM(copy, idx)); |
| } |
| |
| ++copy->tv_refcount; |
| if (idx != TUPLE_LEN(orig)) |
| { |
| tuple_unref(copy); |
| copy = NULL; |
| } |
| |
| return copy; |
| } |
| |
| /* |
| * Allocate a variable for a tuple and fill it from "*arg". |
| * "*arg" points to the "," after the first element. |
| * "rettv" contains the first element. |
| * Returns OK or FAIL. |
| */ |
| int |
| eval_tuple(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int do_error) |
| { |
| int evaluate = evalarg == NULL ? FALSE |
| : evalarg->eval_flags & EVAL_EVALUATE; |
| tuple_T *tuple = NULL; |
| typval_T tv; |
| int vim9script = in_vim9script(); |
| int had_comma; |
| |
| if (check_typval_is_value(rettv) == FAIL) |
| { |
| // the first item is not a valid value type |
| clear_tv(rettv); |
| return FAIL; |
| } |
| |
| if (evaluate) |
| { |
| tuple = tuple_alloc(); |
| if (tuple == NULL) |
| return FAIL; |
| |
| if (rettv->v_type != VAR_UNKNOWN) |
| { |
| // Add the first item to the tuple from "rettv" |
| if (tuple_append_tv(tuple, rettv) == FAIL) |
| return FAIL; |
| // The first item in "rettv" is added to the tuple. Set the rettv |
| // type to unknown, so that the caller doesn't free it. |
| rettv->v_type = VAR_UNKNOWN; |
| } |
| } |
| |
| if (**arg == ')') |
| // empty tuple |
| goto done; |
| |
| if (vim9script && !IS_WHITE_NL_OR_NUL((*arg)[1]) && (*arg)[1] != ')') |
| { |
| semsg(_(e_white_space_required_after_str_str), ",", *arg); |
| goto failret; |
| } |
| |
| *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) |
| { |
| if (tuple_append_tv(tuple, &tv) == FAIL) |
| { |
| clear_tv(&tv); |
| goto failret; |
| } |
| } |
| |
| 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_tuple_str), *arg); |
| } |
| goto failret; |
| } |
| } |
| |
| if (**arg != ')') |
| { |
| if (do_error) |
| semsg(_(e_missing_end_of_tuple_rsp_str), *arg); |
| failret: |
| if (evaluate) |
| tuple_free(tuple); |
| return FAIL; |
| } |
| |
| done: |
| *arg += 1; |
| if (evaluate) |
| rettv_tuple_set(rettv, tuple); |
| |
| return OK; |
| } |
| |
| /* |
| * Lock or unlock a tuple. "deep" is number of levels to go. |
| * When "check_refcount" is TRUE do not lock a tuple with a reference |
| * count larger than 1. |
| */ |
| void |
| tuple_lock(tuple_T *tuple, int deep, int lock, int check_refcount) |
| { |
| if (tuple == NULL || (check_refcount && tuple->tv_refcount > 1)) |
| return; |
| |
| if (lock) |
| tuple->tv_lock |= VAR_LOCKED; |
| else |
| tuple->tv_lock &= ~VAR_LOCKED; |
| |
| if (deep < 0 || deep > 1) |
| { |
| // recursive: lock/unlock the items the Tuple contains |
| for (int i = 0; i < TUPLE_LEN(tuple); i++) |
| item_lock(TUPLE_ITEM(tuple, i), deep - 1, lock, check_refcount); |
| } |
| } |
| |
| typedef struct join_S { |
| char_u *s; |
| char_u *tofree; |
| } join_T; |
| |
| static int |
| tuple_join_inner( |
| garray_T *gap, // to store the result in |
| tuple_T *tuple, |
| char_u *sep, |
| int echo_style, |
| int restore_copyID, |
| int copyID, |
| garray_T *join_gap) // to keep each tuple item string |
| { |
| int i; |
| join_T *p; |
| int len; |
| int sumlen = 0; |
| int first = TRUE; |
| char_u *tofree; |
| char_u numbuf[NUMBUFLEN]; |
| char_u *s; |
| typval_T *tv; |
| |
| // Stringify each item in the tuple. |
| for (i = 0; i < TUPLE_LEN(tuple) && !got_int; i++) |
| { |
| tv = TUPLE_ITEM(tuple, i); |
| s = echo_string_core(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(); |
| } |
| |
| // If there is only one item in the tuple, then add the separator after |
| // that. |
| if (join_gap->ga_len == 1) |
| ga_concat(gap, sep); |
| |
| return OK; |
| } |
| |
| /* |
| * Join tuple "tuple" into a string in "*gap", using separator "sep". |
| * When "echo_style" is TRUE use String as echoed, otherwise as inside a Tuple. |
| * Return FAIL or OK. |
| */ |
| int |
| tuple_join( |
| garray_T *gap, |
| tuple_T *tuple, |
| char_u *sep, |
| int echo_style, |
| int restore_copyID, |
| int copyID) |
| { |
| garray_T join_ga; |
| int retval; |
| join_T *p; |
| int i; |
| |
| if (TUPLE_LEN(tuple) < 1) |
| return OK; // nothing to do |
| ga_init2(&join_ga, sizeof(join_T), TUPLE_LEN(tuple)); |
| retval = tuple_join_inner(gap, tuple, 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; |
| } |
| |
| /* |
| * Return an allocated string with the string representation of a tuple. |
| * May return NULL. |
| */ |
| char_u * |
| tuple2string(typval_T *tv, int copyID, int restore_copyID) |
| { |
| garray_T ga; |
| |
| if (tv->vval.v_tuple == NULL) |
| return NULL; |
| ga_init2(&ga, sizeof(char), 80); |
| ga_append(&ga, '('); |
| if (tuple_join(&ga, tv->vval.v_tuple, (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; |
| } |
| |
| /* |
| * Implementation of foreach() for a Tuple. Apply "expr" to |
| * every item in Tuple "tuple" and return the result in "rettv". |
| */ |
| void |
| tuple_foreach( |
| tuple_T *tuple, |
| filtermap_T filtermap, |
| typval_T *expr) |
| { |
| int len = tuple_len(tuple); |
| int rem; |
| typval_T newtv; |
| funccall_T *fc; |
| |
| // set_vim_var_nr() doesn't set the type |
| set_vim_var_type(VV_KEY, VAR_NUMBER); |
| |
| // Create one funccall_T for all eval_expr_typval() calls. |
| fc = eval_expr_get_funccal(expr, &newtv); |
| |
| for (int idx = 0; idx < len; idx++) |
| { |
| set_vim_var_nr(VV_KEY, idx); |
| if (filter_map_one(TUPLE_ITEM(tuple, idx), expr, filtermap, fc, |
| &newtv, &rem) == FAIL) |
| break; |
| } |
| |
| if (fc != NULL) |
| remove_funccal(); |
| } |
| |
| /* |
| * Count the number of times item "needle" occurs in Tuple "l" starting at index |
| * "idx". Case is ignored if "ic" is TRUE. |
| */ |
| long |
| tuple_count(tuple_T *tuple, typval_T *needle, long idx, int ic) |
| { |
| long n = 0; |
| |
| if (tuple == NULL) |
| return 0; |
| |
| int len = TUPLE_LEN(tuple); |
| if (len == 0) |
| return 0; |
| |
| if (idx < 0 || idx >= len) |
| { |
| semsg(_(e_tuple_index_out_of_range_nr), idx); |
| return 0; |
| } |
| |
| for (int i = idx; i < len; i++) |
| { |
| if (tv_equal(TUPLE_ITEM(tuple, i), needle, ic)) |
| ++n; |
| } |
| |
| return n; |
| } |
| |
| /* |
| * "items(tuple)" function |
| * Caller must have already checked that argvars[0] is a tuple. |
| */ |
| void |
| tuple2items(typval_T *argvars, typval_T *rettv) |
| { |
| tuple_T *tuple = argvars[0].vval.v_tuple; |
| varnumber_T idx; |
| |
| if (rettv_list_alloc(rettv) == FAIL) |
| return; |
| |
| if (tuple == NULL) |
| return; // null tuple behaves like an empty list |
| |
| for (idx = 0; idx < TUPLE_LEN(tuple); idx++) |
| { |
| list_T *l = list_alloc(); |
| |
| if (l == NULL) |
| break; |
| |
| if (list_append_list(rettv->vval.v_list, l) == FAIL) |
| { |
| vim_free(l); |
| break; |
| } |
| if (list_append_number(l, idx) == FAIL |
| || list_append_tv(l, TUPLE_ITEM(tuple, idx)) == FAIL) |
| break; |
| } |
| } |
| |
| /* |
| * Search for item "tv" in tuple "tuple" starting from index "start_idx". |
| * If "ic" is set to TRUE, then case is ignored. |
| * |
| * Returns the index where "tv" is present or -1 if it is not found. |
| */ |
| int |
| index_tuple(tuple_T *tuple, typval_T *tv, int start_idx, int ic) |
| { |
| if (start_idx < 0) |
| { |
| start_idx = TUPLE_LEN(tuple) + start_idx; |
| if (start_idx < 0) |
| start_idx = 0; |
| } |
| |
| for (int idx = start_idx; idx < TUPLE_LEN(tuple); idx++) |
| { |
| if (tv_equal(TUPLE_ITEM(tuple, idx), tv, ic)) |
| return idx; |
| } |
| |
| return -1; // "tv" not found |
| } |
| |
| /* |
| * Evaluate 'expr' for each item in the Tuple 'tuple' starting with the item at |
| * 'startidx' and return the index of the item where 'expr' is TRUE. Returns |
| * -1 if 'expr' doesn't evaluate to TRUE for any of the items. |
| */ |
| int |
| indexof_tuple(tuple_T *tuple, long startidx, typval_T *expr) |
| { |
| long idx = 0; |
| int len; |
| int found; |
| |
| if (tuple == NULL) |
| return -1; |
| |
| len = TUPLE_LEN(tuple); |
| |
| if (startidx < 0) |
| { |
| // negative index: index from the end |
| startidx = len + startidx; |
| if (startidx < 0) |
| startidx = 0; |
| } |
| |
| set_vim_var_type(VV_KEY, VAR_NUMBER); |
| |
| int called_emsg_start = called_emsg; |
| |
| for (idx = startidx; idx < len; idx++) |
| { |
| set_vim_var_nr(VV_KEY, idx); |
| copy_tv(TUPLE_ITEM(tuple, idx), get_vim_var_tv(VV_VAL)); |
| |
| found = indexof_eval_expr(expr); |
| clear_tv(get_vim_var_tv(VV_VAL)); |
| |
| if (found) |
| return idx; |
| |
| if (called_emsg != called_emsg_start) |
| return -1; |
| } |
| |
| return -1; |
| } |
| |
| /* |
| * Return the max or min of the items in tuple "tuple". |
| * If a tuple item is not a number, then "error" is set to TRUE. |
| */ |
| varnumber_T |
| tuple_max_min(tuple_T *tuple, int domax, int *error) |
| { |
| varnumber_T n = 0; |
| varnumber_T v; |
| |
| if (tuple == NULL || TUPLE_LEN(tuple) == 0) |
| return 0; |
| |
| n = tv_get_number_chk(TUPLE_ITEM(tuple, 0), error); |
| if (*error) |
| return n; // type error; errmsg already given |
| |
| for (int idx = 1; idx < TUPLE_LEN(tuple); idx++) |
| { |
| v = tv_get_number_chk(TUPLE_ITEM(tuple, idx), error); |
| if (*error) |
| return n; // type error; errmsg already given |
| if (domax ? v > n : v < n) |
| n = v; |
| } |
| |
| return n; |
| } |
| |
| /* |
| * Repeat the tuple "tuple" "n" times and set "rettv" to the new tuple. |
| */ |
| void |
| tuple_repeat(tuple_T *tuple, int n, typval_T *rettv) |
| { |
| rettv->v_type = VAR_TUPLE; |
| rettv->vval.v_tuple = NULL; |
| |
| if (tuple == NULL || TUPLE_LEN(tuple) == 0 || n <= 0) |
| return; |
| |
| if (rettv_tuple_set_with_items(rettv, TUPLE_LEN(tuple) * n) == FAIL) |
| return; |
| |
| tuple_T *new_tuple = rettv->vval.v_tuple; |
| for (int count = 0; count < n; count++) |
| { |
| for (int idx = 0; idx < TUPLE_LEN(tuple); idx++) |
| { |
| copy_tv(TUPLE_ITEM(tuple, idx), |
| TUPLE_ITEM(new_tuple, TUPLE_LEN(new_tuple))); |
| new_tuple->tv_items.ga_len++; |
| } |
| } |
| } |
| |
| /* |
| * Reverse "tuple" and return the new tuple in "rettv" |
| */ |
| void |
| tuple_reverse(tuple_T *tuple, typval_T *rettv) |
| { |
| rettv->v_type = VAR_TUPLE; |
| rettv->vval.v_tuple = NULL; |
| |
| int len = tuple_len(tuple); |
| |
| if (len == 0) |
| return; |
| |
| if (rettv_tuple_set_with_items(rettv, len) == FAIL) |
| return; |
| |
| tuple_T *new_tuple = rettv->vval.v_tuple; |
| for (int i = 0; i < len; i++) |
| copy_tv(TUPLE_ITEM(tuple, i), TUPLE_ITEM(new_tuple, len - i - 1)); |
| new_tuple->tv_items.ga_len = tuple->tv_items.ga_len; |
| } |
| |
| /* |
| * Tuple reduce() function |
| */ |
| void |
| tuple_reduce(typval_T *argvars, typval_T *expr, typval_T *rettv) |
| { |
| tuple_T *tuple = argvars[0].vval.v_tuple; |
| int called_emsg_start = called_emsg; |
| typval_T initial; |
| int idx = 0; |
| funccall_T *fc; |
| typval_T argv[3]; |
| int r; |
| |
| if (argvars[2].v_type == VAR_UNKNOWN) |
| { |
| if (tuple == NULL || TUPLE_LEN(tuple) == 0) |
| { |
| semsg(_(e_reduce_of_an_empty_str_with_no_initial_value), "Tuple"); |
| return; |
| } |
| initial = *TUPLE_ITEM(tuple, 0); |
| idx = 1; |
| } |
| else |
| { |
| initial = argvars[2]; |
| idx = 0; |
| } |
| |
| copy_tv(&initial, rettv); |
| |
| if (tuple == NULL) |
| return; |
| |
| // Create one funccall_T for all eval_expr_typval() calls. |
| fc = eval_expr_get_funccal(expr, rettv); |
| |
| for ( ; idx < TUPLE_LEN(tuple); idx++) |
| { |
| argv[0] = *rettv; |
| rettv->v_type = VAR_UNKNOWN; |
| argv[1] = *TUPLE_ITEM(tuple, idx); |
| |
| r = eval_expr_typval(expr, TRUE, argv, 2, fc, rettv); |
| |
| clear_tv(&argv[0]); |
| |
| if (r == FAIL || called_emsg != called_emsg_start) |
| break; |
| } |
| |
| if (fc != NULL) |
| remove_funccal(); |
| } |
| |
| /* |
| * Returns TRUE if two tuples with types "type1" and "type2" are addable. |
| * Otherwise returns FALSE. |
| */ |
| int |
| check_tuples_addable(type_T *type1, type_T *type2) |
| { |
| int addable = TRUE; |
| |
| // If the first operand is a variadic tuple and the second argument is |
| // non-variadic, then concatenation is not possible. |
| if ((type1->tt_flags & TTFLAG_VARARGS) |
| && !(type2->tt_flags & TTFLAG_VARARGS) |
| && (type2->tt_argcount > 0)) |
| addable = FALSE; |
| |
| if ((type1->tt_flags & TTFLAG_VARARGS) |
| && (type2->tt_flags & TTFLAG_VARARGS)) |
| { |
| // two variadic tuples |
| if (type1->tt_argcount > 1 || type2->tt_argcount > 1) |
| // one of the variadic tuple has fixed number of items |
| addable = FALSE; |
| else if ((type1->tt_argcount == 1 && type2->tt_argcount == 1) |
| && !equal_type(type1->tt_args[0], type2->tt_args[0], 0)) |
| // the tuples have different item types |
| addable = FALSE; |
| } |
| |
| if (!addable) |
| { |
| emsg(_(e_cannot_use_variadic_tuple_in_concatenation)); |
| return FAIL; |
| } |
| |
| return OK; |
| } |
| |
| #endif // defined(FEAT_EVAL) |