Skip to content

Instantly share code, notes, and snippets.

@mattn
Last active April 18, 2016 01:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattn/5bc8ded21e1033c9c0ea8cd5ecbbce11 to your computer and use it in GitHub Desktop.
Save mattn/5bc8ded21e1033c9c0ea8cd5ecbbce11 to your computer and use it in GitHub Desktop.
lambda patch
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 2d7beb7..623e417 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -119,9 +119,10 @@ You will not get an error if you try to change the type of a variable.
1.2 Function references ~
*Funcref* *E695* *E718*
-A Funcref variable is obtained with the |function()| function. It can be used
-in an expression in the place of a function name, before the parenthesis
-around the arguments, to invoke the function it refers to. Example: >
+A Funcref variable is obtained with the |function()| function or created with
+the |lambda()| function. It can be used in an expression in the place of a
+function name, before the parenthesis around the arguments, to invoke the
+function it refers to. Example: >
:let Fn = function("MyFunc")
:echo Fn()
@@ -2003,6 +2004,7 @@ json_decode({string}) any decode JSON
json_encode({expr}) String encode JSON
keys({dict}) List keys in {dict}
len({expr}) Number the length of {expr}
+lambda({expr}) Funcref create a lambda function
libcall({lib}, {func}, {arg}) String call {func} in library {lib} with {arg}
libcallnr({lib}, {func}, {arg}) Number idem, but return a Number
line({expr}) Number line nr of cursor, last line or mark
@@ -3438,11 +3440,12 @@ filewritable({file}) *filewritable()*
directory, and we can write to it, the result is 2.
-filter({expr}, {string}) *filter()*
- {expr} must be a |List| or a |Dictionary|.
- For each item in {expr} evaluate {string} and when the result
+filter({expr1}, {expr2}) *filter()*
+ {expr1} must be a |List| or a |Dictionary|.
+ For each item in {expr1} evaluate {expr2} and when the result
is zero remove the item from the |List| or |Dictionary|.
- Inside {string} |v:val| has the value of the current item.
+ {expr2} must be a |string| or |Funcref|. If it is a |string|,
+ inside {expr2} |v:val| has the value of the current item.
For a |Dictionary| |v:key| has the key of the current item.
Examples: >
:call filter(mylist, 'v:val !~ "OLD"')
@@ -3452,7 +3455,7 @@ filter({expr}, {string}) *filter()*
:call filter(var, 0)
< Removes all the items, thus clears the |List| or |Dictionary|.
- Note that {string} is the result of expression and is then
+ Note that {expr2} is the result of expression and is then
used as an expression again. Often it is good to use a
|literal-string| to avoid having to double backslashes.
@@ -3460,9 +3463,13 @@ filter({expr}, {string}) *filter()*
|Dictionary| to remain unmodified make a copy first: >
:let l = filter(copy(mylist), 'v:val =~ "KEEP"')
-< Returns {expr}, the |List| or |Dictionary| that was filtered.
- When an error is encountered while evaluating {string} no
- further items in {expr} are processed.
+< Returns {expr1}, the |List| or |Dictionary| that was filtered.
+ When an error is encountered while evaluating {expr2} no
+ further items in {expr1} are processed.
+
+ {expr2} is possible to be given as |Funcref|. The function
+ should return TRUE if the item should be kept. The value is
+ given as "a:1".
finddir({name}[, {path}[, {count}]]) *finddir()*
@@ -4785,6 +4792,72 @@ len({expr}) The result is a Number, which is the length of the argument.
|Dictionary| is returned.
Otherwise an error is given.
+ *lambda()*
+lambda({expr})
+ Create a new lambda function which constructed with {expr}.
+ The result is a |Funcref|. So you can call it just like a
+ normal function.
+ Example: >
+ :let F = lambda("return 1 + 2")
+ :echo F()
+< 3
+
+ Lambda function can take variadic arguments.
+ Example: >
+ :let F = lambda("return a:1 + 2")
+ :echo F(2)
+< 4
+
+ |sort()|, |map()| and |filter()| can be used with |lambda()|.
+ Examples: >
+ :echo map([1, 2, 3], lambda("return a:1 + 1"))
+< [2, 3, 4] >
+ :echo sort([3,7,2,1,4], lambda("return a:1 - a:2"))
+< [1, 2, 3, 4, 7]
+
+ Channel, job and timer can also be used with |lambda()|.
+ Example: >
+ :let timer = timer_start(500,
+ \ lambda("echo 'Handler called'"),
+ \ {'repeat': 3})
+<
+ Lambda function can reference the variables in the defined
+ scope.
+ Examples: >
+ :let s:x = 2
+ :echo filter([1, 2, 3], lambda("return a:1 >= s:x"))
+< [2, 3] >
+
+ :function! Foo()
+ : let x = 1
+ : return lambda("return x")
+ :endfunction
+ :echo Foo()()
+< 1
+
+ And if let new variable, it will be stored in the lambda's
+ scope.
+ Example: >
+ :function! Foo()
+ : call lambda("let x = 1")()
+ : echo x | " Should be error
+ :endfunction
+<
+ For example, you can create a counter generator. >
+ :function! s:counter(x)
+ : let x = a:x
+ : return lambda("
+ : \ let x += 1 \n
+ : \ return x
+ : \")
+ :endfunction
+
+ :let F = s:counter(0)
+ :echo F() | " 1
+ :echo F() | " 2
+ :echo F() | " 3
+ :echo F() | " 4
+<
*libcall()* *E364* *E368*
libcall({libname}, {funcname}, {argument})
Call function {funcname} in the run-time library {libname}
@@ -4931,18 +5004,19 @@ luaeval({expr}[, {expr}]) *luaeval()*
See |lua-luaeval| for more details.
{only available when compiled with the |+lua| feature}
-map({expr}, {string}) *map()*
- {expr} must be a |List| or a |Dictionary|.
- Replace each item in {expr} with the result of evaluating
- {string}.
- Inside {string} |v:val| has the value of the current item.
+map({expr1}, {expr2}) *map()*
+ {expr1} must be a |List| or a |Dictionary|.
+ Replace each item in {expr1} with the result of evaluating
+ {expr2}.
+ {expr2} must be a |string| or |Funcref|. If it is a |string|,
+ inside {expr2} |v:val| has the value of the current item.
For a |Dictionary| |v:key| has the key of the current item
and for a |List| |v:key| has the index of the current item.
Example: >
:call map(mylist, '"> " . v:val . " <"')
< This puts "> " before and " <" after each item in "mylist".
- Note that {string} is the result of an expression and is then
+ Note that {expr2} is the result of an expression and is then
used as an expression again. Often it is good to use a
|literal-string| to avoid having to double backslashes. You
still have to double ' quotes
@@ -4951,10 +5025,12 @@ map({expr}, {string}) *map()*
|Dictionary| to remain unmodified make a copy first: >
:let tlist = map(copy(mylist), ' v:val . "\t"')
-< Returns {expr}, the |List| or |Dictionary| that was filtered.
- When an error is encountered while evaluating {string} no
- further items in {expr} are processed.
+< Returns {expr1}, the |List| or |Dictionary| that was filtered.
+ When an error is encountered while evaluating {expr2} no
+ further items in {expr1} are processed.
+ {expr2} is possible to be given as |Funcref|. The function
+ should return value proceeded from "a:1".
maparg({name}[, {mode} [, {abbr} [, {dict}]]]) *maparg()*
When {dict} is omitted or zero: Return the rhs of mapping
diff --git a/src/eval.c b/src/eval.c
index bd4a11a..081b58c 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -163,6 +163,7 @@ static int echo_attr = 0; /* attributes used for ":echo" */
* Structure to hold info for a user function.
*/
typedef struct ufunc ufunc_T;
+typedef struct funccall_S funccall_T;
struct ufunc
{
@@ -191,6 +192,7 @@ struct ufunc
scid_T uf_script_ID; /* ID of script where function was defined,
used for s: variables */
int uf_refcount; /* for numbered function: reference count */
+ funccall_T *uf_scoped; /* l: local function variables */
char_u uf_name[1]; /* name of function (actually longer); can
start with <SNR>123_ (<SNR> is K_SPECIAL
KS_EXTRA KE_SNR) */
@@ -229,7 +231,6 @@ static ufunc_T dumuf;
#define FIXVAR_CNT 12 /* number of fixed variables */
/* structure to hold info for a function that is currently being executed. */
-typedef struct funccall_S funccall_T;
struct funccall_S
{
@@ -255,6 +256,10 @@ struct funccall_S
proftime_T prof_child; /* time spent in a child */
#endif
funccall_T *caller; /* calling function or NULL */
+
+ /* for lambda */
+ int ref_by_lambda;
+ int lambda_copyID; /* for garbage collection */
};
/*
@@ -654,6 +659,7 @@ static void f_js_decode(typval_T *argvars, typval_T *rettv);
static void f_js_encode(typval_T *argvars, typval_T *rettv);
static void f_json_decode(typval_T *argvars, typval_T *rettv);
static void f_json_encode(typval_T *argvars, typval_T *rettv);
+static void f_lambda(typval_T *argvars, typval_T *rettv);
static void f_keys(typval_T *argvars, typval_T *rettv);
static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv);
static void f_len(typval_T *argvars, typval_T *rettv);
@@ -3764,6 +3770,7 @@ do_unlet(char_u *name, int forceit)
char_u *varname;
dict_T *d;
dictitem_T *di;
+ funccall_T *old_current_funccal;
ht = find_var_ht(name, &varname);
if (ht != NULL && *varname != NUL)
@@ -3786,6 +3793,26 @@ do_unlet(char_u *name, int forceit)
return FAIL;
}
hi = hash_find(ht, varname);
+
+ if (HASHITEM_EMPTY(hi) && current_funccal != NULL &&
+ current_funccal->func->uf_scoped != NULL)
+ {
+ /* Search in parent scope for lambda */
+ old_current_funccal = current_funccal;
+ current_funccal = current_funccal->func->uf_scoped;
+ while (current_funccal)
+ {
+ ht = find_var_ht(name, &varname);
+ if (ht != NULL && *varname != NUL)
+ {
+ hi = hash_find(ht, varname);
+ if (!HASHITEM_EMPTY(hi))
+ break;
+ }
+ current_funccal = current_funccal->func->uf_scoped;
+ }
+ current_funccal = old_current_funccal;
+ }
if (!HASHITEM_EMPTY(hi))
{
di = HI2DI(hi);
@@ -6957,6 +6984,7 @@ garbage_collect(int testing)
* the item is referenced elsewhere the funccal must not be freed. */
for (fc = previous_funccal; fc != NULL; fc = fc->caller)
{
+ fc->lambda_copyID = copyID + 1;
abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1,
NULL);
abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1,
@@ -7333,6 +7361,25 @@ set_ref_in_item(
ht_stack, list_stack);
}
}
+ else if (tv->v_type == VAR_FUNC)
+ {
+ ufunc_T *fp;
+ funccall_T *fc;
+
+ fp = find_func(tv->vval.v_string);
+ if (fp != NULL)
+ {
+ for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped)
+ {
+ if (fc->lambda_copyID != copyID)
+ {
+ fc->lambda_copyID = copyID;
+ set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
+ set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
+ }
+ }
+ }
+ }
#ifdef FEAT_JOB_CHANNEL
else if (tv->v_type == VAR_JOB)
{
@@ -8517,6 +8564,7 @@ static struct fst
{"json_decode", 1, 1, f_json_decode},
{"json_encode", 1, 1, f_json_encode},
{"keys", 1, 1, f_keys},
+ {"lambda", 1, 1, f_lambda},
{"last_buffer_nr", 0, 0, f_last_buffer_nr},/* obsolete */
{"len", 1, 1, f_len},
{"libcall", 3, 3, f_libcall},
@@ -9090,7 +9138,7 @@ call_func(
rettv->vval.v_number = 0;
error = ERROR_UNKNOWN;
- if (!builtin_function(rfname, -1))
+ if (!builtin_function(rfname, -1) || !STRNICMP(rfname, "<LAMBDA>", 8))
{
/*
* User defined function.
@@ -11705,7 +11753,7 @@ findfilendir(
}
static void filter_map(typval_T *argvars, typval_T *rettv, int map);
-static int filter_map_one(typval_T *tv, char_u *expr, int map, int *remp);
+static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp);
/*
* Implementation of map() and filter().
@@ -11713,8 +11761,7 @@ static int filter_map_one(typval_T *tv, char_u *expr, int map, int *remp);
static void
filter_map(typval_T *argvars, typval_T *rettv, int map)
{
- char_u buf[NUMBUFLEN];
- char_u *expr;
+ typval_T *expr;
listitem_T *li, *nli;
list_T *l = NULL;
dictitem_T *di;
@@ -11749,14 +11796,13 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
return;
}
- expr = get_tv_string_buf_chk(&argvars[1], buf);
+ expr = &argvars[1];
/* On type errors, the preceding call has already displayed an error
* message. Avoid a misleading error message for an empty string that
* was not passed as argument. */
- if (expr != NULL)
+ if (expr->v_type != VAR_UNKNOWN)
{
prepare_vimvar(VV_VAL, &save_val);
- expr = skipwhite(expr);
/* We reset "did_emsg" to be able to detect whether an error
* occurred during evaluation of the expression. */
@@ -11828,21 +11874,31 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
}
static int
-filter_map_one(typval_T *tv, char_u *expr, int map, int *remp)
+filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp)
{
typval_T rettv;
char_u *s;
int retval = FAIL;
+ int dummy;
copy_tv(tv, &vimvars[VV_VAL].vv_tv);
- s = expr;
- if (eval1(&s, &rettv, TRUE) == FAIL)
- goto theend;
- if (*s != NUL) /* check for trailing chars after expr */
+ s = expr->vval.v_string;
+ if (expr->v_type == VAR_FUNC)
{
- EMSG2(_(e_invexpr2), s);
- clear_tv(&rettv);
- goto theend;
+ if (call_func(s, (int)STRLEN(s),
+ &rettv, 1, tv, 0L, 0L, &dummy, TRUE, NULL, NULL) == FALSE)
+ goto theend;
+ }
+ else
+ {
+ s = skipwhite(s);
+ if (eval1(&s, &rettv, TRUE) == FAIL)
+ goto theend;
+ if (*s != NUL) /* check for trailing chars after expr */
+ {
+ EMSG2(_(e_invexpr2), s);
+ goto theend;
+ }
}
if (map)
{
@@ -15191,6 +15247,80 @@ f_keys(typval_T *argvars, typval_T *rettv)
}
/*
+ * "lambda()" function.
+ */
+ static void
+f_lambda(typval_T *argvars, typval_T *rettv)
+{
+ char_u *s, *e;
+ garray_T newargs;
+ garray_T newlines;
+ ufunc_T *fp;
+ char_u name[20];
+ static int lambda_no = 0;
+
+ if (check_secure())
+ return;
+
+ s = get_tv_string_chk(&argvars[0]);
+ if (s == NULL)
+ goto erret;
+
+ fp = (ufunc_T *)alloc((unsigned)(sizeof(ufunc_T) + 20));
+ if (fp == NULL)
+ goto erret;
+
+ sprintf((char*)name, "<LAMBDA>%d", ++lambda_no);
+ rettv->vval.v_string = vim_strsave(name);
+
+ ga_init2(&newargs, (int)sizeof(char_u *), 1);
+ ga_init2(&newlines, (int)sizeof(char_u *), 1);
+
+ while (*s)
+ {
+ s = skipwhite(s);
+ e = s;
+ while (*e && *e != '\n')
+ e++;
+ if (ga_grow(&newlines, 1) == FAIL)
+ goto erret;
+ ((char_u **)(newlines.ga_data))[newlines.ga_len++] = vim_strnsave(s, e - s);
+ s = *e == 0 ? e : e + 1;
+ }
+
+ 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;
+ if (current_funccal)
+ {
+ fp->uf_scoped = current_funccal;
+ current_funccal->ref_by_lambda = TRUE;
+ }
+ else
+ fp->uf_scoped = NULL;
+ rettv->v_type = VAR_FUNC;
+ return;
+
+erret:
+ ga_clear_strings(&newargs);
+ ga_clear_strings(&newlines);
+}
+
+/*
* "last_buffer_nr()" function.
*/
static void
@@ -20642,8 +20772,15 @@ get_callback(typval_T *arg, partial_T **pp)
return (*pp)->pt_name;
}
*pp = NULL;
- if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING)
+ if (arg->v_type == VAR_FUNC)
+ {
+ func_ref(arg->vval.v_string);
+ return arg->vval.v_string;
+ }
+ if (arg->v_type == VAR_STRING)
+ {
return arg->vval.v_string;
+ }
if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0)
return (char_u *)"";
EMSG(_("E921: Invalid callback argument"));
@@ -22712,6 +22849,34 @@ get_tv_string_buf_chk(typval_T *varp, char_u *buf)
return NULL;
}
+ static dictitem_T*
+find_var_in_scoped_ht(char_u *name, char_u **varname, int no_autoload)
+{
+ dictitem_T *v = NULL;
+ funccall_T *old_current_funccal = current_funccal;
+ hashtab_T *ht;
+
+ /* Search in parent scope which is possible to reference from lambda */
+ current_funccal = current_funccal->func->uf_scoped;
+ while (current_funccal)
+ {
+ ht = find_var_ht(name, varname ? &(*varname) : NULL);
+ if (ht != NULL)
+ {
+ v = find_var_in_ht(ht, *name,
+ varname ? *varname : NULL, no_autoload);
+ if (v != NULL)
+ break;
+ }
+ if (current_funccal == current_funccal->func->uf_scoped)
+ break;
+ current_funccal = current_funccal->func->uf_scoped;
+ }
+ current_funccal = old_current_funccal;
+
+ return v;
+}
+
/*
* Find variable "name" in the list of variables.
* Return a pointer to it if found, NULL if not found.
@@ -22724,13 +22889,23 @@ find_var(char_u *name, hashtab_T **htp, int no_autoload)
{
char_u *varname;
hashtab_T *ht;
+ dictitem_T *ret = NULL;
ht = find_var_ht(name, &varname);
if (htp != NULL)
*htp = ht;
if (ht == NULL)
return NULL;
- return find_var_in_ht(ht, *name, varname, no_autoload || htp != NULL);
+ ret = find_var_in_ht(ht, *name, varname, no_autoload || htp != NULL);
+ if (ret != NULL)
+ return ret;
+
+ /* Search in parent scope for lambda */
+ if (current_funccal != NULL && current_funccal->func->uf_scoped != NULL)
+ return find_var_in_scoped_ht(name, varname ? &varname : NULL,
+ no_autoload || htp != NULL);
+
+ return NULL;
}
/*
@@ -23093,6 +23268,24 @@ set_var(
}
v = find_var_in_ht(ht, 0, varname, TRUE);
+ /* Search in parent scope which is possible to reference from lambda */
+ if (v == NULL && current_funccal != NULL &&
+ current_funccal->func->uf_scoped != NULL)
+ {
+ v = find_var_in_scoped_ht(name, varname ? &varname : NULL, TRUE);
+
+ /* When the variable is not found, let scope should be parent of the
+ * lambda.
+ */
+ if (v == NULL)
+ {
+ hashtab_T *ht_tmp;
+ ht_tmp = find_var_ht(name, &varname);
+ if (ht_tmp != NULL)
+ ht = ht_tmp;
+ }
+ }
+
if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL)
&& var_check_func_name(name, v == NULL))
return;
@@ -24345,6 +24538,7 @@ ex_function(exarg_T *eap)
fp->uf_flags = flags;
fp->uf_calls = 0;
fp->uf_script_ID = current_SID;
+ fp->uf_scoped = NULL;
goto ret_free;
erret:
@@ -25163,7 +25357,9 @@ func_unref(char_u *name)
{
ufunc_T *fp;
- if (name != NULL && isdigit(*name))
+ if (name == NULL)
+ return;
+ else if (isdigit(*name))
{
fp = find_func(name);
if (fp == NULL)
@@ -25176,6 +25372,18 @@ func_unref(char_u *name)
func_free(fp);
}
}
+ else if (!STRNICMP(name, "<LAMBDA>", 8))
+ {
+ /* 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);
+ }
+ }
}
/*
@@ -25186,7 +25394,9 @@ func_ref(char_u *name)
{
ufunc_T *fp;
- if (name != NULL && isdigit(*name))
+ if (name == NULL)
+ return;
+ else if (isdigit(*name))
{
fp = find_func(name);
if (fp == NULL)
@@ -25194,6 +25404,13 @@ func_ref(char_u *name)
else
++fp->uf_refcount;
}
+ else if (!STRNICMP(name, "<LAMBDA>", 8))
+ {
+ /* fail silently, when lambda function isn't found. */
+ fp = find_func(name);
+ if (fp != NULL)
+ ++fp->uf_refcount;
+ }
}
/*
@@ -25251,6 +25468,9 @@ call_user_func(
/* Check if this function has a breakpoint. */
fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0);
fc->dbg_tick = debug_tick;
+ /* Set up fields for lambda. */
+ fc->ref_by_lambda = FALSE;
+ fc->lambda_copyID = current_copyID;
/*
* Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables
@@ -25534,7 +25754,8 @@ call_user_func(
* free the funccall_T and what's in it. */
if (fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT
&& fc->l_vars.dv_refcount == DO_NOT_FREE_CNT
- && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT)
+ && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT
+ && !fc->ref_by_lambda)
{
free_funccal(fc, FALSE);
}
@@ -25577,7 +25798,8 @@ can_free_funccal(funccall_T *fc, int copyID)
{
return (fc->l_varlist.lv_copyID != copyID
&& fc->l_vars.dv_copyID != copyID
- && fc->l_avars.dv_copyID != copyID);
+ && fc->l_avars.dv_copyID != copyID
+ && (!fc->ref_by_lambda && fc->lambda_copyID != copyID));
}
/*
diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c
index 657c772..5488de7 100644
--- a/src/ex_cmds2.c
+++ b/src/ex_cmds2.c
@@ -1122,8 +1122,11 @@ remove_timer(timer_T *timer)
static void
free_timer(timer_T *timer)
{
+ if (timer->tr_partial == NULL)
+ func_unref(timer->tr_callback);
+ else
+ partial_unref(timer->tr_partial);
vim_free(timer->tr_callback);
- partial_unref(timer->tr_partial);
vim_free(timer);
}
diff --git a/src/testdir/test_alot.vim b/src/testdir/test_alot.vim
index d393fe7..3e719e1 100644
--- a/src/testdir/test_alot.vim
+++ b/src/testdir/test_alot.vim
@@ -14,6 +14,7 @@ source test_file_perm.vim
source test_glob2regpat.vim
source test_help_tagjump.vim
source test_join.vim
+source test_lambda.vim
source test_lispwords.vim
source test_matchstrpos.vim
source test_menu.vim
diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim
index 05df50a..da4729d 100644
--- a/src/testdir/test_channel.vim
+++ b/src/testdir/test_channel.vim
@@ -182,6 +182,19 @@ func s:communicate(port)
endif
call assert_equal('got it', s:responseMsg)
+ if exists('*lambda')
+ let s:responseMsg = ''
+ call ch_sendexpr(handle, 'hello!', {'callback': lambda(':return s:RequestHandler(a:1, a:2)')})
+ call s:waitFor('exists("s:responseHandle")')
+ if !exists('s:responseHandle')
+ call assert_false(1, 's:responseHandle was not set')
+ else
+ call assert_equal(handle, s:responseHandle)
+ unlet s:responseHandle
+ endif
+ call assert_equal('got it', s:responseMsg)
+ endif
+
" Collect garbage, tests that our handle isn't collected.
call garbagecollect_for_testing()
@@ -1048,6 +1061,32 @@ func Test_out_cb()
endtry
endfunc
+func Test_out_cb_lambda()
+ if !has('job') || !exists('*lambda')
+ return
+ endif
+ call ch_log('Test_out_cb_lambda()')
+
+ let job = job_start(s:python . " test_channel_pipe.py",
+ \ {'out_cb': lambda(":let s:outmsg = 'lambda: ' . a:2"),
+ \ 'out_mode': 'json',
+ \ 'err_cb': lambda(":let s:errmsg = 'lambda: ' . a:2"),
+ \ 'err_mode': 'json'})
+ call assert_equal("run", job_status(job))
+ try
+ let s:outmsg = ''
+ let s:errmsg = ''
+ call ch_sendraw(job, "echo [0, \"hello\"]\n")
+ call ch_sendraw(job, "echoerr [0, \"there\"]\n")
+ call s:waitFor('s:outmsg != ""')
+ call assert_equal("lambda: hello", s:outmsg)
+ call s:waitFor('s:errmsg != ""')
+ call assert_equal("lambda: there", s:errmsg)
+ finally
+ call job_stop(job)
+ endtry
+endfunc
+
""""""""""
let s:unletResponse = ''
@@ -1249,6 +1288,28 @@ func Test_using_freed_memory()
call garbagecollect_for_testing()
endfunc
+function s:test_close_lambda(port)
+ if !exists('*lambda')
+ return
+ endif
+
+ let handle = ch_open('localhost:' . a:port, s:chopt)
+ if ch_status(handle) == "fail"
+ call assert_false(1, "Can't open channel")
+ return
+ endif
+ let s:close_ret = ''
+ call ch_setoptions(handle, {'close_cb': lambda("let s:close_ret = 'closed'")})
+
+ call assert_equal('', ch_evalexpr(handle, 'close me'))
+ call s:waitFor('"closed" == s:close_ret')
+ call assert_equal('closed', s:close_ret)
+endfunc
+
+func Test_close_lambda()
+ call ch_log('Test_close_lambda()')
+ call s:run_server('s:test_close_lambda')
+endfunc
" Uncomment this to see what happens, output is in src/testdir/channellog.
diff --git a/src/testdir/test_lambda.vim b/src/testdir/test_lambda.vim
new file mode 100644
index 0000000..f96e5af
--- /dev/null
+++ b/src/testdir/test_lambda.vim
@@ -0,0 +1,249 @@
+function! Test_lambda_with_filter()
+ let s:x = 2
+ call assert_equal([2, 3], filter([1, 2, 3], lambda('return a:1 >= s:x')))
+endfunction
+
+function! Test_lambda_with_map()
+ let s:x = 1
+ call assert_equal([2, 3, 4], map([1, 2, 3], lambda('return a:1 + s:x')))
+endfunction
+
+function! Test_lambda_with_sort()
+ call assert_equal([1, 2, 3, 4, 7], sort([3,7,2,1,4], lambda('return a:1 - a:2')))
+endfunction
+
+function! Test_lambda_in_local_variable()
+ let l:X = lambda("let x = 1 | return x + a:1")
+ call assert_equal(2, l:X(1))
+ call assert_equal(3, l:X(2))
+endfunction
+
+function! Test_lambda_capture_by_reference()
+ let v = 1
+ let l:F = lambda('return a:1 + v')
+ let v = 2
+ call assert_equal(12, l:F(10))
+endfunction
+
+function! Test_lambda_side_effect()
+ function! s:update_and_return(arr)
+ let a:arr[1] = 5
+ return a:arr
+ endfunction
+
+ function! s:foo(arr)
+ return lambda('return s:update_and_return(a:arr)')
+ endfunction
+
+ let arr = [3,2,1]
+ call assert_equal([3, 5, 1], s:foo(arr)())
+endfunction
+
+function! Test_lambda_refer_local_variable_from_other_scope()
+ function! s:foo(X)
+ return a:X() " refer l:x in s:bar()
+ endfunction
+
+ function! s:bar()
+ let x = 123
+ return s:foo(lambda('return x'))
+ endfunction
+
+ call assert_equal(123, s:bar())
+endfunction
+
+function! Test_lambda_do_not_share_local_variable()
+ function! s:define_funcs()
+ let l:One = lambda('let a = 111 | return a')
+ let l:Two = lambda('return exists("a") ? a : "no"')
+ return [l:One, l:Two]
+ endfunction
+
+ let l:F = s:define_funcs()
+
+ call assert_equal('no', l:F[1]())
+ call assert_equal(111, l:F[0]())
+ call assert_equal('no', l:F[1]())
+endfunction
+
+function! Test_lambda_closure()
+ function! s:foo()
+ let x = 0
+ return lambda("let x += 1 | return x")
+ endfunction
+
+ let l:F = s:foo()
+ call assert_equal(1, l:F())
+ call assert_equal(2, l:F())
+ call assert_equal(3, l:F())
+ call assert_equal(4, l:F())
+endfunction
+
+function! Test_lambda_with_a_var()
+ function! s:foo()
+ let x = 2
+ return lambda('return a:000 + [x]')
+ endfunction
+ function! s:bar()
+ return s:foo()(1)
+ endfunction
+
+ call assert_equal([1, 2], s:bar())
+endfunction
+
+function! Test_lambda_in_lambda()
+ let l:Counter_generator = lambda(':let init = a:1 | return lambda("let init += 1 | return init")')
+ let l:Counter = l:Counter_generator(0)
+ let l:Counter2 = l:Counter_generator(9)
+
+ call assert_equal(1, l:Counter())
+ call assert_equal(2, l:Counter())
+ call assert_equal(3, l:Counter())
+
+ call assert_equal(10, l:Counter2())
+ call assert_equal(11, l:Counter2())
+ call assert_equal(12, l:Counter2())
+endfunction
+
+function! Test_lambda_unlet()
+ function! s:foo()
+ let x = 1
+ call lambda('unlet x')()
+ return l:
+ endfunction
+
+ call assert_false(has_key(s:foo(), 'x'))
+endfunction
+
+function! Test_lambda_call_lambda_from_lambda()
+ function! s:foo(x)
+ let l:F1 = lambda('
+ \ return lambda("return a:x")')
+ return lambda('return l:F1()')
+ endfunction
+
+ let l:F = s:foo(1)
+ call assert_equal(1, l:F()())
+endfunction
+
+function! Test_lambda_garbage_collection()
+ function! s:new_counter()
+ let c = 0
+ return lambda('let c += 1 | return c')
+ endfunction
+
+ let l:C = s:new_counter()
+ call garbagecollect()
+ call assert_equal(1, l:C())
+ call assert_equal(2, l:C())
+ call assert_equal(3, l:C())
+ call assert_equal(4, l:C())
+endfunction
+
+function! Test_lambda_delfunc()
+ function! s:gen()
+ let pl = l:
+ let l:Hoge = lambda('return get(pl, "Hoge", get(pl, "Fuga", lambda("")))')
+ let l:Fuga = l:Hoge
+ delfunction l:Hoge
+ return l:Fuga
+ endfunction
+
+ let l:F = s:gen()
+ call assert_fails(':call l:F()', 'E117:')
+endfunction
+
+function! Test_lambda_gen_lambda_from_funcdef()
+ function! s:GetFuncDef(fr)
+ redir => str
+ silent execute 'function a:fr'
+ redir END
+ delfunction a:fr
+ let lines = split(str, '\n')
+ let lines = map(lines, 'substitute(v:val, "\\\m^\\\d*\\\s* ", "", "")')
+ let lines = lines[1:-2]
+ return join(lines, "\n")
+ endfunction
+
+ function! s:NewCounter()
+ let n = 0
+ function! s:Countup()
+ let n += 1
+ return n
+ endfunction
+ return lambda(s:GetFuncDef(function('s:Countup')))
+ endfunction
+
+ let l:C = s:NewCounter()
+ let l:D = s:NewCounter()
+
+ call assert_equal(1, l:C())
+ call assert_equal(2, l:C())
+ call assert_equal(3, l:C())
+ call assert_equal(1, l:D())
+ call assert_equal(2, l:D())
+ call assert_equal(3, l:D())
+endfunction
+
+function! Test_lambda_scope()
+ function! s:NewCounter()
+ let c = 0
+ return lambda('let c += 1 | return c')
+ endfunction
+
+ function! s:NewCounter2()
+ return lambda('let c += 100 | return c')
+ endfunction
+
+ let l:C = s:NewCounter()
+ let l:D = s:NewCounter2()
+
+ call assert_equal(1, l:C())
+ call assert_fails(':call l:D()', 'E15:') " E121: then E15:
+ call assert_equal(2, l:C())
+endfunction
+
+function! Test_lambdas_share_scope()
+ function! s:New()
+ let c = 0
+ let l:Inc0 = lambda('let c += 1 | return c')
+ let l:Dec0 = lambda('let c -= 1 | return c')
+ return [l:Inc0, l:Dec0]
+ endfunction
+
+ let [l:Inc, l:Dec] = s:New()
+
+ call assert_equal(1, l:Inc())
+ call assert_equal(2, l:Inc())
+ call assert_equal(1, l:Dec())
+endfunction
+
+function! Test_lambda_in_sandbox()
+ call assert_fails(':sandbox call lambda("")', 'E48:')
+endfunction
+
+function! Test_lambda_with_timer()
+ if !has('timers')
+ return
+ endif
+
+ let s:n = 0
+ let s:timer_id = 0
+ function! s:Foo()
+ let n = 0
+ let s:timer_id = timer_start(50, lambda("let n += 1 | let s:n = n | echo n"), {"repeat": -1})
+ endfunction
+
+ call s:Foo()
+ sleep 100ms
+ " do not collect lambda nor l:n in lambda
+ call garbagecollect_for_testing()
+ sleep 100ms
+ call timer_stop(s:timer_id)
+ call assert_true(s:n > 3)
+endfunction
+
+function! Test_lambda_with_partial()
+ let l:Cb = function(lambda(":return ['zero', a:1, a:2, a:3]"), ['one', 'two'])
+ call assert_equal(['zero', 'one', 'two', 'three'], l:Cb('three'))
+endfunction
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment