# HG changeset patch # User Bram Moolenaar # Date 1608819305 -3600 # Node ID c76240efdf023f2f46539589c71b74a7c7249b61 # Parent 0aa1d1862cb923ad24d6b239094a5f726d671b6d patch 8.2.2204: Vim9: using -> both for method and lambda is confusing Commit: https://github.com/vim/vim/commit/65c4415276394c871c7a8711c7633c19ec9235b1 Author: Bram Moolenaar Date: Thu Dec 24 15:14:01 2020 +0100 patch 8.2.2204: Vim9: using -> both for method and lambda is confusing Problem: Vim9: using -> both for method and lambda is confusing. Solution: Use => for lambda in :def function. diff --git a/runtime/doc/vim9.txt b/runtime/doc/vim9.txt --- a/runtime/doc/vim9.txt +++ b/runtime/doc/vim9.txt @@ -1,4 +1,4 @@ -*vim9.txt* For Vim version 8.2. Last change: 2020 Dec 23 +*vim9.txt* For Vim version 8.2. Last change: 2020 Dec 24 VIM REFERENCE MANUAL by Bram Moolenaar @@ -340,6 +340,40 @@ When using `function()` the resulting ty number of arguments and any return type. The function can be defined later. +Lamba using => instead of -> ~ + +In legacy script there can be confusion between using "->" for a method call +and for a lambda. Also, when a "{" is found the parser needs to figure out if +it is the start of a lambda or a dictionary, which is now more complicated +because of the use of argument types. + +To avoid these problems Vim9 script uses a different syntax for a lambda, +which is similar to Javascript: > + var Lambda = (arg) => expression + +No line break is allowed in the arguments of a lambda up to and includeing the +"=>". This is OK: > + filter(list, (k, v) => + v > 0) +This does not work: > + filter(list, (k, v) + => v > 0) +This also does not work: + filter(list, (k, + v) => v > 0) + +Additionally, a lambda can contain statements in {}: > + var Lambda = (arg) => { + g:was_called = 'yes' + return expression + } +NOT IMPLEMENTED YET + +Note that the "{" must be followed by white space, otherwise it is assumed to +be the start of a dictionary: > + var Lambda = (arg) => {key: 42} + + Automatic line continuation ~ In many cases it is obvious that an expression continues on the next line. In @@ -405,7 +439,7 @@ arguments: > ): string Since a continuation line cannot be easily recognized the parsing of commands -has been made sticter. E.g., because of the error in the first line, the +has been made stricter. E.g., because of the error in the first line, the second line is seen as a separate command: > popup_create(some invalid expression, { exit_cb: Func}) @@ -433,14 +467,6 @@ Notes: < This does not work: > echo [1, 2] [3, 4] -- No line break is allowed in the arguments of a lambda, between the "{" and - "->". This is OK: > - filter(list, {k, v -> - v > 0}) -< This does not work: > - filter(list, {k, - v -> v > 0}) - No curly braces expansion ~ diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -1887,6 +1887,100 @@ def Test_expr7_lambda() CheckDefFailure(['var Fx = {a -> [0', ' 1]}'], 'E696:', 2) enddef +def NewLambdaWithComments(): func + return (x) => + # some comment + x == 1 + # some comment + || + x == 2 +enddef + +def NewLambdaUsingArg(x: number): func + return () => + # some comment + x == 1 + # some comment + || + x == 2 +enddef + +def Test_expr7_new_lambda() + var lines =<< trim END + var La = () => 'result' + assert_equal('result', La()) + assert_equal([1, 3, 5], [1, 2, 3]->map((key, val) => key + val)) + + # line continuation inside lambda with "cond ? expr : expr" works + var ll = range(3) + map(ll, (k, v) => v % 2 ? { + ['111']: 111 } : {} + ) + assert_equal([{}, {111: 111}, {}], ll) + + ll = range(3) + map(ll, (k, v) => v == 8 || v + == 9 + || v % 2 ? 111 : 222 + ) + assert_equal([222, 111, 222], ll) + + ll = range(3) + map(ll, (k, v) => v != 8 && v + != 9 + && v % 2 == 0 ? 111 : 222 + ) + assert_equal([111, 222, 111], ll) + + var dl = [{key: 0}, {key: 22}]->filter(( _, v) => v['key'] ) + assert_equal([{key: 22}], dl) + + dl = [{key: 12}, {['foo']: 34}] + assert_equal([{key: 12}], filter(dl, + (_, v) => has_key(v, 'key') ? v['key'] == 12 : 0)) + + assert_equal(false, NewLambdaWithComments()(0)) + assert_equal(true, NewLambdaWithComments()(1)) + assert_equal(true, NewLambdaWithComments()(2)) + assert_equal(false, NewLambdaWithComments()(3)) + + assert_equal(false, NewLambdaUsingArg(0)()) + assert_equal(true, NewLambdaUsingArg(1)()) + + var res = map([1, 2, 3], (i: number, v: number) => i + v) + assert_equal([1, 3, 5], res) + + # Lambda returning a dict + var Lmb = () => {key: 42} + assert_equal({key: 42}, Lmb()) + END + CheckDefSuccess(lines) + + CheckDefFailure(["var Ref = (a)=>a + 1"], 'E1001:') + CheckDefFailure(["var Ref = (a)=> a + 1"], 'E1001:') + CheckDefFailure(["var Ref = (a) =>a + 1"], 'E1001:') + + CheckDefFailure(["filter([1, 2], (k,v) => 1)"], 'E1069:', 1) + # error is in first line of the lambda + CheckDefFailure(["var L = (a) -> a + b"], 'E1001:', 1) + +# TODO: lambda after -> doesn't work yet +# assert_equal('xxxyyy', 'xxx'->((a, b) => a .. b)('yyy')) + +# CheckDefExecFailure(["var s = 'asdf'->{a -> a}('x')"], +# 'E1106: One argument too many') +# CheckDefExecFailure(["var s = 'asdf'->{a -> a}('x', 'y')"], +# 'E1106: 2 arguments too many') +# CheckDefFailure(["echo 'asdf'->{a -> a}(x)"], 'E1001:', 1) + + CheckDefSuccess(['var Fx = (a) => {k1: 0,', ' k2: 1}']) + CheckDefFailure(['var Fx = (a) => {k1: 0', ' k2: 1}'], 'E722:', 2) + CheckDefFailure(['var Fx = (a) => {k1: 0,', ' k2 1}'], 'E720:', 2) + + CheckDefSuccess(['var Fx = (a) => [0,', ' 1]']) + CheckDefFailure(['var Fx = (a) => [0', ' 1]'], 'E696:', 2) +enddef + def Test_expr7_lambda_vim9script() var lines =<< trim END vim9script diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -154,6 +154,7 @@ one_function_arg( /* * Get function arguments. + * "argp" is advanced just after "endchar". */ int get_function_args( @@ -458,7 +459,31 @@ register_cfunc(cfunc_T cb, cfunc_free_T #endif /* + * Skip over "->" or "=>" after the arguments of a lambda. + * Return NULL if no valid arrow found. + */ + static char_u * +skip_arrow(char_u *start, int equal_arrow) +{ + char_u *s = start; + + if (equal_arrow) + { + if (*s == ':') + s = skip_type(skipwhite(s + 1), TRUE); + s = skipwhite(s); + if (*s != '=') + return NULL; + ++s; + } + if (*s != '>') + return NULL; + return skipwhite(s + 1); +} + +/* * Parse a lambda expression and get a Funcref from "*arg". + * "arg" points to the { in "{arg -> expr}" or the ( in "(arg) => expr" * When "types_optional" is TRUE optionally take argument types. * Return OK or FAIL. Returns NOTDONE for dict or {expr}. */ @@ -484,16 +509,20 @@ get_lambda_tv( int *old_eval_lavars = eval_lavars_used; int eval_lavars = FALSE; char_u *tofree = NULL; + int equal_arrow = **arg == '('; + + if (equal_arrow && !in_vim9script()) + return NOTDONE; ga_init(&newargs); ga_init(&newlines); - // First, check if this is a lambda expression. "->" must exist. + // First, check if this is a lambda expression. "->" or "=>" must exist. s = skipwhite(*arg + 1); - ret = get_function_args(&s, '-', NULL, + ret = get_function_args(&s, equal_arrow ? ')' : '-', NULL, types_optional ? &argtypes : NULL, types_optional, NULL, NULL, TRUE, NULL, NULL); - if (ret == FAIL || *s != '>') + if (ret == FAIL || skip_arrow(s, equal_arrow) == NULL) return NOTDONE; // Parse the arguments again. @@ -502,18 +531,28 @@ get_lambda_tv( else pnewargs = NULL; *arg = skipwhite(*arg + 1); - ret = get_function_args(arg, '-', pnewargs, + ret = get_function_args(arg, equal_arrow ? ')' : '-', pnewargs, types_optional ? &argtypes : NULL, types_optional, &varargs, NULL, FALSE, NULL, NULL); - if (ret == FAIL || **arg != '>') - goto errret; + if (ret == FAIL || (*arg = skip_arrow(*arg, equal_arrow)) == NULL) + return NOTDONE; // Set up a flag for checking local variables and arguments. if (evaluate) eval_lavars_used = &eval_lavars; + *arg = skipwhite_and_linebreak(*arg, evalarg); + + // Only recognize "{" as the start of a function body when followed by + // white space, "{key: val}" is a dict. + if (equal_arrow && **arg == '{' && IS_WHITE_OR_NUL((*arg)[1])) + { + // TODO: process the function body upto the "}". + emsg("Lambda function body not supported yet"); + goto errret; + } + // Get the start and the end of the expression. - *arg = skipwhite_and_linebreak(*arg + 1, evalarg); start = *arg; ret = skip_expr_concatenate(arg, &start, &end, evalarg); if (ret == FAIL) @@ -525,13 +564,16 @@ get_lambda_tv( evalarg->eval_tofree = NULL; } - *arg = skipwhite_and_linebreak(*arg, evalarg); - if (**arg != '}') + if (!equal_arrow) { - semsg(_("E451: Expected }: %s"), *arg); - goto errret; + *arg = skipwhite_and_linebreak(*arg, evalarg); + if (**arg != '}') + { + semsg(_("E451: Expected }: %s"), *arg); + goto errret; + } + ++*arg; } - ++*arg; if (evaluate) { diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -751,6 +751,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 2204, +/**/ 2203, /**/ 2202, diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -2967,12 +2967,12 @@ compile_lambda(char_u **arg, cctx_T *cct return FAIL; } + // "rettv" will now be a partial referencing the function. ufunc = rettv.vval.v_partial->pt_func; ++ufunc->uf_refcount; clear_tv(&rettv); - // The function will have one line: "return {expr}". - // Compile it into instructions. + // Compile the function into instructions. compile_def_function(ufunc, TRUE, cctx); clear_evalarg(&evalarg, NULL); @@ -3565,6 +3565,15 @@ compile_subscript( if (**arg == '{') { // lambda call: list->{lambda} + // TODO: remove this + if (compile_lambda_call(arg, cctx) == FAIL) + return FAIL; + } + else if (**arg == '(') + { + // Funcref call: list->(Refs[2])() + // or lambda: list->((arg) => expr)() + // TODO: make this work if (compile_lambda_call(arg, cctx) == FAIL) return FAIL; } @@ -3928,6 +3937,8 @@ compile_expr7( && VIM_ISWHITE(after[-2])) || after == start + 1) && IS_WHITE_OR_NUL(after[1])) + // TODO: if we go with the "(arg) => expr" syntax + // remove this ret = compile_lambda(arg, cctx); else ret = compile_dict(arg, cctx, ppconst); @@ -3959,28 +3970,55 @@ compile_expr7( break; /* * nested expression: (expression). + * lambda: (arg, arg) => expr + * funcref: (arg, arg) => { statement } */ - case '(': *arg = skipwhite(*arg + 1); - - // recursive! - if (ppconst->pp_used <= PPSIZE - 10) - { - ret = compile_expr1(arg, cctx, ppconst); - } - else - { - // Not enough space in ppconst, flush constants. - if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - ret = compile_expr0(arg, cctx); - } - *arg = skipwhite(*arg); - if (**arg == ')') - ++*arg; - else if (ret == OK) - { - emsg(_(e_missing_close)); - ret = FAIL; + case '(': { + char_u *start = skipwhite(*arg + 1); + char_u *after = start; + garray_T ga_arg; + + // Find out if "=>" comes after the (). + ret = get_function_args(&after, ')', NULL, + &ga_arg, TRUE, NULL, NULL, + TRUE, NULL, NULL); + if (ret == OK && VIM_ISWHITE( + *after == ':' ? after[1] : *after)) + { + if (*after == ':') + // Skip over type in "(arg): type". + after = skip_type(skipwhite(after + 1), TRUE); + + after = skipwhite(after); + if (after[0] == '=' && after[1] == '>' + && IS_WHITE_OR_NUL(after[2])) + { + ret = compile_lambda(arg, cctx); + break; + } + } + + // (expression): recursive! + *arg = skipwhite(*arg + 1); + if (ppconst->pp_used <= PPSIZE - 10) + { + ret = compile_expr1(arg, cctx, ppconst); + } + else + { + // Not enough space in ppconst, flush constants. + if (generate_ppconst(cctx, ppconst) == FAIL) + return FAIL; + ret = compile_expr0(arg, cctx); + } + *arg = skipwhite(*arg); + if (**arg == ')') + ++*arg; + else if (ret == OK) + { + emsg(_(e_missing_close)); + ret = FAIL; + } } break;