]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 8.2.1155: Vim9: cannot handle line break inside lambda v8.2.1155
authorBram Moolenaar <Bram@vim.org>
Wed, 8 Jul 2020 15:36:21 +0000 (17:36 +0200)
committerBram Moolenaar <Bram@vim.org>
Wed, 8 Jul 2020 15:36:21 +0000 (17:36 +0200)
Problem:    Vim9: cannot handle line break inside lambda.
Solution:   Pass the compilation context through. (closes #6407, closes #6409)

src/eval.c
src/proto/vim9compile.pro
src/structs.h
src/testdir/test_vim9_func.vim
src/version.c
src/vim9compile.c

index 1468070ae6c09aa7ccda515221ba3f604ae0dc0b..68fb9cddf88c393d150a45eeacc2a20ae7bca0f6 100644 (file)
@@ -390,11 +390,12 @@ skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg)
     garray_T    *gap = &evalarg->eval_ga;
     int                save_flags = evalarg == NULL ? 0 : evalarg->eval_flags;
 
-    if (vim9script && evalarg->eval_cookie != NULL)
+    if (vim9script
+              && (evalarg->eval_cookie != NULL || evalarg->eval_cctx != NULL))
     {
        ga_init2(gap, sizeof(char_u *), 10);
+       // leave room for "start"
        if (ga_grow(gap, 1) == OK)
-           // leave room for "start"
            ++gap->ga_len;
     }
 
@@ -406,32 +407,49 @@ skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg)
     if (evalarg != NULL)
        evalarg->eval_flags = save_flags;
 
