Hi mattn and all, 2016/5/27 Fri 22:38:34 UTC+9 Ken Takata wrote: > Hi mattn, > > 2016/4/18 Mon 0:30:19 UTC+9 mattn wrote: > > Hi, Bram and list. > > > > Updated lambda patch to be applied for latest code. > > > > https://gist.github.com/mattn/5bc8ded21e1033c9c0ea8cd5ecbbce11 > > > > This include examples for timer on help file. I'm thinking lambda() have > > cooperative to work with job/timer/channel. > > > > So I hope to add this into vim8. How do you think? > > > > Thanks to k-takata, haya14busa, and all of members on vim-jp. > > I have tested the lambda patch with the latest Vim and I found two problems. > > 1. garbagecollect_for_testing() was renamed. > 2. Test_lambda_with_timer() fails on Cygwin. It seems that we need margins > as we do in test_timers.vim. > > Please check attached patch. > > I hope that the lambda patch will be merged in Vim 8.0. > This makes filter(), map() and sort() easy to use. > It also works nicely with job/channel/timer features.
I have merged my patch into your patch, and also fixed that the function declaration was not sorted in alphabetical order. Please check the attached patch. Regards, Ken Takata -- -- You received this message from the "vim_dev" maillist. Do not top-post! Type your reply below the text you are replying to. For more information, visit http://www.vim.org/maillist.php --- You received this message because you are subscribed to the Google Groups "vim_dev" group. To unsubscribe from this group and stop receiving emails from it, send an email to vim_dev+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
# HG changeset patch # Parent 96b65a1b79647a0144a4926733a6ef48cc338108 diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -123,9 +123,10 @@ You will not get an error if you try to 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() @@ -2057,6 +2058,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 @@ -3511,11 +3513,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"') @@ -3525,7 +3528,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. @@ -3533,9 +3536,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()* @@ -4878,6 +4885,72 @@ len({expr}) The result is a Number, whic |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} @@ -5024,18 +5097,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 @@ -5044,10 +5118,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 --- a/src/eval.c +++ b/src/eval.c @@ -166,6 +166,7 @@ static int echo_attr = 0; /* attribute * Structure to hold info for a user function. */ typedef struct ufunc ufunc_T; +typedef struct funccall_S funccall_T; struct ufunc { @@ -194,6 +195,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) */ @@ -232,7 +234,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 { @@ -258,6 +259,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 */ }; /* @@ -659,6 +664,7 @@ static void f_js_encode(typval_T *argvar 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_keys(typval_T *argvars, typval_T *rettv); +static void f_lambda(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); static void f_libcall(typval_T *argvars, typval_T *rettv); @@ -3786,6 +3792,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) @@ -3808,6 +3815,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); @@ -7053,6 +7080,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, @@ -7437,6 +7465,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) { @@ -8652,6 +8699,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}, @@ -9236,7 +9284,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. @@ -11841,7 +11889,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(). @@ -11849,8 +11897,7 @@ static int filter_map_one(typval_T *tv, 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; @@ -11885,14 +11932,13 @@ filter_map(typval_T *argvars, typval_T * 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. */ @@ -11964,21 +12010,31 @@ filter_map(typval_T *argvars, typval_T * } 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 */ - { - EMSG2(_(e_invexpr2), s); - clear_tv(&rettv); - goto theend; + s = expr->vval.v_string; + if (expr->v_type == VAR_FUNC) + { + 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) { @@ -15385,6 +15441,82 @@ f_keys(typval_T *argvars, typval_T *rett } /* + * "lambda()" function. + */ + static void +f_lambda(argvars, rettv) + typval_T *argvars UNUSED; + 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 @@ -20917,8 +21049,15 @@ get_callback(typval_T *arg, partial_T ** 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")); @@ -23001,6 +23140,37 @@ get_tv_string_buf_chk(typval_T *varp, ch return NULL; } + static dictitem_T* +find_var_in_scoped_ht(name, varname, no_autoload) + 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. @@ -23013,13 +23183,23 @@ find_var(char_u *name, hashtab_T **htp, { 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; } /* @@ -23382,6 +23562,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; @@ -24634,6 +24832,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: @@ -25452,7 +25651,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) @@ -25470,6 +25671,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); + } + } } /* @@ -25480,7 +25693,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) @@ -25488,6 +25703,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; + } } /* @@ -25545,6 +25767,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 @@ -25828,7 +26053,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); } @@ -25871,7 +26097,8 @@ can_free_funccal(funccall_T *fc, int cop { 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 --- a/src/ex_cmds2.c +++ b/src/ex_cmds2.c @@ -1121,8 +1121,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 --- a/src/testdir/test_alot.vim +++ b/src/testdir/test_alot.vim @@ -16,6 +16,7 @@ source test_glob2regpat.vim source test_goto.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 --- 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 test_garbagecollect_now() @@ -1134,6 +1147,32 @@ func Test_read_in_close_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 = '' @@ -1350,6 +1389,28 @@ func Test_collapse_buffers() bwipe! 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. " call ch_logfile('channellog', 'w') diff --git a/src/testdir/test_lambda.vim b/src/testdir/test_lambda.vim new file mode 100644 --- /dev/null +++ b/src/testdir/test_lambda.vim @@ -0,0 +1,252 @@ +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 200ms + " do not collect lambda nor l:n in lambda + call test_garbagecollect_now() + let m = s:n + sleep 200ms + call timer_stop(s:timer_id) + call assert_true(m > 1) + call assert_true(s:n > m + 1) + call assert_true(s:n < 9) +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