To: vim_dev@googlegroups.com Subject: Patch 8.2.2658 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.2658 Problem: :for cannot loop over a string. Solution: Accept a string argument and iterate over its characters. Files: runtime/doc/eval.txt, src/eval.c, src/vim9compile.c, src/vim9execute.c, src/errors.h, src/testdir/test_vimscript.vim, src/testdir/test_vim9_disassemble.vim, src/testdir/test_vim9_script.vim *** ../vim-8.2.2657/runtime/doc/eval.txt 2021-03-22 16:19:37.525354304 +0100 --- runtime/doc/eval.txt 2021-03-26 20:06:17.439450912 +0100 *************** *** 439,446 **** For loop ~ ! The |:for| loop executes commands for each item in a list. A variable is set ! to each item in the list in sequence. Example: > :for item in mylist : call Doit(item) :endfor --- 439,446 ---- For loop ~ ! The |:for| loop executes commands for each item in a List, String or Blob. ! A variable is set to each item in sequence. Example with a List: > :for item in mylist : call Doit(item) :endfor *************** *** 457,463 **** function will be a simpler method than a for loop. Just like the |:let| command, |:for| also accepts a list of variables. This ! requires the argument to be a list of lists. > :for [lnum, col] in [[1, 3], [2, 8], [3, 0]] : call Doit(lnum, col) :endfor --- 457,463 ---- function will be a simpler method than a for loop. Just like the |:let| command, |:for| also accepts a list of variables. This ! requires the argument to be a List of Lists. > :for [lnum, col] in [[1, 3], [2, 8], [3, 0]] : call Doit(lnum, col) :endfor *************** *** 473,478 **** --- 473,486 ---- : endif :endfor + For a Blob one byte at a time is used. + + For a String one character, including any composing characters, is used as a + String. Example: > + for c in text + echo 'This character is ' .. c + endfor + List functions ~ *E714* *** ../vim-8.2.2657/src/eval.c 2021-03-22 16:19:37.529354296 +0100 --- src/eval.c 2021-03-26 19:57:30.976667734 +0100 *************** *** 41,46 **** --- 41,48 ---- list_T *fi_list; // list being used int fi_bi; // index of blob blob_T *fi_blob; // blob being used + char_u *fi_string; // copy of string being used + int fi_byte_idx; // byte index in fi_string } forinfo_T; static int tv_op(typval_T *tv1, typval_T *tv2, char_u *op); *************** *** 1738,1743 **** --- 1740,1753 ---- } clear_tv(&tv); } + else if (tv.v_type == VAR_STRING) + { + fi->fi_byte_idx = 0; + fi->fi_string = tv.vval.v_string; + tv.vval.v_string = NULL; + if (fi->fi_string == NULL) + fi->fi_string = vim_strsave((char_u *)""); + } else { emsg(_(e_listreq)); *************** *** 1790,1796 **** tv.vval.v_number = blob_get(fi->fi_blob, fi->fi_bi); ++fi->fi_bi; return ex_let_vars(arg, &tv, TRUE, fi->fi_semicolon, ! fi->fi_varcount, flag, NULL) == OK; } item = fi->fi_lw.lw_item; --- 1800,1822 ---- tv.vval.v_number = blob_get(fi->fi_blob, fi->fi_bi); ++fi->fi_bi; return ex_let_vars(arg, &tv, TRUE, fi->fi_semicolon, ! fi->fi_varcount, flag, NULL) == OK; ! } ! ! if (fi->fi_string != NULL) ! { ! typval_T tv; ! int len; ! ! len = mb_ptr2len(fi->fi_string + fi->fi_byte_idx); ! if (len == 0) ! return FALSE; ! tv.v_type = VAR_STRING; ! tv.v_lock = VAR_FIXED; ! tv.vval.v_string = vim_strnsave(fi->fi_string + fi->fi_byte_idx, len); ! fi->fi_byte_idx += len; ! return ex_let_vars(arg, &tv, TRUE, fi->fi_semicolon, ! fi->fi_varcount, flag, NULL) == OK; } item = fi->fi_lw.lw_item; *************** *** 1800,1806 **** { fi->fi_lw.lw_item = item->li_next; result = (ex_let_vars(arg, &item->li_tv, TRUE, fi->fi_semicolon, ! fi->fi_varcount, flag, NULL) == OK); } return result; } --- 1826,1832 ---- { fi->fi_lw.lw_item = item->li_next; result = (ex_let_vars(arg, &item->li_tv, TRUE, fi->fi_semicolon, ! fi->fi_varcount, flag, NULL) == OK); } return result; } *************** *** 1813,1825 **** { forinfo_T *fi = (forinfo_T *)fi_void; ! if (fi != NULL && fi->fi_list != NULL) { list_rem_watch(fi->fi_list, &fi->fi_lw); list_unref(fi->fi_list); } ! if (fi != NULL && fi->fi_blob != NULL) blob_unref(fi->fi_blob); vim_free(fi); } --- 1839,1855 ---- { forinfo_T *fi = (forinfo_T *)fi_void; ! if (fi == NULL) ! return; ! if (fi->fi_list != NULL) { list_rem_watch(fi->fi_list, &fi->fi_lw); list_unref(fi->fi_list); } ! else if (fi->fi_blob != NULL) blob_unref(fi->fi_blob); + else + vim_free(fi->fi_string); vim_free(fi); } *** ../vim-8.2.2657/src/vim9compile.c 2021-03-25 22:15:24.404073755 +0100 --- src/vim9compile.c 2021-03-26 20:36:11.194826047 +0100 *************** *** 7264,7274 **** } arg_end = arg; ! // Now that we know the type of "var", check that it is a list, now or at ! // runtime. vartype = ((type_T **)stack->ga_data)[stack->ga_len - 1]; ! if (need_type(vartype, &t_list_any, -1, 0, cctx, FALSE, FALSE) == FAIL) { drop_scope(cctx); return NULL; } --- 7264,7278 ---- } arg_end = arg; ! // If we know the type of "var" and it is a not a list or string we can ! // give an error now. vartype = ((type_T **)stack->ga_data)[stack->ga_len - 1]; ! if (vartype->tt_type != VAR_LIST && vartype->tt_type != VAR_STRING ! && vartype->tt_type != VAR_ANY) { + // TODO: support Blob + semsg(_(e_for_loop_on_str_not_supported), + vartype_name(vartype->tt_type)); drop_scope(cctx); return NULL; } *** ../vim-8.2.2657/src/vim9execute.c 2021-03-26 13:33:59.556825543 +0100 --- src/vim9execute.c 2021-03-26 20:29:27.952037032 +0100 *************** *** 2741,2776 **** // top of a for loop case ISN_FOR: { ! list_T *list = STACK_TV_BOT(-1)->vval.v_list; typval_T *idxtv = STACK_TV_VAR(iptr->isn_arg.forloop.for_idx); - // push the next item from the list if (GA_GROW(&ectx.ec_stack, 1) == FAIL) goto failed; ! ++idxtv->vval.v_number; ! if (list == NULL || idxtv->vval.v_number >= list->lv_len) { ! // past the end of the list, jump to "endfor" ! ectx.ec_iidx = iptr->isn_arg.forloop.for_end; ! may_restore_cmdmod(&funclocal); } ! else if (list->lv_first == &range_list_item) { ! // non-materialized range() list ! tv = STACK_TV_BOT(0); ! tv->v_type = VAR_NUMBER; ! tv->v_lock = 0; ! tv->vval.v_number = list_find_nr( ! list, idxtv->vval.v_number, NULL); ! ++ectx.ec_stack.ga_len; } else { ! listitem_T *li = list_find(list, idxtv->vval.v_number); ! ! copy_tv(&li->li_tv, STACK_TV_BOT(0)); ! ++ectx.ec_stack.ga_len; } } break; --- 2741,2816 ---- // top of a for loop case ISN_FOR: { ! typval_T *ltv = STACK_TV_BOT(-1); typval_T *idxtv = STACK_TV_VAR(iptr->isn_arg.forloop.for_idx); if (GA_GROW(&ectx.ec_stack, 1) == FAIL) goto failed; ! if (ltv->v_type == VAR_LIST) { ! list_T *list = ltv->vval.v_list; ! ! // push the next item from the list ! ++idxtv->vval.v_number; ! if (list == NULL ! || idxtv->vval.v_number >= list->lv_len) ! { ! // past the end of the list, jump to "endfor" ! ectx.ec_iidx = iptr->isn_arg.forloop.for_end; ! may_restore_cmdmod(&funclocal); ! } ! else if (list->lv_first == &range_list_item) ! { ! // non-materialized range() list ! tv = STACK_TV_BOT(0); ! tv->v_type = VAR_NUMBER; ! tv->v_lock = 0; ! tv->vval.v_number = list_find_nr( ! list, idxtv->vval.v_number, NULL); ! ++ectx.ec_stack.ga_len; ! } ! else ! { ! listitem_T *li = list_find(list, ! idxtv->vval.v_number); ! ! copy_tv(&li->li_tv, STACK_TV_BOT(0)); ! ++ectx.ec_stack.ga_len; ! } } ! else if (ltv->v_type == VAR_STRING) { ! char_u *str = ltv->vval.v_string; ! int len = str == NULL ? 0 : (int)STRLEN(str); ! ! // Push the next character from the string. The index ! // is for the last byte of the previous character. ! ++idxtv->vval.v_number; ! if (idxtv->vval.v_number >= len) ! { ! // past the end of the string, jump to "endfor" ! ectx.ec_iidx = iptr->isn_arg.forloop.for_end; ! may_restore_cmdmod(&funclocal); ! } ! else ! { ! int clen = mb_ptr2len(str + idxtv->vval.v_number); ! ! tv = STACK_TV_BOT(0); ! tv->v_type = VAR_STRING; ! tv->vval.v_string = vim_strnsave( ! str + idxtv->vval.v_number, clen); ! ++ectx.ec_stack.ga_len; ! idxtv->vval.v_number += clen - 1; ! } } else { ! // TODO: support Blob ! semsg(_(e_for_loop_on_str_not_supported), ! vartype_name(ltv->v_type)); ! goto failed; } } break; *** ../vim-8.2.2657/src/errors.h 2021-03-25 22:22:26.486934365 +0100 --- src/errors.h 2021-03-26 20:21:47.701193669 +0100 *************** *** 389,391 **** --- 389,393 ---- INIT(= N_("E1175: Non-empty string required for argument %d")); EXTERN char e_misplaced_command_modifier[] INIT(= N_("E1176: Misplaced command modifier")); + EXTERN char e_for_loop_on_str_not_supported[] + INIT(= N_("E1177: For loop on %s not supported")); *** ../vim-8.2.2657/src/testdir/test_vimscript.vim 2021-03-18 22:15:00.589208304 +0100 --- src/testdir/test_vimscript.vim 2021-03-26 20:00:43.028183055 +0100 *************** *** 7484,7489 **** --- 7484,7509 ---- call assert_equal(v:false, eval(string(v:false))) endfunction + func Test_for_over_string() + let res = '' + for c in 'aéc̀d' + let res ..= c .. '-' + endfor + call assert_equal('a-é-c̀-d-', res) + + let res = '' + for c in '' + let res ..= c .. '-' + endfor + call assert_equal('', res) + + let res = '' + for c in test_null_string() + let res ..= c .. '-' + endfor + call assert_equal('', res) + endfunc + "------------------------------------------------------------------------------- " Modelines {{{1 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker *** ../vim-8.2.2657/src/testdir/test_vim9_disassemble.vim 2021-03-25 22:15:24.408073745 +0100 --- src/testdir/test_vim9_disassemble.vim 2021-03-26 20:39:31.518032869 +0100 *************** *** 1061,1067 **** '\d STORE -1 in $1\_s*' .. '\d PUSHS "\["one", "two"\]"\_s*' .. '\d BCALL eval(argc 1)\_s*' .. - '\d CHECKTYPE list stack\[-1\]\_s*' .. '\d FOR $1 -> \d\+\_s*' .. '\d STORE $2\_s*' .. 'res ..= str\_s*' .. --- 1061,1066 ---- *************** *** 1071,1077 **** '\d\+ CONCAT\_s*' .. '\d\+ STORE $0\_s*' .. 'endfor\_s*' .. ! '\d\+ JUMP -> 6\_s*' .. '\d\+ DROP\_s*' .. 'return res\_s*' .. '\d\+ LOAD $0\_s*' .. --- 1070,1076 ---- '\d\+ CONCAT\_s*' .. '\d\+ STORE $0\_s*' .. 'endfor\_s*' .. ! '\d\+ JUMP -> 5\_s*' .. '\d\+ DROP\_s*' .. 'return res\_s*' .. '\d\+ LOAD $0\_s*' .. *** ../vim-8.2.2657/src/testdir/test_vim9_script.vim 2021-03-26 18:49:19.467527666 +0100 --- src/testdir/test_vim9_script.vim 2021-03-26 20:36:42.806694381 +0100 *************** *** 2322,2327 **** --- 2322,2346 ---- res ..= n .. s endfor assert_equal('1a2b', res) + + # loop over string + res = '' + for c in 'aéc̀d' + res ..= c .. '-' + endfor + assert_equal('a-é-c̀-d-', res) + + res = '' + for c in '' + res ..= c .. '-' + endfor + assert_equal('', res) + + res = '' + for c in test_null_string() + res ..= c .. '-' + endfor + assert_equal('', res) enddef def Test_for_loop_fails() *************** *** 2333,2342 **** CheckDefFailure(['var x = 5', 'for x in range(5)'], 'E1017:') CheckScriptFailure(['def Func(arg: any)', 'for arg in range(5)', 'enddef', 'defcompile'], 'E1006:') delfunc! g:Func - CheckDefFailure(['for i in "text"'], 'E1012:') CheckDefFailure(['for i in xxx'], 'E1001:') CheckDefFailure(['endfor'], 'E588:') CheckDefFailure(['for i in range(3)', 'echo 3'], 'E170:') enddef def Test_for_loop_script_var() --- 2352,2368 ---- CheckDefFailure(['var x = 5', 'for x in range(5)'], 'E1017:') CheckScriptFailure(['def Func(arg: any)', 'for arg in range(5)', 'enddef', 'defcompile'], 'E1006:') delfunc! g:Func CheckDefFailure(['for i in xxx'], 'E1001:') CheckDefFailure(['endfor'], 'E588:') CheckDefFailure(['for i in range(3)', 'echo 3'], 'E170:') + + # wrong type detected at compile time + CheckDefFailure(['for i in {a: 1}', 'echo 3', 'endfor'], 'E1177: For loop on dict not supported') + + # wrong type detected at runtime + g:adict = {a: 1} + CheckDefExecFailure(['for i in g:adict', 'echo 3', 'endfor'], 'E1177: For loop on dict not supported') + unlet g:adict enddef def Test_for_loop_script_var() *** ../vim-8.2.2657/src/version.c 2021-03-26 18:49:19.467527666 +0100 --- src/version.c 2021-03-26 20:40:06.313903844 +0100 *************** *** 752,753 **** --- 752,755 ---- { /* Add new patch number below this line */ + /**/ + 2658, /**/ -- In many of the more relaxed civilizations on the Outer Eastern Rim of the Galaxy, "The Hitchhiker's Guide to the Galaxy" has already supplanted the great "Encyclopedia Galactica" as the standard repository of all knowledge and wisdom, for though it has many omissions and contains much that is apocryphal, or at least wildly inaccurate, it scores over the older, more pedestrian work in two important respects. First, it is slightly cheaper; and second, it has the words "DON'T PANIC" inscribed in large friendly letters on its cover. -- Douglas Adams, "The Hitchhiker's Guide to the Galaxy" /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///