-    if (vim9script && evalarg->eval_cookie != NULL
-                                               && evalarg->eval_ga.ga_len > 1)
+    if (vim9script
+           && (evalarg->eval_cookie != NULL || evalarg->eval_cctx != NULL))
     {
-       char_u      *p;
-       size_t      endoff = STRLEN(*end);
-
-       // Line breaks encountered, concatenate all the lines.
-       *((char_u **)gap->ga_data) = *start;
-       p = ga_concat_strings(gap, "");
-       *((char_u **)gap->ga_data) = NULL;
-       ga_clear_strings(gap);
-       gap->ga_itemsize = 0;
-       if (p == NULL)
-           return FAIL;
-       *start = p;
-       vim_free(evalarg->eval_tofree);
-       evalarg->eval_tofree = p;
-       // Compute "end" relative to the end.
-       *end = *start + STRLEN(*start) - endoff;
+       if (evalarg->eval_ga.ga_len == 1)
+       {
+           // just one line, no need to concatenate
+           ga_clear(gap);
+           gap->ga_itemsize = 0;
+       }
+       else
+       {
+           char_u          *p;
+           size_t          endoff = STRLEN(*end);
+
+           // Line breaks encountered, concatenate all the lines.
+           *((char_u **)gap->ga_data) = *start;
+           p = ga_concat_strings(gap, "");
+
+           // free the lines only when using getsourceline()
+           if (evalarg->eval_cookie != NULL)
+           {
+               *((char_u **)gap->ga_data) = NULL;
+               ga_clear_strings(gap);
+           }
+           else
+               ga_clear(gap);
+           gap->ga_itemsize = 0;
+           if (p == NULL)
+               return FAIL;
+           *start = p;
+           vim_free(evalarg->eval_tofree);
+           evalarg->eval_tofree = p;
+           // Compute "end" relative to the end.
+           *end = *start + STRLEN(*start) - endoff;
+       }
     }
 
     return res;
 }
 
 /*
- * Top level evaluation function, returning a string.
+ * Top level evaluation function, returning a string.  Does not handle line
+ * breaks.
  * When "convert" is TRUE convert a List into a sequence of lines and convert
  * a Float to a String.
  * Return pointer to allocated memory, or NULL for failure.
@@ -1878,11 +1896,16 @@ eval_next_non_blank(char_u *arg, evalarg_T *evalarg, int *getnext)
     *getnext = FALSE;
     if (current_sctx.sc_version == SCRIPT_VERSION_VIM9
            && evalarg != NULL
-           && evalarg->eval_cookie != NULL
+           && (evalarg->eval_cookie != NULL || evalarg->eval_cctx != NULL)
            && (*arg == NUL || (VIM_ISWHITE(arg[-1])
                                             && *arg == '#' && arg[1] != '{')))
     {
-       char_u *p = getline_peek(evalarg->eval_getline, evalarg->eval_cookie);
+       char_u *p;
+
+       if (evalarg->eval_cookie != NULL)
+           p = getline_peek(evalarg->eval_getline, evalarg->eval_cookie);
+       else
+           p = peek_next_line_from_context(evalarg->eval_cctx);
 
        if (p != NULL)
        {
@@ -1902,7 +1925,10 @@ eval_next_line(evalarg_T *evalarg)
     garray_T   *gap = &evalarg->eval_ga;
     char_u     *line;
 
-    line = evalarg->eval_getline(0, evalarg->eval_cookie, 0, TRUE);
+    if (evalarg->eval_cookie != NULL)
+       line = evalarg->eval_getline(0, evalarg->eval_cookie, 0, TRUE);
+    else
+       line = next_line_from_context(evalarg->eval_cctx, TRUE);
     ++evalarg->eval_break_count;
     if (gap->ga_itemsize > 0 && ga_grow(gap, 1) == OK)
     {
@@ -5034,35 +5060,27 @@ handle_subscript(
     int                ret = OK;
     dict_T     *selfdict = NULL;
     int                check_white = TRUE;
+    int                getnext;
+    char_u     *p;
 
-    // When at the end of the line and ".name" follows in the next line then
-    // consume the line break.  Only when rettv is a dict.
-    if (rettv->v_type == VAR_DICT)
+    while (ret == OK)
     {
-       int     getnext;
-       char_u  *p = eval_next_non_blank(*arg, evalarg, &getnext);
-
-       if (getnext && *p == '.' && ASCII_ISALPHA(p[1]))
+       // When at the end of the line and ".name" or "->{" or "->X" follows in
+       // the next line then consume the line break.
+       p = eval_next_non_blank(*arg, evalarg, &getnext);
+       if (getnext
+           && ((rettv->v_type == VAR_DICT && *p == '.'
+                                                      && ASCII_ISALPHA(p[1]))
+               || (*p == '-' && p[1] == '>'
+                                    && (p[2] == '{' || ASCII_ISALPHA(p[2])))))
        {
            *arg = eval_next_line(evalarg);
            check_white = FALSE;
        }
-    }
 
-    // "." is ".name" lookup when we found a dict or when evaluating and
-    // scriptversion is at least 2, where string concatenation is "..".
-    while (ret == OK
-           && (((**arg == '['
-                   || (**arg == '.' && (rettv->v_type == VAR_DICT
-                       || (!evaluate
-                           && (*arg)[1] != '.'
-                           && current_sctx.sc_version >= 2)))
-                   || (**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC
-                                           || rettv->v_type == VAR_PARTIAL)))
-               && (!check_white || !VIM_ISWHITE(*(*arg - 1))))
-           || (**arg == '-' && (*arg)[1] == '>')))
-    {
-       if (**arg == '(')
+       if ((**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC
+                           || rettv->v_type == VAR_PARTIAL))
+                   && (!check_white || !VIM_ISWHITE(*(*arg - 1))))
        {
            ret = call_func_rettv(arg, evalarg, rettv, evaluate,
                                                               selfdict, NULL);
@@ -5079,7 +5097,7 @@ handle_subscript(
            dict_unref(selfdict);
            selfdict = NULL;
        }
-       else if (**arg == '-')
+       else if (**arg == '-' && (*arg)[1] == '>')
        {
            if (ret == OK)
            {
@@ -5091,7 +5109,13 @@ handle_subscript(
                    ret = eval_method(arg, rettv, evalarg, verbose);
            }
        }
-       else // **arg == '[' || **arg == '.'
+       // "." is ".name" lookup when we found a dict or when evaluating and
+       // scriptversion is at least 2, where string concatenation is "..".
+       else if (**arg == '['
+               || (**arg == '.' && (rettv->v_type == VAR_DICT
+                       || (!evaluate
+                           && (*arg)[1] != '.'
+                           && current_sctx.sc_version >= 2))))
        {
            dict_unref(selfdict);
            if (rettv->v_type == VAR_DICT)
@@ -5108,6 +5132,8 @@ handle_subscript(
                ret = FAIL;
            }
        }
+       else
+           break;
     }
 
     // Turn "dict.Func" into a partial for "Func" bound to "dict".
index 05f6d50220d83d4b86b6d98e5ed3d8f6384e1756..b2677c8fe504ec3f3bfc1adcb88170e2c39e259c 100644 (file)
@@ -8,6 +8,8 @@ char *vartype_name(vartype_T type);
 char *type_name(type_T *type, char **tofree);
 int get_script_item_idx(int sid, char_u *name, int check_writable);
 imported_T *find_imported(char_u *name, size_t len, cctx_T *cctx);
+char_u *peek_next_line_from_context(cctx_T *cctx);
+char_u *next_line_from_context(cctx_T *cctx, int skip_comment);
 char_u *to_name_const_end(char_u *arg);
 int assignment_len(char_u *p, int *heredoc);
 void vim9_declare_error(char_u *name);
index 5c606d8839bc357536cb5de35b84b5ea1f46b313..4ae94fd04e582b96f1232c289575ae688f549b52 100644 (file)
@@ -1765,6 +1765,9 @@ typedef struct {
     char_u     *(*eval_getline)(int, void *, int, int);
     void       *eval_cookie;       // argument for eval_getline()
 
+    // used when compiling a :def function, NULL otherwise
+    cctx_T     *eval_cctx;
+
     // Used to collect lines while parsing them, so that they can be
     // concatenated later.  Used when "eval_ga.ga_itemsize" is not zero.
     // "eval_ga.ga_data" is a list of pointers to lines.
index de30a620d14361841841a4a02e76f102112c0a70..f09ecc9b3b1f5ec57523bcf1524109701b8d6f58 100644 (file)
@@ -965,6 +965,18 @@ def Test_line_continuation_in_def()
   assert_equal('full', Line_continuation_in_def('.'))
 enddef
 
+def Line_continuation_in_lambda(): list<number>
+  let x = range(97, 100)
+      ->map({_,v -> nr2char(v)
+          ->toupper()})
+      ->reverse()
+  return x
+enddef
+
+def Test_line_continuation_in_lambda()
+  assert_equal(['D', 'C', 'B', 'A'], Line_continuation_in_lambda())
+enddef
+
 func Test_silent_echo()
   CheckScreendump
 
index 7633792ed5765816dda169994116a9de10ec0013..c77d0bc7cdb862aafb57abb46b8fb03c38864f58 100644 (file)
@@ -754,6 +754,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1155,
 /**/
     1154,
 /**/
