]> granicus.if.org Git - vim/commitdiff
patch 8.2.1462: Vim9: string slice not supported yet v8.2.1462
authorBram Moolenaar <Bram@vim.org>
Sat, 15 Aug 2020 19:10:16 +0000 (21:10 +0200)
committerBram Moolenaar <Bram@vim.org>
Sat, 15 Aug 2020 19:10:16 +0000 (21:10 +0200)
Problem:    Vim9: string slice not supported yet.
Solution:   Add support for string slicing.

src/errors.h
src/eval.c
src/proto/eval.pro
src/testdir/test_vim9_disassemble.vim
src/testdir/test_vim9_expr.vim
src/version.c
src/vim9.h
src/vim9compile.c
src/vim9execute.c

index 6a2e50ee3dea38444aee69e5d0578f840d74ed47..3f9607146928c53672cddbd6e416430863fb7cb3 100644 (file)
@@ -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"));
index 5a61a50fa717f4195af5ec5596084f06f3dfef5b..cbbb9002bb2f1ce6e204e2f2298e097ef33a7cf6 100644 (file)
@@ -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
@@ -5312,6 +5315,69 @@ char_from_string(char_u *str, varnumber_T index)
     return vim_strnsave(str + nbyte, MB_CPTR2LEN(str + nbyte));
 }
 
+/*
+ * 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
index c2df7a253be9c479b89c0eb94a7efb8575043a55..551dacbccdac1164b712ab9766128c68e5ec236e 100644 (file)
@@ -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);
index ef89322dff07976d5f30ee8caec2ebf52c8f2c5d..d901cb033bab6cbea43a10860e70ba3d10feeb65 100644 (file)
@@ -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
index 182295cc11c57543ac1b6f7b4686fe1659361ece..cae719d5ec7d5818507a0a4829d6587c62cccd46 100644 (file)
@@ -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)
index 135336037f43cdaad2f82a3fea52713427ebee98..febb53b8aaa5cca5988ee0b17aa3a884928c78c4 100644 (file)
@@ -754,6 +754,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1462,
 /**/
     1461,
 /**/
index 87894144a922e9f3c3cc2551792979e81e4d141d..5494486876745a2155228a59f470688243c28c57 100644 (file)
@@ -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
index 11ffee9ad7dfaaac148cb3877b9605c07e19b53f..c086c1cf81b41e4dc34516e2fae90f1c10932396 100644 (file)
@@ -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 (**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 (may_get_next_line_error(p, arg, cctx) == FAIL)
-               return FAIL;
            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:
index 5131108cc4786ca5d3b822e456fb3d43f050aa0d..a0a4ea2a65ada160f217e286983e010af9b3e463 100644 (file)
@@ -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;
+                   n2 = 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;
+                   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;