summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2020-12-24 15:14:01 +0100
committerBram Moolenaar <Bram@vim.org>2020-12-24 15:14:01 +0100
commit65c4415276394c871c7a8711c7633c19ec9235b1 (patch)
tree6c72bac928d4993ac4940114ba089f4c08357342
parentb34f33747223d9cba4b32a27aee70c1705b36ed9 (diff)
patch 8.2.2204: Vim9: using -> both for method and lambda is confusingv8.2.2204
Problem: Vim9: using -> both for method and lambda is confusing. Solution: Use => for lambda in :def function.
-rw-r--r--runtime/doc/vim9.txt46
-rw-r--r--src/testdir/test_vim9_expr.vim94
-rw-r--r--src/userfunc.c66
-rw-r--r--src/version.c2
-rw-r--r--src/vim9compile.c82
5 files changed, 246 insertions, 44 deletions
diff --git a/runtime/doc/vim9.txt b/runtime/doc/vim9.txt
index 9458ad9d03..d68d936e5f 100644
--- 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 type is "func", a function with any
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
index 31c8827801..f79c1f6ff5 100644
--- 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
index ff19afd40b..4da1dc0386 100644
--- 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 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}.
*/
@@ -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
index e1e78568b5..df91534a84 100644
--- 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
index 7d653a7bd6..18bf68b0c0 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -2967,12 +2967,12 @@ compile_lambda(char_u **arg, cctx_T *cctx)
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);
+ case '(': {
+ char_u *start = skipwhite(*arg + 1);
+ char_u *after = start;
+ garray_T ga_arg;
- // 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;
+ // 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;