]> granicus.if.org Git - vim/commitdiff
patch 9.0.0632: calling a function from an "expr" option has overhead v9.0.0632
authorBram Moolenaar <Bram@vim.org>
Sat, 1 Oct 2022 14:32:46 +0000 (15:32 +0100)
committerBram Moolenaar <Bram@vim.org>
Sat, 1 Oct 2022 14:32:46 +0000 (15:32 +0100)
Problem:    Calling a function from an "expr" option has too much overhead.
Solution:   Add call_simple_func() and use it for 'foldexpr'

runtime/doc/fold.txt
runtime/doc/vim9.txt
src/eval.c
src/proto/userfunc.pro
src/testdir/test_fold.vim
src/userfunc.c
src/version.c
src/vim9execute.c

index 93efcbb66443641a2a654f996864028f610358e4..f11ca0812d37a706baa5419b2b52797480b08609 100644 (file)
@@ -74,8 +74,6 @@ method.  The value of the 'foldexpr' option is evaluated to get the foldlevel
 of a line.  Examples:
 This will create a fold for all consecutive lines that start with a tab: >
        :set foldexpr=getline(v:lnum)[0]==\"\\t\"
-This will call a function to compute the fold level: >
-       :set foldexpr=MyFoldLevel(v:lnum)
 This will make a fold out of paragraphs separated by blank lines: >
        :set foldexpr=getline(v:lnum)=~'^\\s*$'&&getline(v:lnum+1)=~'\\S'?'<1':1
 This does the same: >
@@ -84,6 +82,10 @@ This does the same: >
 Note that backslashes must be used to escape characters that ":set" handles
 differently (space, backslash, double quote, etc., see |option-backslash|).
 
+The most efficient is to call a compiled function without arguments: >
+       :set foldexpr=MyFoldLevel()
+The function must use v:lnum.  See |expr-option-function|.
+
 These are the conditions with which the expression is evaluated:
 - The current buffer and window are set for the line.
 - The variable "v:lnum" is set to the line number.
index b73011a092c34a06c5d7a62c129760f9525e54b0..15e9a702bcd74007aa3cda66e71d10f6c58bfd32 100644 (file)
@@ -1410,6 +1410,21 @@ to a Vim9 function:
                'three'
                ]
 
+
+Calling a function in an expr option ~
+                                                       *expr-option-function*
+A few options, such as 'foldexpr', are an expresison that is evaluated to get
+a value.  The evaluation can have quite a bit of overhead.  One way to
+minimize the overhead, and also to keep the option value very simple, is to
+defined a compiled function and set the option to call it without arguments.
+Example: >
+       vim9script
+       def MyFoldFunc(): any
+          ... compute fold level for line v:lnum
+          return level
+       enddef
+       set foldexpr=s:MyFoldFunc()
+
 ==============================================================================
 
 4. Types                                       *vim9-types*
index 2330bd6a10682d618b08f0863a641b3f92b8d917..aeb8fee7ad3fe2a0c6781b671b112f267a475e59 100644 (file)
@@ -899,13 +899,14 @@ eval_foldexpr(win_T *wp, int *cp)
 {
     char_u     *arg;
     typval_T   tv;
+    int                r = NOTDONE;
     varnumber_T        retval;
     char_u     *s;
     sctx_T     saved_sctx = current_sctx;
     int                use_sandbox = was_set_insecurely((char_u *)"foldexpr",
                                                                   OPT_LOCAL);
 
-    arg = wp->w_p_fde;
+    arg = skipwhite(wp->w_p_fde);
     current_sctx = wp->w_p_script_ctx[WV_FDE];
 
     ++emsg_off;
@@ -913,7 +914,21 @@ eval_foldexpr(win_T *wp, int *cp)
        ++sandbox;
     ++textlock;
     *cp = NUL;
-    if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL)
+
+    // If the expression is "FuncName()" then we can skip a lot of overhead.
+    char_u *parens = (char_u *)strstr((char *)arg, "()");
+    if (parens != NULL && *skipwhite(parens + 2) == NUL)
+    {
+       char_u *p = STRNCMP(arg, "<SNR>", 5) == 0 ? skipdigits(arg + 5) : arg;
+
+       if (to_name_end(p, TRUE) == parens)
+           r = call_simple_func(arg, (int)(parens - arg), &tv);
+    }
+
+    if (r == NOTDONE)
+       r = eval0(arg, &tv, NULL, &EVALARG_EVALUATE);
+
+    if (r == FAIL)
        retval = 0;
     else
     {
index f509523dba6410a9f76e292fdfdf8a6a2d8a75d8..ee17049ce6dad1053b39fb1303ee1c6e2d6d1289 100644 (file)
@@ -36,8 +36,9 @@ int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict
 int get_callback_depth(void);
 int call_callback(callback_T *callback, int len, typval_T *rettv, int argcount, typval_T *argvars);
 varnumber_T call_callback_retnr(callback_T *callback, int argcount, typval_T *argvars);
-void user_func_error(int error, char_u *name, funcexe_T *funcexe);
+void user_func_error(int error, char_u *name, int found_var);
 int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe);
+int call_simple_func(char_u *funcname, int len, typval_T *rettv);
 char_u *printable_func_name(ufunc_T *fp);
 char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial, type_T **type);
 char_u *get_scriptlocal_funcname(char_u *funcname);
index d15f607ae090354f92332780e3d2d995b0765ae8..9b8cd7310d01afe394a6bf18859b7b71b042a8fc 100644 (file)
@@ -249,6 +249,31 @@ func Test_foldexpr_no_interrupt_addsub()
   set foldmethod& foldexpr&
 endfunc
 
