blob: 9ca1f34092d693a8e4f1a55cae4e17f89785afe3 [file] [log] [blame]
/* 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)