To: vim_dev@googlegroups.com Subject: Patch 8.2.4930 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.4930 Problem: Interpolated string expression requires escaping. Solution: Do not require escaping in the expression. Files: runtime/doc/eval.txt, src/typval.c, src/proto/typval.pro, src/dict.c, src/eval.c, src/evalvars.c, src/proto/evalvars.pro, src/vim9compile.c, src/proto/vim9compile.pro, src/vim9expr.c, src/vim9instr.c, src/alloc.c, src/proto/alloc.pro, src/testdir/test_expr.vim, src/testdir/test_let.vim *** ../vim-8.2.4929/runtime/doc/eval.txt 2022-05-06 13:14:43.789076617 +0100 --- runtime/doc/eval.txt 2022-05-10 11:11:12.243900263 +0100 *************** *** 3215,3234 **** {endmarker}. If "eval" is not specified, then each line of text is ! used as a |literal-string|. If "eval" is specified, ! then any Vim expression in the form ``={expr}`` is ! evaluated and the result replaces the expression. Example where $HOME is expanded: > let lines =<< trim eval END some text ! See the file `=$HOME`/.vimrc more text END < There can be multiple Vim expressions in a single line but an expression cannot span multiple lines. If any expression evaluation fails, then the assignment fails. - once the "`=" has been found {expr} and a backtick - must follow. {expr} cannot be empty. {endmarker} must not contain white space. {endmarker} cannot start with a lower case character. --- 3261,3280 ---- {endmarker}. If "eval" is not specified, then each line of text is ! used as a |literal-string|, except that single quotes ! doe not need to be doubled. ! If "eval" is specified, then any Vim expression in the ! form {expr} is evaluated and the result replaces the ! expression, like with |interp-string|. Example where $HOME is expanded: > let lines =<< trim eval END some text ! See the file {$HOME}/.vimrc more text END < There can be multiple Vim expressions in a single line but an expression cannot span multiple lines. If any expression evaluation fails, then the assignment fails. {endmarker} must not contain white space. {endmarker} cannot start with a lower case character. *** ../vim-8.2.4929/src/typval.c 2022-05-09 20:09:19.294641425 +0100 --- src/typval.c 2022-05-10 12:47:12.209820875 +0100 *************** *** 2065,2083 **** } /* ! * Allocate a variable for a string constant. * Return OK or FAIL. */ int ! eval_string(char_u **arg, typval_T *rettv, int evaluate) { char_u *p; char_u *end; ! int extra = 0; int len; // Find the end of the string, skipping backslashed characters. ! for (p = *arg + 1; *p != NUL && *p != '"'; MB_PTR_ADV(p)) { if (*p == '\\' && p[1] != NUL) { --- 2065,2087 ---- } /* ! * 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, int interpolate) { char_u *p; char_u *end; ! int extra = interpolate ? 1 : 0; ! int off = interpolate ? 0 : 1; int len; // Find the end of the string, skipping backslashed characters. ! for (p = *arg + off; *p != NUL && *p != '"'; MB_PTR_ADV(p)) { if (*p == '\\' && p[1] != NUL) { *************** *** 2088,2096 **** if (*p == '<') extra += 5; } } ! if (*p != '"') { semsg(_(e_missing_double_quote_str), *arg); return FAIL; --- 2092,2112 ---- 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 != '"' && !(interpolate && *p == '{')) { semsg(_(e_missing_double_quote_str), *arg); return FAIL; *************** *** 2099,2105 **** // If only parsing, set *arg and return here if (!evaluate) { ! *arg = p + 1; return OK; } --- 2115,2121 ---- // If only parsing, set *arg and return here if (!evaluate) { ! *arg = p + off; return OK; } *************** *** 2112,2118 **** return FAIL; end = rettv->vval.v_string; ! for (p = *arg + 1; *p != NUL && *p != '"'; ) { if (*p == '\\') { --- 2128,2134 ---- return FAIL; end = rettv->vval.v_string; ! for (p = *arg + off; *p != NUL && *p != '"'; ) { if (*p == '\\') { *************** *** 2192,2206 **** } // FALLTHROUGH ! default: MB_COPY_CHAR(p, end); break; } } else MB_COPY_CHAR(p, end); } *end = NUL; ! if (*p != NUL) // just in case ++p; *arg = p; --- 2208,2230 ---- } // FALLTHROUGH ! 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 == '"' && !interpolate) ++p; *arg = p; *************** *** 2209,2225 **** /* * Allocate a variable for a 'str''ing' constant. ! * Return OK or FAIL. */ int ! eval_lit_string(char_u **arg, typval_T *rettv, int evaluate) { char_u *p; char_u *str; ! int reduce = 0; // Find the end of the string, skipping ''. ! for (p = *arg + 1; *p != NUL; MB_PTR_ADV(p)) { if (*p == '\'') { --- 2233,2252 ---- /* * Allocate a variable for a 'str''ing' constant. ! * 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, int interpolate) { char_u *p; char_u *str; ! int reduce = interpolate ? -1 : 0; ! int off = interpolate ? 0 : 1; // Find the end of the string, skipping ''. ! for (p = *arg + off; *p != NUL; MB_PTR_ADV(p)) { if (*p == '\'') { *************** *** 2228,2236 **** ++reduce; ++p; } } ! if (*p != '\'') { semsg(_(e_missing_single_quote_str), *arg); return FAIL; --- 2255,2283 ---- ++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 != '\'' && !(interpolate && *p == '{')) { semsg(_(e_missing_single_quote_str), *arg); return FAIL; *************** *** 2239,2256 **** // If only parsing return after setting "*arg" if (!evaluate) { ! *arg = p + 1; return OK; } ! // Copy the string into allocated memory, handling '' to ' reduction. 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; ) { if (*p == '\'') { --- 2286,2304 ---- // If only parsing return after setting "*arg" if (!evaluate) { ! *arg = p + off; return OK; } ! // 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 + off; *p != NUL; ) { if (*p == '\'') { *************** *** 2258,2295 **** break; ++p; } MB_COPY_CHAR(p, str); } *str = NUL; ! *arg = p + 1; return OK; } int eval_interp_string(char_u **arg, typval_T *rettv, int evaluate) { typval_T tv; ! int ret; ! ! // *arg is on the '$' character. ! (*arg)++; ! rettv->v_type = VAR_STRING; ! if (**arg == '"') ! ret = eval_string(arg, &tv, evaluate); ! else ! ret = eval_lit_string(arg, &tv, evaluate); ! if (ret == FAIL || !evaluate) ! return ret; ! rettv->vval.v_string = eval_all_expr_in_str(tv.vval.v_string); ! clear_tv(&tv); ! return rettv->vval.v_string != NULL ? OK : FAIL; } /* --- 2306,2387 ---- break; ++p; } + else if (interpolate && (*p == '{' || *p == '}')) + { + if (*p == '{' && p[1] != '{') + break; + ++p; + } MB_COPY_CHAR(p, str); } *str = NUL; ! *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 = OK; ! int quote; ! garray_T ga; ! char_u *p; ! ga_init2(&ga, 1, 80); ! // *arg is on the '$' character, move it to the first string character. ! ++*arg; ! quote = **arg; ! ++*arg; ! 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); ! } ! if (**arg != '{') ! { ! // found terminating quote ! ++*arg; ! break; ! } ! p = eval_one_expr_in_str(*arg, &ga); ! if (p == NULL) ! { ! ret = FAIL; ! break; ! } ! *arg = p; ! } ! rettv->v_type = VAR_STRING; ! if (ret == FAIL || !evaluate || ga_append(&ga, NUL) == FAIL) ! { ! ga_clear(&ga); ! rettv->vval.v_string = NULL; ! return ret; ! } ! rettv->vval.v_string = ga.ga_data; ! return OK; } /* *** ../vim-8.2.4929/src/proto/typval.pro 2022-05-06 13:14:43.793076613 +0100 --- src/proto/typval.pro 2022-05-09 21:54:39.619665829 +0100 *************** *** 68,78 **** 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); 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); --- 68,78 ---- 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 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); 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); *** ../vim-8.2.4929/src/dict.c 2022-04-04 15:16:50.738014123 +0100 --- src/dict.c 2022-05-09 21:50:55.507907210 +0100 *************** *** 866,878 **** if (**arg == '\'') { ! if (eval_lit_string(arg, &rettv, TRUE) == FAIL) return NULL; key = rettv.vval.v_string; } else if (**arg == '"') { ! if (eval_string(arg, &rettv, TRUE) == FAIL) return NULL; key = rettv.vval.v_string; } --- 866,878 ---- if (**arg == '\'') { ! 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, FALSE) == FAIL) return NULL; key = rettv.vval.v_string; } *** ../vim-8.2.4929/src/eval.c 2022-05-07 21:14:01.642973330 +0100 --- src/eval.c 2022-05-09 21:51:03.947897209 +0100 *************** *** 3726,3738 **** /* * String constant: "string". */ ! case '"': ret = eval_string(arg, rettv, evaluate); break; /* * Literal string constant: 'str''ing'. */ ! case '\'': ret = eval_lit_string(arg, rettv, evaluate); break; /* --- 3726,3738 ---- /* * String constant: "string". */ ! case '"': ret = eval_string(arg, rettv, evaluate, FALSE); break; /* * Literal string constant: 'str''ing'. */ ! case '\'': ret = eval_lit_string(arg, rettv, evaluate, FALSE); break; /* *** ../vim-8.2.4929/src/evalvars.c 2022-05-06 13:14:43.789076617 +0100 --- src/evalvars.c 2022-05-10 13:06:18.053795337 +0100 *************** *** 603,618 **** } /* ! * Evaluate all the Vim expressions ({expr}) in string "str" and return the ! * resulting string. The caller must free the returned string. */ 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; --- 603,654 ---- } /* ! * 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; ga_init2(&ga, 1, 80); p = str; *************** *** 620,627 **** 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,661 ---- *************** *** 656,690 **** 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) - { - ga_clear(&ga); - return NULL; - } - ga_concat(&ga, expr_val); - vim_free(expr_val); - - p = block_end + 1; } ga_append(&ga, NUL); --- 690,702 ---- continue; } ! // Evaluate the expression and append the result. ! p = eval_one_expr_in_str(p, &ga); ! if (p == NULL) { ga_clear(&ga); return NULL; } } ga_append(&ga, NUL); *** ../vim-8.2.4929/src/proto/evalvars.pro 2022-05-06 13:14:43.793076613 +0100 --- src/proto/evalvars.pro 2022-05-09 21:55:08.719637657 +0100 *************** *** 13,18 **** --- 13,20 ---- 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,110 **** 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 : */ --- 107,110 ---- *** ../vim-8.2.4929/src/vim9compile.c 2022-05-09 20:09:19.294641425 +0100 --- src/vim9compile.c 2022-05-10 12:32:36.730408912 +0100 *************** *** 969,974 **** --- 969,1004 ---- } /* + * 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 * when compiling a heredoc assignment to a variable or an interpolated string *************** *** 997,1004 **** while (*p != NUL) { char_u *lit_start; - char_u *block_start; - char_u *block_end; int escaped_brace = FALSE; // Look for a block start. --- 1027,1032 ---- *************** *** 1038,1065 **** 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) - 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) return generate_CONCAT(cctx, count); return OK; --- 1066,1079 ---- continue; } ! p = compile_one_expr_in_str(p, cctx); ! if (p == NULL) return FAIL; ++count; } // Small optimization, if there's only a single piece skip the ISN_CONCAT. ! if (count > 1) return generate_CONCAT(cctx, count); return OK; *** ../vim-8.2.4929/src/proto/vim9compile.pro 2022-05-06 13:14:43.793076613 +0100 --- src/proto/vim9compile.pro 2022-05-10 12:32:07.306419821 +0100 *************** *** 16,21 **** --- 16,22 ---- 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); *** ../vim-8.2.4929/src/vim9expr.c 2022-05-06 13:14:43.793076613 +0100 --- src/vim9expr.c 2022-05-10 13:21:14.224792916 +0100 *************** *** 762,770 **** argvars[0].v_type = VAR_UNKNOWN; if (*s == '"') ! (void)eval_string(&s, &argvars[0], TRUE); else if (*s == '\'') ! (void)eval_lit_string(&s, &argvars[0], TRUE); s = skipwhite(s); if (*s == ')' && argvars[0].v_type == VAR_STRING && ((is_has && !dynamic_feature(argvars[0].vval.v_string)) --- 762,770 ---- argvars[0].v_type = VAR_UNKNOWN; if (*s == '"') ! (void)eval_string(&s, &argvars[0], TRUE, FALSE); else if (*s == '\'') ! (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,1404 **** } /* ! * Compile "$"string"" or "$'string'". */ static int compile_interp_string(char_u **arg, cctx_T *cctx) { typval_T tv; int ret; int evaluate = cctx->ctx_skip != SKIP_YES; ! // *arg is on the '$' character. ! (*arg)++; ! if (**arg == '"') ! ret = eval_string(arg, &tv, evaluate); ! else ! ret = eval_lit_string(arg, &tv, evaluate); if (ret == FAIL || !evaluate) return ret; ! ret = compile_all_expr_in_str(tv.vval.v_string, TRUE, cctx); ! clear_tv(&tv); ! return ret; } /* --- 1375,1447 ---- } /* ! * 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, move it to the first string character. ! ++*arg; ! quote = **arg; ! ++*arg; ! 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; ! // Small optimization, if there's only a single piece skip the ISN_CONCAT. ! if (count > 1) ! return generate_CONCAT(cctx, count); ! return OK; } /* *************** *** 2161,2174 **** /* * String constant: "string". */ ! case '"': if (eval_string(arg, rettv, TRUE) == FAIL) return FAIL; break; /* * Literal string constant: 'str''ing'. */ ! case '\'': if (eval_lit_string(arg, rettv, TRUE) == FAIL) return FAIL; break; --- 2204,2217 ---- /* * String constant: "string". */ ! 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, FALSE) == FAIL) return FAIL; break; *** ../vim-8.2.4929/src/vim9instr.c 2022-05-04 16:46:51.349318219 +0100 --- src/vim9instr.c 2022-05-10 13:20:25.392819519 +0100 *************** *** 726,731 **** --- 726,733 ---- /* * 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) *** ../vim-8.2.4929/src/alloc.c 2022-04-09 11:09:03.526052266 +0100 --- src/alloc.c 2022-05-10 12:08:35.776303190 +0100 *************** *** 832,838 **** /* * Concatenate a string to a growarray which contains bytes. ! * When "s" is NULL does not do anything. * Note: Does NOT copy the NUL at the end! */ void --- 832,838 ---- /* * Concatenate a string to a growarray which contains bytes. ! * When "s" is NULL memory allocation fails does not do anything. * Note: Does NOT copy the NUL at the end! */ void *************** *** 869,882 **** /* * Append one byte to a growarray which contains bytes. */ ! void 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 (defined(UNIX) && !defined(USE_SYSTEM)) || defined(MSWIN) \ --- 869,882 ---- /* * Append one byte to a growarray which contains bytes. */ ! int ga_append(garray_T *gap, int c) { ! 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) \ *** ../vim-8.2.4929/src/proto/alloc.pro 2022-04-09 11:09:03.526052266 +0100 --- src/proto/alloc.pro 2022-05-10 12:08:39.188316249 +0100 *************** *** 19,31 **** 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_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); void append_ga_line(garray_T *gap); /* vim: set ft=c : */ --- 19,31 ---- 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); 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); ! int ga_append(garray_T *gap, int c); void append_ga_line(garray_T *gap); /* vim: set ft=c : */ *** ../vim-8.2.4929/src/testdir/test_expr.vim 2022-05-06 13:14:43.793076613 +0100 --- src/testdir/test_expr.vim 2022-05-10 12:11:55.768948906 +0100 *************** *** 897,903 **** #" Escaping rules. call assert_equal('"foo"{bar}', $"\"foo\"{{bar}}") call assert_equal('"foo"{bar}', $'"foo"{{bar}}') ! call assert_equal('foobar', $"{\"foo\"}" .. $'{''bar''}') #" Whitespace before/after the expression. call assert_equal('3', $"{ 1 + 2 }") #" String conversion. --- 897,903 ---- #" Escaping rules. call assert_equal('"foo"{bar}', $"\"foo\"{{bar}}") call assert_equal('"foo"{bar}', $'"foo"{{bar}}') ! call assert_equal('foobar', $"{"foo"}" .. $'{'bar'}') #" Whitespace before/after the expression. call assert_equal('3', $"{ 1 + 2 }") #" String conversion. *************** *** 907,914 **** 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") VAR var1 = "sun" VAR var2 = "shine" call assert_equal('sunshine', $"{var1}{var2}") --- 907,914 ---- 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{123}ape") ! call assert_equal('me{}me', $"me{"\x7b"}\x7dme") VAR var1 = "sun" VAR var2 = "shine" call assert_equal('sunshine', $"{var1}{var2}") *************** *** 916,922 **** #" Multibyte strings. call assert_equal('say ハロー・ワールド', $"say {'ハロー・ワールド'}") #" Nested. ! call assert_equal('foobarbaz', $"foo{$\"{'bar'}\"}baz") #" Do not evaluate blocks when the expr is skipped. VAR tmp = 0 if v:false --- 916,922 ---- #" Multibyte strings. call assert_equal('say ハロー・ワールド', $"say {'ハロー・ワールド'}") #" Nested. ! call assert_equal('foobarbaz', $"foo{$"{'bar'}"}baz") #" Do not evaluate blocks when the expr is skipped. VAR tmp = 0 if v:false *** ../vim-8.2.4929/src/testdir/test_let.vim 2022-05-06 13:14:43.793076613 +0100 --- src/testdir/test_let.vim 2022-05-10 13:12:25.437216707 +0100 *************** *** 387,395 **** 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 .. \"{{\"}") endfunc " Test for the setting a variable using the heredoc syntax. --- 387,394 ---- let text = 'text' 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. *** ../vim-8.2.4929/src/version.c 2022-05-09 21:03:30.781853316 +0100 --- src/version.c 2022-05-10 11:11:45.423852626 +0100 *************** *** 748,749 **** --- 748,751 ---- { /* Add new patch number below this line */ + /**/ + 4930, /**/ -- Send $25.00 for handy leaflet on how to make money by selling leaflets /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///