Merge pull request #23105 from zeertzjq/vim-8.2.4770

vim-patch:8.2.{4770,4783,4840,4883,4930,4934},9.0.0104: interpolated string
This commit is contained in:
zeertzjq 2023-04-15 19:46:17 +08:00 committed by GitHub
commit 62b7b1daf3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 573 additions and 54 deletions

View File

@ -1406,6 +1406,34 @@ to be doubled. These two commands are equivalent: >
if a =~ '\s*'
------------------------------------------------------------------------------
interpolated-string *$quote* *interpolated-string*
$"string" interpolated string constant *expr-$quote*
$'string' interpolated literal string constant *expr-$'*
Interpolated strings are an extension of the |string| and |literal-string|,
allowing the inclusion of Vim script expressions (see |expr1|). Any
expression returning a value can be enclosed between curly braces. The value
is converted to a string. All the text and results of the expressions
are concatenated to make a new string.
*E1278*
To include an opening brace '{' or closing brace '}' in the string content
double it. For double quoted strings using a backslash also works. A single
closing brace '}' will result in an error.
Examples: >
let your_name = input("What's your name? ")
< What's your name? Peter ~
>
echo
echo $"Hello, {your_name}!"
< Hello, Peter! ~
>
echo $"The square root of {{9}} is {sqrt(9)}"
< The square root of {9} is 3.0 ~
------------------------------------------------------------------------------
option *expr-option* *E112* *E113*
@ -2540,14 +2568,30 @@ This does NOT work: >
*:let=<<* *:let-heredoc*
*E990* *E991* *E172* *E221* *E1145*
:let {var-name} =<< [trim] {endmarker}
:let {var-name} =<< [trim] [eval] {endmarker}
text...
text...
{endmarker}
Set internal variable {var-name} to a |List|
containing the lines of text bounded by the string
{endmarker}. The lines of text is used as a
|literal-string|.
{endmarker}.
If "eval" is not specified, then each line of text is
used as a |literal-string|, except that single quotes
does 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 |interpolated-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.
The last line should end only with the {endmarker}
@ -2597,6 +2641,13 @@ text...
1 2 3 4
5 6 7 8
DATA
let code =<< trim eval CODE
let v = {10 + 20}
let h = "{$HOME}"
let s = "{Str1()} abc {Str2()}"
let n = {MyFunc(3, 4)}
CODE
<
*E121*
:let {var-name} .. List the value of variable {var-name}. Multiple

View File

@ -3332,8 +3332,8 @@ A jump table for the options with a short description can be found at |Q_op|.
If the expression starts with s: or |<SID>|, then it is replaced with
the script ID (|local-function|). Example: >
set includeexpr=s:MyIncludeExpr(v:fname)
set includeexpr=<SID>SomeIncludeExpr(v:fname)
setlocal includeexpr=s:MyIncludeExpr(v:fname)
setlocal includeexpr=<SID>SomeIncludeExpr(v:fname)
<
The expression will be evaluated in the |sandbox| when set from a
modeline, see |sandbox-option|.

View File

@ -84,9 +84,7 @@ syn case ignore
let s:digits = "0123456789ABCDEF"
for s:radix in range(2, 16)
" Nvim does not support interpolated strings yet.
" exe $'syn match modula3Integer "\<{s:radix}_[{s:digits[:s:radix - 1]}]\+L\=\>"'
exe 'syn match modula3Integer "\<' .. s:radix .. '_[' .. s:digits[:s:radix - 1] .. ']\+L\=\>"'
exe $'syn match modula3Integer "\<{s:radix}_[{s:digits[:s:radix - 1]}]\+L\=\>"'
endfor
unlet s:digits s:radix

View File

