# HG changeset patch # User Christian Brabandt # Date 1469217607 -7200 # Node ID 172131507c85eeea4b74eded1d42cda1733706ed # Parent 4c2524dd240359f5295eff24dca9c1c3ec3abebe commit https://github.com/vim/vim/commit/df48fb456fb6bf63d94cad9b302ff01d8ee8d311 Author: Bram Moolenaar Date: Fri Jul 22 21:50:18 2016 +0200 patch 7.4.2090 Problem: Using submatch() in a lambda passed to substitute() is verbose. Solution: Use a static list and pass it as an optional argument to the function. Fix memory leak. diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1,4 +1,4 @@ -*eval.txt* For Vim version 7.4. Last change: 2016 Jul 16 +*eval.txt* For Vim version 7.4. Last change: 2016 Jul 22 VIM REFERENCE MANUAL by Bram Moolenaar @@ -1535,7 +1535,7 @@ v:false A Number with value zero. Used echo v:false < v:false ~ That is so that eval() can parse the string back to the same - value. + value. Read-only. *v:fcs_reason* *fcs_reason-variable* v:fcs_reason The reason why the |FileChangedShell| event was triggered. @@ -1682,7 +1682,7 @@ v:none An empty String. Used to put an echo v:none < v:none ~ That is so that eval() can parse the string back to the same - value. + value. Read-only. *v:null* *null-variable* v:null An empty String. Used to put "null" in JSON. See @@ -1692,7 +1692,7 @@ v:null An empty String. Used to put "nu echo v:null < v:null ~ That is so that eval() can parse the string back to the same - value. + value. Read-only. *v:oldfiles* *oldfiles-variable* v:oldfiles List of file names that is loaded from the |viminfo| file on @@ -1890,7 +1890,7 @@ v:true A Number with value one. Used to echo v:true < v:true ~ That is so that eval() can parse the string back to the same - value. + value. Read-only. *v:val* *val-variable* v:val Value of the current item of a |List| or |Dictionary|. Only valid while evaluating the expression used with |map()| and @@ -7144,16 +7144,24 @@ substitute({expr}, {pat}, {sub}, {flags} unmodified. Example: > - :let &path = substitute(&path, ",\\=[^,]*$", "", "") + :let &path = substitute(&path, ",\\=[^,]*$", "", "") < This removes the last component of the 'path' option. > - :echo substitute("testing", ".*", "\\U\\0", "") + :echo substitute("testing", ".*", "\\U\\0", "") < results in "TESTING". When {sub} starts with "\=", the remainder is interpreted as an expression. See |sub-replace-expression|. Example: > - :echo substitute(s, '%\(\x\x\)', + :echo substitute(s, '%\(\x\x\)', \ '\=nr2char("0x" . submatch(1))', 'g') +< When {sub} is a Funcref that function is called, with one + optional argument. Example: > + :echo substitute(s, '%\(\x\x\)', SubNr, 'g') +< The optional argument is a list which contains the whole + matched string and up to nine submatches,like what + |submatch()| returns. Example: > + :echo substitute(s, '\(\x\x\)', {m -> '0x' . m[1]}, 'g') + synID({lnum}, {col}, {trans}) *synID()* The result is a Number, which is the syntax ID at the position {lnum} and {col} in the current window. @@ -7546,18 +7554,20 @@ trunc({expr}) *trunc()* {only available when compiled with the |+float| feature} *type()* -type({expr}) The result is a Number, depending on the type of {expr}: - Number: 0 - String: 1 - Funcref: 2 - List: 3 - Dictionary: 4 - Float: 5 - Boolean: 6 (v:false and v:true) - None 7 (v:null and v:none) - Job 8 - Channel 9 - To avoid the magic numbers it should be used this way: > +type({expr}) The result is a Number representing the type of {expr}. + Instead of using the number directly, it is better to use the + v:t_ variable that has the value: + Number: 0 |v:t_number| + String: 1 |v:t_string| + Funcref: 2 |v:t_func| + List: 3 |v:t_list| + Dictionary: 4 |v:t_dict| + Float: 5 |v:t_float| + Boolean: 6 |v:t_bool| (v:false and v:true) + None 7 |v:t_none| (v:null and v:none) + Job 8 |v:t_job| + Channel 9 |v:t_channel| + For backward compatibility, this method can be used: > :if type(myvar) == type(0) :if type(myvar) == type("") :if type(myvar) == type(function("tr")) @@ -7566,6 +7576,8 @@ type({expr}) The result is a Number, dep :if type(myvar) == type(0.0) :if type(myvar) == type(v:false) :if type(myvar) == type(v:none) +< To check if the v:t_ variables exist use this: > + :if exists('v:t_number') undofile({name}) *undofile()* Return the name of the undo file that would be used for a file diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -1533,8 +1533,8 @@ invoke_callback(channel_T *channel, char argv[0].v_type = VAR_CHANNEL; argv[0].vval.v_channel = channel; - call_func(callback, (int)STRLEN(callback), - &rettv, 2, argv, 0L, 0L, &dummy, TRUE, partial, NULL); + call_func(callback, (int)STRLEN(callback), &rettv, 2, argv, NULL, + 0L, 0L, &dummy, TRUE, partial, NULL); clear_tv(&rettv); channel_need_redraw = TRUE; } @@ -2695,7 +2695,7 @@ channel_close(channel_T *channel, int in argv[0].v_type = VAR_CHANNEL; argv[0].vval.v_channel = channel; call_func(channel->ch_close_cb, (int)STRLEN(channel->ch_close_cb), - &rettv, 1, argv, 0L, 0L, &dummy, TRUE, + &rettv, 1, argv, NULL, 0L, 0L, &dummy, TRUE, channel->ch_close_partial, NULL); clear_tv(&rettv); channel_need_redraw = TRUE; @@ -4758,7 +4758,7 @@ job_status(job_T *job) argv[1].v_type = VAR_NUMBER; argv[1].vval.v_number = job->jv_exitval; call_func(job->jv_exit_cb, (int)STRLEN(job->jv_exit_cb), - &rettv, 2, argv, 0L, 0L, &dummy, TRUE, + &rettv, 2, argv, NULL, 0L, 0L, &dummy, TRUE, job->jv_exit_partial, NULL); clear_tv(&rettv); --job->jv_refcount; diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -988,7 +988,7 @@ call_vim_function( } rettv->v_type = VAR_UNKNOWN; /* clear_tv() uses this */ - ret = call_func(func, (int)STRLEN(func), rettv, argc, argvars, + ret = call_func(func, (int)STRLEN(func), rettv, argc, argvars, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &doesrange, TRUE, NULL, NULL); if (safe) @@ -9930,8 +9930,8 @@ filter_map_one(typval_T *tv, typval_T *e if (expr->v_type == VAR_FUNC) { s = expr->vval.v_string; - if (call_func(s, (int)STRLEN(s), - &rettv, 2, argv, 0L, 0L, &dummy, TRUE, NULL, NULL) == FAIL) + if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL, + 0L, 0L, &dummy, TRUE, NULL, NULL) == FAIL) goto theend; } else if (expr->v_type == VAR_PARTIAL) @@ -9939,9 +9939,8 @@ filter_map_one(typval_T *tv, typval_T *e partial_T *partial = expr->vval.v_partial; s = partial->pt_name; - if (call_func(s, (int)STRLEN(s), - &rettv, 2, argv, 0L, 0L, &dummy, TRUE, partial, NULL) - == FAIL) + if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL, + 0L, 0L, &dummy, TRUE, partial, NULL) == FAIL) goto theend; } else diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -10101,7 +10101,7 @@ item_compare2(const void *s1, const void rettv.v_type = VAR_UNKNOWN; /* clear_tv() uses this */ res = call_func(func_name, (int)STRLEN(func_name), - &rettv, 2, argv, 0L, 0L, &dummy, TRUE, + &rettv, 2, argv, NULL, 0L, 0L, &dummy, TRUE, partial, sortinfo->item_compare_selfdict); clear_tv(&argv[0]); clear_tv(&argv[1]); diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c --- a/src/ex_cmds2.c +++ b/src/ex_cmds2.c @@ -1163,7 +1163,7 @@ timer_callback(timer_T *timer) argv[1].v_type = VAR_UNKNOWN; call_func(timer->tr_callback, (int)STRLEN(timer->tr_callback), - &rettv, 1, argv, 0L, 0L, &dummy, TRUE, + &rettv, 1, argv, NULL, 0L, 0L, &dummy, TRUE, timer->tr_partial, NULL); clear_tv(&rettv); } diff --git a/src/list.c b/src/list.c --- a/src/list.c +++ b/src/list.c @@ -924,4 +924,35 @@ write_list(FILE *fd, list_T *list, int b return ret; } +/* + * Initialize a static list with 10 items. + */ + void +init_static_list(staticList10_T *sl) +{ + list_T *l = &sl->sl_list; + int i; + + memset(sl, 0, sizeof(staticList10_T)); + l->lv_first = &sl->sl_items[0]; + l->lv_last = &sl->sl_items[9]; + l->lv_refcount = DO_NOT_FREE_CNT; + l->lv_lock = VAR_FIXED; + sl->sl_list.lv_len = 10; + + for (i = 0; i < 10; ++i) + { + listitem_T *li = &sl->sl_items[i]; + + if (i == 0) + li->li_prev = NULL; + else + li->li_prev = li - 1; + if (i == 9) + li->li_next = NULL; + else + li->li_next = li + 1; + } +} + #endif /* defined(FEAT_EVAL) */ diff --git a/src/proto/list.pro b/src/proto/list.pro --- a/src/proto/list.pro +++ b/src/proto/list.pro @@ -32,4 +32,5 @@ char_u *list2string(typval_T *tv, int co int list_join(garray_T *gap, list_T *l, char_u *sep, int echo_style, int restore_copyID, int copyID); int get_list_tv(char_u **arg, typval_T *rettv, int evaluate); int write_list(FILE *fd, list_T *list, int binary); +void init_static_list(staticList10_T *sl); /* vim: set ft=c : */ diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -5,7 +5,7 @@ char_u *deref_func_name(char_u *name, in int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, linenr_T firstline, linenr_T lastline, int *doesrange, int evaluate, partial_T *partial, dict_T *selfdict); void free_all_functions(void); int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv); -int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, linenr_T firstline, linenr_T lastline, int *doesrange, int evaluate, partial_T *partial, dict_T *selfdict_in); +int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, int (*argv_func)(int, typval_T *, int), linenr_T firstline, linenr_T lastline, int *doesrange, int evaluate, partial_T *partial, dict_T *selfdict_in); void ex_function(exarg_T *eap); int eval_fname_script(char_u *p); int translated_function_exists(char_u *name); diff --git a/src/regexp.c b/src/regexp.c --- a/src/regexp.c +++ b/src/regexp.c @@ -7290,6 +7290,50 @@ static int submatch_line_lbr; #endif #if defined(FEAT_MODIFY_FNAME) || defined(FEAT_EVAL) || defined(PROTO) + +/* + * Put the submatches in "argv[0]" which is a list passed into call_func() by + * vim_regsub_both(). + */ + static int +fill_submatch_list(int argc UNUSED, typval_T *argv, int argcount) +{ + listitem_T *li; + int i; + char_u *s; + + if (argcount == 0) + /* called function doesn't take an argument */ + return 0; + + /* Relies on sl_list to be the first item in staticList10_T. */ + init_static_list((staticList10_T *)(argv->vval.v_list)); + + /* There are always 10 list items in staticList10_T. */ + li = argv->vval.v_list->lv_first; + for (i = 0; i < 10; ++i) + { + s = submatch_match->startp[i]; + if (s == NULL || submatch_match->endp[i] == NULL) + s = NULL; + else + s = vim_strnsave(s, (int)(submatch_match->endp[i] - s)); + li->li_tv.v_type = VAR_STRING; + li->li_tv.vval.v_string = s; + li = li->li_next; + } + return 1; +} + + static void +clear_submatch_list(staticList10_T *sl) +{ + int i; + + for (i = 0; i < 10; ++i) + vim_free(sl->sl_items[i].li_tv.vval.v_string); +} + /* * vim_regsub() - perform substitutions after a vim_regexec() or * vim_regexec_multi() match. @@ -7427,10 +7471,11 @@ vim_regsub_both( if (expr != NULL) { - typval_T argv[1]; + typval_T argv[2]; int dummy; char_u buf[NUMBUFLEN]; typval_T rettv; + staticList10_T matchList; rettv.v_type = VAR_STRING; rettv.vval.v_string = NULL; @@ -7438,23 +7483,35 @@ vim_regsub_both( { /* can't do this recursively */ } - else if (expr->v_type == VAR_FUNC) + else { - s = expr->vval.v_string; - call_func(s, (int)STRLEN(s), &rettv, 0, argv, + argv[0].v_type = VAR_LIST; + argv[0].vval.v_list = &matchList.sl_list; + matchList.sl_list.lv_len = 0; + if (expr->v_type == VAR_FUNC) + { + s = expr->vval.v_string; + call_func(s, (int)STRLEN(s), &rettv, + 1, argv, fill_submatch_list, 0L, 0L, &dummy, TRUE, NULL, NULL); - } - else if (expr->v_type == VAR_PARTIAL) - { - partial_T *partial = expr->vval.v_partial; - - s = partial->pt_name; - call_func(s, (int)STRLEN(s), &rettv, 0, argv, + } + else if (expr->v_type == VAR_PARTIAL) + { + partial_T *partial = expr->vval.v_partial; + + s = partial->pt_name; + call_func(s, (int)STRLEN(s), &rettv, + 1, argv, fill_submatch_list, 0L, 0L, &dummy, TRUE, partial, NULL); + } + if (matchList.sl_list.lv_len > 0) + /* fill_submatch_list() was called */ + clear_submatch_list(&matchList); } eval_result = get_tv_string_buf_chk(&rettv, buf); if (eval_result != NULL) eval_result = vim_strsave(eval_result); + clear_tv(&rettv); } else eval_result = eval_to_string(source + 2, NULL, TRUE); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1245,6 +1245,14 @@ struct listvar_S }; /* + * Static list with 10 items. Use init_static_list() to initialize. + */ +typedef struct { + list_T sl_list; /* must be first */ + listitem_T sl_items[10]; +} staticList10_T; + +/* * Structure to hold an item of a Dictionary. * Also used for a variable. * The key is copied into "di_key" to avoid an extra alloc/free for it. diff --git a/src/testdir/test_expr.vim b/src/testdir/test_expr.vim --- a/src/testdir/test_expr.vim +++ b/src/testdir/test_expr.vim @@ -153,3 +153,22 @@ func Test_substitute_expr() endfunc call assert_equal('--', substitute('xxx', 'x*', {-> '-' . Recurse() . '-'}, '')) endfunc + +func Test_substitute_expr_arg() + call assert_equal('123456789-123456789=', substitute('123456789', + \ '\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)', + \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) + + call assert_equal('123456-123456=789', substitute('123456789', + \ '\(.\)\(.\)\(.\)\(a*\)\(n*\)\(.\)\(.\)\(.\)\(x*\)', + \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) + + call assert_equal('123456789-123456789x=', substitute('123456789', + \ '\(.\)\(.\)\(.*\)', + \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . 'x' . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) + + call assert_fails("call substitute('xxx', '.', {m -> string(add(m, 'x'))}, '')", 'E742:') + call assert_fails("call substitute('xxx', '.', {m -> string(insert(m, 'x'))}, '')", 'E742:') + call assert_fails("call substitute('xxx', '.', {m -> string(extend(m, ['x']))}, '')", 'E742:') + call assert_fails("call substitute('xxx', '.', {m -> string(remove(m, 1))}, '')", 'E742:') +endfunc diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -480,7 +480,7 @@ get_func_tv( &argvars[i]; } - ret = call_func(name, len, rettv, argcount, argvars, + ret = call_func(name, len, rettv, argcount, argvars, NULL, firstline, lastline, doesrange, evaluate, partial, selfdict); funcargs.ga_len -= i; @@ -1139,7 +1139,7 @@ func_call( } if (item == NULL) - r = call_func(name, (int)STRLEN(name), rettv, argc, argv, + r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, TRUE, partial, selfdict); @@ -1152,6 +1152,11 @@ func_call( /* * Call a function with its resolved parameters + * + * "argv_func", when not NULL, can be used to fill in arguments only when the + * invoked function uses them. It is called like this: + * new_argcount = argv_func(current_argcount, argv, called_func_argcount) + * * Return FAIL when the function can't be called, OK otherwise. * Also returns OK when an error was encountered while executing the function. */ @@ -1163,6 +1168,8 @@ call_func( int argcount_in, /* number of "argvars" */ typval_T *argvars_in, /* vars for arguments, must have "argcount" PLUS ONE elements! */ + int (* argv_func)(int, typval_T *, int), + /* function to fill in argvars */ linenr_T firstline, /* first line of range */ linenr_T lastline, /* last line of range */ int *doesrange, /* return: function handled range */ @@ -1254,6 +1261,9 @@ call_func( if (fp != NULL) { + if (argv_func != NULL) + argcount = argv_func(argcount, argvars, fp->uf_args.ga_len); + if (fp->uf_flags & FC_RANGE) *doesrange = TRUE; if (argcount < fp->uf_args.ga_len) diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -759,6 +759,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 2090, +/**/ 2089, /**/ 2088,