]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 8.2.4930: interpolated string expression requires escaping v8.2.4930
authorBram Moolenaar <Bram@vim.org>
Tue, 10 May 2022 12:24:30 +0000 (13:24 +0100)
committerBram Moolenaar <Bram@vim.org>
Tue, 10 May 2022 12:24:30 +0000 (13:24 +0100)
Problem:    Interpolated string expression requires escaping.
Solution:   Do not require escaping in the expression.

15 files changed:
src/alloc.c
src/dict.c
src/eval.c
src/evalvars.c
src/proto/alloc.pro
src/proto/evalvars.pro
src/proto/typval.pro
src/proto/vim9compile.pro
src/testdir/test_expr.vim
src/testdir/test_let.vim
src/typval.c
src/version.c
src/vim9compile.c
src/vim9expr.c
src/vim9instr.c

index 5218d0046577a91641501845ca19e69612b594c1..6d4db629e1aa48e7424bbd719f8c5fc1d396b914 100644 (file)
@@ -832,7 +832,7 @@ ga_add_string(garray_T *gap, char_u *p)
 
 /*
  * Concatenate a string to a growarray which contains bytes.
- * When "s" is NULL does not do anything.
+ * When "s" is NULL memory allocation fails does not do anything.
  * Note: Does NOT copy the NUL at the end!
  */
     void
@@ -869,14 +869,14 @@ ga_concat_len(garray_T *gap, char_u *s, size_t len)
 /*
  * Append one byte to a growarray which contains bytes.
  */
-    void
+    int
 ga_append(garray_T *gap, int c)
 {
-    if (ga_grow(gap, 1) == OK)
-    {
-       *((char *)gap->ga_data + gap->ga_len) = c;
-       ++gap->ga_len;
-    }
+    if (ga_grow(gap, 1) == FAIL)
+       return FAIL;
+    *((char *)gap->ga_data + gap->ga_len) = c;
+    ++gap->ga_len;
+    return OK;
 }
 
 #if (defined(UNIX) && !defined(USE_SYSTEM)) || defined(MSWIN) \
index 88cc4b3fc8152768b970175c76eedc5e2fae8191..538dd8ba264911719a547fdb2596306e8787cdaf 100644 (file)
@@ -866,13 +866,13 @@ get_literal_key(char_u **arg)
 
     if (**arg == '\'')
     {
-       if (eval_lit_string(arg, &rettv, TRUE) == FAIL)
+       if (eval_lit_string(arg, &rettv, TRUE, FALSE) == FAIL)
            return NULL;
        key = rettv.vval.v_string;
     }
     else if (**arg == '"')
     {
-       if (eval_string(arg, &rettv, TRUE) == FAIL)
+       if (eval_string(arg, &rettv, TRUE, FALSE) == FAIL)
            return NULL;
        key = rettv.vval.v_string;
     }
