To: vim_dev@googlegroups.com Subject: Patch 9.0.0632 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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, "", 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 -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///