From 3ad8c08acc506555667a070cf83c410ac9334f1e Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 14 Apr 2023 19:45:54 +0800 Subject: vim-patch:8.2.4770: cannot easily mix expression and heredoc Problem: Cannot easily mix expression and heredoc. Solution: Support in heredoc. (Yegappan Lakshmanan, closes vim/vim#10138) https://github.com/vim/vim/commit/efbfa867a146fcd93fdec2435597aa4ae7f1325c Co-authored-by: Yegappan Lakshmanan --- src/nvim/eval/userfunc.c | 17 +++++-- src/nvim/eval/vars.c | 119 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 117 insertions(+), 19 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 67c73924c8..67b1e53a35 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -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; diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index b8a8f39437..3905cf82a6 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -53,6 +53,56 @@ 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. +static char *eval_all_expr_in_str(char *str) +{ + garray_T ga; + ga_init(&ga, 1, 80); + char *p = str; + + // Look for `=expr`, evaluate the expression and replace `=expr` with the + // result. + while (*p != NUL) { + char *s = p; + while (*p != NUL && (*p != '`' || p[1] != '=')) { + p++; + } + ga_concat_len(&ga, s, (size_t)(p - s)); + if (*p == NUL) { + break; // no backtick expression found + } + s = p; + p += 2; // skip `= + + int status = *p == NUL ? OK : skip_expr(&p, NULL); + if (status == FAIL || *p != '`') { + // invalid expression or missing ending backtick + if (status != FAIL) { + emsg(_("E1083: Missing backtick")); + } + xfree(ga.ga_data); + return NULL; + } + s += 2; // skip `= + char save_c = *p; + *p = NUL; + char *exprval = eval_to_string(s, true); + *p = save_c; + p++; + if (exprval == NULL) { + // expression evaluation failed + xfree(ga.ga_data); + return NULL; + } + ga_concat(&ga, exprval); + xfree(exprval); + } + 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 +115,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 +131,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); - - // 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++; + 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++; + } + text_indent_len = -1; + + continue; + } + if (strncmp(cmd, "eval", 4) == 0 + && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) { + cmd = skipwhite(cmd + 4); + evalstr = true; + continue; } - text_indent_len = -1; + break; } // The marker is the next word. @@ -136,6 +199,14 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd) xfree(theline); break; } + + // If expression evaluation failed in the heredoc, then skip till the + // end marker. + if (eval_failed) { + xfree(theline); + continue; + } + if (text_indent_len == -1 && *theline != NUL) { // set the text indent from the first line. p = theline; @@ -155,11 +226,29 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd) } } - tv_list_append_string(l, theline + ti, -1); + char *str = theline + ti; + if (evalstr) { + str = eval_all_expr_in_str(str); + if (str == NULL) { + // expression evaluation failed + xfree(theline); + 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; } -- cgit From 3c16e75ae194f728c703032084a8f6dd0833a563 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 14 Apr 2023 21:00:08 +0800 Subject: vim-patch:8.2.4783: Coverity warns for leaking memory Problem: Coverity warns for leaking memory. Solution: Use another strategy freeing "theline". https://github.com/vim/vim/commit/42ccb8d74700506936567b0eb6d11def5e25e1dd Co-authored-by: Bram Moolenaar --- src/nvim/eval/vars.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 3905cf82a6..048b5ee2aa 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -178,12 +178,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; @@ -196,14 +198,12 @@ 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) { - xfree(theline); continue; } @@ -231,7 +231,6 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd) str = eval_all_expr_in_str(str); if (str == NULL) { // expression evaluation failed - xfree(theline); eval_failed = true; continue; } @@ -240,8 +239,8 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd) } tv_list_append_string(l, str, -1); - xfree(theline); } + xfree(theline); xfree(text_indent); if (eval_failed) { -- cgit From 2cf8f01e7d0469b592bacecd5f224b4fe3149a62 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 14 Apr 2023 21:06:15 +0800 Subject: vim-patch:8.2.4840: heredoc expression evaluated even when skipping Problem: Heredoc expression evaluated even when skipping. Solution: Don't evaluate when "skip" is set. (closes vim/vim#10306) https://github.com/vim/vim/commit/05c7f5d3d03440da6f69604f8c06c4e3d90d2a26 Co-authored-by: Bram Moolenaar --- src/nvim/eval/vars.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 048b5ee2aa..a8d1e01152 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -227,7 +227,7 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd) } char *str = theline + ti; - if (evalstr) { + if (evalstr && !eap->skip) { str = eval_all_expr_in_str(str); if (str == NULL) { // expression evaluation failed -- cgit From bacb5021d4eff33c67eb659fb01125b2abcacd79 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 14 Apr 2023 21:08:00 +0800 Subject: vim-patch:8.2.4883: string interpolation only works in heredoc Problem: String interpolation only works in heredoc. Solution: Support interpolated strings. Use syntax for heredoc consistent with strings, similar to C#. (closes vim/vim#10327) https://github.com/vim/vim/commit/2eaef106e4a7fc9dc74a7e672b5f550ec1f9786e Cherry-pick Test_Debugger_breakadd_expr() from Vim. Co-authored-by: LemonBoy --- src/nvim/eval/vars.c | 81 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 29 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index a8d1e01152..b86c49fd98 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -53,50 +53,73 @@ 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 +/// Evaluate all the Vim expressions ({expr}) in string "str" and return the /// resulting string. The caller must free the returned string. -static char *eval_all_expr_in_str(char *str) +char *eval_all_expr_in_str(char *str) { garray_T ga; ga_init(&ga, 1, 80); char *p = str; - // Look for `=expr`, evaluate the expression and replace `=expr` with the - // result. while (*p != NUL) { - char *s = p; - while (*p != NUL && (*p != '`' || p[1] != '=')) { - p++; + 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; } - ga_concat_len(&ga, s, (size_t)(p - s)); + + // Append the literal part. + ga_concat_len(&ga, lit_start, (size_t)(p - lit_start)); + if (*p == NUL) { - break; // no backtick expression found + break; } - s = p; - p += 2; // skip `= - int status = *p == NUL ? OK : skip_expr(&p, NULL); - if (status == FAIL || *p != '`') { - // invalid expression or missing ending backtick - if (status != FAIL) { - emsg(_("E1083: Missing backtick")); - } - xfree(ga.ga_data); + if (escaped_brace) { + // Skip the second brace. + ++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; } - s += 2; // skip `= - char save_c = *p; - *p = NUL; - char *exprval = eval_to_string(s, true); - *p = save_c; - p++; - if (exprval == NULL) { - // expression evaluation failed - xfree(ga.ga_data); + 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; } - ga_concat(&ga, exprval); - xfree(exprval); + 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); -- cgit From ef9af89da753235c64cbd8b7d700c686bc94dad7 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 15 Apr 2023 17:51:39 +0800 Subject: 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 --- src/nvim/eval/vars.c | 70 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 28 deletions(-) (limited to 'src/nvim/eval') 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); -- cgit From 29efd54e0284727a7dde5608e5eedaef9c00c65f Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 15 Apr 2023 18:19:47 +0800 Subject: vim-patch:8.2.4934: string interpolation fails when not evaluating Problem: String interpolation fails when not evaluating. Solution: Skip the expression when not evaluating. (closes vim/vim#10398) https://github.com/vim/vim/commit/70c41241c2701f26a99085e433925a206ca265a3 Co-authored-by: Bram Moolenaar --- src/nvim/eval/vars.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) (limited to 'src/nvim/eval') diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 7ae7b5a57a..d3ca2624eb 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -55,8 +55,9 @@ 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) +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; @@ -73,14 +74,16 @@ char *eval_one_expr_in_str(char *p, garray_T *gap) 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; + 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); } - ga_concat(gap, expr_val); - xfree(expr_val); return block_end + 1; } @@ -129,7 +132,7 @@ char *eval_all_expr_in_str(char *str) } // Evaluate the expression and append the result. - p = eval_one_expr_in_str(p, &ga); + p = eval_one_expr_in_str(p, &ga, true); if (p == NULL) { ga_clear(&ga); return NULL; -- cgit