index 4906fa6142751c100d77dc0a34b0c04ee1a23001..ac3c998a512c08b98b3b947ab379b7a7b89171aa 100644 (file)
@@ -3726,13 +3726,13 @@ eval7(
     /*
      * String constant: "string".
      */
-    case '"':  ret = eval_string(arg, rettv, evaluate);
+    case '"':  ret = eval_string(arg, rettv, evaluate, FALSE);
                break;
 
     /*
      * Literal string constant: 'str''ing'.
      */
-    case '\'': ret = eval_lit_string(arg, rettv, evaluate);
+    case '\'': ret = eval_lit_string(arg, rettv, evaluate, FALSE);
                break;
 
     /*
index 2f285189b6df7f169cd3e82c34475e26e0ff3c67..d8ee2160842f71950d05282b1cfb5b70df0bced4 100644 (file)
@@ -603,16 +603,52 @@ list_script_vars(int *first)
 }
 
 /*
- * Evaluate all the Vim expressions ({expr}) in string "str" and return the
- * resulting string.  The caller must free the returned string.
+ * Evaluate one Vim expression {expr} in string "p" and append the
+ * resulting string to "gap".  "p" points to the opening "{".
+ * Return a pointer to the character after "}", NULL for an error.
+ */
+    char_u *
+eval_one_expr_in_str(char_u *p, garray_T *gap)
+{
+    char_u     *block_start = skipwhite(p + 1);  // skip the opening {
+    char_u     *block_end = block_start;
+    char_u     *expr_val;
+
+    if (*block_start == NUL)
+    {
+       semsg(_(e_missing_close_curly_str), p);
+       return NULL;
+    }
+    if (skip_expr(&block_end, NULL) == FAIL)
+       return NULL;
+    block_end = skipwhite(block_end);
+    if (*block_end != '}')
+    {
+       semsg(_(e_missing_close_curly_str), p);
+       return NULL;
+    }
+    *block_end = NUL;
+    expr_val = eval_to_string(block_start, TRUE);
+    *block_end = '}';
+    if (expr_val == NULL)
+       return NULL;
+    ga_concat(gap, expr_val);
+    vim_free(expr_val);
+
+    return block_end + 1;
+}
+
+/*
+ * Evaluate all the Vim expressions {expr} in "str" and return the resulting
+ * string in allocated memory.  "{{" is reduced to "{" and "}}" to "}".
+ * Used for a heredoc assignment.
+ * Returns NULL for an error.
  */
     char_u *
 eval_all_expr_in_str(char_u *str)
 {
     garray_T   ga;
     char_u     *p;
-    char_u     save_c;
-    char_u     *expr_val;
 
     ga_init2(&ga, 1, 80);
     p = str;
@@ -620,8 +656,6 @@ eval_all_expr_in_str(char_u *str)
     while (*p != NUL)
     {
        char_u  *lit_start;
-       char_u  *block_start;
-       char_u  *block_end;
        int     escaped_brace = FALSE;
 
        // Look for a block start.
@@ -656,35 +690,13 @@ eval_all_expr_in_str(char_u *str)
            continue;
        }
 
-       // Skip the opening {.
-       block_start = ++p;
-       block_end = block_start;
-       if (*block_start != NUL && skip_expr(&block_end, NULL) == FAIL)
-       {
-           ga_clear(&ga);
-           return NULL;
-       }
-       block_end = skipwhite(block_end);
-       // The block must be closed by a }.
-       if (*block_end != '}')
-       {
-           semsg(_(e_missing_close_curly_str), str);
-           ga_clear(&ga);
-           return NULL;
-       }
-       save_c = *block_end;
-       *block_end = NUL;
-       expr_val = eval_to_string(block_start, TRUE);
-       *block_end = save_c;
-       if (expr_val == NULL)
+       // Evaluate the expression and append the result.
+       p = eval_one_expr_in_str(p, &ga);
+       if (p == NULL)
        {
            ga_clear(&ga);
            return NULL;
        }
-       ga_concat(&ga, expr_val);
-       vim_free(expr_val);
-
-       p = block_end + 1;
     }
     ga_append(&ga, NUL);
 
index b605f28a516d1e88372699113e2b938ce7a5a133..fe20810c14a9ae44eee98e384a65de1b209a6ba5 100644 (file)
@@ -19,13 +19,13 @@ int ga_copy_strings(garray_T *from, garray_T *to);
 void ga_init(garray_T *gap);
 void ga_init2(garray_T *gap, size_t itemsize, int growsize);
 int ga_grow(garray_T *gap, int n);
-int ga_grow_id(garray_T *gap, int n, alloc_id_T id UNUSED);
+int ga_grow_id(garray_T *gap, int n, alloc_id_T id);
 int ga_grow_inner(garray_T *gap, int n);
 char_u *ga_concat_strings(garray_T *gap, char *sep);
 int ga_copy_string(garray_T *gap, char_u *p);
 int ga_add_string(garray_T *gap, char_u *p);
 void ga_concat(garray_T *gap, char_u *s);
 void ga_concat_len(garray_T *gap, char_u *s, size_t len);
-void ga_append(garray_T *gap, int c);
+int ga_append(garray_T *gap, int c);
 void append_ga_line(garray_T *gap);
 /* vim: set ft=c : */
index 97bec7df3304f9886ded47fcf3a2c7873df47f79..ce794351dd64282405b4ca963591243e4c83745b 100644 (file)
@@ -13,6 +13,8 @@ list_T *eval_spell_expr(char_u *badword, char_u *expr);
 int get_spellword(list_T *list, char_u **pp);
 void prepare_vimvar(int idx, typval_T *save_tv);
 void restore_vimvar(int idx, typval_T *save_tv);
+char_u *eval_one_expr_in_str(char_u *p, garray_T *gap);
+char_u *eval_all_expr_in_str(char_u *str);
 list_T *heredoc_get(exarg_T *eap, char_u *cmd, int script_get, int vim9compile);
 void ex_var(exarg_T *eap);
 void ex_let(exarg_T *eap);
@@ -105,6 +107,4 @@ void set_callback(callback_T *dest, callback_T *src);
 void copy_callback(callback_T *dest, callback_T *src);
 void expand_autload_callback(callback_T *cb);
 void free_callback(callback_T *callback);
-char_u *eval_all_expr_in_str(char_u *str);
-
 /* vim: set ft=c : */
index 5b1f97431fd84a1dca863c5e551fabba08da88b1..6a8d0acf497827fa6ad82aab0fd1eb597ca07514 100644 (file)
@@ -68,11 +68,11 @@ int tv_islocked(typval_T *tv);
 int tv_equal(typval_T *tv1, typval_T *tv2, int ic, int recursive);
 int eval_option(char_u **arg, typval_T *rettv, int evaluate);
 int eval_number(char_u **arg, typval_T *rettv, int evaluate, int want_string);
-int eval_string(char_u **arg, typval_T *rettv, int evaluate);
-int eval_lit_string(char_u **arg, typval_T *rettv, int evaluate);
+int eval_string(char_u **arg, typval_T *rettv, int evaluate, int interpolate);
+int eval_lit_string(char_u **arg, typval_T *rettv, int evaluate, int interpolate);
+int eval_interp_string(char_u **arg, typval_T *rettv, int evaluate);
 char_u *tv2string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID);
 int eval_env_var(char_u **arg, typval_T *rettv, int evaluate);
