forked from aniani/vim
1123 lines
23 KiB
C
1123 lines
23 KiB
C
|
/* 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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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)
|