To: vim_dev@googlegroups.com Subject: Patch 8.2.2344 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.2344 Problem: Using inclusive index for slice is not always desired. Solution: Add the slice() method, which has an exclusive index. (closes #7408) Files: runtime/doc/eval.txt, runtime/doc/usr_41.txt, src/evalfunc.c, src/eval.c, src/proto/eval.pro, src/vim9execute.c, src/proto/vim9execute.pro, src/list.c, src/proto/list.pro, src/testdir/test_vim9_builtin.vim *** ../vim-8.2.2343/runtime/doc/eval.txt 2021-01-13 20:37:44.435646254 +0100 --- runtime/doc/eval.txt 2021-01-13 21:41:17.497497979 +0100 *************** *** 291,296 **** --- 314,322 ---- :let shortlist = mylist[2:2] " List with one item: [3] :let otherlist = mylist[:] " make a copy of the List + Notice that the last index is inclusive. If you prefer using an exclusive + index use the |slice()| method. + If the first index is beyond the last item of the List or the second item is before the first item, the result is an empty list. There is no error message. *************** *** 1167,1172 **** --- 1195,1201 ---- If the length of the String is less than the index, the result is an empty String. A negative index always results in an empty string (reason: backward compatibility). Use [-1:] to get the last byte or character. + In Vim9 script a negative index is used like with a list: count from the end. If expr8 is a |List| then it results the item at index expr1. See |list-index| for possible index values. If the index is out of range this results in an *************** *** 1180,1196 **** expr8[expr1a : expr1b] substring or sublist *expr-[:]* ! If expr8 is a String this results in the substring with the bytes from expr1a ! to and including expr1b. expr8 is used as a String, expr1a and expr1b are ! used as a Number. In legacy Vim script the indexes are byte indexes. This doesn't recognize ! multi-byte encodings, see |byteidx()| for computing the indexes. If expr8 is a Number it is first converted to a String. In Vim9 script the indexes are character indexes. To use byte indexes use |strpart()|. If expr1a is omitted zero is used. If expr1b is omitted the length of the string minus one is used. --- 1209,1228 ---- expr8[expr1a : expr1b] substring or sublist *expr-[:]* ! If expr8 is a String this results in the substring with the bytes or ! characters from expr1a to and including expr1b. expr8 is used as a String, ! expr1a and expr1b are used as a Number. In legacy Vim script the indexes are byte indexes. This doesn't recognize ! multibyte encodings, see |byteidx()| for computing the indexes. If expr8 is a Number it is first converted to a String. In Vim9 script the indexes are character indexes. To use byte indexes use |strpart()|. + The item at index expr1b is included, it is inclusive. For an exclusive index + use the |slice()| function. + If expr1a is omitted zero is used. If expr1b is omitted the length of the string minus one is used. *************** *** 1202,1207 **** --- 1234,1240 ---- Examples: > :let c = name[-1:] " last byte of a string + :let c = name[0:-1] " the whole string :let c = name[-2:-2] " last but one byte of a string :let s = line(".")[4:] " from the fifth byte to the end :let s = s[:-3] " remove last two bytes *************** *** 2848,2853 **** --- 2890,2897 ---- simplify({filename}) String simplify filename as much as possible sin({expr}) Float sine of {expr} sinh({expr}) Float hyperbolic sine of {expr} + slice({expr}, {start} [, {end}]) String, List or Blob + slice of a String, List or Blob sort({list} [, {func} [, {dict}]]) List sort {list}, using {func} to compare sound_clear() none stop playing all sounds *************** *** 9779,9784 **** --- 9870,9887 ---- {only available when compiled with the |+float| feature} + slice({expr}, {start} [, {end}]) *slice()* + Similar to using a |slice| "expr[start : end]", but "end" is + used exclusive. And for a string the indexes are used as + character indexes instead of byte indexes, like in + |vim9script|. + When {end} is omitted the slice continues to the last item. + When {end} is -1 the last item is omitted. + + Can also be used as a |method|: > + GetList()->slice(offset) + + sort({list} [, {func} [, {dict}]]) *sort()* *E702* Sort the items in {list} in-place. Returns {list}. *** ../vim-8.2.2343/runtime/doc/usr_41.txt 2021-01-13 20:37:44.439646243 +0100 --- runtime/doc/usr_41.txt 2021-01-13 20:58:28.483723310 +0100 *************** *** 611,616 **** --- 619,626 ---- submatch() get a specific match in ":s" and substitute() strpart() get part of a string using byte index strcharpart() get part of a string using char index + slice() take a slice of a string, using char index in + Vim9 script strgetchar() get character from a string using char index expand() expand special keywords expandcmd() expand a command like done for `:edit` *************** *** 640,645 **** --- 650,656 ---- map() change each List item mapnew() make a new List with changed items reduce() reduce a List to a value + slice() take a slice of a List sort() sort a List reverse() reverse the order of a List uniq() remove copies of repeated adjacent items *** ../vim-8.2.2343/src/evalfunc.c 2021-01-13 20:37:44.439646243 +0100 --- src/evalfunc.c 2021-01-13 20:59:23.915553385 +0100 *************** *** 1500,1505 **** --- 1500,1507 ---- ret_float, FLOAT_FUNC(f_sin)}, {"sinh", 1, 1, FEARG_1, NULL, ret_float, FLOAT_FUNC(f_sinh)}, + {"slice", 2, 3, FEARG_1, NULL, + ret_first_arg, f_slice}, {"sort", 1, 3, FEARG_1, NULL, ret_first_arg, f_sort}, {"sound_clear", 0, 0, 0, NULL, *** ../vim-8.2.2343/src/eval.c 2021-01-13 20:08:34.729054901 +0100 --- src/eval.c 2021-01-13 21:40:19.405652823 +0100 *************** *** 3877,3884 **** if (evaluate) { int res = eval_index_inner(rettv, range, ! empty1 ? NULL : &var1, empty2 ? NULL : &var2, key, keylen, verbose); if (!empty1) clear_tv(&var1); if (range) --- 3877,3885 ---- if (evaluate) { int res = eval_index_inner(rettv, range, ! empty1 ? NULL : &var1, empty2 ? NULL : &var2, FALSE, key, keylen, verbose); + if (!empty1) clear_tv(&var1); if (range) *************** *** 3938,3946 **** --- 3939,3964 ---- } /* + * slice() function + */ + void + f_slice(typval_T *argvars, typval_T *rettv) + { + if (check_can_index(argvars, TRUE, FALSE) == OK) + { + copy_tv(argvars, rettv); + eval_index_inner(rettv, TRUE, argvars + 1, + argvars[2].v_type == VAR_UNKNOWN ? NULL : argvars + 2, + TRUE, NULL, 0, FALSE); + } + } + + /* * Apply index or range to "rettv". * "var1" is the first index, NULL for [:expr]. * "var2" is the second index, NULL for [expr] and [expr: ] + * "exclusive" is TRUE for slice(): second index is exclusive, use character + * index for string. * Alternatively, "key" is not NULL, then key[keylen] is the dict index. */ int *************** *** 3949,3960 **** int is_range, typval_T *var1, typval_T *var2, char_u *key, int keylen, int verbose) { ! long n1, n2 = 0; ! long len; n1 = 0; if (var1 != NULL && rettv->v_type != VAR_DICT) --- 3967,3979 ---- int is_range, typval_T *var1, typval_T *var2, + int exclusive, char_u *key, int keylen, int verbose) { ! varnumber_T n1, n2 = 0; ! long len; n1 = 0; if (var1 != NULL && rettv->v_type != VAR_DICT) *************** *** 3968,3977 **** emsg(_(e_cannot_slice_dictionary)); return FAIL; } ! if (var2 == NULL) ! n2 = -1; ! else n2 = tv_get_number(var2); } switch (rettv->v_type) --- 3987,3996 ---- emsg(_(e_cannot_slice_dictionary)); return FAIL; } ! if (var2 != NULL) n2 = tv_get_number(var2); + else + n2 = VARNUM_MAX; } switch (rettv->v_type) *************** *** 3994,4003 **** char_u *s = tv_get_string(rettv); len = (long)STRLEN(s); ! if (in_vim9script()) { if (is_range) ! s = string_slice(s, n1, n2); else s = char_from_string(s, n1); } --- 4013,4022 ---- char_u *s = tv_get_string(rettv); len = (long)STRLEN(s); ! if (in_vim9script() || exclusive) { if (is_range) ! s = string_slice(s, n1, n2, exclusive); else s = char_from_string(s, n1); } *************** *** 4015,4020 **** --- 4034,4041 ---- n2 = len + n2; else if (n2 >= len) n2 = len; + if (exclusive) + --n2; if (n1 >= len || n2 < 0 || n1 > n2) s = NULL; else *************** *** 4051,4057 **** if (n2 < 0) n2 = len + n2; else if (n2 >= len) ! n2 = len - 1; if (n1 >= len || n2 < 0 || n1 > n2) { clear_tv(rettv); --- 4072,4080 ---- if (n2 < 0) n2 = len + n2; else if (n2 >= len) ! n2 = len - (exclusive ? 0 : 1); ! if (exclusive) ! --n2; if (n1 >= len || n2 < 0 || n1 > n2) { clear_tv(rettv); *************** *** 4103,4111 **** if (var1 == NULL) n1 = 0; if (var2 == NULL) ! n2 = -1; if (list_slice_or_index(rettv->vval.v_list, ! is_range, n1, n2, rettv, verbose) == FAIL) return FAIL; break; --- 4126,4134 ---- if (var1 == NULL) n1 = 0; if (var2 == NULL) ! n2 = VARNUM_MAX; if (list_slice_or_index(rettv->vval.v_list, ! is_range, n1, n2, exclusive, rettv, verbose) == FAIL) return FAIL; break; *** ../vim-8.2.2343/src/proto/eval.pro 2021-01-10 20:22:20.767926906 +0100 --- src/proto/eval.pro 2021-01-13 21:07:06.738649262 +0100 *************** *** 41,47 **** int eval_addlist(typval_T *tv1, typval_T *tv2); int eval_leader(char_u **arg, int vim9); int check_can_index(typval_T *rettv, int evaluate, int verbose); ! int eval_index_inner(typval_T *rettv, int is_range, typval_T *var1, typval_T *var2, char_u *key, int keylen, int verbose); char_u *partial_name(partial_T *pt); void partial_unref(partial_T *pt); int get_copyID(void); --- 41,48 ---- int eval_addlist(typval_T *tv1, typval_T *tv2); int eval_leader(char_u **arg, int vim9); int check_can_index(typval_T *rettv, int evaluate, int verbose); ! void f_slice(typval_T *argvars, typval_T *rettv); ! int eval_index_inner(typval_T *rettv, int is_range, typval_T *var1, typval_T *var2, int exclusive, char_u *key, int keylen, int verbose); char_u *partial_name(partial_T *pt); void partial_unref(partial_T *pt); int get_copyID(void); *************** *** 58,64 **** int buf_byteidx_to_charidx(buf_T *buf, int lnum, int byteidx); int buf_charidx_to_byteidx(buf_T *buf, int lnum, int charidx); pos_T *var2fpos(typval_T *varp, int dollar_lnum, int *fnum, int charcol); ! int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, int char_col); int get_env_len(char_u **arg); int get_id_len(char_u **arg); int get_name_len(char_u **arg, char_u **alias, int evaluate, int verbose); --- 59,65 ---- int buf_byteidx_to_charidx(buf_T *buf, int lnum, int byteidx); int buf_charidx_to_byteidx(buf_T *buf, int lnum, int charidx); pos_T *var2fpos(typval_T *varp, int dollar_lnum, int *fnum, int charcol); ! int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, int charcol); int get_env_len(char_u **arg); int get_id_len(char_u **arg); int get_name_len(char_u **arg, char_u **alias, int evaluate, int verbose); *** ../vim-8.2.2343/src/vim9execute.c 2021-01-12 17:16:58.101985157 +0100 --- src/vim9execute.c 2021-01-13 21:25:02.548128755 +0100 *************** *** 965,974 **** /* * Return the slice "str[first:last]" using character indexes. * Return NULL when the result is empty. */ char_u * ! string_slice(char_u *str, varnumber_T first, varnumber_T last) { long start_byte, end_byte; size_t slen; --- 965,975 ---- /* * Return the slice "str[first:last]" using character indexes. + * "exclusive" is TRUE for slice(). * Return NULL when the result is empty. */ char_u * ! string_slice(char_u *str, varnumber_T first, varnumber_T last, int exclusive) { long start_byte, end_byte; size_t slen; *************** *** 979,990 **** start_byte = char_idx2byte(str, slen, first); if (start_byte < 0) start_byte = 0; // first index very negative: use zero ! if (last == -1) end_byte = (long)slen; else { end_byte = char_idx2byte(str, slen, last); ! if (end_byte >= 0 && end_byte < (long)slen) // end index is inclusive end_byte += MB_CPTR2LEN(str + end_byte); } --- 980,991 ---- start_byte = char_idx2byte(str, slen, first); if (start_byte < 0) start_byte = 0; // first index very negative: use zero ! if ((last == -1 && !exclusive) || last == VARNUM_MAX) end_byte = (long)slen; else { end_byte = char_idx2byte(str, slen, last); ! if (!exclusive && end_byte >= 0 && end_byte < (long)slen) // end index is inclusive end_byte += MB_CPTR2LEN(str + end_byte); } *************** *** 2992,2998 **** tv = STACK_TV_BOT(-1); if (is_slice) // Slice: Select the characters from the string ! res = string_slice(tv->vval.v_string, n1, n2); else // Index: The resulting variable is a string of a // single character. If the index is too big or --- 2993,2999 ---- tv = STACK_TV_BOT(-1); if (is_slice) // Slice: Select the characters from the string ! res = string_slice(tv->vval.v_string, n1, n2, FALSE); else // Index: The resulting variable is a string of a // single character. If the index is too big or *************** *** 3030,3037 **** ectx.ec_stack.ga_len -= is_slice ? 2 : 1; tv = STACK_TV_BOT(-1); SOURCING_LNUM = iptr->isn_lnum; ! if (list_slice_or_index(list, is_slice, n1, n2, tv, TRUE) ! == FAIL) goto on_error; } break; --- 3031,3038 ---- ectx.ec_stack.ga_len -= is_slice ? 2 : 1; tv = STACK_TV_BOT(-1); SOURCING_LNUM = iptr->isn_lnum; ! if (list_slice_or_index(list, is_slice, n1, n2, FALSE, ! tv, TRUE) == FAIL) goto on_error; } break; *************** *** 3052,3059 **** goto on_error; var1 = is_slice ? STACK_TV_BOT(-2) : STACK_TV_BOT(-1); var2 = is_slice ? STACK_TV_BOT(-1) : NULL; ! res = eval_index_inner(tv, is_slice, ! var1, var2, NULL, -1, TRUE); clear_tv(var1); if (is_slice) clear_tv(var2); --- 3053,3060 ---- goto on_error; var1 = is_slice ? STACK_TV_BOT(-2) : STACK_TV_BOT(-1); var2 = is_slice ? STACK_TV_BOT(-1) : NULL; ! res = eval_index_inner(tv, is_slice, var1, var2, ! FALSE, NULL, -1, TRUE); clear_tv(var1); if (is_slice) clear_tv(var2); *** ../vim-8.2.2343/src/proto/vim9execute.pro 2021-01-09 13:20:32.200472552 +0100 --- src/proto/vim9execute.pro 2021-01-13 21:20:26.944851366 +0100 *************** *** 2,8 **** void to_string_error(vartype_T vartype); void funcstack_check_refcount(funcstack_T *funcstack); char_u *char_from_string(char_u *str, varnumber_T index); ! char_u *string_slice(char_u *str, varnumber_T first, varnumber_T last); int fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx); int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, partial_T *partial, typval_T *rettv); void ex_disassemble(exarg_T *eap); --- 2,8 ---- void to_string_error(vartype_T vartype); void funcstack_check_refcount(funcstack_T *funcstack); char_u *char_from_string(char_u *str, varnumber_T index); ! char_u *string_slice(char_u *str, varnumber_T first, varnumber_T last, int exclusive); int fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx); int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, partial_T *partial, typval_T *rettv); void ex_disassemble(exarg_T *eap); *** ../vim-8.2.2343/src/list.c 2021-01-12 20:23:35.778707890 +0100 --- src/list.c 2021-01-13 21:32:15.458955185 +0100 *************** *** 905,918 **** list_slice_or_index( list_T *list, int range, ! long n1_arg, ! long n2_arg, typval_T *rettv, int verbose) { long len = list_len(list); ! long n1 = n1_arg; ! long n2 = n2_arg; typval_T var1; if (n1 < 0) --- 905,919 ---- list_slice_or_index( list_T *list, int range, ! varnumber_T n1_arg, ! varnumber_T n2_arg, ! int exclusive, typval_T *rettv, int verbose) { long len = list_len(list); ! varnumber_T n1 = n1_arg; ! varnumber_T n2 = n2_arg; typval_T var1; if (n1 < 0) *************** *** 936,942 **** if (n2 < 0) n2 = len + n2; else if (n2 >= len) ! n2 = len - 1; if (n2 < 0 || n2 + 1 < n1) n2 = -1; l = list_slice(list, n1, n2); --- 937,945 ---- if (n2 < 0) n2 = len + n2; else if (n2 >= len) ! n2 = len - (exclusive ? 0 : 1); ! if (exclusive) ! --n2; if (n2 < 0 || n2 + 1 < n1) n2 = -1; l = list_slice(list, n1, n2); *** ../vim-8.2.2343/src/proto/list.pro 2021-01-12 20:23:35.778707890 +0100 --- src/proto/list.pro 2021-01-13 21:26:06.983956423 +0100 *************** *** 34,40 **** int list_extend(list_T *l1, list_T *l2, listitem_T *bef); int list_concat(list_T *l1, list_T *l2, typval_T *tv); list_T *list_slice(list_T *ol, long n1, long n2); ! int list_slice_or_index(list_T *list, int range, long n1_arg, long n2_arg, typval_T *rettv, int verbose); list_T *list_copy(list_T *orig, int deep, int copyID); void vimlist_remove(list_T *l, listitem_T *item, listitem_T *item2); char_u *list2string(typval_T *tv, int copyID, int restore_copyID); --- 34,40 ---- int list_extend(list_T *l1, list_T *l2, listitem_T *bef); int list_concat(list_T *l1, list_T *l2, typval_T *tv); list_T *list_slice(list_T *ol, long n1, long n2); ! int list_slice_or_index(list_T *list, int range, varnumber_T n1_arg, varnumber_T n2_arg, int exclusive, typval_T *rettv, int verbose); list_T *list_copy(list_T *orig, int deep, int copyID); void vimlist_remove(list_T *l, listitem_T *item, listitem_T *item2); char_u *list2string(typval_T *tv, int copyID, int restore_copyID); *** ../vim-8.2.2343/src/testdir/test_vim9_builtin.vim 2021-01-13 20:37:44.439646243 +0100 --- src/testdir/test_vim9_builtin.vim 2021-01-13 21:35:00.722500435 +0100 *************** *** 741,746 **** --- 741,769 ---- getreginfo('a')->assert_equal(reginfo) enddef + def Test_slice() + assert_equal('12345', slice('012345', 1)) + assert_equal('123', slice('012345', 1, 4)) + assert_equal('1234', slice('012345', 1, -1)) + assert_equal('1', slice('012345', 1, -4)) + assert_equal('', slice('012345', 1, -5)) + assert_equal('', slice('012345', 1, -6)) + + assert_equal([1, 2, 3, 4, 5], slice(range(6), 1)) + assert_equal([1, 2, 3], slice(range(6), 1, 4)) + assert_equal([1, 2, 3, 4], slice(range(6), 1, -1)) + assert_equal([1], slice(range(6), 1, -4)) + assert_equal([], slice(range(6), 1, -5)) + assert_equal([], slice(range(6), 1, -6)) + + assert_equal(0z1122334455, slice(0z001122334455, 1)) + assert_equal(0z112233, slice(0z001122334455, 1, 4)) + assert_equal(0z11223344, slice(0z001122334455, 1, -1)) + assert_equal(0z11, slice(0z001122334455, 1, -4)) + assert_equal(0z, slice(0z001122334455, 1, -5)) + assert_equal(0z, slice(0z001122334455, 1, -6)) + enddef + def Test_spellsuggest() if !has('spell') MissingFeature 'spell' *** ../vim-8.2.2343/src/version.c 2021-01-13 20:37:44.439646243 +0100 --- src/version.c 2021-01-13 21:35:59.810337538 +0100 *************** *** 752,753 **** --- 752,755 ---- { /* Add new patch number below this line */ + /**/ + 2344, /**/ -- Amazing but true: If all the salmon caught in Canada in one year were laid end to end across the Sahara Desert, the smell would be absolutely awful. /// Bram Moolenaar -- Bram@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 ///