Patch 8.2.2034
Problem: Vim9: list unpack in for statement not compiled yet.
Solution: Compile list unpack. (closes #7345)
Files: src/vim9.h, src/vim9compile.c, src/vim9execute.c, src/errors.h,
src/eval.c, src/testdir/test_vim9_disassemble.vim,
src/testdir/test_vim9_script.vim
*** ../vim-8.2.2033/src/vim9.h 2020-11-22 18:15:40.171258382 +0100
--- src/vim9.h 2020-11-22 20:20:23.307105098 +0100
***************
*** 146,151 ****
--- 146,152 ----
ISN_CMDMOD, // set cmdmod
ISN_CMDMOD_REV, // undo ISN_CMDMOD
+ ISN_UNPACK, // unpack list into items, uses isn_arg.unpack
ISN_SHUFFLE, // move item on stack up or down
ISN_DROP // pop stack and discard value
} isntype_T;
***************
*** 284,289 ****
--- 285,296 ----
cmdmod_T *cf_cmdmod; // allocated
} cmod_T;
+ // arguments to ISN_UNPACK
+ typedef struct {
+ int unp_count; // number of items to produce
+ int unp_semicolon; // last item gets list of remainder
+ } unpack_T;
+
/*
* Instruction
*/
***************
*** 321,326 ****
--- 328,334 ----
shuffle_T shuffle;
put_T put;
cmod_T cmdmod;
+ unpack_T unpack;
} isn_arg;
};
*** ../vim-8.2.2033/src/vim9compile.c 2020-11-22 18:15:40.171258382 +0100
--- src/vim9compile.c 2020-11-22 21:48:59.898204382 +0100
***************
*** 1888,1893 ****
--- 1888,1906 ----
return OK;
}
+ static int
+ generate_UNPACK(cctx_T *cctx, int var_count, int semicolon)
+ {
+ isn_T *isn;
+
+ RETURN_OK_IF_SKIP(cctx);
+ if ((isn = generate_instr(cctx, ISN_UNPACK)) == NULL)
+ return FAIL;
+ isn->isn_arg.unpack.unp_count = var_count;
+ isn->isn_arg.unpack.unp_semicolon = semicolon;
+ return OK;
+ }
+
/*
* Generate an instruction for any command modifiers.
*/
***************
*** 6323,6334 ****
}
/*
! * compile "for var in expr"
*
* Produces instructions:
* PUSHNR -1
* STORE loop-idx Set index to -1
! * EVAL expr Push result of "expr"
* top: FOR loop-idx, end Increment index, use list on bottom of stack
* - if beyond end, jump to "end"
* - otherwise get item from list and push it
--- 6336,6347 ----
}
/*
! * Compile "for var in expr":
*
* Produces instructions:
* PUSHNR -1
* STORE loop-idx Set index to -1
! * EVAL expr result of "expr" on top of stack
* top: FOR loop-idx, end Increment index, use list on bottom of stack
* - if beyond end, jump to "end"
* - otherwise get item from list and push it
***************
*** 6337,6347 ****
* JUMP top Jump back to repeat
* end: DROP Drop the result of "expr"
*
*/
static char_u *
! compile_for(char_u *arg, cctx_T *cctx)
{
char_u *p;
size_t varlen;
garray_T *instr = &cctx->ctx_instr;
garray_T *stack = &cctx->ctx_type_stack;
--- 6350,6368 ----
* JUMP top Jump back to repeat
* end: DROP Drop the result of "expr"
*
+ * Compile "for [var1, var2] in expr" - as above, but instead of "STORE var":
+ * UNPACK 2 Split item in 2
+ * STORE var1 Store item in "var1"
+ * STORE var2 Store item in "var2"
*/
static char_u *
! compile_for(char_u *arg_start, cctx_T *cctx)
{
+ char_u *arg;
+ char_u *arg_end;
char_u *p;
+ int var_count = 0;
+ int semicolon = FALSE;
size_t varlen;
garray_T *instr = &cctx->ctx_instr;
garray_T *stack = &cctx->ctx_type_stack;
***************
*** 6349,6366 ****
lvar_T *loop_lvar; // loop iteration variable
lvar_T *var_lvar; // variable for "var"
type_T *vartype;
! // TODO: list of variables: "for [key, value] in dict"
! // parse "var"
! for (p = arg; eval_isnamec1(*p); ++p)
! ;
! varlen = p - arg;
! var_lvar = lookup_local(arg, varlen, cctx);
! if (var_lvar != NULL)
! {
! semsg(_(e_variable_already_declared), arg);
! return NULL;
! }
// consume "in"
p = skipwhite(p);
--- 6370,6381 ----
lvar_T *loop_lvar; // loop iteration variable
lvar_T *var_lvar; // variable for "var"
type_T *vartype;
+ type_T *item_type = &t_any;
+ int idx;
! p = skip_var_list(arg_start, TRUE, &var_count, &semicolon, FALSE);
! if (var_count == 0)
! var_count = 1;
// consume "in"
p = skipwhite(p);
***************
*** 6371,6382 ****
}
p = skipwhite(p + 2);
-
scope = new_scope(cctx, FOR_SCOPE);
if (scope == NULL)
return NULL;
! // Reserve a variable to store the loop iteration counter.
loop_lvar = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number);
if (loop_lvar == NULL)
{
--- 6386,6397 ----
}
p = skipwhite(p + 2);
scope = new_scope(cctx, FOR_SCOPE);
if (scope == NULL)
return NULL;
! // Reserve a variable to store the loop iteration counter and initialize it
! // to -1.
loop_lvar = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number);
if (loop_lvar == NULL)
{
***************
*** 6384,6399 ****
drop_scope(cctx);
return NULL;
}
-
- // Reserve a variable to store "var"
- var_lvar = reserve_local(cctx, arg, varlen, FALSE, &t_any);
- if (var_lvar == NULL)
- {
- // out of memory or used as an argument
- drop_scope(cctx);
- return NULL;
- }
-
generate_STORENR(cctx, loop_lvar->lv_idx, -1);
// compile "expr", it remains on the stack until "endfor"
--- 6399,6404 ----
***************
*** 6403,6408 ****
--- 6408,6414 ----
drop_scope(cctx);
return NULL;
}
+ arg_end = arg;
// Now that we know the type of "var", check that it is a list, now or at
// runtime.
***************
*** 6412,6427 ****
drop_scope(cctx);
return NULL;
}
if (vartype->tt_type == VAR_LIST && vartype->tt_member->tt_type != VAR_ANY)
! var_lvar->lv_type = vartype->tt_member;
// "for_end" is set when ":endfor" is found
scope->se_u.se_for.fs_top_label = instr->ga_len;
-
generate_FOR(cctx, loop_lvar->lv_idx);
- generate_STORE(cctx, ISN_STORE, var_lvar->lv_idx, NULL);
! return arg;
}
/*
--- 6418,6495 ----
drop_scope(cctx);
return NULL;
}
+
if (vartype->tt_type == VAR_LIST && vartype->tt_member->tt_type != VAR_ANY)
! {
! if (var_count == 1)
! item_type = vartype->tt_member;
! else if (vartype->tt_member->tt_type == VAR_LIST
! && vartype->tt_member->tt_member->tt_type != VAR_ANY)
! item_type = vartype->tt_member->tt_member;
! }
// "for_end" is set when ":endfor" is found
scope->se_u.se_for.fs_top_label = instr->ga_len;
generate_FOR(cctx, loop_lvar->lv_idx);
! arg = arg_start;
! if (var_count > 1)
! {
! generate_UNPACK(cctx, var_count, semicolon);
! arg = skipwhite(arg + 1); // skip white after '['
!
! // the list item is replaced by a number of items
! if (ga_grow(stack, var_count - 1) == FAIL)
! {
! drop_scope(cctx);
! return NULL;
! }
! --stack->ga_len;
! for (idx = 0; idx < var_count; ++idx)
! {
! ((type_T **)stack->ga_data)[stack->ga_len] =
! (semicolon && idx == 0) ? vartype : item_type;
! ++stack->ga_len;
! }
! }
!
! for (idx = 0; idx < var_count; ++idx)
! {
! // TODO: use skip_var_one, also assign to @r, $VAR, etc.
! p = arg;
! while (eval_isnamec(*p))
! ++p;
! varlen = p - arg;
! var_lvar = lookup_local(arg, varlen, cctx);
! if (var_lvar != NULL)
! {
! semsg(_(e_variable_already_declared), arg);
! drop_scope(cctx);
! return NULL;
! }
!
! // Reserve a variable to store "var".
! // TODO: check for type
! var_lvar = reserve_local(cctx, arg, varlen, FALSE, &t_any);
! if (var_lvar == NULL)
! {
! // out of memory or used as an argument
! drop_scope(cctx);
! return NULL;
! }
!
! if (semicolon && idx == var_count - 1)
! var_lvar->lv_type = vartype;
! else
! var_lvar->lv_type = item_type;
! generate_STORE(cctx, ISN_STORE, var_lvar->lv_idx, NULL);
!
! if (*p == ',' || *p == ';')
! ++p;
! arg = skipwhite(p);
! }
!
! return arg_end;
}
/*
***************
*** 7957,7962 ****
--- 8025,8031 ----
case ISN_STRSLICE:
case ISN_THROW:
case ISN_TRY:
+ case ISN_UNPACK:
// nothing allocated
break;
}
*** ../vim-8.2.2033/src/vim9execute.c 2020-11-22 18:15:40.171258382 +0100
--- src/vim9execute.c 2020-11-22 22:07:56.790707418 +0100
***************
*** 2877,2887 ****
restore_cmdmod = FALSE;
break;
case ISN_SHUFFLE:
{
! typval_T tmp_tv;
! int item = iptr->isn_arg.shuffle.shfl_item;
! int up = iptr->isn_arg.shuffle.shfl_up;
tmp_tv = *STACK_TV_BOT(-item);
for ( ; up > 0 && item > 1; --up)
--- 2877,2960 ----
restore_cmdmod = FALSE;
break;
+ case ISN_UNPACK:
+ {
+ int count = iptr->isn_arg.unpack.unp_count;
+ int semicolon = iptr->isn_arg.unpack.unp_semicolon;
+ list_T *l;
+ listitem_T *li;
+ int i;
+
+ // Check there is a valid list to unpack.
+ tv = STACK_TV_BOT(-1);
+ if (tv->v_type != VAR_LIST)
+ {
+ SOURCING_LNUM = iptr->isn_lnum;
+ emsg(_(e_for_argument_must_be_sequence_of_lists));
+ goto on_error;
+ }
+ l = tv->vval.v_list;
+ if (l == NULL
+ || l->lv_len < (semicolon ? count - 1 : count))
+ {
+ SOURCING_LNUM = iptr->isn_lnum;
+ emsg(_(e_list_value_does_not_have_enough_items));
+ goto on_error;
+ }
+ else if (!semicolon && l->lv_len > count)
+ {
+ SOURCING_LNUM = iptr->isn_lnum;
+ emsg(_(e_list_value_has_more_items_than_targets));
+ goto on_error;
+ }
+
+ CHECK_LIST_MATERIALIZE(l);
+ if (GA_GROW(&ectx.ec_stack, count - 1) == FAIL)
+ goto failed;
+ ectx.ec_stack.ga_len += count - 1;
+
+ // Variable after semicolon gets a list with the remaining
+ // items.
+ if (semicolon)
+ {
+ list_T *rem_list =
+ list_alloc_with_items(l->lv_len - count + 1);
+
+ if (rem_list == NULL)
+ goto failed;
+ tv = STACK_TV_BOT(-count);
+ tv->vval.v_list = rem_list;
+ ++rem_list->lv_refcount;
+ tv->v_lock = 0;
+ li = l->lv_first;
+ for (i = 0; i < count - 1; ++i)
+ li = li->li_next;
+ for (i = 0; li != NULL; ++i)
+ {
+ list_set_item(rem_list, i, &li->li_tv);
+ li = li->li_next;
+ }
+ --count;
+ }
+
+ // Produce the values in reverse order, first item last.
+ li = l->lv_first;
+ for (i = 0; i < count; ++i)
+ {
+ tv = STACK_TV_BOT(-i - 1);
+ copy_tv(&li->li_tv, tv);
+ li = li->li_next;
+ }
+
+ list_unref(l);
+ }
+ break;
+
case ISN_SHUFFLE:
{
! typval_T tmp_tv;
! int item = iptr->isn_arg.shuffle.shfl_item;
! int up = iptr->isn_arg.shuffle.shfl_up;
tmp_tv = *STACK_TV_BOT(-item);
for ( ; up > 0 && item > 1; --up)
***************
*** 3606,3611 ****
--- 3679,3688 ----
}
case ISN_CMDMOD_REV: smsg("%4d CMDMOD_REV", current); break;
+ case ISN_UNPACK: smsg("%4d UNPACK %d%s", current,
+ iptr->isn_arg.unpack.unp_count,
+ iptr->isn_arg.unpack.unp_semicolon ? " semicolon" : "");
+ break;
case ISN_SHUFFLE: smsg("%4d SHUFFLE %d up %d", current,
iptr->isn_arg.shuffle.shfl_item,
iptr->isn_arg.shuffle.shfl_up);
*** ../vim-8.2.2033/src/errors.h 2020-11-19 18:53:15.188492574 +0100
--- src/errors.h 2020-11-22 21:42:51.307411580 +0100
***************
*** 21,26 ****
--- 21,30 ----
#ifdef FEAT_EVAL
EXTERN char e_invalid_command_str[]
INIT(= N_("E476: Invalid command: %s"));
+ EXTERN char e_list_value_has_more_items_than_targets[]
+ INIT(= N_("E710: List value has more items than targets"));
+ EXTERN char e_list_value_does_not_have_enough_items[]
+ INIT(= N_("E711: List value does not have enough items"));
EXTERN char e_cannot_slice_dictionary[]
INIT(= N_("E719: Cannot slice a Dictionary"));
EXTERN char e_assert_fails_second_arg[]
***************
*** 305,307 ****
--- 309,313 ----
INIT(= N_("E1138: Using a Bool as a Number"));
EXTERN char e_missing_matching_bracket_after_dict_key[]
INIT(= N_("E1139: Missing matching bracket after dict key"));
+ EXTERN char e_for_argument_must_be_sequence_of_lists[]
+ INIT(= N_("E1140: For argument must be a sequence of lists"));
*** ../vim-8.2.2033/src/eval.c 2020-11-21 14:03:39.851158161 +0100
--- src/eval.c 2020-11-22 21:42:24.835499546 +0100
***************
*** 1397,1407 ****
++lp->ll_n1;
}
if (ri != NULL)
! emsg(_("E710: List value has more items than target"));
else if (lp->ll_empty2
? (lp->ll_li != NULL && lp->ll_li->li_next != NULL)
: lp->ll_n1 != lp->ll_n2)
! emsg(_("E711: List value has not enough items"));
}
else
{
--- 1397,1407 ----
++lp->ll_n1;
}
if (ri != NULL)
! emsg(_(e_list_value_has_more_items_than_targets));
else if (lp->ll_empty2
? (lp->ll_li != NULL && lp->ll_li->li_next != NULL)
: lp->ll_n1 != lp->ll_n2)
! emsg(_(e_list_value_does_not_have_enough_items));
}
else
{
*** ../vim-8.2.2033/src/testdir/test_vim9_disassemble.vim 2020-11-22 18:15:40.171258382 +0100
--- src/testdir/test_vim9_disassemble.vim 2020-11-23 08:30:03.170053889 +0100
***************
*** 1026,1031 ****
--- 1026,1065 ----
instr)
enddef
+ def ForLoopUnpack()
+ for [x1, x2] in [[1, 2], [3, 4]]
+ echo x1 x2
+ endfor
+ enddef
+
+ def Test_disassemble_for_loop_unpack()
+ var instr = execute('disassemble ForLoopUnpack')
+ assert_match('ForLoopUnpack\_s*' ..
+ 'for \[x1, x2\] in \[\[1, 2\], \[3, 4\]\]\_s*' ..
+ '\d\+ STORE -1 in $0\_s*' ..
+ '\d\+ PUSHNR 1\_s*' ..
+ '\d\+ PUSHNR 2\_s*' ..
+ '\d\+ NEWLIST size 2\_s*' ..
+ '\d\+ PUSHNR 3\_s*' ..
+ '\d\+ PUSHNR 4\_s*' ..
+ '\d\+ NEWLIST size 2\_s*' ..
+ '\d\+ NEWLIST size 2\_s*' ..
+ '\d\+ FOR $0 -> 16\_s*' ..
+ '\d\+ UNPACK 2\_s*' ..
+ '\d\+ STORE $1\_s*' ..
+ '\d\+ STORE $2\_s*' ..
+ 'echo x1 x2\_s*' ..
+ '\d\+ LOAD $1\_s*' ..
+ '\d\+ LOAD $2\_s*' ..
+ '\d\+ ECHO 2\_s*' ..
+ 'endfor\_s*' ..
+ '\d\+ JUMP -> 8\_s*' ..
+ '\d\+ DROP\_s*' ..
+ '\d\+ PUSHNR 0\_s*' ..
+ '\d\+ RETURN',
+ instr)
+ enddef
+
let g:number = 42
def TypeCast()
*** ../vim-8.2.2033/src/testdir/test_vim9_script.vim 2020-11-19 18:53:15.188492574 +0100
--- src/testdir/test_vim9_script.vim 2020-11-23 08:18:05.500332324 +0100
***************
*** 1862,1867 ****
--- 1862,1905 ----
CheckDefFailure(['for i in range(3)', 'echo 3'], 'E170:')
enddef
+ def Test_for_loop_unpack()
+ var result = []
+ for [v1, v2] in [[1, 2], [3, 4]]
+ result->add(v1)
+ result->add(v2)
+ endfor
+ assert_equal([1, 2, 3, 4], result)
+
+ result = []
+ for [v1, v2; v3] in [[1, 2], [3, 4, 5, 6]]
+ result->add(v1)
+ result->add(v2)
+ result->add(v3)
+ endfor
+ assert_equal([1, 2, [], 3, 4, [5, 6]], result)
+
+ var lines =<< trim END
+ for [v1, v2] in [[1, 2, 3], [3, 4]]
+ echo v1 v2
+ endfor
+ END
+ CheckDefExecFailure(lines, 'E710:', 1)
+
+ lines =<< trim END
+ for [v1, v2] in [[1], [3, 4]]
+ echo v1 v2
+ endfor
+ END
+ CheckDefExecFailure(lines, 'E711:', 1)
+
+ lines =<< trim END
+ for [v1, v1] in [[1, 2], [3, 4]]
+ echo v1
+ endfor
+ END
+ CheckDefExecFailure(lines, 'E1017:', 1)
+ enddef
+
def Test_while_loop()
var result = ''
var cnt = 0
*** ../vim-8.2.2033/src/version.c 2020-11-22 18:15:40.171258382 +0100
--- src/version.c 2020-11-22 22:09:41.730406054 +0100
***************
*** 752,753 ****
--- 752,755 ----
{ /* Add new patch number below this line */
+ /**/
+ 2034,
/**/
--
Even got a Datapoint 3600(?) with a DD50 connector instead of the
usual DB25... what a nightmare trying to figure out the pinout
for *that* with no spex...
/// Bram Moolenaar -- Br...@Moolenaar.net --
http://www.Moolenaar.net \\\
/// sponsor Vim, vote for features --
http://www.Vim.org/sponsor/ \\\
\\\ an exciting new programming language --
http://www.Zimbu.org ///
\\\ help me help AIDS victims --
http://ICCF-Holland.org ///