Patch 9.0.0632
Problem:    Calling a function from an "expr" option has too much overhead.
Solution:   Add call_simple_func() and use it for 'foldexpr'
Files:      runtime/doc/fold.txt, runtime/doc/vim9.txt, src/userfunc.c,
            src/proto/userfunc.pro, src/eval.c, src/vim9execute.c,
            src/testdir/test_fold.vim


*** ../vim-9.0.0631/runtime/doc/fold.txt        2022-06-28 11:21:05.000000000 
+0100
--- runtime/doc/fold.txt        2022-10-01 14:55:34.774355708 +0100
***************
*** 74,81 ****
  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: >
--- 74,79 ----
***************
*** 84,89 ****
--- 82,91 ----
  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.
*** ../vim-9.0.0631/runtime/doc/vim9.txt        2022-09-01 12:22:19.743659145 
+0100
--- runtime/doc/vim9.txt        2022-10-01 15:02:01.149962921 +0100
***************
*** 1363,1368 ****
--- 1410,1430 ----
                '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*
*** ../vim-9.0.0631/src/userfunc.c      2022-09-30 19:19:00.765677725 +0100
--- src/userfunc.c      2022-10-01 15:08:33.595521793 +0100
***************
*** 3447,3458 ****
   * Nothing if "error" is FCERR_NONE.
   */
      void
! user_func_error(int error, char_u *name, funcexe_T *funcexe)
  {
      switch (error)
      {
        case FCERR_UNKNOWN:
!               if (funcexe->fe_found_var)
                    emsg_funcname(e_not_callable_type_str, name);
                else
                    emsg_funcname(e_unknown_function_str, name);
--- 3447,3458 ----
   * Nothing if "error" is FCERR_NONE.
   */
      void
! user_func_error(int error, char_u *name, int found_var)
  {
      switch (error)
      {
        case FCERR_UNKNOWN:
!               if (found_var)
                    emsg_funcname(e_not_callable_type_str, name);
                else
                    emsg_funcname(e_unknown_function_str, name);
***************
*** 3702,3708 ****
       * cancelled due to an aborting error, an interrupt, or an exception.
       */
      if (!aborting())
!       user_func_error(error, (name != NULL) ? name : funcname, funcexe);
  
      // clear the copies made from the partial
      while (argv_clear > 0)
--- 3702,3709 ----
       * cancelled due to an aborting error, an interrupt, or an exception.
       */
      if (!aborting())
!       user_func_error(error, (name != NULL) ? name : funcname,
!                                                       funcexe->fe_found_var);
  
      // clear the copies made from the partial
      while (argv_clear > 0)
***************
*** 3714,3719 ****
--- 3715,3791 ----
      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,5682 ****
  
                if (error != FCERR_UNKNOWN)
                {
!                   user_func_error(error, name, NULL);
                    r = FAIL;
                }
            }
--- 5748,5754 ----
  
                if (error != FCERR_UNKNOWN)
                {
!                   user_func_error(error, name, FALSE);
                    r = FAIL;
                }
            }
*** ../vim-9.0.0631/src/proto/userfunc.pro      2022-09-19 15:54:29.543117874 
+0100
--- src/proto/userfunc.pro      2022-10-01 15:07:06.779300462 +0100
***************
*** 36,43 ****
  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);
  int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, 
typval_T *argvars_in, funcexe_T *funcexe);
  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);
--- 36,44 ----
  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, 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);
*** ../vim-9.0.0631/src/eval.c  2022-09-28 16:16:10.252335644 +0100
--- src/eval.c  2022-10-01 15:14:26.171975492 +0100
***************
*** 899,911 ****
  {
      char_u    *arg;
      typval_T  tv;
      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;
      current_sctx = wp->w_p_script_ctx[WV_FDE];
  
      ++emsg_off;
--- 899,912 ----
  {
      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 = skipwhite(wp->w_p_fde);
      current_sctx = wp->w_p_script_ctx[WV_FDE];
  
      ++emsg_off;
***************
*** 913,919 ****
        ++sandbox;
      ++textlock;
      *cp = NUL;
!     if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL)
        retval = 0;
      else
      {
--- 914,934 ----
        ++sandbox;
      ++textlock;
      *cp = NUL;
! 
!     // 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
      {
*** ../vim-9.0.0631/src/vim9execute.c   2022-09-28 15:19:06.466869975 +0100
--- src/vim9execute.c   2022-10-01 15:06:52.099257377 +0100
***************
*** 1267,1273 ****
  
      if (error != FCERR_NONE)
      {
!       user_func_error(error, printable_func_name(ufunc), &funcexe);
        return FAIL;
      }
      if (did_emsg > did_emsg_before)
--- 1267,1274 ----
  
      if (error != FCERR_NONE)
      {
!       user_func_error(error, printable_func_name(ufunc),
!                                                        funcexe.fe_found_var);
        return FAIL;
      }
      if (did_emsg > did_emsg_before)
***************
*** 4244,4250 ****
                    if (jump)
                        ectx->ec_iidx = iptr->isn_arg.whileloop.while_end;
  
!                   // Store the current funccal count, may be used by
                    // ISN_ENDLOOP later
                    tv = STACK_TV_VAR(
                                    iptr->isn_arg.whileloop.while_funcref_idx);
--- 4245,4251 ----
                    if (jump)
                        ectx->ec_iidx = iptr->isn_arg.whileloop.while_end;
  
!                   // Store the current funcref count, may be used by
                    // ISN_ENDLOOP later
                    tv = STACK_TV_VAR(
                                    iptr->isn_arg.whileloop.while_funcref_idx);
*** ../vim-9.0.0631/src/testdir/test_fold.vim   2022-09-27 19:34:30.658212345 
+0100
--- src/testdir/test_fold.vim   2022-10-01 15:23:13.107912272 +0100
***************
*** 249,254 ****
--- 249,279 ----
    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
*** ../vim-9.0.0631/src/version.c       2022-09-30 21:57:07.547153409 +0100
--- src/version.c       2022-10-01 15:02:21.434085865 +0100
***************
*** 701,702 ****
--- 701,704 ----
  {   /* Add new patch number below this line */
+ /**/
+     632,
  /**/

-- 
hundred-and-one symptoms of being an internet addict:
239. You think "surfing" is something you do on dry land.

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/20221001143352.1BC091C065C%40moolenaar.net.

Raspunde prin e-mail lui