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>
diff --git a/src/eval.c b/src/eval.c
index 9a140c1..bd8e7cf 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -107,7 +107,7 @@
// 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 @@
/*
* 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;
}
- 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;
+ }
}
- 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 @@
/*
* 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 @@
*
* 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 @@
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 @@
{
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 @@
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);
@@ -1524,6 +1555,64 @@
}
/*
+ * 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'.
* 'v_type' is VAR_CLASS or VAR_OBJECT.
@@ -1630,6 +1719,7 @@
{
lp->ll_dict = NULL;
lp->ll_list = NULL;
+ lp->ll_tuple = NULL;
class_T *cl;
if (v_type == VAR_OBJECT)
@@ -1697,8 +1787,8 @@
/*
* 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 @@
/*
* 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 @@
}
/*
- * 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 @@
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 @@
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 @@
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 @@
case VAR_OBJECT:
case VAR_CLASS:
case VAR_TYPEALIAS:
+ case VAR_TUPLE:
break;
case VAR_BLOB:
@@ -2619,6 +2732,7 @@
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 @@
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 @@
}
else
{
- emsg(_(e_string_list_or_blob_required));
+ emsg(_(e_string_list_tuple_or_blob_required));
clear_tv(&tv);
}
}
@@ -2780,6 +2910,22 @@
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 @@
}
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);
@@ -3960,6 +4108,36 @@
}
/*
+ * 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 @@
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 @@
*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 @@
/*
* 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 @@
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 @@
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 @@
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 @@
* $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 @@
* ! 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 @@
/*
* nested expression: (expression).
* or lambda: (arg) => expr
+ * or tuple
*/
case '(': ret = eval9_nested_expr(arg, rettv, evalarg, evaluate);
break;
@@ -5484,7 +5694,8 @@
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 @@
case VAR_STRING:
case VAR_LIST:
+ case VAR_TUPLE:
case VAR_DICT:
case VAR_BLOB:
break;
@@ -5735,6 +5947,16 @@
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;
@@ -6080,6 +6302,51 @@
}
/*
+ * 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.
* When "copyID" is not zero replace recursive dicts with "...".
@@ -6316,6 +6583,10 @@
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 @@
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;