]> granicus.if.org Git - vim/commitdiff
patch 8.2.2204: Vim9: using -> both for method and lambda is confusing v8.2.2204
authorBram Moolenaar <Bram@vim.org>
Thu, 24 Dec 2020 14:14:01 +0000 (15:14 +0100)
committerBram Moolenaar <Bram@vim.org>
Thu, 24 Dec 2020 14:14:01 +0000 (15:14 +0100)
Problem:    Vim9: using -> both for method and lambda is confusing.
Solution:   Use => for lambda in :def function.

runtime/doc/vim9.txt
src/testdir/test_vim9_expr.vim
src/userfunc.c
src/version.c
src/vim9compile.c

index 9458ad9d035ceab5339db3dee8eba3aa8041f50b..d68d936e5ff38400eddb27573e94aff44e565d89 100644 (file)
@@ -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 ~
 
index 31c8827801fd2fae233774902421b57eadb1f29b..f79c1f6ff5004550a04bcc0029eaec525fdf32a6 100644 (file)
@@ -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
index ff19afd40b3ff55d4907d45c5a5821476dd7233b..4da1dc03863bdfba85fa79e14c0b9c2dba900343 100644 (file)
@@ -154,6 +154,7 @@ one_function_arg(
 
 /*
  * Get function arguments.
+ * "argp" is advanced just after "endchar".
  */
     int
 get_function_args(
@@ -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}.
  */
@@ -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)
     {
index e1e78568b5d0e4f04bcb9b83b4e95e5be11529ea..df91534a8416c554dee717a85f1598e5677b21fa 100644 (file)
@@ -750,6 +750,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    2204,
 /**/
     2203,
 /**/
index 7d653a7bd65f7c24bb14ffd233fd2dd91cb0b473..18bf68b0c020d32096d99b62eb02cda6e39e3b0a 100644 (file)
@@ -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;