# HG changeset patch # User Bram Moolenaar # Date 1597518904 -7200 # Node ID ccad66ac6c3e7ed4d6412996a099f7294c0423b9 # Parent 0db0640e16e07d60aeeed88106f59a8f22db92f0 patch 8.2.1462: Vim9: string slice not supported yet Commit: https://github.com/vim/vim/commit/11107bab7ead9124f46a7ddf6aa3bb66b43a8246 Author: Bram Moolenaar Date: Sat Aug 15 21:10:16 2020 +0200 patch 8.2.1462: Vim9: string slice not supported yet Problem: Vim9: string slice not supported yet. Solution: Add support for string slicing. diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -69,7 +69,8 @@ EXTERN char e_const_requires_a_value[] INIT(= N_("E1021: const requires a value")); EXTERN char e_type_or_initialization_required[] INIT(= N_("E1022: type or initialization required")); -// E1023 unused +EXTERN char e_cannot_slice_dictionary[] + INIT(= N_("E1023: cannot slice a dictionary")); // E1024 unused EXTERN char e_using_rcurly_outside_if_block_scope[] INIT(= N_("E1025: using } outside of a block scope")); diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -3699,7 +3699,14 @@ eval_index( case VAR_STRING: s = tv_get_string(rettv); len = (long)STRLEN(s); - if (range) + if (in_vim9script()) + { + if (range) + s = string_slice(s, n1, n2); + else + s = char_from_string(s, n1); + } + else if (range) { // The resulting variable is a substring. If the indexes // are out of range the result is empty. @@ -3718,10 +3725,6 @@ eval_index( else s = vim_strnsave(s + n1, n2 - n1 + 1); } - else if (in_vim9script()) - { - s = char_from_string(s, n1); - } else { // The resulting variable is a string of a single @@ -5313,6 +5316,69 @@ char_from_string(char_u *str, varnumber_ } /* + * Get the byte index for character index "idx" in string "str" with length + * "str_len". + * If going over the end return "str_len". + * If "idx" is negative count from the end, -1 is the last character. + * When going over the start return zero. + */ + static size_t +char_idx2byte(char_u *str, size_t str_len, varnumber_T idx) +{ + varnumber_T nchar = idx; + size_t nbyte = 0; + + if (nchar >= 0) + { + while (nchar > 0 && nbyte < str_len) + { + nbyte += MB_CPTR2LEN(str + nbyte); + --nchar; + } + } + else + { + nbyte = str_len; + while (nchar < 0 && nbyte > 0) + { + --nbyte; + nbyte -= mb_head_off(str, str + nbyte); + ++nchar; + } + } + return nbyte; +} + +/* + * 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) +{ + size_t start_byte, end_byte; + size_t slen; + + if (str == NULL) + return NULL; + slen = STRLEN(str); + start_byte = char_idx2byte(str, slen, first); + if (last == -1) + end_byte = slen; + else + { + end_byte = char_idx2byte(str, slen, last); + if (end_byte < slen) + // end index is inclusive + end_byte += MB_CPTR2LEN(str + end_byte); + } + + if (start_byte >= slen || end_byte <= start_byte) + return NULL; + return vim_strnsave(str + start_byte, end_byte - start_byte); +} + +/* * Handle: * - expr[expr], expr[expr:expr] subscript * - ".name" lookup diff --git a/src/proto/eval.pro b/src/proto/eval.pro --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -60,6 +60,7 @@ int eval_isnamec(int c); int eval_isnamec1(int c); int eval_isdictc(int c); 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 handle_subscript(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int verbose); int item_copy(typval_T *from, typval_T *to, int deep, int copyID); void echo_one(typval_T *rettv, int with_space, int *atstart, int *needclr); diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -971,7 +971,7 @@ def Test_disassemble_concat() assert_equal('aabb', ConcatString()) enddef -def StringIndex(): number +def StringIndex(): string let s = "abcd" let res = s[1] return res diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -2074,7 +2074,7 @@ def Test_expr7_trailing() assert_equal(123, d.key) enddef -def Test_expr7_subscript() +def Test_expr7_string_subscript() let lines =<< trim END let text = 'abcdef' assert_equal('', text[-1]) @@ -2094,6 +2094,28 @@ def Test_expr7_subscript() assert_equal('f', text[5]) assert_equal('', text[6]) assert_equal('', text[999]) + + assert_equal('ábçdëf', text[0:-1]) + assert_equal('ábçdëf', text[0 :-1]) + assert_equal('ábçdëf', text[0: -1]) + assert_equal('ábçdëf', text[0 : -1]) + assert_equal('ábçdëf', text[0 + :-1]) + assert_equal('ábçdëf', text[0: + -1]) + assert_equal('ábçdëf', text[0 : -1 + ]) + assert_equal('bçdëf', text[1:-1]) + assert_equal('çdëf', text[2:-1]) + assert_equal('dëf', text[3:-1]) + assert_equal('ëf', text[4:-1]) + assert_equal('f', text[5:-1]) + assert_equal('', text[6:-1]) + assert_equal('', text[999:-1]) + + assert_equal('ábçd', text[:3]) + assert_equal('bçdëf', text[1:]) + assert_equal('ábçdëf', text[:]) END CheckDefSuccess(lines) CheckScriptSuccess(['vim9script'] + lines) diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -755,6 +755,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1462, +/**/ 1461, /**/ 1460, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -117,6 +117,7 @@ typedef enum { // expression operations ISN_CONCAT, ISN_STRINDEX, // [expr] string index + ISN_STRSLICE, // [expr:expr] string slice ISN_LISTINDEX, // [expr] list index ISN_SLICE, // drop isn_arg.number items from start of list ISN_GETITEM, // push list item, isn_arg.number is the index diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -3068,6 +3068,7 @@ compile_subscript( garray_T *stack = &cctx->ctx_type_stack; type_T **typep; vartype_T vtype; + int is_slice = FALSE; // list index: list[123] // dict member: dict[key] @@ -3082,11 +3083,36 @@ compile_subscript( *arg = skipwhite(p); if (may_get_next_line_error(p, arg, cctx) == FAIL) return FAIL; - if (compile_expr0(arg, cctx) == FAIL) - return FAIL; - - if (may_get_next_line_error(p, arg, cctx) == FAIL) - return FAIL; + if (**arg == ':') + // missing first index is equal to zero + generate_PUSHNR(cctx, 0); + else + { + if (compile_expr0(arg, cctx) == FAIL) + return FAIL; + if (may_get_next_line_error(p, arg, cctx) == FAIL) + return FAIL; + *arg = skipwhite(*arg); + } + if (**arg == ':') + { + *arg = skipwhite(*arg + 1); + if (may_get_next_line_error(p, arg, cctx) == FAIL) + return FAIL; + if (**arg == ']') + // missing second index is equal to end of string + generate_PUSHNR(cctx, -1); + else + { + if (compile_expr0(arg, cctx) == FAIL) + return FAIL; + if (may_get_next_line_error(p, arg, cctx) == FAIL) + return FAIL; + *arg = skipwhite(*arg); + } + is_slice = TRUE; + } + if (**arg != ']') { emsg(_(e_missbrac)); @@ -3098,7 +3124,8 @@ compile_subscript( // we can use the index value type. // TODO: If we don't know use an instruction to figure it out at // runtime. - typep = ((type_T **)stack->ga_data) + stack->ga_len - 2; + typep = ((type_T **)stack->ga_data) + stack->ga_len + - (is_slice ? 3 : 2); vtype = (*typep)->tt_type; if (*typep == &t_any) { @@ -3109,6 +3136,11 @@ compile_subscript( } if (vtype == VAR_DICT) { + if (is_slice) + { + emsg(_(e_cannot_slice_dictionary)); + return FAIL; + } if ((*typep)->tt_type == VAR_DICT) *typep = (*typep)->tt_member; else @@ -3124,12 +3156,24 @@ compile_subscript( } else if (vtype == VAR_STRING) { - *typep = &t_number; - if (generate_instr_drop(cctx, ISN_STRINDEX, 1) == FAIL) + *typep = &t_string; + if ((is_slice + ? generate_instr_drop(cctx, ISN_STRSLICE, 2) + : generate_instr_drop(cctx, ISN_STRINDEX, 1)) == FAIL) return FAIL; } + else if (vtype == VAR_BLOB) + { + emsg("Sorry, blob index and slice not implemented yet"); + return FAIL; + } else if (vtype == VAR_LIST || *typep == &t_any) { + if (is_slice) + { + emsg("Sorry, list slice not implemented yet"); + return FAIL; + } if ((*typep)->tt_type == VAR_LIST) *typep = (*typep)->tt_member; if (generate_instr_drop(cctx, ISN_LISTINDEX, 1) == FAIL) @@ -7052,6 +7096,7 @@ delete_instr(isn_T *isn) case ISN_FOR: case ISN_LISTINDEX: case ISN_STRINDEX: + case ISN_STRSLICE: case ISN_GETITEM: case ISN_SLICE: case ISN_MEMBER: diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -70,7 +70,7 @@ typedef struct { } ectx_T; // Get pointer to item relative to the bottom of the stack, -1 is the last one. -#define STACK_TV_BOT(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_stack.ga_len + idx) +#define STACK_TV_BOT(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_stack.ga_len + (idx)) void to_string_error(vartype_T vartype) @@ -2232,12 +2232,16 @@ call_def_function( break; case ISN_STRINDEX: + case ISN_STRSLICE: { - varnumber_T n; + int is_slice = iptr->isn_type == ISN_STRSLICE; + varnumber_T n1 = 0, n2; char_u *res; // string index: string is at stack-2, index at stack-1 - tv = STACK_TV_BOT(-2); + // string slice: string is at stack-3, first index at + // stack-2, second index at stack-1 + tv = is_slice ? STACK_TV_BOT(-3) : STACK_TV_BOT(-2); if (tv->v_type != VAR_STRING) { SOURCING_LNUM = iptr->isn_lnum; @@ -2245,6 +2249,18 @@ call_def_function( goto on_error; } + if (is_slice) + { + tv = STACK_TV_BOT(-2); + if (tv->v_type != VAR_NUMBER) + { + SOURCING_LNUM = iptr->isn_lnum; + emsg(_(e_number_exp)); + goto on_error; + } + n1 = tv->vval.v_number; + } + tv = STACK_TV_BOT(-1); if (tv->v_type != VAR_NUMBER) { @@ -2252,14 +2268,18 @@ call_def_function( emsg(_(e_number_exp)); goto on_error; } - n = tv->vval.v_number; - - // The resulting variable is a string of a single - // character. If the index is too big or negative the - // result is empty. - --ectx.ec_stack.ga_len; + n2 = tv->vval.v_number; + + ectx.ec_stack.ga_len -= is_slice ? 2 : 1; tv = STACK_TV_BOT(-1); - res = char_from_string(tv->vval.v_string, n); + 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 + // negative the result is empty. + res = char_from_string(tv->vval.v_string, n2); vim_free(tv->vval.v_string); tv->vval.v_string = res; } @@ -3140,6 +3160,7 @@ ex_disassemble(exarg_T *eap) // expression operations case ISN_CONCAT: smsg("%4d CONCAT", current); break; case ISN_STRINDEX: smsg("%4d STRINDEX", current); break; + case ISN_STRSLICE: smsg("%4d STRSLICE", current); break; case ISN_LISTINDEX: smsg("%4d LISTINDEX", current); break; case ISN_SLICE: smsg("%4d SLICE %lld", current, iptr->isn_arg.number); break;