diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/eval.c | 160 | ||||
-rw-r--r-- | src/nvim/eval/userfunc.c | 17 | ||||
-rw-r--r-- | src/nvim/eval/vars.c | 164 | ||||
-rw-r--r-- | src/nvim/globals.h | 5 |
4 files changed, 303 insertions, 43 deletions
diff --git a/src/nvim/eval.c b/src/nvim/eval.c index b240c36977..a30f9146c5 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] @@ -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,52 +4044,130 @@ 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; } 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..d3ca2624eb 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -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); - - // 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; } - 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; } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index e406d93494..11888a5df8 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -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")); |