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 -- [email protected] -- 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    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/202011230732.0AN7WHCu1410025%40masaka.moolenaar.net.

Raspunde prin e-mail lui