@ -3063,12 +3063,12 @@ static int eval7(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool wan
// String constant: "string".
case '"':
ret = eval_string(arg, rettv, evaluate);
ret = eval_string(arg, rettv, evaluate, false);
break;
// Literal string constant: 'str''ing'.
case '\'':
ret = eval_lit_string(arg, rettv, evaluate);
ret = eval_lit_string(arg, rettv, evaluate, false);
break;
// List: [expr, expr]
@ -3100,8 +3100,13 @@ static int eval7(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool wan
ret = eval_option((const char **)arg, rettv, evaluate);
break;
// Environment variable: $VAR.
// Interpolated string: $"string" or $'string'.
case '$':
ret = eval_env_var(arg, rettv, evaluate);
if ((*arg)[1] == '"' || (*arg)[1] == '\'') {
ret = eval_interp_string(arg, rettv, evaluate);
} else {
ret = eval_env_var(arg, rettv, evaluate);
}
break;
// Register contents: @r.
@ -3863,35 +3868,63 @@ static int eval_number(char **arg, typval_T *rettv, bool evaluate, bool want_str
return OK;
}
/// 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.
static int eval_string(char **arg, typval_T *rettv, int evaluate)
static int eval_string(char **arg, typval_T *rettv, bool evaluate, bool interpolate)
{
char *p;
unsigned int extra = 0;
const char *const arg_end = *arg + strlen(*arg);
unsigned int extra = interpolate ? 1 : 0;
const int off = interpolate ? 0 : 1;
// 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) {
p++;
// A "\<x>" form occupies at least 4 characters, and produces up
// to 9 characters (6 for the char and 3 for a modifier):
// reserve space for 5 extra.
if (*p == '<') {
int modifiers = 0;
int flags = FSK_KEYCODE | FSK_IN_STRING;
extra += 5;
// Skip to the '>' to avoid using '{' inside for string
// interpolation.
if (p[1] != '*') {
flags |= FSK_SIMPLIFY;
}
if (find_special_key((const char **)&p, (size_t)(arg_end - p),
&modifiers, flags, NULL) != 0) {
p--; // leave "p" on the ">"
}
}
} 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(_("E114: Missing quote: %s"), *arg);
return FAIL;
}
// If only parsing, set *arg and return here
if (!evaluate) {
*arg = p + 1;
*arg = p + off;
return OK;
}
@ -3902,7 +3935,7 @@ static int eval_string(char **arg, typval_T *rettv, int evaluate)
rettv->vval.v_string = xmalloc((size_t)len);
char *end = rettv->vval.v_string;
for (p = *arg + 1; *p != NUL && *p != '"';) {
for (p = *arg + off; *p != NUL && *p != '"';) {
if (*p == '\\') {
switch (*++p) {
case 'b':
@ -3975,7 +4008,8 @@ static int eval_string(char **arg, typval_T *rettv, int evaluate)
if (p[1] != '*') {
flags |= FSK_SIMPLIFY;
}
extra = trans_special((const char **)&p, strlen(p), end, flags, false, NULL);
extra = trans_special((const char **)&p, (size_t)(arg_end - p),
end, flags, false, NULL);
if (extra != 0) {
end += extra;
if (end >= rettv->vval.v_string + len) {
@ -3991,11 +4025,17 @@ static int eval_string(char **arg, typval_T *rettv, int evaluate)
break;
}
} else {
if (interpolate && (*p == '{' || *p == '}')) {
if (*p == '{' && p[1] != '{') { // start of expression
break;
}
p++; // reduce "{{" to "{" and "}}" to "}"
}
mb_copy_char((const char **)&p, &end);
}
}
*end = NUL;
if (*p != NUL) { // just in case
if (*p == '"' && !interpolate) {
p++;
}
*arg = p;
@ -4004,55 +4044,133 @@ static int eval_string(char **arg, typval_T *rettv, int evaluate)
}
/// Allocate a variable for a 'str''ing' constant.
/// When "interpolate" is true reduce "{{" to "{" and stop at a single "{".
///
/// @return OK or FAIL.
static int eval_lit_string(char **arg, typval_T *rettv, int evaluate)
/// @return OK when a "rettv" was set to the string.
/// FAIL on error, "rettv" is not set.
static int eval_lit_string(char **arg, typval_T *rettv, bool evaluate, bool interpolate)
{
char *p;
int reduce = 0;
int reduce = interpolate ? -1 : 0;
const 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 == '\'') {
if (p[1] != '\'') {
break;
}
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(_("E115: Missing quote: %s"), *arg);
return FAIL;
}
// 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.
char *str = xmalloc((size_t)((p - *arg) - reduce));
rettv->v_type = VAR_STRING;
rettv->vval.v_string = str;
for (p = *arg + 1; *p != NUL;) {
for (p = *arg + off; *p != NUL;) {
if (*p == '\'') {
if (p[1] != '\'') {
break;
}
p++;
} else if (interpolate && (*p == '{' || *p == '}')) {
if (*p == '{' && p[1] != '{') {
break;
}
p++;
}
mb_copy_char((const 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".
///
/// @return OK or FAIL.
int eval_interp_string(char **arg, typval_T *rettv, bool evaluate)
{
int ret = OK;
garray_T ga;
ga_init(&ga, 1, 80);
// *arg is on the '$' character, move it to the first string character.
(*arg)++;
const int quote = (uint8_t)(**arg);
(*arg)++;
for (;;) {
typval_T tv;
// 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);
tv_clear(&tv);
}
if (**arg != '{') {
// found terminating quote
(*arg)++;
break;
}
char *p = eval_one_expr_in_str(*arg, &ga, evaluate);
if (p == NULL) {
ret = FAIL;
break;
}
*arg = p;
}
rettv->v_type = VAR_STRING;
if (ret != FAIL && evaluate) {
ga_append(&ga, NUL);
}
rettv->vval.v_string = ga.ga_data;
return OK;
}
/// @return the function name of the partial.
char *partial_name(partial_T *pt)
FUNC_ATTR_PURE

View File

@ -2483,10 +2483,19 @@ void ex_function(exarg_T *eap)
&& (!ASCII_ISALNUM(p[2])
|| (p[2] == 't' && !ASCII_ISALNUM(p[3]))))) {
p = skipwhite(arg + 3);
if (strncmp(p, "trim", 4) == 0) {
// Ignore leading white space.
p = skipwhite(p + 4);
heredoc_trimmed = xstrnsave(theline, (size_t)(skipwhite(theline) - theline));
while (true) {
if (strncmp(p, "trim", 4) == 0) {
// Ignore leading white space.
p = skipwhite(p + 4);
heredoc_trimmed = xstrnsave(theline, (size_t)(skipwhite(theline) - theline));
continue;
}
if (strncmp(p, "eval", 4) == 0) {
// Ignore leading white space.
p = skipwhite(p + 4);
continue;
}
break;
}
skip_until = xstrnsave(p, (size_t)(skiptowhite(p) - p));
do_concat = false;

View File

@ -53,6 +53,96 @@
static const char *e_letunexp = N_("E18: Unexpected characters in :let");
static const char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
/// Evaluate one Vim expression {expr} in string "p" and append the
/// resulting string to "gap". "p" points to the opening "{".
/// When "evaluate" is false only skip over the expression.
/// Return a pointer to the character after "}", NULL for an error.
char *eval_one_expr_in_str(char *p, garray_T *gap, bool evaluate)
{
char *block_start = skipwhite(p + 1); // skip the opening {
char *block_end = block_start;
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;
}
if (evaluate) {
*block_end = NUL;
char *expr_val = eval_to_string(block_start, true);
*block_end = '}';
if (expr_val == NULL) {
return NULL;
}
ga_concat(gap, expr_val);
xfree(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 *eval_all_expr_in_str(char *str)
{
garray_T ga;
ga_init(&ga, 1, 80);
char *p = str;
while (*p != NUL) {
bool escaped_brace = false;
// Look for a block start.
char *lit_start = p;
while (*p != '{' && *p != '}' && *p != NUL) {
p++;
}
if (*p != NUL && *p == p[1]) {
// Escaped brace, unescape and continue.
// Include the brace in the literal string.
p++;
escaped_brace = true;
} else if (*p == '}') {
semsg(_(e_stray_closing_curly_str), str);
ga_clear(&ga);
return NULL;
}
// Append the literal part.
ga_concat_len(&ga, lit_start, (size_t)(p - lit_start));
if (*p == NUL) {
break;
}
if (escaped_brace) {
// Skip the second brace.
p++;
continue;
}
// Evaluate the expression and append the result.
p = eval_one_expr_in_str(p, &ga, true);
if (p == NULL) {
ga_clear(&ga);
return NULL;
}
}
ga_append(&ga, NUL);
return ga.ga_data;
}
/// Get a list of lines from a HERE document. The here document is a list of
/// lines surrounded by a marker.
/// cmd << {marker}
@ -65,7 +155,7 @@ static const char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s")
/// marker, then the leading indentation before the lines (matching the
/// indentation in the 'cmd' line) is stripped.
///
/// @return a List with {lines} or NULL.
/// @return a List with {lines} or NULL on failure.
static list_T *heredoc_get(exarg_T *eap, char *cmd)
{
char *marker;
@ -81,20 +171,33 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd)
// Check for the optional 'trim' word before the marker
cmd = skipwhite(cmd);
if (strncmp(cmd, "trim", 4) == 0
&& (cmd[4] == NUL || ascii_iswhite(cmd[4]))) {
cmd = skipwhite(cmd + 4);
bool evalstr = false;
bool eval_failed = false;
while (true) {
if (strncmp(cmd, "trim", 4) == 0
&& (cmd[4] == NUL || ascii_iswhite(cmd[4]))) {
cmd = skipwhite(cmd + 4);
// Trim the indentation from all the lines in the here document.
// The amount of indentation trimmed is the same as the indentation of
// the first line after the :let command line. To find the end marker
// the indent of the :let command line is trimmed.
p = *eap->cmdlinep;
while (ascii_iswhite(*p)) {
p++;
marker_indent_len++;
// Trim the indentation from all the lines in the here document.
// The amount of indentation trimmed is the same as the indentation
// of the first line after the :let command line. To find the end
// marker the indent of the :let command line is trimmed.
p = *eap->cmdlinep;
while (ascii_iswhite(*p)) {
p++;
marker_indent_len++;
}
text_indent_len = -1;
continue;
}
text_indent_len = -1;
if (strncmp(cmd, "eval", 4) == 0
&& (cmd[4] == NUL || ascii_iswhite(cmd[4]))) {
cmd = skipwhite(cmd + 4);
evalstr = true;
continue;
}
break;
}
// The marker is the next word.
@ -115,12 +218,14 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd)
return NULL;
}
char *theline = NULL;
list_T *l = tv_list_alloc(0);
for (;;) {
int mi = 0;
int ti = 0;
char *theline = eap->getline(NUL, eap->cookie, 0, false);
xfree(theline);
theline = eap->getline(NUL, eap->cookie, 0, false);
if (theline == NULL) {
semsg(_("E990: Missing end marker '%s'"), marker);
break;
@ -133,9 +238,15 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd)
mi = marker_indent_len;
}
if (strcmp(marker, theline + mi) == 0) {
xfree(theline);
break;
}
// If expression evaluation failed in the heredoc, then skip till the
// end marker.
if (eval_failed) {
continue;
}
if (text_indent_len == -1 && *theline != NUL) {
// set the text indent from the first line.
p = theline;
@ -155,11 +266,28 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd)
}
}
tv_list_append_string(l, theline + ti, -1);
xfree(theline);
char *str = theline + ti;
if (evalstr && !eap->skip) {
str = eval_all_expr_in_str(str);
if (str == NULL) {
// expression evaluation failed
eval_failed = true;
continue;
}
xfree(theline);
theline = str;
}
tv_list_append_string(l, str, -1);
}
xfree(theline);
xfree(text_indent);
if (eval_failed) {
// expression evaluation in the heredoc failed
tv_list_free(l);
return NULL;
}
return l;
}

View File

@ -1024,6 +1024,11 @@ EXTERN const char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight
EXTERN const char e_invalid_line_number_nr[] INIT(= N_("E966: Invalid line number: %ld"));
EXTERN char e_stray_closing_curly_str[]
INIT(= N_("E1278: Stray '}' without a matching '{': %s"));
EXTERN char e_missing_close_curly_str[]
INIT(= N_("E1279: Missing '}': %s"));
EXTERN const char e_undobang_cannot_redo_or_move_branch[]
INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch"));

View File

@ -348,6 +348,39 @@ func Test_Debugger_breakadd()
call assert_fails('breakadd file Xtest.vim /\)/', 'E55:')
endfunc
" Test for expression breakpoint set using ":breakadd expr <expr>"
func Test_Debugger_breakadd_expr()
CheckRunVimInTerminal
let lines =<< trim END
let g:Xtest_var += 1
END
call writefile(lines, 'Xtest.vim')
" Start Vim in a terminal
let buf = RunVimInTerminal('Xtest.vim', {})
call RunDbgCmd(buf, ':let g:Xtest_var = 10')
call RunDbgCmd(buf, ':breakadd expr g:Xtest_var')
call RunDbgCmd(buf, ':source %')
let expected =<< eval trim END
Oldval = "10"
Newval = "11"
{fnamemodify('Xtest.vim', ':p')}
line 1: let g:Xtest_var += 1
END
call RunDbgCmd(buf, ':source %', expected)
call RunDbgCmd(buf, 'cont')
let expected =<< eval trim END
Oldval = "11"
Newval = "12"
{fnamemodify('Xtest.vim', ':p')}
line 1: let g:Xtest_var += 1
END
call RunDbgCmd(buf, ':source %', expected)
call StopVimInTerminal(buf)
call delete('Xtest.vim')
endfunc
func Test_Backtrace_Through_Source()
CheckRunVimInTerminal
CheckCWD

View File

@ -407,4 +407,9 @@ func Test_modified_char_no_escape_special()
nunmap <M->
endfunc
func Test_eval_string_in_special_key()
" this was using the '{' inside <> as the start of an interpolated string
silent! echo 0{1-$"\<S--{>n|%
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@ -848,4 +848,60 @@ func Test_float_compare()
call CheckLegacyAndVim9Success(lines)
endfunc
func Test_string_interp()
let lines =<< trim END
call assert_equal('', $"")
call assert_equal('foobar', $"foobar")
#" 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.
call assert_equal('hello from ' .. v:version, $"hello from {v:version}")
call assert_equal('hello from ' .. v:version, $'hello from {v:version}')
#" Paper over a small difference between VimScript behaviour.
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}")
call assert_equal('sunsunsun', $"{var1->repeat(3)}")
#" 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
echo "${ LET tmp += 1 }"
endif
call assert_equal(0, tmp)
#" Stray closing brace.
call assert_fails('echo $"moo}"', 'E1278:')
#" Undefined variable in expansion.
call assert_fails('echo $"{moo}"', 'E121:')
#" Empty blocks are rejected.
call assert_fails('echo $"{}"', 'E15:')
call assert_fails('echo $"{ }"', 'E15:')
END
call CheckLegacyAndVim9Success(lines)
let lines =<< trim END
call assert_equal('5', $"{({x -> x + 1})(4)}")
END
call CheckLegacySuccess(lines)
let lines =<< trim END
call assert_equal('5', $"{((x) => x + 1)(4)}")
call assert_fails('echo $"{ # foo }"', 'E1279:')
END
call CheckDefAndScriptSuccess(lines)
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@ -1,5 +1,7 @@
" Tests for the :let command.
source vim9.vim
func Test_let()
" Test to not autoload when assigning. It causes internal error.
set runtimepath+=./sautest
@ -385,7 +387,18 @@ END
call assert_equal(['Text', 'with', 'indent'], text)
endfunc
" Test for the setting a variable using the heredoc syntax
func Test_let_interpolated()
call assert_equal('{text}', $'{{text}}')
call assert_equal('{{text}}', $'{{{{text}}}}')
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.
" Keep near the end, this messes up highlighting.
func Test_let_heredoc()
let var1 =<< END
Some sample text
@ -493,4 +506,109 @@ END
call assert_equal([' x', ' \y', ' z'], [a, b, c])
endfunc
" Test for evaluating Vim expressions in a heredoc using {expr}
" Keep near the end, this messes up highlighting.
func Test_let_heredoc_eval()
let str = ''
let code =<< trim eval END
let a = {5 + 10}
let b = {min([10, 6])} + {max([4, 6])}
{str}
let c = "abc{str}d"
END
call assert_equal(['let a = 15', 'let b = 6 + 6', '', 'let c = "abcd"'], code)
let $TESTVAR = "Hello"
let code =<< eval trim END
let s = "{$TESTVAR}"
END
call assert_equal(['let s = "Hello"'], code)
let code =<< eval END
let s = "{$TESTVAR}"
END
call assert_equal([' let s = "Hello"'], code)
let a = 10
let data =<< eval END
{a}
END
call assert_equal(['10'], data)
let x = 'X'
let code =<< eval trim END
let a = {{abc}}
let b = {x}
let c = {{
END
call assert_equal(['let a = {abc}', 'let b = X', 'let c = {'], code)
let code = 'xxx'
let code =<< eval trim END
let n = {5 +
6}
END
call assert_equal('xxx', code)
let code =<< eval trim END
let n = {min([1, 2]} + {max([3, 4])}
END
call assert_equal('xxx', code)
let lines =<< trim LINES
let text =<< eval trim END
let b = {
END
LINES
call CheckScriptFailure(lines, 'E1279:')
let lines =<< trim LINES
let text =<< eval trim END
let b = {abc
END
LINES
call CheckScriptFailure(lines, 'E1279:')
let lines =<< trim LINES
let text =<< eval trim END
let b = {}
END
LINES
call CheckScriptFailure(lines, 'E15:')
" skipped heredoc
if 0
let msg =<< trim eval END
n is: {n}
END
endif
" Test for sourcing a script containing a heredoc with invalid expression.
" Variable assignment should fail, if expression evaluation fails
new
let g:Xvar = 'test'
let g:b = 10
let lines =<< trim END
let Xvar =<< eval CODE
let a = 1
let b = {5+}
let c = 2
CODE
let g:Count += 1
END
call setline(1, lines)
let g:Count = 0
call assert_fails('source', 'E15:')
call assert_equal(1, g:Count)
call setline(3, 'let b = {abc}')
call assert_fails('source', 'E121:')
call assert_equal(2, g:Count)
call setline(3, 'let b = {abc} + {min([9, 4])} + 2')
call assert_fails('source', 'E121:')
call assert_equal(3, g:Count)
call assert_equal('test', g:Xvar)
call assert_equal(10, g:b)
bw!
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@ -35,9 +35,7 @@ func Test_getscriptinfo()
source Xscript
let l = getscriptinfo()
call assert_match('Xscript$', l[-1].name)
" Nvim does not support interpolated strings yet.
" call assert_equal(g:loaded_script_id, $"<SNR>{l[-1].sid}_")
call assert_equal(g:loaded_script_id, '<SNR>' . l[-1].sid . '_')
call assert_equal(g:loaded_script_id, $"<SNR>{l[-1].sid}_")
call delete('Xscript')
endfunc