vim-patch:8.2.3332: Vim9: cannot assign to range in list

Problem:    Vim9: cannot assign to range in list.
Solution:   Implement overwriting a list range.

4f0884d6e2

Co-authored-by: Bram Moolenaar <Bram@vim.org>
This commit is contained in:
zeertzjq 2023-06-12 20:35:01 +08:00
parent 0eb02ea90a
commit aa92a04bee
3 changed files with 127 additions and 78 deletions

View File

@ -1609,12 +1609,9 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
lp->ll_dict = NULL;
lp->ll_list = lp->ll_tv->vval.v_list;
lp->ll_li = tv_list_find_index(lp->ll_list, &lp->ll_n1);
lp->ll_li = tv_list_check_range_index_one(lp->ll_list, &lp->ll_n1, quiet);
if (lp->ll_li == NULL) {
tv_clear(&var2);
if (!quiet) {
semsg(_(e_list_index_out_of_range_nr), (int64_t)lp->ll_n1);
}
return NULL;
}
@ -1625,25 +1622,9 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
if (lp->ll_range && !lp->ll_empty2) {
lp->ll_n2 = (long)tv_get_number(&var2); // Is number or string.
tv_clear(&var2);
if (lp->ll_n2 < 0) {
listitem_T *ni = tv_list_find(lp->ll_list, (int)lp->ll_n2);
if (ni == NULL) {
if (!quiet) {
semsg(_(e_list_index_out_of_range_nr), (int64_t)lp->ll_n2);
}
return NULL;
}
lp->ll_n2 = tv_list_idx_of_item(lp->ll_list, ni);
}
// Check that lp->ll_n2 isn't before lp->ll_n1.
if (lp->ll_n1 < 0) {
lp->ll_n1 = tv_list_idx_of_item(lp->ll_list, lp->ll_li);
}
if (lp->ll_n2 < lp->ll_n1) {
if (!quiet) {
semsg(_(e_list_index_out_of_range_nr), (int64_t)lp->ll_n2);
}
if (tv_list_check_range_index_two(lp->ll_list,
&lp->ll_n1, lp->ll_li,
&lp->ll_n2, quiet) == FAIL) {
return NULL;
}
}
@ -1672,7 +1653,6 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool
const char *op)
{
int cc;
listitem_T *ri;
dictitem_T *di;
if (lp->ll_tv == NULL) {
@ -1733,60 +1713,13 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool
lp->ll_name, TV_CSTRING)) {
// Skip
} else if (lp->ll_range) {
listitem_T *ll_li = lp->ll_li;
int ll_n1 = (int)lp->ll_n1;
if (is_const) {
emsg(_("E996: Cannot lock a range"));
return;
}
// Check whether any of the list items is locked
for (ri = tv_list_first(rettv->vval.v_list);
ri != NULL && ll_li != NULL;) {
if (value_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, lp->ll_name,
TV_CSTRING)) {
return;
}
ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri);
if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == ll_n1)) {
break;
}
ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, ll_li);
ll_n1++;
}
// Assign the List values to the list items.
for (ri = tv_list_first(rettv->vval.v_list); ri != NULL;) {
if (op != NULL && *op != '=') {
eexe_mod_op(TV_LIST_ITEM_TV(lp->ll_li), TV_LIST_ITEM_TV(ri), op);
} else {
tv_clear(TV_LIST_ITEM_TV(lp->ll_li));
tv_copy(TV_LIST_ITEM_TV(ri), TV_LIST_ITEM_TV(lp->ll_li));
}
ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri);
if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == lp->ll_n1)) {
break;
}
assert(lp->ll_li != NULL);
if (TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) == NULL) {
// Need to add an empty item.
tv_list_append_number(lp->ll_list, 0);
// ll_li may have become invalid after append, dont use it.
lp->ll_li = tv_list_last(lp->ll_list); // Valid again.
} else {
lp->ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li);
}
lp->ll_n1++;
}
if (ri != NULL) {
emsg(_("E710: List value has more items than target"));
} else if (lp->ll_empty2
? (lp->ll_li != NULL
&& TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) != NULL)
: lp->ll_n1 != lp->ll_n2) {
emsg(_("E711: List value has not enough items"));
}
(void)tv_list_assign_range(lp->ll_list, rettv->vval.v_list,
lp->ll_n1, lp->ll_n2, lp->ll_empty2, op, lp->ll_name);
} else {
typval_T oldtv = TV_INITIAL_VALUE;
dict_T *dict = lp->ll_dict;

View File

@ -594,6 +594,119 @@ tv_list_copy_error:
return NULL;
}
/// Get the list item in "l" with index "n1". "n1" is adjusted if needed.
/// Return NULL if there is no such item.
listitem_T *tv_list_check_range_index_one(list_T *const l, long *const n1, const bool quiet)
{
listitem_T *li = tv_list_find_index(l, n1);
if (li == NULL) {
if (!quiet) {
semsg(_(e_list_index_out_of_range_nr), (int64_t)n1);
}
return NULL;
}
return li;
}
/// Check that "n2" can be used as the second index in a range of list "l".
/// If "n1" or "n2" is negative it is changed to the positive index.
/// "li1" is the item for item "n1".
/// Return OK or FAIL.
int tv_list_check_range_index_two(list_T *const l, long *const n1, const listitem_T *const li1,
long *const n2, const bool quiet)
{
if (*n2 < 0) {
listitem_T *ni = tv_list_find(l, (int)(*n2));
if (ni == NULL) {
if (!quiet) {
semsg(_(e_list_index_out_of_range_nr), (int64_t)(*n2));
}
return FAIL;
}
*n2 = tv_list_idx_of_item(l, ni);
}
// Check that n2 isn't before n1.
if (*n1 < 0) {
*n1 = tv_list_idx_of_item(l, li1);
}
if (*n2 < *n1) {
if (!quiet) {
semsg(_(e_list_index_out_of_range_nr), (int64_t)(*n2));
}
return FAIL;
}
return OK;
}
/// Assign values from list "src" into a range of "dest".
/// "idx1_arg" is the index of the first item in "dest" to be replaced.
/// "idx2" is the index of last item to be replaced, but when "empty_idx2" is
/// true then replace all items after "idx1".
/// "op" is the operator, normally "=" but can be "+=" and the like.
/// "varname" is used for error messages.
/// Returns OK or FAIL.
int tv_list_assign_range(list_T *const dest, list_T *const src, const long idx1_arg,
const long idx2, const bool empty_idx2, const char *const op,
const char *const varname)
{
long idx1 = idx1_arg;
listitem_T *const first_li = tv_list_find_index(dest, &idx1);
listitem_T *src_li;
// Check whether any of the list items is locked before making any changes.
long idx = idx1;
listitem_T *dest_li = first_li;
for (src_li = tv_list_first(src); src_li != NULL && dest_li != NULL;) {
if (value_check_lock(TV_LIST_ITEM_TV(dest_li)->v_lock, varname, TV_CSTRING)) {
return FAIL;
}
src_li = TV_LIST_ITEM_NEXT(src, src_li);
if (src_li == NULL || (!empty_idx2 && idx2 == idx)) {
break;
}
dest_li = TV_LIST_ITEM_NEXT(dest, dest_li);
idx++;
}
// Assign the List values to the list items.
idx = idx1;
dest_li = first_li;
for (src_li = tv_list_first(src); src_li != NULL;) {
if (op != NULL && *op != '=') {
eexe_mod_op(TV_LIST_ITEM_TV(dest_li), TV_LIST_ITEM_TV(src_li), op);
} else {
tv_clear(TV_LIST_ITEM_TV(dest_li));
tv_copy(TV_LIST_ITEM_TV(src_li), TV_LIST_ITEM_TV(dest_li));
}
src_li = TV_LIST_ITEM_NEXT(src, src_li);
if (src_li == NULL || (!empty_idx2 && idx2 == idx)) {
break;
}
assert(dest_li != NULL);
if (TV_LIST_ITEM_NEXT(dest, dest_li) == NULL) {
// Need to add an empty item.
tv_list_append_number(dest, 0);
// "dest_li" may have become invalid after append, dont use it.
dest_li = tv_list_last(dest); // Valid again.
} else {
dest_li = TV_LIST_ITEM_NEXT(dest, dest_li);
}
idx++;
}
if (src_li != NULL) {
emsg(_("E710: List value has more items than target"));
return FAIL;
}
if (empty_idx2
? (dest_li != NULL && TV_LIST_ITEM_NEXT(dest, dest_li) != NULL)
: idx != idx2) {
emsg(_("E711: List value has not enough items"));
return FAIL;
}
return OK;
}
/// Flatten up to "maxitems" in "list", starting at "first" to depth "maxdepth".
/// When "first" is NULL use the first item.
/// Does nothing if "maxdepth" is 0.

View File

@ -178,11 +178,14 @@ endfunc
" test for range assign
func Test_list_range_assign()
let l = [0]
let l[:] = [1, 2]
call assert_equal([1, 2], l)
let l[-4:-1] = [5, 6]
call assert_equal([5, 6], l)
let lines =<< trim END
VAR l = [0]
LET l[:] = [1, 2]
call assert_equal([1, 2], l)
LET l[-4 : -1] = [5, 6]
call assert_equal([5, 6], l)
END
call CheckLegacyAndVim9Success(lines)
endfunc
" Test removing items in list