Skip to content

Commit

Permalink
patch 8.2.2204: Vim9: using -> both for method and lambda is confusing
Browse files Browse the repository at this point in the history
Problem:    Vim9: using -> both for method and lambda is confusing.
Solution:   Use => for lambda in :def function.
  • Loading branch information
brammool committed Dec 24, 2020
1 parent b34f337 commit 65c4415
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 44 deletions.
46 changes: 36 additions & 10 deletions runtime/doc/vim9.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -340,6 +340,40 @@ When using `function()` the resulting type is "func", a function with any
number of arguments and any return type. The function can be defined later.


Lamba using => instead of -> ~

This comment has been minimized.

Copy link
@lacygoill

lacygoill Dec 24, 2020

Typo in runtime/doc/vim9.txt on line 343:

Lamba using => instead of -> ~
   ^
   ✘

Lambda instead of Lamba.


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 comment has been minimized.

Copy link
@lacygoill

lacygoill Dec 24, 2020

Typo in runtime/doc/vim9.txt on line 354:

No line break is allowed in the arguments of a lambda up to and includeing the
                                                                      ^
                                                                      ✘

including instead of includeing.

"=>". 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

This comment has been minimized.

Copy link
@lacygoill

lacygoill Dec 24, 2020

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}

I wonder how JavaScript handles the ambiguity between a series of statements in curly brackets on the rhs of an arrow function, and a dictionary in the same location.

It seems they have other syntaxes, including one where the expression is surrounded by parentheses.

Maybe Vim could parse curly brackets as a dictionary when surrounded by parentheses; otherwise as a series of Ex commands. For example, here, the rhs would be parsed as a series of Ex commands:

vim9script
var Lambda = (arg) => {key: 42}

Since :key is not a valid Ex command, it would raise an error.
And here, the rhs would be parsed as a dictionary:

vim9script
var Lambda = (arg) => ({key: 42})

IOW, maybe it would be more predictable for the users if Vim decided how to parse the rhs of a lambda based on the presence or absence of extra parentheses, which are easy to spot, rather than whitespace which is easy to miss.

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
Expand Down Expand Up @@ -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})
Expand Down Expand Up @@ -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 ~

Expand Down
94 changes: 94 additions & 0 deletions src/testdir/test_vim9_expr.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
66 changes: 54 additions & 12 deletions src/userfunc.c
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ one_function_arg(

/*
* Get function arguments.
* "argp" is advanced just after "endchar".
*/
int
get_function_args(
Expand Down Expand Up @@ -457,8 +458,32 @@ register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state)
}
#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}.
*/
Expand All @@ -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.
Expand All @@ -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)
Expand All @@ -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)
{
Expand Down
2 changes: 2 additions & 0 deletions src/version.c
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
/**/
2204,
/**/
2203,
/**/
Expand Down

0 comments on commit 65c4415

Please sign in to comment.