Last active
April 18, 2016 01:32
-
-
Save mattn/5bc8ded21e1033c9c0ea8cd5ecbbce11 to your computer and use it in GitHub Desktop.
lambda patch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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