Merge #10086 'vim-patch:8.1.{902,1114}'

This commit is contained in:
Justin M. Keyes 2019-06-02 23:32:28 +02:00
commit 8a33cb32ba
5 changed files with 208 additions and 59 deletions

View File

@ -610,10 +610,10 @@ Expression syntax summary, from least to most significant:
expr2 ? expr1 : expr1 if-then-else expr2 ? expr1 : expr1 if-then-else
|expr2| expr3 |expr2| expr3
expr3 || expr3 .. logical OR expr3 || expr3 ... logical OR
|expr3| expr4 |expr3| expr4
expr4 && expr4 .. logical AND expr4 && expr4 ... logical AND
|expr4| expr5 |expr4| expr5
expr5 == expr5 equal expr5 == expr5 equal
@ -634,14 +634,15 @@ Expression syntax summary, from least to most significant:
expr5 isnot expr5 different |List| instance expr5 isnot expr5 different |List| instance
|expr5| expr6 |expr5| expr6
expr6 + expr6 .. number addition or list concatenation expr6 + expr6 ... number addition, list or blob concatenation
expr6 - expr6 .. number subtraction expr6 - expr6 ... number subtraction
expr6 . expr6 .. string concatenation expr6 . expr6 ... string concatenation
expr6 .. expr6 ... string concatenation
|expr6| expr7 |expr6| expr7
expr7 * expr7 .. number multiplication expr7 * expr7 ... number multiplication
expr7 / expr7 .. number division expr7 / expr7 ... number division
expr7 % expr7 .. number modulo expr7 % expr7 ... number modulo
|expr7| expr8 |expr7| expr8
! expr7 logical NOT ! expr7 logical NOT
@ -670,7 +671,7 @@ Expression syntax summary, from least to most significant:
{args -> expr1} lambda expression {args -> expr1} lambda expression
".." indicates that the operations in this level can be concatenated. "..." indicates that the operations in this level can be concatenated.
Example: > Example: >
&nu || &list && &shell == "csh" &nu || &list && &shell == "csh"
@ -707,7 +708,9 @@ use in a variable such as "a:1".
expr2 and expr3 *expr2* *expr3* expr2 and expr3 *expr2* *expr3*
--------------- ---------------
*expr-barbar* *expr-&&* expr3 || expr3 .. logical OR *expr-barbar*
expr4 && expr4 .. logical AND *expr-&&*
The "||" and "&&" operators take one argument on each side. The arguments The "||" and "&&" operators take one argument on each side. The arguments
are (converted to) Numbers. The result is: are (converted to) Numbers. The result is:
@ -847,18 +850,22 @@ can be matched like an ordinary character. Examples:
expr5 and expr6 *expr5* *expr6* expr5 and expr6 *expr5* *expr6*
--------------- ---------------
expr6 + expr6 .. Number addition or |List| concatenation *expr-+* expr6 + expr6 Number addition, |List| or |Blob| concatenation *expr-+*
expr6 - expr6 .. Number subtraction *expr--* expr6 - expr6 Number subtraction *expr--*
expr6 . expr6 .. String concatenation *expr-.* expr6 . expr6 String concatenation *expr-.*
expr6 .. expr6 String concatenation *expr-..*
For |Lists| only "+" is possible and then both expr6 must be a list. The For |Lists| only "+" is possible and then both expr6 must be a list. The
result is a new list with the two lists Concatenated. result is a new list with the two lists Concatenated.
expr7 * expr7 .. Number multiplication *expr-star* For String concatenation ".." is preferred, since "." is ambiguous, it is also
expr7 / expr7 .. Number division *expr-/* used for |Dict| member access and floating point numbers.
expr7 % expr7 .. Number modulo *expr-%*
For all, except ".", Strings are converted to Numbers. expr7 * expr7 Number multiplication *expr-star*
expr7 / expr7 Number division *expr-/*
expr7 % expr7 Number modulo *expr-%*
For all, except "." and "..", Strings are converted to Numbers.
For bitwise operators see |and()|, |or()| and |xor()|. For bitwise operators see |and()|, |or()| and |xor()|.
Note the difference between "+" and ".": Note the difference between "+" and ".":
@ -1049,11 +1056,6 @@ These are INVALID:
3. empty {M} 3. empty {M}
1e40 missing .{M} 1e40 missing .{M}
*float-pi* *float-e*
A few useful values to copy&paste: >
:let pi = 3.14159265359
:let e = 2.71828182846
Rationale: Rationale:
Before floating point was introduced, the text "123.456" was interpreted as Before floating point was introduced, the text "123.456" was interpreted as
the two numbers "123" and "456", both converted to a string and concatenated, the two numbers "123" and "456", both converted to a string and concatenated,
@ -1062,6 +1064,15 @@ could not find it intentionally being used in Vim scripts, this backwards
incompatibility was accepted in favor of being able to use the normal notation incompatibility was accepted in favor of being able to use the normal notation
for floating point numbers. for floating point numbers.
*float-pi* *float-e*
A few useful values to copy&paste: >
:let pi = 3.14159265359
:let e = 2.71828182846
Or, if you don't want to write them in as floating-point literals, you can
also use functions, like the following: >
:let pi = acos(-1.0)
:let e = exp(1.0)
<
*floating-point-precision* *floating-point-precision*
The precision and range of floating points numbers depends on what "double" The precision and range of floating points numbers depends on what "double"
means in the library Vim was compiled with. There is no way to change this at means in the library Vim was compiled with. There is no way to change this at
@ -1101,8 +1112,10 @@ A string constant accepts these special characters:
\\ backslash \\ backslash
\" double quote \" double quote
\<xxx> Special key named "xxx". e.g. "\<C-W>" for CTRL-W. This is for use \<xxx> Special key named "xxx". e.g. "\<C-W>" for CTRL-W. This is for use
in mappings, the 0x80 byte is escaped. Don't use <Char-xxxx> to get a in mappings, the 0x80 byte is escaped.
utf-8 character, use \uxxxx as mentioned above. To use the double quote character it must be escaped: "<M-\">".
Don't use <Char-xxxx> to get a utf-8 character, use \uxxxx as
mentioned above.
Note that "\xff" is stored as the byte 255, which may be invalid in some Note that "\xff" is stored as the byte 255, which may be invalid in some
encodings. Use "\u00ff" to store character 255 correctly as UTF-8. encodings. Use "\u00ff" to store character 255 correctly as UTF-8.
@ -1211,8 +1224,8 @@ The arguments are optional. Example: >
*closure* *closure*
Lambda expressions can access outer scope variables and arguments. This is Lambda expressions can access outer scope variables and arguments. This is
often called a closure. Example where "i" and "a:arg" are used in a lambda often called a closure. Example where "i" and "a:arg" are used in a lambda
while they exist in the function scope. They remain valid even after the while they already exist in the function scope. They remain valid even after
function returns: > the function returns: >
:function Foo(arg) :function Foo(arg)
: let i = 3 : let i = 3
: return {x -> x + i - a:arg} : return {x -> x + i - a:arg}
@ -1220,8 +1233,11 @@ function returns: >
:let Bar = Foo(4) :let Bar = Foo(4)
:echo Bar(6) :echo Bar(6)
< 5 < 5
See also |:func-closure|. Lambda and closure support can be checked with: > Note that the variables must exist in the outer scope before the lamba is
if has('lambda') defined for this to work. See also |:func-closure|.
Lambda and closure support can be checked with: >
if has('lambda')
Examples for using a lambda expression with |sort()|, |map()| and |filter()|: > Examples for using a lambda expression with |sort()|, |map()| and |filter()|: >
:echo map([1, 2, 3], {idx, val -> val + 1}) :echo map([1, 2, 3], {idx, val -> val + 1})
@ -9459,9 +9475,13 @@ This does NOT work: >
When the selected range of items is partly past the When the selected range of items is partly past the
end of the list, items will be added. end of the list, items will be added.
*:let+=* *:let-=* *:let.=* *E734* *:let+=* *:let-=* *:letstar=*
*:let/=* *:let%=* *:let.=* *E734*
:let {var} += {expr1} Like ":let {var} = {var} + {expr1}". :let {var} += {expr1} Like ":let {var} = {var} + {expr1}".
:let {var} -= {expr1} Like ":let {var} = {var} - {expr1}". :let {var} -= {expr1} Like ":let {var} = {var} - {expr1}".
:let {var} *= {expr1} Like ":let {var} = {var} * {expr1}".
:let {var} /= {expr1} Like ":let {var} = {var} / {expr1}".
:let {var} %= {expr1} Like ":let {var} = {var} % {expr1}".
:let {var} .= {expr1} Like ":let {var} = {var} . {expr1}". :let {var} .= {expr1} Like ":let {var} = {var} . {expr1}".
These fail if {var} was not set yet and when the type These fail if {var} was not set yet and when the type
of {var} and {expr1} don't fit the operator. of {var} and {expr1} don't fit the operator.

View File

@ -1442,7 +1442,11 @@ int eval_foldexpr(char_u *arg, int *cp)
* ":let var = expr" assignment command. * ":let var = expr" assignment command.
* ":let var += expr" assignment command. * ":let var += expr" assignment command.
* ":let var -= expr" assignment command. * ":let var -= expr" assignment command.
* ":let var *= expr" assignment command.
* ":let var /= expr" assignment command.
* ":let var %= expr" assignment command.
* ":let var .= expr" assignment command. * ":let var .= expr" assignment command.
* ":let var ..= expr" assignment command.
* ":let [var1, var2] = expr" unpack list. * ":let [var1, var2] = expr" unpack list.
*/ */
void ex_let(exarg_T *eap) void ex_let(exarg_T *eap)
@ -1465,8 +1469,8 @@ void ex_let(exarg_T *eap)
argend--; argend--;
} }
expr = skipwhite(argend); expr = skipwhite(argend);
if (*expr != '=' && !(vim_strchr((char_u *)"+-.", *expr) != NULL if (*expr != '=' && !((vim_strchr((char_u *)"+-*/%.", *expr) != NULL
&& expr[1] == '=')) { && expr[1] == '=') || STRNCMP(expr, "..=", 3) == 0)) {
// ":let" without "=": list variables // ":let" without "=": list variables
if (*arg == '[') { if (*arg == '[') {
EMSG(_(e_invarg)); EMSG(_(e_invarg));
@ -1488,8 +1492,11 @@ void ex_let(exarg_T *eap)
op[0] = '='; op[0] = '=';
op[1] = NUL; op[1] = NUL;
if (*expr != '=') { if (*expr != '=') {
if (vim_strchr((char_u *)"+-.", *expr) != NULL) { if (vim_strchr((char_u *)"+-*/%.", *expr) != NULL) {
op[0] = *expr; // +=, -=, .= op[0] = *expr; // +=, -=, *=, /=, %= or .=
if (expr[0] == '.' && expr[1] == '.') { // ..=
expr++;
}
} }
expr = skipwhite(expr + 2); expr = skipwhite(expr + 2);
} else { } else {
@ -1864,7 +1871,7 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
if (len == 0) { if (len == 0) {
EMSG2(_(e_invarg2), name - 1); EMSG2(_(e_invarg2), name - 1);
} else { } else {
if (op != NULL && (*op == '+' || *op == '-')) { if (op != NULL && vim_strchr((char_u *)"+-*/%", *op) != NULL) {
EMSG2(_(e_letwrong), op); EMSG2(_(e_letwrong), op);
} else if (endchars != NULL } else if (endchars != NULL
&& vim_strchr(endchars, *skipwhite(arg)) == NULL) { && vim_strchr(endchars, *skipwhite(arg)) == NULL) {
@ -1927,10 +1934,12 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
s = NULL; // don't set the value s = NULL; // don't set the value
} else { } else {
if (opt_type == 1) { // number if (opt_type == 1) { // number
if (*op == '+') { switch (*op) {
n = numval + n; case '+': n = numval + n; break;
} else { case '-': n = numval - n; break;
n = numval - n; case '*': n = numval * n; break;
case '/': n = numval / n; break;
case '%': n = numval % n; break;
} }
} else if (opt_type == 0 && stringval != NULL) { // string } else if (opt_type == 0 && stringval != NULL) { // string
char *const oldstringval = stringval; char *const oldstringval = stringval;
@ -1951,7 +1960,7 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
// ":let @r = expr": Set register contents. // ":let @r = expr": Set register contents.
} else if (*arg == '@') { } else if (*arg == '@') {
arg++; arg++;
if (op != NULL && (*op == '+' || *op == '-')) { if (op != NULL && vim_strchr((char_u *)"+-*/%", *op) != NULL) {
emsgf(_(e_letwrong), op); emsgf(_(e_letwrong), op);
} else if (endchars != NULL } else if (endchars != NULL
&& vim_strchr(endchars, *skipwhite(arg + 1)) == NULL) { && vim_strchr(endchars, *skipwhite(arg + 1)) == NULL) {
@ -2350,7 +2359,8 @@ static void clear_lval(lval_T *lp)
/* /*
* Set a variable that was parsed by get_lval() to "rettv". * Set a variable that was parsed by get_lval() to "rettv".
* "endp" points to just after the parsed name. * "endp" points to just after the parsed name.
* "op" is NULL, "+" for "+=", "-" for "-=", "." for ".=" or "=" for "=". * "op" is NULL, "+" for "+=", "-" for "-=", "*" for "*=", "/" for "/=",
* "%" for "%=", "." for ".=" or "=" for "=".
*/ */
static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
int copy, const char_u *op) int copy, const char_u *op)
@ -2365,7 +2375,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
if (op != NULL && *op != '=') { if (op != NULL && *op != '=') {
typval_T tv; typval_T tv;
// handle +=, -= and .= // handle +=, -=, *=, /=, %= and .=
di = NULL; di = NULL;
if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name), if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name),
&tv, &di, true, false) == OK) { &tv, &di, true, false) == OK) {
@ -3783,6 +3793,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
* + number addition * + number addition
* - number subtraction * - number subtraction
* . string concatenation * . string concatenation
* .. string concatenation
* *
* "arg" must point to the first non-white of the expression. * "arg" must point to the first non-white of the expression.
* "arg" is advanced to the next non-white after the recognized expression. * "arg" is advanced to the next non-white after the recognized expression.
@ -3830,6 +3841,9 @@ static int eval5(char_u **arg, typval_T *rettv, int evaluate)
/* /*
* Get the second variable. * Get the second variable.
*/ */
if (op == '.' && *(*arg + 1) == '.') { // ..string concatenation
(*arg)++;
}
*arg = skipwhite(*arg + 1); *arg = skipwhite(*arg + 1);
if (eval6(arg, &var2, evaluate, op == '.') == FAIL) { if (eval6(arg, &var2, evaluate, op == '.') == FAIL) {
tv_clear(rettv); tv_clear(rettv);

View File

@ -16,7 +16,7 @@ static char *e_letwrong = N_("E734: Wrong variable type for %s=");
char *e_listidx = N_("E684: list index out of range: %" PRId64); char *e_listidx = N_("E684: list index out of range: %" PRId64);
/// Hanle tv1 += tv2, -=, .= /// Hanle tv1 += tv2, -=, *=, /=, %=, .=
/// ///
/// @param[in,out] tv1 First operand, modified typval. /// @param[in,out] tv1 First operand, modified typval.
/// @param[in] tv2 Second operand. /// @param[in] tv2 Second operand.
@ -51,25 +51,31 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2,
if (tv2->v_type == VAR_LIST) { if (tv2->v_type == VAR_LIST) {
break; break;
} }
if (*op == '+' || *op == '-') { if (vim_strchr((char_u *)"+-*/%", *op) != NULL) {
// nr += nr or nr -= nr // nr += nr or nr -= nr, nr *= nr, nr /= nr, nr %= nr
varnumber_T n = tv_get_number(tv1); varnumber_T n = tv_get_number(tv1);
if (tv2->v_type == VAR_FLOAT) { if (tv2->v_type == VAR_FLOAT) {
float_T f = (float_T)n; float_T f = (float_T)n;
if (*op == '+') { if (*op == '%') {
f += tv2->vval.v_float; break;
} else { }
f -= tv2->vval.v_float; switch (*op) {
case '+': f += tv2->vval.v_float; break;
case '-': f -= tv2->vval.v_float; break;
case '*': f *= tv2->vval.v_float; break;
case '/': f /= tv2->vval.v_float; break;
} }
tv_clear(tv1); tv_clear(tv1);
tv1->v_type = VAR_FLOAT; tv1->v_type = VAR_FLOAT;
tv1->vval.v_float = f; tv1->vval.v_float = f;
} else { } else {
if (*op == '+') { switch (*op) {
n += tv_get_number(tv2); case '+': n += tv_get_number(tv2); break;
} else { case '-': n -= tv_get_number(tv2); break;
n -= tv_get_number(tv2); case '*': n *= tv_get_number(tv2); break;
case '/': n /= tv_get_number(tv2); break;
case '%': n %= tv_get_number(tv2); break;
} }
tv_clear(tv1); tv_clear(tv1);
tv1->v_type = VAR_NUMBER; tv1->v_type = VAR_NUMBER;
@ -92,18 +98,20 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2,
return OK; return OK;
} }
case VAR_FLOAT: { case VAR_FLOAT: {
if (*op == '.' || (tv2->v_type != VAR_FLOAT if (*op == '%' || *op == '.'
&& tv2->v_type != VAR_NUMBER || (tv2->v_type != VAR_FLOAT
&& tv2->v_type != VAR_STRING)) { && tv2->v_type != VAR_NUMBER
&& tv2->v_type != VAR_STRING)) {
break; break;
} }
const float_T f = (tv2->v_type == VAR_FLOAT const float_T f = (tv2->v_type == VAR_FLOAT
? tv2->vval.v_float ? tv2->vval.v_float
: (float_T)tv_get_number(tv2)); : (float_T)tv_get_number(tv2));
if (*op == '+') { switch (*op) {
tv1->vval.v_float += f; case '+': tv1->vval.v_float += f; break;
} else { case '-': tv1->vval.v_float -= f; break;
tv1->vval.v_float -= f; case '*': tv1->vval.v_float *= f; break;
case '/': tv1->vval.v_float /= f; break;
} }
return OK; return OK;
} }

View File

@ -49,3 +49,32 @@ func Test_line_continuation()
"\ and some more "\ and some more
call assert_equal([5, 6], array) call assert_equal([5, 6], array)
endfunc endfunc
func Test_string_concatenation()
call assert_equal('ab', 'a'.'b')
call assert_equal('ab', 'a' .'b')
call assert_equal('ab', 'a'. 'b')
call assert_equal('ab', 'a' . 'b')
call assert_equal('ab', 'a'..'b')
call assert_equal('ab', 'a' ..'b')
call assert_equal('ab', 'a'.. 'b')
call assert_equal('ab', 'a' .. 'b')
let a = 'a'
let b = 'b'
let a .= b
call assert_equal('ab', a)
let a = 'a'
let a.=b
call assert_equal('ab', a)
let a = 'a'
let a ..= b
call assert_equal('ab', a)
let a = 'a'
let a..=b
call assert_equal('ab', a)
endfunc

View File

@ -1294,6 +1294,84 @@ func Test_script_local_func()
enew! | close enew! | close
endfunc endfunc
func Test_compound_assignment_operators()
" Test for number
let x = 1
let x += 10
call assert_equal(11, x)
let x -= 5
call assert_equal(6, x)
let x *= 4
call assert_equal(24, x)
let x /= 3
call assert_equal(8, x)
let x %= 3
call assert_equal(2, x)
let x .= 'n'
call assert_equal('2n', x)
" Test for string
let x = 'str'
let x .= 'ing'
call assert_equal('string', x)
let x += 1
call assert_equal(1, x)
let x -= 1.5
call assert_equal(-0.5, x)
if has('float')
" Test for float
let x = 0.5
let x += 4.5
call assert_equal(5.0, x)
let x -= 1.5
call assert_equal(3.5, x)
let x *= 3.0
call assert_equal(10.5, x)
let x /= 2.5
call assert_equal(4.2, x)
call assert_fails('let x %= 0.5', 'E734')
call assert_fails('let x .= "f"', 'E734')
endif
" Test for environment variable
let $FOO = 1
call assert_fails('let $FOO += 1', 'E734')
call assert_fails('let $FOO -= 1', 'E734')
call assert_fails('let $FOO *= 1', 'E734')
call assert_fails('let $FOO /= 1', 'E734')
call assert_fails('let $FOO %= 1', 'E734')
let $FOO .= 's'
call assert_equal('1s', $FOO)
unlet $FOO
" Test for option variable (type: number)
let &scrolljump = 1
let &scrolljump += 5
call assert_equal(6, &scrolljump)
let &scrolljump -= 2
call assert_equal(4, &scrolljump)
let &scrolljump *= 3
call assert_equal(12, &scrolljump)
let &scrolljump /= 2
call assert_equal(6, &scrolljump)
let &scrolljump %= 5
call assert_equal(1, &scrolljump)
call assert_fails('let &scrolljump .= "j"', 'E734')
set scrolljump&vim
" Test for register
let @/ = 1
call assert_fails('let @/ += 1', 'E734')
call assert_fails('let @/ -= 1', 'E734')
call assert_fails('let @/ *= 1', 'E734')
call assert_fails('let @/ /= 1', 'E734')
call assert_fails('let @/ %= 1', 'E734')
let @/ .= 's'
call assert_equal('1s', @/)
let @/ = ''
endfunc
"------------------------------------------------------------------------------- "-------------------------------------------------------------------------------
" Modelines {{{1 " Modelines {{{1
" vim: ts=8 sw=4 tw=80 fdm=marker " vim: ts=8 sw=4 tw=80 fdm=marker