index 07e0d2727769434d407a7312f623680ebda6eebd..c3c380852a9c6149ca45ed78732475f44ef52e44 100644 (file)
@@ -2397,8 +2397,8 @@ comment_start(char_u *p)
  * comment. Skips over white space.
  * Returns NULL if there is none.
  */
-    static char_u *
-peek_next_line(cctx_T *cctx)
+    char_u *
+peek_next_line_from_context(cctx_T *cctx)
 {
     int lnum = cctx->ctx_lnum;
 
@@ -2430,7 +2430,7 @@ may_peek_next_line(cctx_T *cctx, char_u *arg, char_u **nextp)
     *nextp = NULL;
     if (*p == NUL || (VIM_ISWHITE(*arg) && comment_start(p)))
     {
-       *nextp = peek_next_line(cctx);
+       *nextp = peek_next_line_from_context(cctx);
        if (*nextp != NULL)
            return *nextp;
     }
@@ -2442,7 +2442,7 @@ may_peek_next_line(cctx_T *cctx, char_u *arg, char_u **nextp)
  * Skips over empty lines.  Skips over comment lines if "skip_comment" is TRUE.
  * Returns NULL when at the end.
  */
-    static char_u *
+    char_u *
 next_line_from_context(cctx_T *cctx, int skip_comment)
 {
     char_u     *line;
@@ -3079,9 +3079,14 @@ compile_lambda(char_u **arg, cctx_T *cctx)
 {
     typval_T   rettv;
     ufunc_T    *ufunc;
+    evalarg_T  evalarg;
+
+    CLEAR_FIELD(evalarg);
+    evalarg.eval_flags = EVAL_EVALUATE;
+    evalarg.eval_cctx = cctx;
 
     // Get the funcref in "rettv".
-    if (get_lambda_tv(arg, &rettv, &EVALARG_EVALUATE) != OK)
+    if (get_lambda_tv(arg, &rettv, &evalarg) != OK)
        return FAIL;
 
     ufunc = rettv.vval.v_partial->pt_func;
@@ -3535,6 +3540,7 @@ compile_leader(cctx_T *cctx, char_u *start, char_u *end)
 
 /*
  * Compile whatever comes after "name" or "name()".
+ * Advances "*arg" only when something was recognized.
  */
     static int
 compile_subscript(
@@ -3550,7 +3556,7 @@ compile_subscript(
 
        if (*p == NUL || (VIM_ISWHITE(**arg) && comment_start(p)))
        {
-           char_u *next = peek_next_line(cctx);
+           char_u *next = peek_next_line_from_context(cctx);
 
            // If a following line starts with "->{" or "->X" advance to that
            // line, so that a line break before "->" is allowed.
@@ -3560,11 +3566,12 @@ compile_subscript(
                next = next_line_from_context(cctx, TRUE);
                if (next == NULL)
                    return FAIL;
-               *arg = skipwhite(next);
+               *arg = next;
+               p = skipwhite(*arg);
            }
        }
 
-       if (**arg == '(')
+       if (*p == '(')
        {
            garray_T    *stack = &cctx->ctx_type_stack;
            type_T      *type;
@@ -3576,13 +3583,13 @@ compile_subscript(
            // funcref(arg)
            type = ((type_T **)stack->ga_data)[stack->ga_len - 1];
 
-           *arg = skipwhite(*arg + 1);
+           *arg = skipwhite(p + 1);
            if (compile_arguments(arg, cctx, &argcount) == FAIL)
                return FAIL;
            if (generate_PCALL(cctx, argcount, end_leader, type, TRUE) == FAIL)
                return FAIL;
        }
-       else if (**arg == '-' && (*arg)[1] == '>')
+       else if (*p == '-' && p[1] == '>')
        {
            if (generate_ppconst(cctx, ppconst) == FAIL)
                return FAIL;
@@ -3594,7 +3601,7 @@ compile_subscript(
                return FAIL;
            *start_leader = end_leader;   // don't apply again later
 
-           p = *arg + 2;
+           p += 2;
            *arg = skipwhite(p);
            if (may_get_next_line(p, arg, cctx) == FAIL)
                return FAIL;
@@ -3622,7 +3629,7 @@ compile_subscript(
                    return FAIL;
            }
        }
-       else if (**arg == '[')
+       else if (*p == '[')
        {
            garray_T    *stack = &cctx->ctx_type_stack;
            type_T      **typep;
@@ -3635,7 +3642,7 @@ compile_subscript(
            if (generate_ppconst(cctx, ppconst) == FAIL)
                return FAIL;
 
-           p = *arg + 1;
+           ++p;
            *arg = skipwhite(p);
            if (may_get_next_line(p, arg, cctx) == FAIL)
                return FAIL;
@@ -3671,12 +3678,12 @@ compile_subscript(
                return FAIL;
            }
        }
-       else if (**arg == '.' && (*arg)[1] != '.')
+       else if (*p == '.' && p[1] != '.')
        {
            if (generate_ppconst(cctx, ppconst) == FAIL)
                return FAIL;
 
-           ++*arg;
+           *arg = p + 1;
            if (may_get_next_line(*arg, arg, cctx) == FAIL)
                return FAIL;
            // dictionary member: dict.name