+" Fold function defined in another script
+func Test_foldexpr_compiled()
+  new
+  let lines =<< trim END
+      vim9script
+      def FoldFunc(): number
+        return v:lnum
+      enddef
+
+      set foldmethod=expr
+      set foldexpr=s:FoldFunc()
+  END
+  call writefile(lines, 'XfoldExpr', 'D')
+  source XfoldExpr
+
+  call setline(1, ['one', 'two', 'three'])
+  redraw
+  call assert_equal(1, foldlevel(1))
+  call assert_equal(2, foldlevel(2))
+  call assert_equal(3, foldlevel(3))
+
+  bwipe!
+  set foldmethod& foldexpr&
+endfunc
+
 func Check_foldlevels(expected)
   call assert_equal(a:expected, map(range(1, line('$')), 'foldlevel(v:val)'))
 endfunc
index 92b5203c1cb220ae6150ab99869a755cb5a68ae3..14d1c72beeb0e5d335a5944c335c2adb7811175e 100644 (file)
@@ -3447,12 +3447,12 @@ call_callback_retnr(
  * Nothing if "error" is FCERR_NONE.
  */
     void
-user_func_error(int error, char_u *name, funcexe_T *funcexe)
+user_func_error(int error, char_u *name, int found_var)
 {
     switch (error)
     {
        case FCERR_UNKNOWN:
-               if (funcexe->fe_found_var)
+               if (found_var)
                    emsg_funcname(e_not_callable_type_str, name);
                else
                    emsg_funcname(e_unknown_function_str, name);
@@ -3702,7 +3702,8 @@ theend:
      * cancelled due to an aborting error, an interrupt, or an exception.
      */
     if (!aborting())
-       user_func_error(error, (name != NULL) ? name : funcname, funcexe);
+       user_func_error(error, (name != NULL) ? name : funcname,
+                                                       funcexe->fe_found_var);
 
     // clear the copies made from the partial
     while (argv_clear > 0)
@@ -3714,6 +3715,77 @@ theend:
     return ret;
 }
 
+/*
+ * Call a function without arguments, partial or dict.
+ * This is like call_func() when the call is only "FuncName()".
+ * To be used by "expr" options.
+ * Returns NOTDONE when the function could not be found.
+ */
+    int
+call_simple_func(
+    char_u     *funcname,      // name of the function
+    int                len,            // length of "name" or -1 to use strlen()
+    typval_T   *rettv)         // return value goes here
+{
+    int                ret = FAIL;
+    int                error = FCERR_NONE;
+    char_u     fname_buf[FLEN_FIXED + 1];
+    char_u     *tofree = NULL;
+    char_u     *name;
+    char_u     *fname;
+    char_u     *rfname;
+    int                is_global = FALSE;
+    ufunc_T    *fp;
+
+    rettv->v_type = VAR_NUMBER;        // default rettv is number zero
+    rettv->vval.v_number = 0;
+
+    // Make a copy of the name, an option can be changed in the function.
+    name = vim_strnsave(funcname, len);
+    if (name == NULL)
+       return ret;
+
+    fname = fname_trans_sid(name, fname_buf, &tofree, &error);
+
+    // Skip "g:" before a function name.
+    if (fname[0] == 'g' && fname[1] == ':')
+    {
+       is_global = TRUE;
+       rfname = fname + 2;
+    }
+    else
+       rfname = fname;
+    fp = find_func(rfname, is_global);
+    if (fp != NULL && !is_global && in_vim9script()
+                                                && func_requires_g_prefix(fp))
+       // In Vim9 script g: is required to find a global non-autoload
+       // function.
+       fp = NULL;
+    if (fp == NULL)
+       ret = NOTDONE;
+    else if (fp != NULL && (fp->uf_flags & FC_DELETED))
+       error = FCERR_DELETED;
+    else if (fp != NULL)
+    {
+       typval_T argvars[1];
+       funcexe_T       funcexe;
+
+       argvars[0].v_type = VAR_UNKNOWN;
+       CLEAR_FIELD(funcexe);
+       funcexe.fe_evaluate = TRUE;
+
+       error = call_user_func_check(fp, 0, argvars, rettv, &funcexe, NULL);
+       if (error == FCERR_NONE)
+           ret = OK;
+    }
+
+    user_func_error(error, name, FALSE);
+    vim_free(tofree);
+    vim_free(name);
+
+    return ret;
+}
+
     char_u *
 printable_func_name(ufunc_T *fp)
 {
@@ -5676,7 +5748,7 @@ ex_defer_inner(
 
                if (error != FCERR_UNKNOWN)
                {
-                   user_func_error(error, name, NULL);
+                   user_func_error(error, name, FALSE);
                    r = FAIL;
                }
            }
index 354a570fc6800d3439018ab23d09f1a1a3793972..894840e08621882224b0c83fd5147b96f885ae66 100644 (file)
@@ -699,6 +699,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    632,
 /**/
     631,
 /**/
index ddcbe2c76b68b1815e7c5c2f5eb74b8a28d6922a..df18f9ebb51851105809dbac35b9f078d623d397 100644 (file)
@@ -1267,7 +1267,8 @@ call_ufunc(
 
     if (error != FCERR_NONE)
     {
-       user_func_error(error, printable_func_name(ufunc), &funcexe);
+       user_func_error(error, printable_func_name(ufunc),
+                                                        funcexe.fe_found_var);
        return FAIL;
     }
     if (did_emsg > did_emsg_before)
@@ -4244,7 +4245,7 @@ exec_instructions(ectx_T *ectx)
                    if (jump)
                        ectx->ec_iidx = iptr->isn_arg.whileloop.while_end;
 
-                   // Store the current funccal count, may be used by
+                   // Store the current funcref count, may be used by
                    // ISN_ENDLOOP later
                    tv = STACK_TV_VAR(
                                    iptr->isn_arg.whileloop.while_funcref_idx);