vim-patch:7.4.2044

Problem:    filter() and map() either require a string or defining a function.
Solution:   Support lambda, a short way to define a function that evaluates an
            expression. (Yasuhiro Matsumoto, Ken Takata)

069c1e7fa9
This commit is contained in:
Michael Ennen 2016-12-14 17:06:36 -07:00
parent 6c423989fc
commit bb7d0deb2f
6 changed files with 338 additions and 93 deletions

View File

@ -112,9 +112,10 @@ You will not get an error if you try to change the type of a variable.
1.2 Function references ~ 1.2 Function references ~
*Funcref* *E695* *E718* *Funcref* *E695* *E718*
A Funcref variable is obtained with the |function()| function. It can be used A Funcref variable is obtained with the |function()| function or created with
in an expression in the place of a function name, before the parenthesis the lambda expression |expr-lambda|. It can be used in an expression in the
around the arguments, to invoke the function it refers to. Example: > place of a function name, before the parenthesis around the arguments, to
invoke the function it refers to. Example: >
:let Fn = function("MyFunc") :let Fn = function("MyFunc")
:echo Fn() :echo Fn()
@ -667,6 +668,7 @@ Expression syntax summary, from least to most significant:
@r contents of register 'r' @r contents of register 'r'
function(expr1, ...) function call function(expr1, ...) function call
func{ti}on(expr1, ...) function call with curly braces func{ti}on(expr1, ...) function call with curly braces
{args -> expr1} lambda expression
".." indicates that the operations in this level can be concatenated. ".." indicates that the operations in this level can be concatenated.
@ -1174,6 +1176,42 @@ function(expr1, ...) function call
See below |functions|. See below |functions|.
lambda expression *expr-lambda* *lambda*
-----------------
{args -> expr1} lambda expression
A lambda expression creates a new unnamed function which returns the result of
evaluating |expr1|. Lambda expressions are differ from |user-functions| in
the following ways:
1. The body of the lambda expression is an |expr1| and not a sequence of |Ex|
commands.
2. The prefix "a:" is optional for arguments. E.g.: >
:let F = {arg1, arg2 -> arg1 - arg2}
:echo F(5, 2)
< 3
The arguments are optional. Example: >
:let F = {-> 'error function'}
:echo F()
< error function
Examples for using a lambda expression with |sort()|, |map()| and |filter()|: >
:echo map([1, 2, 3], {idx, val -> val + 1})
< [2, 3, 4] >
:echo sort([3,7,2,1,4], {a, b -> a - b})
< [1, 2, 3, 4, 7]
The lambda expression is also useful for Channel, Job and timer: >
:let timer = timer_start(500,
\ {-> execute("echo 'Handler called'", "")},
\ {'repeat': 3})
< Handler called
Handler called
Handler called
Note how execute() is used to execute an Ex command. That's ugly though.
============================================================================== ==============================================================================
3. Internal variable *internal-variables* *E461* 3. Internal variable *internal-variables* *E461*
@ -8094,10 +8132,10 @@ can be 0). "a:000" is set to a |List| that contains these arguments. Note
that "a:1" is the same as "a:000[0]". that "a:1" is the same as "a:000[0]".
*E742* *E742*
The a: scope and the variables in it cannot be changed, they are fixed. The a: scope and the variables in it cannot be changed, they are fixed.
However, if a |List| or |Dictionary| is used, you can change their contents. However, if a composite type is used, such as |List| or |Dictionary| , you can
Thus you can pass a |List| to a function and have the function add an item to change their contents. Thus you can pass a |List| to a function and have the
it. If you want to make sure the function cannot change a |List| or function add an item to it. If you want to make sure the function cannot
|Dictionary| use |:lockvar|. change a |List| or |Dictionary| use |:lockvar|.
When not using "...", the number of arguments in a function call must be equal When not using "...", the number of arguments in a function call must be equal
to the number of named arguments. When using "...", the number of arguments to the number of named arguments. When using "...", the number of arguments
@ -8109,9 +8147,8 @@ until the matching |:endfunction|. It is allowed to define another function
inside a function body. inside a function body.
*local-variables* *local-variables*
Inside a function variables can be used. These are local variables, which Inside a function local variables can be used. These will disappear when the
will disappear when the function returns. Global variables need to be function returns. Global variables need to be accessed with "g:".
accessed with "g:".
Example: > Example: >
:function Table(title, ...) :function Table(title, ...)

