0
0
mirror of https://github.com/vim/vim.git synced 2025-09-24 03:44:06 -04:00

patch 9.1.1232: Vim script is missing the tuple data type

Problem:  Vim script is missing the tuple data type
Solution: Add support for the tuple data type
          (Yegappan Lakshmanan)

closes: #16776

Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Yegappan Lakshmanan
2025-03-23 16:42:16 +01:00
committed by Christian Brabandt
parent adb703e1b9
commit 9cb865e95b
75 changed files with 7155 additions and 691 deletions

View File

@@ -107,7 +107,7 @@ eval_clear(void)
// autoloaded script names
free_autoload_scriptnames();
// unreferenced lists and dicts
// unreferenced lists, tuples and dicts
(void)garbage_collect(FALSE);
// functions not garbage collected
@@ -620,28 +620,48 @@ skip_expr_concatenate(
/*
* Convert "tv" to a string.
* When "join_list" is TRUE convert a List into a sequence of lines.
* When "join_list" is TRUE convert a List or a Tuple into a sequence of lines.
* Returns an allocated string (NULL when out of memory).
*/
char_u *
typval2string(typval_T *tv, int join_list)
{
garray_T ga;
char_u *retval;
char_u *retval = NULL;
if (join_list && tv->v_type == VAR_LIST)
if (join_list && (tv->v_type == VAR_LIST || tv->v_type == VAR_TUPLE))
{
ga_init2(&ga, sizeof(char), 80);
if (tv->vval.v_list != NULL)
if (tv->v_type == VAR_LIST)
{
list_join(&ga, tv->vval.v_list, (char_u *)"\n", TRUE, FALSE, 0);
if (tv->vval.v_list->lv_len > 0)
ga_append(&ga, NL);
ga_init2(&ga, sizeof(char), 80);
if (tv->vval.v_list != NULL)
{
list_join(&ga, tv->vval.v_list, (char_u *)"\n", TRUE, FALSE,
0);
if (tv->vval.v_list->lv_len > 0)
ga_append(&ga, NL);
}
ga_append(&ga, NUL);
retval = (char_u *)ga.ga_data;
}
else
{
// tuple
ga_init2(&ga, sizeof(char), 80);
if (tv->vval.v_tuple != NULL)
{
tuple_join(&ga, tv->vval.v_tuple, (char_u *)"\n", TRUE, FALSE,
0);
if (TUPLE_LEN(tv->vval.v_tuple) > 0)
ga_append(&ga, NL);
}
ga_append(&ga, NUL);
retval = (char_u *)ga.ga_data;
}
ga_append(&ga, NUL);
retval = (char_u *)ga.ga_data;
}
else if (tv->v_type == VAR_LIST || tv->v_type == VAR_DICT)
else if (tv->v_type == VAR_LIST
|| tv->v_type == VAR_TUPLE
|| tv->v_type == VAR_DICT)
{
char_u *tofree;
char_u numbuf[NUMBUFLEN];
@@ -659,7 +679,8 @@ typval2string(typval_T *tv, int join_list)
/*
* Top level evaluation function, returning a string. Does not handle line
* breaks.
* When "join_list" is TRUE convert a List into a sequence of lines.
* When "join_list" is TRUE convert a List and a Tuple into a sequence of
* lines.
* Return pointer to allocated memory, or NULL for failure.
*/
char_u *
@@ -1095,7 +1116,7 @@ flag_string_T glv_flag_strings[] = {
*
* This is typically called with "lval_root" as "root". For a class, find
* the name from lp in the class from root, fill in lval_T if found. For a
* complex type, list/dict use it as the result; just put the root into
* complex type, list/tuple/dict use it as the result; just put the root into
* ll_tv.
*
* "lval_root" is a hack used during run-time/instr-execution to provide the
@@ -1322,8 +1343,11 @@ get_lval_dict_item(
return GLV_FAIL;
}
lp->ll_list = NULL;
lp->ll_list = NULL;
lp->ll_blob = NULL;
lp->ll_object = NULL;
lp->ll_class = NULL;
lp->ll_tuple = NULL;
// a NULL dict is equivalent with an empty dict
if (lp->ll_tv->vval.v_dict == NULL)
@@ -1425,7 +1449,13 @@ get_lval_blob(
{
long bloblen = blob_len(lp->ll_tv->vval.v_blob);
// Get the number and item for the only or first index of the List.
lp->ll_list = NULL;
lp->ll_dict = NULL;
lp->ll_object = NULL;
lp->ll_class = NULL;
lp->ll_tuple = NULL;
// Get the number and item for the only or first index of a List or Tuple.
if (empty1)
lp->ll_n1 = 0;
else
@@ -1484,6 +1514,7 @@ get_lval_list(
lp->ll_dict = NULL;
lp->ll_object = NULL;
lp->ll_class = NULL;
lp->ll_tuple = NULL;
lp->ll_list = lp->ll_tv->vval.v_list;
lp->ll_li = check_range_index_one(lp->ll_list, &lp->ll_n1,
(flags & GLV_ASSIGN_WITH_OP) == 0, quiet);
@@ -1523,6 +1554,64 @@ get_lval_list(
return OK;
}
/*
* Get a tuple lval variable that can be assigned a value to: "name",
* "na{me}", "name[expr]", "name[expr][expr]", etc.
*
* 'idx' specifies the tuple index.
* If 'quiet' is TRUE, then error messages are not displayed for an invalid
* index.
*
* The typval is returned in 'lp'. Returns GLV_OK on success and GLV_FAIL on
* failure.
*/
static int
get_lval_tuple(
lval_T *lp,
typval_T *idx,
int quiet)
{
// is number or string
lp->ll_n1 = (long)tv_get_number(idx);
lp->ll_list = NULL;
lp->ll_dict = NULL;
lp->ll_blob = NULL;
lp->ll_object = NULL;
lp->ll_class = NULL;
lp->ll_tuple = lp->ll_tv->vval.v_tuple;
lp->ll_tv = tuple_find(lp->ll_tuple, lp->ll_n1);
if (lp->ll_tv == NULL)
{
if (!quiet)
semsg(_(e_tuple_index_out_of_range_nr), lp->ll_n1);
return GLV_FAIL;
}
// use the type of the member
if (lp->ll_valtype != NULL)
{
if (lp->ll_valtype != NULL
&& lp->ll_valtype->tt_type == VAR_TUPLE
&& lp->ll_valtype->tt_argcount == 1)
{
// a variadic tuple or a single item tuple
if (lp->ll_valtype->tt_flags & TTFLAG_VARARGS)
lp->ll_valtype = lp->ll_valtype->tt_args[0]->tt_member;
else
lp->ll_valtype = lp->ll_valtype->tt_args[0];
}
else
// If the LHS member type is not known (VAR_ANY), then get it from
// the tuple item (after indexing)
lp->ll_valtype = typval2type(lp->ll_tv, get_copyID(),
&lp->ll_type_list, TVTT_DO_MEMBER);
}
return GLV_OK;
}
/*
* Get a class or object lval method in class "cl". The 'key' argument points
* to the method name and 'key_end' points to the character after 'key'.
@@ -1630,6 +1719,7 @@ get_lval_class_or_obj(
{
lp->ll_dict = NULL;
lp->ll_list = NULL;
lp->ll_tuple = NULL;
class_T *cl;
if (v_type == VAR_OBJECT)
@@ -1697,8 +1787,8 @@ dot_allowed_after_type(char_u *name, vartype_T v_type, int quiet)
/*
* Check whether left bracket ("[") is allowed after the variable "name" with
* type "v_type". Only Dict, List and Blob types support a bracket after the
* variable name. Returns TRUE if bracket is allowed after the name.
* type "v_type". Only Dict, List, Tuple and Blob types support a bracket
* after the variable name. Returns TRUE if bracket is allowed after the name.
*/
static int
bracket_allowed_after_type(char_u *name, vartype_T v_type, int quiet)
@@ -1716,14 +1806,18 @@ bracket_allowed_after_type(char_u *name, vartype_T v_type, int quiet)
/*
* Check whether the variable "name" with type "v_type" can be followed by an
* index. Only Dict, List, Blob, Object and Class types support indexing.
* Returns TRUE if indexing is allowed after the name.
* index. Only Dict, List, Tuple, Blob, Object and Class types support
* indexing. Returns TRUE if indexing is allowed after the name.
*/
static int
index_allowed_after_type(char_u *name, vartype_T v_type, int quiet)
{
if (v_type != VAR_LIST && v_type != VAR_DICT && v_type != VAR_BLOB &&
v_type != VAR_OBJECT && v_type != VAR_CLASS)
if (v_type != VAR_LIST
&& v_type != VAR_TUPLE
&& v_type != VAR_DICT
&& v_type != VAR_BLOB
&& v_type != VAR_OBJECT
&& v_type != VAR_CLASS)
{
if (!quiet)
semsg(_(e_index_not_allowed_after_str_str),
@@ -1735,8 +1829,8 @@ index_allowed_after_type(char_u *name, vartype_T v_type, int quiet)
}
/*
* Get the lval of a list/dict/blob/object/class subitem starting at "p". Loop
* until no more [idx] or .key is following.
* Get the lval of a list/tuple/dict/blob/object/class subitem starting at "p".
* Loop until no more [idx] or .key is following.
*
* If "rettv" is not NULL it points to the value to be assigned.
* "unlet" is TRUE for ":unlet".
@@ -1863,6 +1957,12 @@ get_lval_subscript(
emsg(_(e_cannot_slice_dictionary));
goto done;
}
if (v_type == VAR_TUPLE)
{
if (!quiet)
emsg(_(e_cannot_slice_tuple));
goto done;
}
if (rettv != NULL
&& !(rettv->v_type == VAR_LIST
&& rettv->vval.v_list != NULL)
@@ -1932,6 +2032,11 @@ get_lval_subscript(
if (get_lval_list(lp, &var1, &var2, empty1, flags, quiet) == FAIL)
goto done;
}
else if (v_type == VAR_TUPLE)
{
if (get_lval_tuple(lp, &var1, quiet) == FAIL)
goto done;
}
else // v_type == VAR_CLASS || v_type == VAR_OBJECT
{
if (get_lval_class_or_obj(lp, key, p, v_type, cl_exec, flags,
@@ -1945,6 +2050,13 @@ get_lval_subscript(
var2.v_type = VAR_UNKNOWN;
}
if (lp->ll_tuple != NULL)
{
if (!quiet)
emsg(_(e_tuple_is_immutable));
goto done;
}
rc = OK;
done:
@@ -2575,6 +2687,7 @@ tv_op(typval_T *tv1, typval_T *tv2, char_u *op)
case VAR_OBJECT:
case VAR_CLASS:
case VAR_TYPEALIAS:
case VAR_TUPLE:
break;
case VAR_BLOB:
@@ -2619,6 +2732,7 @@ eval_for_line(
char_u *expr;
typval_T tv;
list_T *l;
tuple_T *tuple;
int skip = !(evalarg->eval_flags & EVAL_EVALUATE);
*errp = TRUE; // default: there is an error
@@ -2671,6 +2785,22 @@ eval_for_line(
fi->fi_lw.lw_item = l->lv_first;
}
}
else if (tv.v_type == VAR_TUPLE)
{
tuple = tv.vval.v_tuple;
if (tuple == NULL)
{
// a null tuple is like an empty tuple: do nothing
clear_tv(&tv);
}
else
{
// No need to increment the refcount, it's already set for
// the tuple being used in "tv".
fi->fi_tuple = tuple;
fi->fi_tuple_idx = 0;
}
}
else if (tv.v_type == VAR_BLOB)
{
fi->fi_bi = 0;
@@ -2695,7 +2825,7 @@ eval_for_line(
}
else
{
emsg(_(e_string_list_or_blob_required));
emsg(_(e_string_list_tuple_or_blob_required));
clear_tv(&tv);
}
}
@@ -2780,6 +2910,22 @@ next_for_item(void *fi_void, char_u *arg)
return result;
}
if (fi->fi_tuple != NULL)
{
typval_T tv;
if (fi->fi_tuple_idx >= TUPLE_LEN(fi->fi_tuple))
return FALSE;
copy_tv(TUPLE_ITEM(fi->fi_tuple, fi->fi_tuple_idx), &tv);
++fi->fi_tuple_idx;
++fi->fi_bi;
if (skip_assign)
return TRUE;
return ex_let_vars(arg, &tv, TRUE, fi->fi_semicolon,
fi->fi_varcount, flag, NULL) == OK;
}
item = fi->fi_lw.lw_item;
if (item == NULL)
result = FALSE;
@@ -2813,6 +2959,8 @@ free_for_info(void *fi_void)
}
else if (fi->fi_blob != NULL)
blob_unref(fi->fi_blob);
else if (fi->fi_tuple != NULL)
tuple_unref(fi->fi_tuple);
else
vim_free(fi->fi_string);
vim_free(fi);
@@ -3959,6 +4107,36 @@ eval_addlist(typval_T *tv1, typval_T *tv2)
return OK;
}
/*
* Make a copy of tuple "tv1" and append tuple "tv2".
*/
int
eval_addtuple(typval_T *tv1, typval_T *tv2)
{
int vim9script = in_vim9script();
typval_T var3;
if (vim9script && tv1->vval.v_tuple != NULL && tv2->vval.v_tuple != NULL
&& tv1->vval.v_tuple->tv_type != NULL
&& tv2->vval.v_tuple->tv_type != NULL)
{
if (!check_tuples_addable(tv1->vval.v_tuple->tv_type,
tv2->vval.v_tuple->tv_type))
return FAIL;
}
// concatenate tuples
if (tuple_concat(tv1->vval.v_tuple, tv2->vval.v_tuple, &var3) == FAIL)
{
clear_tv(tv1);
clear_tv(tv2);
return FAIL;
}
clear_tv(tv1);
*tv1 = var3;
return OK;
}
/*
* Left or right shift the number "tv1" by the number "tv2" and store the
* result in "tv1".
@@ -4231,6 +4409,7 @@ eval6(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
int concat;
typval_T var2;
int vim9script = in_vim9script();
long op_lnum = SOURCING_LNUM;
// "." is only string concatenation when scriptversion is 1
// "+=", "-=" and "..=" are assignments
@@ -4259,7 +4438,8 @@ eval6(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
*arg = p;
}
if ((op != '+' || (rettv->v_type != VAR_LIST
&& rettv->v_type != VAR_BLOB))
&& rettv->v_type != VAR_TUPLE
&& rettv->v_type != VAR_BLOB))
&& (op == '.' || rettv->v_type != VAR_FLOAT)
&& evaluate)
{
@@ -4302,6 +4482,8 @@ eval6(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
/*
* Compute the result.
*/
// use the line of the operation for messages
SOURCING_LNUM = op_lnum;
if (op == '.')
{
if (eval_concat_str(rettv, &var2) == FAIL)
@@ -4316,6 +4498,12 @@ eval6(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
if (eval_addlist(rettv, &var2) == FAIL)
return FAIL;
}
else if (op == '+' && rettv->v_type == VAR_TUPLE
&& var2.v_type == VAR_TUPLE)
{
if (eval_addtuple(rettv, &var2) == FAIL)
return FAIL;
}
else
{
if (eval_addsub_number(rettv, &var2, op) == FAIL)
@@ -4681,13 +4869,23 @@ handle_predefined(char_u *s, int len, typval_T *rettv)
return OK;
}
break;
case 10: if (STRNCMP(s, "null_class", 10) == 0)
case 10:
if (STRNCMP(s, "null_", 5) != 0)
break;
// null_class
if (STRNCMP(s + 5, "class", 5) == 0)
{
rettv->v_type = VAR_CLASS;
rettv->vval.v_class = NULL;
return OK;
}
break;
if (STRNCMP(s + 5, "tuple", 5) == 0)
{
rettv->v_type = VAR_TUPLE;
rettv->vval.v_tuple = NULL;
return OK;
}
break;
case 11: if (STRNCMP(s, "null_string", 11) == 0)
{
rettv->v_type = VAR_STRING;
@@ -4796,16 +4994,26 @@ eval9_nested_expr(
if (ret == NOTDONE)
{
*arg = skipwhite_and_linebreak(*arg + 1, evalarg);
ret = eval1(arg, rettv, evalarg); // recursive!
*arg = skipwhite_and_linebreak(*arg, evalarg);
if (**arg == ')')
++*arg;
else if (ret == OK)
// empty tuple
ret = eval_tuple(arg, rettv, evalarg, TRUE);
else
{
emsg(_(e_missing_closing_paren));
clear_tv(rettv);
ret = FAIL;
ret = eval1(arg, rettv, evalarg); // recursive!
*arg = skipwhite_and_linebreak(*arg, evalarg);
if (**arg == ',')
// tuple
ret = eval_tuple(arg, rettv, evalarg, TRUE);
else if (**arg == ')')
++*arg;
else if (ret == OK)
{
emsg(_(e_missing_closing_paren));
clear_tv(rettv);
ret = FAIL;
}
}
}
@@ -4896,6 +5104,7 @@ eval9_var_func_name(
* $VAR environment variable
* (expression) nested expression
* [expr, expr] List
* (expr, expr) Tuple
* {arg, arg -> expr} Lambda
* {key: val, key: val} Dictionary
* #{key: val, key: val} Dictionary with literal keys
@@ -4904,7 +5113,7 @@ eval9_var_func_name(
* ! in front logical NOT
* - in front unary minus
* + in front unary plus (ignored)
* trailing [] subscript in String or List
* trailing [] subscript in String or List or Tuple
* trailing .name entry in Dictionary
* trailing ->name() method call
*
@@ -5049,6 +5258,7 @@ eval9(
/*
* nested expression: (expression).
* or lambda: (arg) => expr
* or tuple
*/
case '(': ret = eval9_nested_expr(arg, rettv, evalarg, evaluate);
break;
@@ -5484,7 +5694,8 @@ eval_index(
var1.v_type = VAR_STRING;
}
if (vim9script && rettv->v_type == VAR_LIST)
if (vim9script && (rettv->v_type == VAR_LIST
|| rettv->v_type == VAR_TUPLE))
tv_get_number_chk(&var1, &error);
else
error = tv_get_string_chk(&var1) == NULL;
@@ -5603,6 +5814,7 @@ check_can_index(typval_T *rettv, int evaluate, int verbose)
case VAR_STRING:
case VAR_LIST:
case VAR_TUPLE:
case VAR_DICT:
case VAR_BLOB:
break;
@@ -5735,6 +5947,16 @@ eval_index_inner(
return FAIL;
break;
case VAR_TUPLE:
if (var1 == NULL)
n1 = 0;
if (var2 == NULL)
n2 = VARNUM_MAX;
if (tuple_slice_or_index(rettv->vval.v_tuple,
is_range, n1, n2, exclusive, rettv, verbose) == FAIL)
return FAIL;
break;
case VAR_DICT:
{
dictitem_T *item;
@@ -6079,6 +6301,51 @@ list_tv2string(
return r;
}
/*
* Return a textual representation of a Tuple in "tv".
* If the memory is allocated "tofree" is set to it, otherwise NULL.
* When "copyID" is not zero replace recursive lists with "...". When
* "restore_copyID" is FALSE, repeated items in tuples are replaced with "...".
* May return NULL.
*/
static char_u *
tuple_tv2string(
typval_T *tv,
char_u **tofree,
int copyID,
int restore_copyID)
{
tuple_T *tuple = tv->vval.v_tuple;
char_u *r = NULL;
if (tuple == NULL)
{
// NULL tuple is equivalent to an empty tuple.
*tofree = NULL;
r = (char_u *)"()";
}
else if (copyID != 0 && tuple->tv_copyID == copyID
&& tuple->tv_items.ga_len > 0)
{
*tofree = NULL;
r = (char_u *)"(...)";
}
else
{
int old_copyID;
if (restore_copyID)
old_copyID = tuple->tv_copyID;
tuple->tv_copyID = copyID;
*tofree = tuple2string(tv, copyID, restore_copyID);
if (restore_copyID)
tuple->tv_copyID = old_copyID;
r = *tofree;
}
return r;
}
/*
* Return a textual representation of a Dict in "tv".
* If the memory is allocated "tofree" is set to it, otherwise NULL.
@@ -6316,6 +6583,10 @@ echo_string_core(
r = list_tv2string(tv, tofree, copyID, restore_copyID);
break;
case VAR_TUPLE:
r = tuple_tv2string(tv, tofree, copyID, restore_copyID);
break;
case VAR_DICT:
r = dict_tv2string(tv, tofree, copyID, restore_copyID);
break;
@@ -7257,6 +7528,23 @@ item_copy(
if (to->vval.v_list == NULL)
ret = FAIL;
break;
case VAR_TUPLE:
to->v_type = VAR_TUPLE;
to->v_lock = 0;
if (from->vval.v_tuple == NULL)
to->vval.v_tuple = NULL;
else if (copyID != 0 && from->vval.v_tuple->tv_copyID == copyID)
{
// use the copy made earlier
to->vval.v_tuple = from->vval.v_tuple->tv_copytuple;
++to->vval.v_tuple->tv_refcount;
}
else
to->vval.v_tuple = tuple_copy(from->vval.v_tuple,
deep, top, copyID);
if (to->vval.v_tuple == NULL)
ret = FAIL;
break;
case VAR_BLOB:
ret = blob_copy(from->vval.v_blob, to);
break;