aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2023-04-15 17:51:39 +0800
committerzeertzjq <zeertzjq@outlook.com>2023-04-15 18:19:17 +0800
commitef9af89da753235c64cbd8b7d700c686bc94dad7 (patch)
tree70de460c6f4c421eab66cb202aebfa2b9a166875 /src
parentbacb5021d4eff33c67eb659fb01125b2abcacd79 (diff)
downloadrneovim-ef9af89da753235c64cbd8b7d700c686bc94dad7.tar.gz
rneovim-ef9af89da753235c64cbd8b7d700c686bc94dad7.tar.bz2
rneovim-ef9af89da753235c64cbd8b7d700c686bc94dad7.zip
vim-patch:8.2.4930: interpolated string expression requires escaping
Problem: Interpolated string expression requires escaping. Solution: Do not require escaping in the expression. https://github.com/vim/vim/commit/0abc2871c105882ed1c1effb9a7757fad8a395bd Co-authored-by: Bram Moolenaar <Bram@vim.org>
Diffstat (limited to 'src')
-rw-r--r--src/nvim/eval.c144
-rw-r--r--src/nvim/eval/vars.c70
2 files changed, 150 insertions, 64 deletions
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index f8a9326703..da345e4b53 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -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]
@@ -3868,16 +3868,20 @@ 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;
+ 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
@@ -3886,17 +3890,27 @@ static int eval_string(char **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(_("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;
}
@@ -3907,7 +3921,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':
@@ -3996,11 +4010,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;
@@ -4009,79 +4029,131 @@ 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;
}
-int eval_interp_string(char **arg, typval_T *rettv, int evaluate)
+/// 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)
{
- // *arg is on the '$' character.
+ 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)++;
- rettv->v_type = VAR_STRING;
+ 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);
+ }
- typval_T tv;
- int ret;
- if (**arg == '"') {
- ret = eval_string(arg, &tv, evaluate);
- } else {
- ret = eval_lit_string(arg, &tv, evaluate);
+ if (**arg != '{') {
+ // found terminating quote
+ (*arg)++;
+ break;
+ }
+ char *p = eval_one_expr_in_str(*arg, &ga);
+ if (p == NULL) {
+ ret = FAIL;
+ break;
+ }
+ *arg = p;
}
- if (ret == FAIL || !evaluate) {
- return ret;
+ rettv->v_type = VAR_STRING;
+ if (ret != FAIL && evaluate) {
+ ga_append(&ga, NUL);
}
-
- rettv->vval.v_string = eval_all_expr_in_str(tv.vval.v_string);
-
- tv_clear(&tv);
-
- return rettv->vval.v_string != NULL ? OK : FAIL;
+ rettv->vval.v_string = ga.ga_data;
+ return OK;
}
/// @return the function name of the partial.
diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c
index b86c49fd98..7ae7b5a57a 100644
--- a/src/nvim/eval/vars.c
+++ b/src/nvim/eval/vars.c
@@ -53,8 +53,42 @@
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 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 *eval_one_expr_in_str(char *p, garray_T *gap)
+{
+ 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;
+ }
+ *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;
@@ -67,13 +101,13 @@ char *eval_all_expr_in_str(char *str)
// Look for a block start.
char *lit_start = p;
while (*p != '{' && *p != '}' && *p != NUL) {
- ++p;
+ p++;
}
if (*p != NUL && *p == p[1]) {
// Escaped brace, unescape and continue.
// Include the brace in the literal string.
- ++p;
+ p++;
escaped_brace = true;
} else if (*p == '}') {
semsg(_(e_stray_closing_curly_str), str);
@@ -90,36 +124,16 @@ char *eval_all_expr_in_str(char *str)
if (escaped_brace) {
// Skip the second brace.
- ++p;
+ p++;
continue;
}
- // Skip the opening {.
- char *block_start = ++p;
- char *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);
+ // Evaluate the expression and append the result.
+ p = eval_one_expr_in_str(p, &ga);
+ if (p == NULL) {
ga_clear(&ga);
return NULL;
}
- char save_c = *block_end;
- *block_end = NUL;
- char *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);
- xfree(expr_val);
-
- p = block_end + 1;
}
ga_append(&ga, NUL);