To: vim_dev@googlegroups.com Subject: Patch 7.4.2120 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 7.4.2120 Problem: User defined functions can't be a closure. Solution: Add the "closure" argument. Allow using :unlet on a bound variable. (Yasuhiro Matsumoto, Ken Takata) Files: runtime/doc/eval.txt, src/testdir/test_lambda.vim, src/userfunc.c, src/eval.c src/proto/userfunc.pro *** ../vim-7.4.2119/runtime/doc/eval.txt 2016-07-29 22:14:39.031998331 +0200 --- runtime/doc/eval.txt 2016-07-29 22:25:24.133969732 +0200 *************** *** 1231,1236 **** --- 1240,1246 ---- :let Bar = Foo(4) :echo Bar(6) < 5 + See also |:func-closure|. Examples for using a lambda expression with |sort()|, |map()| and |filter()|: > :echo map([1, 2, 3], {idx, val -> val + 1}) *************** *** 8086,8096 **** See |:verbose-cmd| for more information. *E124* *E125* *E853* *E884* ! :fu[nction][!] {name}([arguments]) [range] [abort] [dict] Define a new function by the name {name}. The name must be made of alphanumeric characters and '_', and must start with a capital or "s:" (see above). Note ! that using "b:" or "g:" is not allowed. {name} can also be a |Dictionary| entry that is a |Funcref|: > --- 8218,8231 ---- See |:verbose-cmd| for more information. *E124* *E125* *E853* *E884* ! :fu[nction][!] {name}([arguments]) [range] [abort] [dict] [closure] Define a new function by the name {name}. The name must be made of alphanumeric characters and '_', and must start with a capital or "s:" (see above). Note ! that using "b:" or "g:" is not allowed. (since patch ! 7.4.260 E884 is given if the function name has a colon ! in the name, e.g. for "foo:bar()". Before that patch ! no error was given). {name} can also be a |Dictionary| entry that is a |Funcref|: > *************** *** 8109,8115 **** For the {arguments} see |function-argument|. ! *a:firstline* *a:lastline* When the [range] argument is added, the function is expected to take care of a range itself. The range is passed as "a:firstline" and "a:lastline". If [range] --- 8244,8250 ---- For the {arguments} see |function-argument|. ! *:func-range* *a:firstline* *a:lastline* When the [range] argument is added, the function is expected to take care of a range itself. The range is passed as "a:firstline" and "a:lastline". If [range] *************** *** 8118,8131 **** of each line. See |function-range-example|. The cursor is still moved to the first line of the range, as is the case with all Ex commands. ! When the [abort] argument is added, the function will abort as soon as an error is detected. ! When the [dict] argument is added, the function must be invoked through an entry in a |Dictionary|. The local variable "self" will then be set to the dictionary. See |Dictionary-function|. *function-search-undo* The last used search pattern and the redo command "." --- 8253,8288 ---- of each line. See |function-range-example|. The cursor is still moved to the first line of the range, as is the case with all Ex commands. ! *:func-abort* When the [abort] argument is added, the function will abort as soon as an error is detected. ! *:func-dict* When the [dict] argument is added, the function must be invoked through an entry in a |Dictionary|. The local variable "self" will then be set to the dictionary. See |Dictionary-function|. + *:func-closure* *E932* + When the [closure] argument is added, the function + can access variables and arguments from the outer + scope. This is usually called a closure. In this + example Bar() uses "x" from the scope of Foo(). It + remains referenced even after Foo() returns: > + :function! Foo() + : let x = 0 + : function! Bar() closure + : let x += 1 + : return x + : endfunction + : return function('Bar') + :endfunction + + :let F = Foo() + :echo F() + < 1 > + :echo F() + < 2 > + :echo F() + < 3 *function-search-undo* The last used search pattern and the redo command "." *** ../vim-7.4.2119/src/testdir/test_lambda.vim 2016-07-29 22:14:39.035998293 +0200 --- src/testdir/test_lambda.vim 2016-07-29 22:26:55.925119254 +0200 *************** *** 1,3 **** --- 1,5 ---- + " Test for lambda and closure + function! Test_lambda_with_filter() let s:x = 2 call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x})) *************** *** 100,106 **** call assert_equal('no', l:F[1]()) endfunction ! function! Test_lambda_closure() function! s:foo() let x = 0 return {-> [execute("let x += 1"), x][-1]} --- 102,108 ---- call assert_equal('no', l:F[1]()) endfunction ! function! Test_lambda_closure_counter() function! s:foo() let x = 0 return {-> [execute("let x += 1"), x][-1]} *************** *** 209,211 **** --- 211,245 ---- let Fact = {f -> {x -> x == 0 ? 1 : x * f(x - 1)}} call assert_equal(120, Z(Fact)(5)) endfunction + + function! Test_closure_counter() + function! s:foo() + let x = 0 + function! s:bar() closure + let x += 1 + return x + endfunction + return function('s:bar') + endfunction + + let l:F = s:foo() + call test_garbagecollect_now() + 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_closure_unlet() + function! s:foo() + let x = 1 + function! s:bar() closure + unlet x + endfunction + call s:bar() + return l: + endfunction + + call assert_false(has_key(s:foo(), 'x')) + call test_garbagecollect_now() + endfunction *** ../vim-7.4.2119/src/userfunc.c 2016-07-29 22:14:39.035998293 +0200 --- src/userfunc.c 2016-07-29 22:29:11.151865988 +0200 *************** *** 59,64 **** --- 59,65 ---- #define FC_ABORT 1 /* abort function on error */ #define FC_RANGE 2 /* function accepts range */ #define FC_DICT 4 /* Dict function, uses "self" */ + #define FC_CLOSURE 8 /* closure, uses outer scope variables */ /* From user function to hashitem and back. */ #define UF2HIKEY(fp) ((fp)->uf_name) *************** *** 312,318 **** if (evaluate) { ! int len; char_u *p; sprintf((char*)name, "%d", ++lambda_no); --- 313,319 ---- if (evaluate) { ! int len, flags = 0; char_u *p; sprintf((char*)name, "%d", ++lambda_no); *************** *** 341,346 **** --- 342,348 ---- fp->uf_lines = newlines; if (current_funccal != NULL && eval_lavars) { + flags |= FC_CLOSURE; fp->uf_scoped = current_funccal; current_funccal->fc_refcount++; if (ga_grow(¤t_funccal->fc_funcs, 1) == FAIL) *************** *** 361,367 **** func_do_profile(fp); #endif fp->uf_varargs = TRUE; ! fp->uf_flags = 0; fp->uf_calls = 0; fp->uf_script_ID = current_SID; --- 363,369 ---- func_do_profile(fp); #endif fp->uf_varargs = TRUE; ! fp->uf_flags = flags; fp->uf_calls = 0; fp->uf_script_ID = current_SID; *************** *** 1487,1492 **** --- 1489,1496 ---- MSG_PUTS(" range"); if (fp->uf_flags & FC_DICT) MSG_PUTS(" dict"); + if (fp->uf_flags & FC_CLOSURE) + MSG_PUTS(" closure"); msg_clr_eos(); if (p_verbose > 0) last_set_msg(fp->uf_script_ID); *************** *** 1948,1954 **** if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) goto errret_2; ! /* find extra arguments "range", "dict" and "abort" */ for (;;) { p = skipwhite(p); --- 1952,1958 ---- if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) goto errret_2; ! /* find extra arguments "range", "dict", "abort" and "closure" */ for (;;) { p = skipwhite(p); *************** *** 1967,1972 **** --- 1971,1981 ---- flags |= FC_ABORT; p += 5; } + else if (STRNCMP(p, "closure", 7) == 0) + { + flags |= FC_CLOSURE; + p += 7; + } else break; } *************** *** 2299,2305 **** } fp->uf_args = newargs; fp->uf_lines = newlines; ! fp->uf_scoped = NULL; #ifdef FEAT_PROFILE fp->uf_tml_count = NULL; fp->uf_tml_total = NULL; --- 2308,2332 ---- } fp->uf_args = newargs; fp->uf_lines = newlines; ! if ((flags & FC_CLOSURE) != 0) ! { ! if (current_funccal == NULL) ! { ! emsg_funcname(N_("E932 Closure function should not be at top level: %s"), ! name); ! goto erret; ! } ! fp->uf_scoped = current_funccal; ! current_funccal->fc_refcount++; ! if (ga_grow(¤t_funccal->fc_funcs, 1) == FAIL) ! goto erret; ! ((ufunc_T **)current_funccal->fc_funcs.ga_data) ! [current_funccal->fc_funcs.ga_len++] = fp; ! func_ref(current_funccal->func->uf_name); ! } ! else ! fp->uf_scoped = NULL; ! #ifdef FEAT_PROFILE fp->uf_tml_count = NULL; fp->uf_tml_total = NULL; *************** *** 3536,3541 **** --- 3563,3604 ---- } /* + * Search hashitem in parent scope. + */ + hashitem_T * + find_hi_in_scoped_ht(char_u *name, char_u **varname, hashtab_T **pht) + { + funccall_T *old_current_funccal = current_funccal; + hashtab_T *ht; + hashitem_T *hi = NULL; + + if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) + return NULL; + + /* 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); + if (ht != NULL && **varname != NUL) + { + hi = hash_find(ht, *varname); + if (!HASHITEM_EMPTY(hi)) + { + *pht = ht; + break; + } + } + if (current_funccal == current_funccal->func->uf_scoped) + break; + current_funccal = current_funccal->func->uf_scoped; + } + current_funccal = old_current_funccal; + + return hi; + } + + /* * Search variable in parent scope. */ dictitem_T * *** ../vim-7.4.2119/src/eval.c 2016-07-29 22:14:39.035998293 +0200 --- src/eval.c 2016-07-29 22:26:55.925119254 +0200 *************** *** 2837,2843 **** } } hi = hash_find(ht, varname); ! if (!HASHITEM_EMPTY(hi)) { di = HI2DI(hi); if (var_check_fixed(di->di_flags, name, FALSE) --- 2837,2845 ---- } } hi = hash_find(ht, varname); ! if (HASHITEM_EMPTY(hi)) ! hi = find_hi_in_scoped_ht(name, &varname, &ht); ! if (hi != NULL && !HASHITEM_EMPTY(hi)) { di = HI2DI(hi); if (var_check_fixed(di->di_flags, name, FALSE) *** ../vim-7.4.2119/src/proto/userfunc.pro 2016-07-29 22:14:39.035998293 +0200 --- src/proto/userfunc.pro 2016-07-29 22:26:55.925119254 +0200 *************** *** 46,51 **** --- 46,52 ---- void restore_current_funccal(void *f); void list_func_vars(int *first); dict_T *get_current_funccal_dict(hashtab_T *ht); + hashitem_T *find_hi_in_scoped_ht(char_u *name, char_u **varname, hashtab_T **pht); dictitem_T *find_var_in_scoped_ht(char_u *name, char_u **varname, int no_autoload); int set_ref_in_previous_funccal(int copyID); int set_ref_in_call_stack(int copyID); *** ../vim-7.4.2119/src/version.c 2016-07-29 22:14:39.035998293 +0200 --- src/version.c 2016-07-29 22:19:50.353073117 +0200 *************** *** 760,761 **** --- 760,763 ---- { /* Add new patch number below this line */ + /**/ + 2120, /**/ -- There are only two hard things in programming: Cache invalidation, naming things and off-by-one errors. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///