-int eval_interp_string(char_u **arg, typval_T *rettv, int evaluate);
 linenr_T tv_get_lnum(typval_T *argvars);
 linenr_T tv_get_lnum_buf(typval_T *argvars, buf_T *buf);
 buf_T *tv_get_buf(typval_T *tv, int curtab_only);
index 03f7df1a675a7b3d6a84ef4839751174ae57cfb3..e6c6f3eff25b11f354797e4b2444842917ac12a6 100644 (file)
@@ -16,6 +16,7 @@ int may_get_next_line(char_u *whitep, char_u **arg, cctx_T *cctx);
 int may_get_next_line_error(char_u *whitep, char_u **arg, cctx_T *cctx);
 void fill_exarg_from_cctx(exarg_T *eap, cctx_T *cctx);
 int func_needs_compiling(ufunc_T *ufunc, compiletype_T compile_type);
+char_u *compile_one_expr_in_str(char_u *p, cctx_T *cctx);
 int compile_all_expr_in_str(char_u *str, int evalstr, cctx_T *cctx);
 int assignment_len(char_u *p, int *heredoc);
 void vim9_declare_error(char_u *name);
index 3ea59eb267106e921c3bf55575e83007528ef7b9..0b6b57e4a2aee7d1d9d7ed3274be8e54427cd2cb 100644 (file)
@@ -897,7 +897,7 @@ func Test_string_interp()
     #" Escaping rules.
     call assert_equal('"foo"{bar}', $"\"foo\"{{bar}}")
     call assert_equal('"foo"{bar}', $'"foo"{{bar}}')
-    call assert_equal('foobar', $"{\"foo\"}" .. $'{''bar''}')
+    call assert_equal('foobar', $"{"foo"}" .. $'{'bar'}')
     #" Whitespace before/after the expression.
     call assert_equal('3', $"{ 1 + 2 }")
     #" String conversion.
@@ -907,8 +907,8 @@ func Test_string_interp()
     call assert_equal(string(v:true), $"{v:true}")
     call assert_equal('(1+1=2)', $"(1+1={1 + 1})")
     #" Hex-escaped opening brace: char2nr('{') == 0x7b
-    call assert_equal('esc123ape', $"esc\x7b123}ape")
-    call assert_equal('me{}me', $"me{\x7b}\x7dme")
+    call assert_equal('esc123ape', $"esc{123}ape")
+    call assert_equal('me{}me', $"me{"\x7b"}\x7dme")
     VAR var1 = "sun"
     VAR var2 = "shine"
     call assert_equal('sunshine', $"{var1}{var2}")
@@ -916,7 +916,7 @@ func Test_string_interp()
     #" Multibyte strings.
     call assert_equal('say ハロー・ワールド', $"say {'ハロー・ワールド'}")
     #" Nested.