View File

@ -4301,8 +4301,12 @@ static int eval7(
case '[': ret = get_list_tv(arg, rettv, evaluate); case '[': ret = get_list_tv(arg, rettv, evaluate);
break; break;
// Lambda: {arg, arg -> expr}
// Dictionary: {key: val, key: val} // Dictionary: {key: val, key: val}
case '{': ret = get_dict_tv(arg, rettv, evaluate); case '{': ret = get_lambda_tv(arg, rettv, evaluate);
if (ret == NOTDONE) {
ret = get_dict_tv(arg, rettv, evaluate);
}
break; break;
// Option value: &name // Option value: &name
@ -6871,6 +6875,202 @@ failret:
return OK; return OK;
} }
/// Get function arguments.
static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs,
int *varargs, int skip)
{
bool mustend = false;
char_u *arg = *argp;
char_u *p = arg;
int c;
int i;
if (newargs != NULL) {
ga_init(newargs, (int)sizeof(char_u *), 3);
}
if (varargs != NULL) {
*varargs = false;
}
// Isolate the arguments: "arg1, arg2, ...)"
while (*p != endchar) {
if (p[0] == '.' && p[1] == '.' && p[2] == '.') {
if (varargs != NULL) {
*varargs = true;
}
p += 3;
mustend = true;
} else {
arg = p;
while (ASCII_ISALNUM(*p) || *p == '_') {
p++;
}
if (arg == p || isdigit(*arg)
|| (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0)
|| (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) {
if (!skip) {
EMSG2(_("E125: Illegal argument: %s"), arg);
}
break;
}
if (newargs != NULL) {
ga_grow(newargs, 1);
c = *p;
*p = NUL;
arg = vim_strsave(arg);
if (arg == NULL) {
goto err_ret;
}
// Check for duplicate argument name.
for (i = 0; i < newargs->ga_len; i++) {
if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) {
EMSG2(_("E853: Duplicate argument name: %s"), arg);
xfree(arg);
goto err_ret;
}
}
((char_u **)(newargs->ga_data))[newargs->ga_len] = arg;
newargs->ga_len++;
*p = c;
}
if (*p == ',') {
p++;
} else {
mustend = true;
}
}
p = skipwhite(p);
if (mustend && *p != endchar) {
if (!skip) {
EMSG2(_(e_invarg2), *argp);
}
break;
}
}
if (*p != endchar) {
goto err_ret;
}
p++; // skip "endchar"
*argp = p;
return OK;
err_ret:
if (newargs != NULL) {
ga_clear_strings(newargs);
}
return FAIL;
}
/// Parse a lambda expression and get a Funcref from "*arg".
/// Return OK or FAIL. Returns NOTDONE for dict or {expr}.
static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
{
garray_T newargs;
garray_T newlines;
ufunc_T *fp = NULL;
int varargs;
int ret;
char_u name[20];
char_u *start = skipwhite(*arg + 1);
char_u *s, *e;
static int lambda_no = 0;
// TODO(mike): What lengths should be used here?
ga_init(&newargs, (int)sizeof(char_u *), 80);
ga_init(&newlines, (int)sizeof(char_u *), 80);
// First, check if this is a lambda expression. "->" must exists.
ret = get_function_args(&start, '-', NULL, NULL, true);
if (ret == FAIL || *start != '>') {
return NOTDONE;
}
// Parse the arguments again.
*arg = skipwhite(*arg + 1);
ret = get_function_args(arg, '-', &newargs, &varargs, false);
if (ret == FAIL || **arg != '>') {
goto errret;
}
// Get the start and the end of the expression.
*arg = skipwhite(*arg + 1);
s = *arg;
ret = skip_expr(arg);
if (ret == FAIL) {
goto errret;
}
e = *arg;
*arg = skipwhite(*arg);
if (**arg != '}') {
goto errret;
}
(*arg)++;
if (evaluate) {
int len;
char_u *p;
fp = (ufunc_T *)xmalloc((unsigned)(sizeof(ufunc_T) + 20));
if (fp == NULL) {
goto errret;
}
snprintf((char *)name, sizeof(name), "<lambda>%d", lambda_no++);
ga_init(&newlines, (int)sizeof(char_u *), 1);
ga_grow(&newlines, 1);
// Add "return " before the expression.
// TODO(vim): Support multiple expressions.
len = 7 + e - s + 1;
p = (char_u *)xmalloc(len);
if (p == NULL) {
goto errret;
}
((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
STRCPY(p, "return ");
STRNCPY(p + 7, s, e - s);
p[7 + e - s] = NUL;
fp->uf_refcount = 1;
STRCPY(fp->uf_name, name);
hash_add(&func_hashtab, UF2HIKEY(fp));
fp->uf_args = newargs;
fp->uf_lines = newlines;
#ifdef FEAT_PROFILE
fp->uf_tml_count = NULL;
fp->uf_tml_total = NULL;
fp->uf_tml_self = NULL;
fp->uf_profiling = false;
if (prof_def_func()) {
func_do_profile(fp);
}
#endif
fp->uf_varargs = true;
fp->uf_flags = 0;
fp->uf_calls = 0;
fp->uf_script_ID = current_SID;
rettv->vval.v_string = vim_strsave(name);
rettv->v_type = VAR_FUNC;
} else {
ga_clear_strings(&newargs);
}
return OK;
errret:
ga_clear_strings(&newargs);
ga_clear_strings(&newlines);
xfree(fp);
return FAIL;
}
/// Convert the string to a floating point number /// Convert the string to a floating point number
/// ///
/// This uses strtod(). setlocale(LC_NUMERIC, "C") has been used earlier to /// This uses strtod(). setlocale(LC_NUMERIC, "C") has been used earlier to
@ -9448,7 +9648,7 @@ static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp)
goto theend; goto theend;
} }
if (*s != NUL) { // check for trailing chars after expr if (*s != NUL) { // check for trailing chars after expr
EMSG2(_(e_invexpr2), s); EMSG2(_(e_invexpr2), s);
goto theend; goto theend;
} }
@ -20495,8 +20695,7 @@ void ex_function(exarg_T *eap)
char_u *line_arg = NULL; char_u *line_arg = NULL;
garray_T newargs; garray_T newargs;
garray_T newlines; garray_T newlines;
int varargs = FALSE; int varargs = false;
int mustend = FALSE;
int flags = 0; int flags = 0;
ufunc_T *fp; ufunc_T *fp;
int indent; int indent;
@ -20679,59 +20878,11 @@ void ex_function(exarg_T *eap)
EMSG(_("E862: Cannot use g: here")); EMSG(_("E862: Cannot use g: here"));
} }
/* if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) {
* Isolate the arguments: "arg1, arg2, ...)" goto errret_2;
*/
while (*p != ')') {
if (p[0] == '.' && p[1] == '.' && p[2] == '.') {
varargs = TRUE;
p += 3;
mustend = TRUE;
} else {
arg = p;
while (ASCII_ISALNUM(*p) || *p == '_')
++p;
if (arg == p || isdigit(*arg)
|| (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0)
|| (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) {
if (!eap->skip)
EMSG2(_("E125: Illegal argument: %s"), arg);
break;
}
ga_grow(&newargs, 1);
c = *p;
*p = NUL;
arg = vim_strsave(arg);
/* Check for duplicate argument name. */
for (int i = 0; i < newargs.ga_len; ++i)
if (STRCMP(((char_u **)(newargs.ga_data))[i], arg) == 0) {
EMSG2(_("E853: Duplicate argument name: %s"), arg);
xfree(arg);
goto erret;
}
((char_u **)(newargs.ga_data))[newargs.ga_len] = arg;
*p = c;
newargs.ga_len++;
if (*p == ',')
++p;
else
mustend = TRUE;
}
p = skipwhite(p);
if (mustend && *p != ')') {
if (!eap->skip)
EMSG2(_(e_invarg2), eap->arg);
break;
}
} }
if (*p != ')') {
goto erret;
}
++p; // skip the ')'
/* find extra arguments "range", "dict" and "abort" */ // find extra arguments "range", "dict" and "abort"
for (;; ) { for (;; ) {
p = skipwhite(p); p = skipwhite(p);
if (STRNCMP(p, "range", 5) == 0) { if (STRNCMP(p, "range", 5) == 0) {
@ -21041,6 +21192,7 @@ void ex_function(exarg_T *eap)
erret: erret:
ga_clear_strings(&newargs); ga_clear_strings(&newargs);
errret_2:
ga_clear_strings(&newlines); ga_clear_strings(&newlines);
ret_free: ret_free:
xfree(skip_until); xfree(skip_until);
@ -21764,7 +21916,9 @@ void func_unref(char_u *name)
{ {
ufunc_T *fp; ufunc_T *fp;
if (name != NULL && isdigit(*name)) { if (name == NULL) {
return;
} else if (isdigit(*name)) {
fp = find_func(name); fp = find_func(name);
if (fp == NULL) { if (fp == NULL) {
#ifdef EXITFREE #ifdef EXITFREE
@ -21777,6 +21931,16 @@ void func_unref(char_u *name)
} else { } else {
user_func_unref(fp); user_func_unref(fp);
} }
} else if (STRNCMP(name, "<lambda>", 8) == 0) {
// fail silently, when lambda function isn't found
fp = find_func(name);
if (fp != NULL && --fp->uf_refcount <= 0) {
// Only delete it when it's not being used. Otherwise it's done
// when "uf_calls" becomes zero.
if (fp->uf_calls == 0) {
func_free(fp);
}
}
} }
} }
@ -21798,12 +21962,21 @@ void func_ref(char_u *name)
{ {
ufunc_T *fp; ufunc_T *fp;
if (name != NULL && isdigit(*name)) { if (name == NULL) {
return;
} else if (isdigit(*name)) {
fp = find_func(name); fp = find_func(name);
if (fp == NULL) if (fp == NULL) {
EMSG2(_(e_intern2), "func_ref()"); EMSG2(_(e_intern2), "func_ref()");
else } else {
++fp->uf_refcount; (fp->uf_refcount)++;
}
} else if (STRNCMP(name, "<lambda>", 8) == 0) {
// fail silently, when lambda function isn't found.
fp = find_func(name);
if (fp != NULL) {
(fp->uf_refcount)++;
}
} }
} }
@ -21830,6 +22003,7 @@ call_user_func (
dictitem_T *v; dictitem_T *v;
int fixvar_idx = 0; /* index in fixvar[] */ int fixvar_idx = 0; /* index in fixvar[] */
int ai; int ai;
bool islambda = false;
char_u numbuf[NUMBUFLEN]; char_u numbuf[NUMBUFLEN];
char_u *name; char_u *name;
proftime_T wait_start; proftime_T wait_start;
@ -21867,14 +22041,15 @@ call_user_func (
fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0); fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0);
fc->dbg_tick = debug_tick; fc->dbg_tick = debug_tick;
/* if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0) {
* Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables islambda = true;
* with names up to VAR_SHORT_LEN long. This avoids having to alloc/free }
* each argument variable and saves a lot of time.
*/ // Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables
/* // with names up to VAR_SHORT_LEN long. This avoids having to alloc/free
* Init l: variables. // each argument variable and saves a lot of time.
*/ //
// Init l: variables.
init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE); init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE);
if (selfdict != NULL) { if (selfdict != NULL) {
/* Set l:self to "selfdict". Use "name" to avoid a warning from /* Set l:self to "selfdict". Use "name" to avoid a warning from
@ -21916,31 +22091,49 @@ call_user_func (
fc->l_varlist.lv_refcount = DO_NOT_FREE_CNT; fc->l_varlist.lv_refcount = DO_NOT_FREE_CNT;
fc->l_varlist.lv_lock = VAR_FIXED; fc->l_varlist.lv_lock = VAR_FIXED;
/* // Set a:firstline to "firstline" and a:lastline to "lastline".
* Set a:firstline to "firstline" and a:lastline to "lastline". // Set a:name to named arguments.
* Set a:name to named arguments. // Set a:N to the "..." arguments.
* Set a:N to the "..." arguments.
*/
add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "firstline", add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "firstline",
(varnumber_T)firstline); (varnumber_T)firstline);
add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "lastline", add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "lastline",
(varnumber_T)lastline); (varnumber_T)lastline);
for (int i = 0; i < argcount; ++i) { for (int i = 0; i < argcount; i++) {
bool addlocal = false;
dictitem_T *v2;
ai = i - fp->uf_args.ga_len; ai = i - fp->uf_args.ga_len;
if (ai < 0) if (ai < 0) {
/* named argument a:name */ // named argument a:name
name = FUNCARG(fp, i); name = FUNCARG(fp, i);
else { if (islambda) {
/* "..." argument a:1, a:2, etc. */ addlocal = true;
sprintf((char *)numbuf, "%d", ai + 1); }
} else {
// "..." argument a:1, a:2, etc.
snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1);
name = numbuf; name = numbuf;
} }
if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) { if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) {
v = &fc->fixvar[fixvar_idx++].var; v = &fc->fixvar[fixvar_idx++].var;
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
if (addlocal) {
v2 = v;
}
} else { } else {
v = xmalloc(sizeof(dictitem_T) + STRLEN(name)); v = xmalloc(sizeof(dictitem_T) + STRLEN(name));
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC;
if (addlocal) {
v2 = (dictitem_T *)xmalloc((unsigned)(sizeof(dictitem_T)
+ STRLEN(name)));
if (v2 == NULL) {
xfree(v);
break;
}
v2->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC;
}
} }
STRCPY(v->di_key, name); STRCPY(v->di_key, name);
hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v)); hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v));
@ -21950,6 +22143,15 @@ call_user_func (
v->di_tv = argvars[i]; v->di_tv = argvars[i];
v->di_tv.v_lock = VAR_FIXED; v->di_tv.v_lock = VAR_FIXED;
// Named arguments can be accessed without the "a:" prefix in lambda
// expressions. Add to the l: dict.
if (addlocal) {
STRCPY(v2->di_key, name);
copy_tv(&v->di_tv, &v2->di_tv);
v2->di_tv.v_lock = VAR_FIXED;
hash_add(&fc->l_vars.dv_hashtab, DI2HIKEY(v2));
}
if (ai >= 0 && ai < MAX_FUNC_ARGS) { if (ai >= 0 && ai < MAX_FUNC_ARGS) {
list_append(&fc->l_varlist, &fc->l_listitems[ai]); list_append(&fc->l_varlist, &fc->l_listitems[ai]);
fc->l_listitems[ai].li_tv = argvars[i]; fc->l_listitems[ai].li_tv = argvars[i];
@ -22167,7 +22369,9 @@ call_user_func (
copy_tv(&li->li_tv, &li->li_tv); copy_tv(&li->li_tv, &li->li_tv);
} }
if (--fp->uf_calls <= 0 && isdigit(*fp->uf_name) && fp->uf_refcount <= 0) { if (--fp->uf_calls <= 0 && (isdigit(*fp->uf_name)
|| STRNCMP(fp->uf_name, "<lambda>", 8) == 0)
&& fp->uf_refcount <= 0) {
// Function was unreferenced while being used, free it now. // Function was unreferenced while being used, free it now.
func_free(fp); func_free(fp);
} }

View File

@ -41,6 +41,7 @@ NEW_TESTS ?= \
test_history.res \ test_history.res \
test_increment.res \ test_increment.res \
test_increment_dbcs.res \ test_increment_dbcs.res \
test_lambda.res \
test_langmap.res \ test_langmap.res \
test_match.res \ test_match.res \
test_matchadd_conceal.res \ test_matchadd_conceal.res \

View File

@ -12,6 +12,7 @@ source test_feedkeys.vim
source test_filter_map.vim source test_filter_map.vim
source test_goto.vim source test_goto.vim
source test_jumps.vim source test_jumps.vim
source test_lambda.vim
source test_match.vim source test_match.vim
source test_matchadd_conceal_utf8.vim source test_matchadd_conceal_utf8.vim
source test_menu.vim source test_menu.vim

View File

@ -284,3 +284,5 @@ func Test_named_function_closure()
call garbagecollect() call garbagecollect()
call assert_equal(14, s:Abar()) call assert_equal(14, s:Abar())
endfunc endfunc
=======
>>>>>>> 381a8796... vim-patch:7.4.2044

View File

@ -396,7 +396,7 @@ static int included_patches[] = {
// 2047, // 2047,
// 2046, // 2046,
// 2045 NA // 2045 NA
// 2044, 2044,
2043, 2043,
// 2042 NA // 2042 NA
// 2041 NA // 2041 NA