diff options
Diffstat (limited to 'mnv/src/tuple.c')
| -rw-r--r-- | mnv/src/tuple.c | 1132 |
1 files changed, 1132 insertions, 0 deletions
diff --git a/mnv/src/tuple.c b/mnv/src/tuple.c new file mode 100644 index 0000000000..b952946618 --- /dev/null +++ b/mnv/src/tuple.c @@ -0,0 +1,1132 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * MNV - MNV is not Vim by Bram Moolenaar + * + * Do ":help uganda" in MNV to read copying and usage conditions. + * Do ":help credits" in MNV to see a list of people who contributed. + * See README.txt for an overview of the MNV source code. + */ + +/* + * tuple.c: Tuple support functions. + */ + +#include "mnv.h" + +#if defined(FEAT_EVAL) + +// 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); + mnv_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 MNV9 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_mnv9script()) + 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 mnv9script = in_mnv9script(); + 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) + goto failret; + // 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 (mnv9script && !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 (!mnv9script) + *arg = skipwhite(*arg); + + // the comma must come after the value + had_comma = **arg == ','; + if (had_comma) + { + if (mnv9script && !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 { + string_T 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 sumlen = 0; + int first = TRUE; + char_u *tofree; + char_u numbuf[NUMBUFLEN]; + string_T s; + typval_T *tv; + size_t seplen = 0; + + // Stringify each item in the tuple. + for (i = 0; i < TUPLE_LEN(tuple) && !got_int; i++) + { + tv = TUPLE_ITEM(tuple, i); + s.string = echo_string_core(tv, &tofree, numbuf, copyID, + echo_style, restore_copyID, !echo_style); + if (s.string == NULL) + return FAIL; + + s.length = STRLEN(s.string); + sumlen += (int)s.length; + + (void)ga_grow(join_gap, 1); + p = ((join_T *)join_gap->ga_data) + (join_gap->ga_len++); + if (tofree != NULL || s.string != numbuf) + { + p->s.string = s.string; + p->s.length = s.length; + p->tofree = tofree; + } + else + { + p->s.string = mnv_strnsave(s.string, s.length); + p->s.length = s.length; + p->tofree = p->s.string; + } + + 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 > 0) + { + seplen = STRLEN(sep); + if (join_gap->ga_len >= 2) + sumlen += (int)seplen * (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_len(gap, sep, seplen); + p = ((join_T *)join_gap->ga_data) + i; + + if (p->s.string != NULL) + ga_concat_len(gap, p->s.string, p->s.length); + 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_len(gap, sep, seplen); + + 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) + { + mnv_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) + { + mnv_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_mnv_var_nr() doesn't set the type + set_mnv_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_mnv_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) + { + mnv_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_mnv_var_type(VV_KEY, VAR_NUMBER); + + int called_emsg_start = called_emsg; + + for (idx = startidx; idx < len; idx++) + { + set_mnv_var_nr(VV_KEY, idx); + copy_tv(TUPLE_ITEM(tuple, idx), get_mnv_var_tv(VV_VAL)); + + found = indexof_eval_expr(expr); + clear_tv(get_mnv_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 an error occurs NULL is returned instead. + */ + typval_T * +tuple_max_min(tuple_T *tuple, int domax) +{ + typval_T *tv = NULL; + int res; + + if (tuple == NULL || TUPLE_LEN(tuple) == 0) + return NULL; + + for (int idx = 0; idx < TUPLE_LEN(tuple); idx++) + { + if (tv == NULL) + tv = TUPLE_ITEM(tuple, idx); + else + { + if (typval_compare2(TUPLE_ITEM(tuple, idx), tv, + domax ? EXPR_GREATER : EXPR_SMALLER, FALSE, &res) == FAIL) + return NULL; + if (res == OK) + tv = TUPLE_ITEM(tuple, idx); + } + } + + return tv; +} + +/* + * 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) |