-    call assert_equal('foobarbaz', $"foo{$\"{'bar'}\"}baz")
+    call assert_equal('foobarbaz', $"foo{$"{'bar'}"}baz")
     #" Do not evaluate blocks when the expr is skipped.
     VAR tmp = 0
     if v:false
index 7e1a9232ef7fe0649de72226f5ef81152645c5a4..fb315e89ed9d4ff8567c26ba5a5a9c9728c3bfa3 100644 (file)
@@ -387,9 +387,8 @@ func Test_let_interpolated()
   let text = 'text'
   call assert_equal('text{{', $'{text .. "{{"}')
   call assert_equal('text{{', $"{text .. '{{'}")
-  " FIXME: should not need to escape quotes in the expression
-  call assert_equal('text{{', $'{text .. ''{{''}')
-  call assert_equal('text{{', $"{text .. \"{{\"}")
+  call assert_equal('text{{', $'{text .. '{{'}')
+  call assert_equal('text{{', $"{text .. "{{"}")
 endfunc
 
 " Test for the setting a variable using the heredoc syntax.
index ba98f1e84f699ea2b3b6d330c0e49b563f9863cf..09dc474935c09ccbe102095d65c4c9176c415c68 100644 (file)
@@ -2065,19 +2065,23 @@ eval_number(
 }
 
 /*
- * Allocate a variable for a string constant.
+ * Evaluate a string constant and put the result in "rettv".
+ * "*arg" points to the double quote or to after it when "interpolate" is TRUE.
+ * When "interpolate" is TRUE reduce "{{" to "{", reduce "}}" to "}" and stop
+ * at a single "{".
  * Return OK or FAIL.
  */
     int
-eval_string(char_u **arg, typval_T *rettv, int evaluate)
+eval_string(char_u **arg, typval_T *rettv, int evaluate, int interpolate)
 {
     char_u     *p;
     char_u     *end;
-    int                extra = 0;
+    int                extra = interpolate ? 1 : 0;
+    int                off = interpolate ? 0 : 1;
     int                len;
 
     // Find the end of the string, skipping backslashed characters.
-    for (p = *arg + 1; *p != NUL && *p != '"'; MB_PTR_ADV(p))
+    for (p = *arg + off; *p != NUL && *p != '"'; MB_PTR_ADV(p))
     {
        if (*p == '\\' && p[1] != NUL)
        {
@@ -2088,9 +2092,21 @@ eval_string(char_u **arg, typval_T *rettv, int evaluate)
            if (*p == '<')
                extra += 5;
        }
+       else if (interpolate && (*p == '{' || *p == '}'))
+       {
+           if (*p == '{' && p[1] != '{') // start of expression
+               break;
+           ++p;
+           if (p[-1] == '}' && *p != '}') // single '}' is an error
+           {
+               semsg(_(e_stray_closing_curly_str), *arg);
+               return FAIL;
+           }
+           --extra;  // "{{" becomes "{", "}}" becomes "}"
+       }
     }
 
-    if (*p != '"')
+    if (*p != '"' && !(interpolate && *p == '{'))
     {
        semsg(_(e_missing_double_quote_str), *arg);
        return FAIL;
@@ -2099,7 +2115,7 @@ eval_string(char_u **arg, typval_T *rettv, int evaluate)
     // If only parsing, set *arg and return here
     if (!evaluate)
     {
-       *arg = p + 1;
+       *arg = p + off;
        return OK;
     }
 
@@ -2112,7 +2128,7 @@ eval_string(char_u **arg, typval_T *rettv, int evaluate)
        return FAIL;
     end = rettv->vval.v_string;
 
-    for (p = *arg + 1; *p != NUL && *p != '"'; )
+    for (p = *arg + off; *p != NUL && *p != '"'; )
     {
        if (*p == '\\')
        {
@@ -2192,15 +2208,23 @@ eval_string(char_u **arg, typval_T *rettv, int evaluate)
                          }
                          // FALLTHROUGH
 
-               default:  MB_COPY_CHAR(p, end);
+               default: MB_COPY_CHAR(p, end);
                          break;
            }
        }
        else
+       {
+           if (interpolate && (*p == '{' || *p == '}'))
+           {
+               if (*p == '{' && p[1] != '{') // start of expression
+                   break;
+               ++p;  // reduce "{{" to "{" and "}}" to "}"
+           }
            MB_COPY_CHAR(p, end);
+       }
     }
     *end = NUL;
-    if (*p != NUL) // just in case
+    if (*p == '"' && !interpolate)
        ++p;
     *arg = p;
 
@@ -2209,17 +2233,20 @@ eval_string(char_u **arg, typval_T *rettv, int evaluate)
 
 /*
  * Allocate a variable for a 'str''ing' constant.
- * Return OK or FAIL.
+ * When "interpolate" is TRUE reduce "{{" to "{" and stop at a single "{".
+ * Return OK when a "rettv" was set to the string.
+ * Return FAIL on error, "rettv" is not set.
  */
     int
-eval_lit_string(char_u **arg, typval_T *rettv, int evaluate)
+eval_lit_string(char_u **arg, typval_T *rettv, int evaluate, int interpolate)
 {
     char_u     *p;
     char_u     *str;
-    int                reduce = 0;
+    int                reduce = interpolate ? -1 : 0;
+    int                off = interpolate ? 0 : 1;
 
     // Find the end of the string, skipping ''.
-    for (p = *arg + 1; *p != NUL; MB_PTR_ADV(p))
+    for (p = *arg + off; *p != NUL; MB_PTR_ADV(p))
     {
        if (*p == '\'')
        {
@@ -2228,9 +2255,29 @@ eval_lit_string(char_u **arg, typval_T *rettv, int evaluate)
            ++reduce;
            ++p;
        }
+       else if (interpolate)
+       {
+           if (*p == '{')
+           {
+               if (p[1] != '{')
+                   break;
+               ++p;
+               ++reduce;
+           }
+           else if (*p == '}')
+           {
+               ++p;
+               if (*p != '}')
+               {
+                   semsg(_(e_stray_closing_curly_str), *arg);
+                   return FAIL;
+               }
+               ++reduce;
+           }
+       }
     }
 
-    if (*p != '\'')
+    if (*p != '\'' && !(interpolate && *p == '{'))
     {
        semsg(_(e_missing_single_quote_str), *arg);
        return FAIL;
@@ -2239,18 +2286,19 @@ eval_lit_string(char_u **arg, typval_T *rettv, int evaluate)
     // If only parsing return after setting "*arg"
     if (!evaluate)
     {
-       *arg = p + 1;
+       *arg = p + off;
        return OK;
     }
 
-    // Copy the string into allocated memory, handling '' to ' reduction.
+    // Copy the string into allocated memory, handling '' to ' reduction and
+    // any expressions.
     str = alloc((p - *arg) - reduce);
     if (str == NULL)
        return FAIL;
     rettv->v_type = VAR_STRING;
     rettv->vval.v_string = str;
 
-    for (p = *arg + 1; *p != NUL; )
+    for (p = *arg + off; *p != NUL; )
     {
        if (*p == '\'')
        {
@@ -2258,38 +2306,82 @@ eval_lit_string(char_u **arg, typval_T *rettv, int evaluate)
                break;
            ++p;
        }
+       else if (interpolate && (*p == '{' || *p == '}'))
+       {
+           if (*p == '{' && p[1] != '{')
+               break;
+           ++p;
+       }
        MB_COPY_CHAR(p, str);
     }
     *str = NUL;
-    *arg = p + 1;
+    *arg = p + off;
 
     return OK;
 }
 
+/*
+ * Evaluate a single or double quoted string possibly containing expressions.
+ * "arg" points to the '$'.  The result is put in "rettv".
+ * Returns OK or FAIL.
+ */
     int
 eval_interp_string(char_u **arg, typval_T *rettv, int evaluate)
 {
     typval_T   tv;
-    int                ret;
-
-    // *arg is on the '$' character.
-    (*arg)++;
+    int                ret = OK;
+    int                quote;
+    garray_T   ga;
+    char_u     *p;
 
-    rettv->v_type = VAR_STRING;
+    ga_init2(&ga, 1, 80);
 
-    if (**arg == '"')
-       ret = eval_string(arg, &tv, evaluate);
-    else
-       ret = eval_lit_string(arg, &tv, evaluate);
+    // *arg is on the '$' character, move it to the first string character.
+    ++*arg;
+    quote = **arg;
+    ++*arg;
 
-    if (ret == FAIL || !evaluate)
-       return ret;
+    for (;;)
+    {
+       // Get the string up to the matching quote or to a single '{'.
+       // "arg" is advanced to either the quote or the '{'.
+       if (quote == '"')
+           ret = eval_string(arg, &tv, evaluate, TRUE);
+       else
+           ret = eval_lit_string(arg, &tv, evaluate, TRUE);
+       if (ret == FAIL)
+           break;
+       if (evaluate)
+       {
+           ga_concat(&ga, tv.vval.v_string);
+           clear_tv(&tv);
+       }
 
-    rettv->vval.v_string = eval_all_expr_in_str(tv.vval.v_string);
+       if (**arg != '{')
+       {
+           // found terminating quote
+           ++*arg;
+           break;
+       }
+       p = eval_one_expr_in_str(*arg, &ga);
+       if (p == NULL)
+       {
+           ret = FAIL;
+           break;
+       }
+       *arg = p;
+    }
 
-    clear_tv(&tv);
+    rettv->v_type = VAR_STRING;
+    if (ret == FAIL || !evaluate || ga_append(&ga, NUL) == FAIL)
+    {
+       ga_clear(&ga);
+       rettv->vval.v_string = NULL;
+       return ret;
+    }
 
-    return rettv->vval.v_string != NULL ? OK : FAIL;
+    rettv->vval.v_string = ga.ga_data;
+    return OK;
 }
 
 /*
index 6fbc3bec02d8d13e88d055eb5442660b6b30bcaa..0a08909cbf037d222325d820c61f438a17d815d4 100644 (file)
@@ -746,6 +746,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    4930,
 /**/
     4929,
 /**/
index bdaf1eddc763251a727603a28c977d71cff83e20..8ab4e2957d7323abe7b96d5c2c268cb0cc1a4d58 100644 (file)
@@ -968,6 +968,36 @@ theend:
     return r == FAIL ? NULL : (char_u *)"";
 }
 
+/*
+ * Compile one Vim expression {expr} in string "p".
+ * "p" points to the opening "{".
+ * Return a pointer to the character after "}", NULL for an error.
+ */
+    char_u *
+compile_one_expr_in_str(char_u *p, cctx_T *cctx)
+{
+    char_u     *block_start;
+    char_u     *block_end;
+
+    // Skip the opening {.
+    block_start = skipwhite(p + 1);
+    block_end = block_start;
+    if (*block_start != NUL && skip_expr(&block_end, NULL) == FAIL)
+       return NULL;
+    block_end = skipwhite(block_end);
+    // The block must be closed by a }.
+    if (*block_end != '}')
+    {
+       semsg(_(e_missing_close_curly_str), p);
+       return NULL;
+    }
+    if (compile_expr0(&block_start, cctx) == FAIL)
+       return NULL;
+    may_generate_2STRING(-1, TRUE, cctx);
+
+    return block_end + 1;
+}
+
 /*
  * Compile a string "str" (either containing a literal string or a mix of
  * literal strings and Vim expressions of the form `{expr}`).  This is used
@@ -997,8 +1027,6 @@ compile_all_expr_in_str(char_u *str, int evalstr, cctx_T *cctx)
     while (*p != NUL)
     {
        char_u  *lit_start;
-       char_u  *block_start;
-       char_u  *block_end;
        int     escaped_brace = FALSE;
 
        // Look for a block start.
@@ -1038,28 +1066,14 @@ compile_all_expr_in_str(char_u *str, int evalstr, cctx_T *cctx)
            continue;
        }
 
-       // Skip the opening {.
-       block_start = skipwhite(p + 1);
-       block_end = block_start;
-       if (*block_start != NUL && skip_expr(&block_end, NULL) == FAIL)
-           return FAIL;
-       block_end = skipwhite(block_end);
-       // The block must be closed by a }.
-       if (*block_end != '}')
-       {
-           semsg(_(e_missing_close_curly_str), str);
-           return FAIL;
-       }
-       if (compile_expr0(&block_start, cctx) == FAIL)
+       p = compile_one_expr_in_str(p, cctx);
+       if (p == NULL)
            return FAIL;
-       may_generate_2STRING(-1, TRUE, cctx);
        ++count;
-
-       p = block_end + 1;
     }
 
     // Small optimization, if there's only a single piece skip the ISN_CONCAT.
-    if (count != 1)
+    if (count > 1)
        return generate_CONCAT(cctx, count);
 
     return OK;
index 8bda461de27a9c4ad8a07b3457d3b398b7390bd5..36a3695e926ef3cdd7e823eaf772d28eb37f55eb 100644 (file)
@@ -762,9 +762,9 @@ compile_call(
 
        argvars[0].v_type = VAR_UNKNOWN;
        if (*s == '"')
-           (void)eval_string(&s, &argvars[0], TRUE);
+           (void)eval_string(&s, &argvars[0], TRUE, FALSE);
        else if (*s == '\'')
-           (void)eval_lit_string(&s, &argvars[0], TRUE);
+           (void)eval_lit_string(&s, &argvars[0], TRUE, FALSE);
        s = skipwhite(s);
        if (*s == ')' && argvars[0].v_type == VAR_STRING
               && ((is_has && !dynamic_feature(argvars[0].vval.v_string))
@@ -1375,30 +1375,73 @@ compile_get_env(char_u **arg, cctx_T *cctx)
 }
 
 /*
- * Compile "$"string"" or "$'string'".
+ * Compile $"string" or $'string'.
  */
     static int
 compile_interp_string(char_u **arg, cctx_T *cctx)
 {
     typval_T   tv;
     int                ret;
+    int                quote;
     int                evaluate = cctx->ctx_skip != SKIP_YES;
+    int                count = 0;
+    char_u     *p;
 
-    // *arg is on the '$' character.
-    (*arg)++;
+    // *arg is on the '$' character, move it to the first string character.
+    ++*arg;
+    quote = **arg;
+    ++*arg;
 
-    if (**arg == '"')
-       ret = eval_string(arg, &tv, evaluate);
-    else
-       ret = eval_lit_string(arg, &tv, evaluate);
+    for (;;)
+    {
+       // Get the string up to the matching quote or to a single '{'.
+       // "arg" is advanced to either the quote or the '{'.
+       if (quote == '"')
+           ret = eval_string(arg, &tv, evaluate, TRUE);
+       else
+           ret = eval_lit_string(arg, &tv, evaluate, TRUE);
+       if (ret == FAIL)
+           break;
+       if (evaluate)
+       {
+           if ((tv.vval.v_string != NULL && *tv.vval.v_string != NUL)
+                   || (**arg != '{' && count == 0))
+           {
+               // generate non-empty string or empty string if it's the only
+               // one
+               if (generate_PUSHS(cctx, &tv.vval.v_string) == FAIL)
+                   return FAIL;
+               tv.vval.v_string = NULL;  // don't free it now
+               ++count;
+           }
+           clear_tv(&tv);
+       }
+
+       if (**arg != '{')
+       {
+           // found terminating quote
+           ++*arg;
+           break;
+       }
+
+       p = compile_one_expr_in_str(*arg, cctx);
+       if (p == NULL)
+       {
+           ret = FAIL;
+           break;
+       }
+       ++count;
+       *arg = p;
+    }
 
     if (ret == FAIL || !evaluate)
        return ret;
 
-    ret = compile_all_expr_in_str(tv.vval.v_string, TRUE, cctx);
-    clear_tv(&tv);
+    // Small optimization, if there's only a single piece skip the ISN_CONCAT.
+    if (count > 1)
+       return generate_CONCAT(cctx, count);
 
-    return ret;
+    return OK;
 }
 
 /*
@@ -2161,14 +2204,14 @@ compile_expr8(
        /*
         * String constant: "string".
         */
-       case '"':   if (eval_string(arg, rettv, TRUE) == FAIL)
+       case '"':   if (eval_string(arg, rettv, TRUE, FALSE) == FAIL)
                        return FAIL;
                    break;
 
        /*
         * Literal string constant: 'str''ing'.
         */
-       case '\'':  if (eval_lit_string(arg, rettv, TRUE) == FAIL)
+       case '\'':  if (eval_lit_string(arg, rettv, TRUE, FALSE) == FAIL)
                        return FAIL;
                    break;
 
index 93ac062a33656c1682154f9a7b2f270c23dba9c1..edc5db045110b23299ce7e8b4fce90043f6a100e 100644 (file)
@@ -726,6 +726,8 @@ generate_PUSHF(cctx_T *cctx, float_T fnumber)
 /*
  * Generate an ISN_PUSHS instruction.
  * Consumes "*str".  When freed *str is set to NULL, unless "str" is NULL.
+ * Note that if "str" is used in the instruction OK is returned and "*str" is
+ * not set to NULL.
  */
     int
 generate_PUSHS(cctx_T *cctx, char_u **str)