summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2016-07-29 22:37:06 +0200
committerBram Moolenaar <Bram@vim.org>2016-07-29 22:37:06 +0200
commit10ce39a0d52272a3dfff2feb8c631529f29e6740 (patch)
tree4e87632c06cd48950bf777c27be36cb309a8fcb4
parent1e96d9bf98f9ab84d5af7f98d6a961d91b17364f (diff)
downloadvim-10ce39a0d52272a3dfff2feb8c631529f29e6740.zip
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)
-rw-r--r--runtime/doc/eval.txt25
-rw-r--r--src/eval.c4
-rw-r--r--src/proto/userfunc.pro1
-rw-r--r--src/testdir/test_lambda.vim36
-rw-r--r--src/userfunc.c71
-rw-r--r--src/version.c2
6 files changed, 132 insertions, 7 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 196e71cf0..8dfbb3a28 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1240,6 +1240,7 @@ function returns: >
: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})
@@ -8217,7 +8218,7 @@ last defined. Example: >
See |:verbose-cmd| for more information.
*E124* *E125* *E853* *E884*
-:fu[nction][!] {name}([arguments]) [range] [abort] [dict]
+: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
@@ -8260,6 +8261,28 @@ See |:verbose-cmd| for more information.
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 "."
diff --git a/src/eval.c b/src/eval.c
index 463c8927e..956f05cb1 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -2837,7 +2837,9 @@ do_unlet(char_u *name, int forceit)
}
}
hi = hash_find(ht, varname);
- if (!HASHITEM_EMPTY(hi))
+ 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)
diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro
index e503bcdcc..42c5883d0 100644
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -46,6 +46,7 @@ void *clear_current_funccal(void);
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);
diff --git a/src/testdir/test_lambda.vim b/src/testdir/test_lambda.vim
index 02face8a7..9eb34e434 100644
--- a/src/testdir/test_lambda.vim
+++ b/src/testdir/test_lambda.vim
@@ -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,7 +102,7 @@ function! Test_lambda_do_not_share_local_variable()
call assert_equal('no', l:F[1]())
endfunction
-function! Test_lambda_closure()
+function! Test_lambda_closure_counter()
function! s:foo()
let x = 0
return {-> [execute("let x += 1"), x][-1]}
@@ -209,3 +211,35 @@ function! Test_lambda_combination()
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
diff --git a/src/userfunc.c b/src/userfunc.c
index caa0cfd66..0c6c613e3 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -59,6 +59,7 @@ struct ufunc
#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,7 +313,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
if (evaluate)
{
- int len;
+ int len, flags = 0;
char_u *p;
sprintf((char*)name, "<lambda>%d", ++lambda_no);
@@ -341,6 +342,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
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(&current_funccal->fc_funcs, 1) == FAIL)
@@ -361,7 +363,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
func_do_profile(fp);
#endif
fp->uf_varargs = TRUE;
- fp->uf_flags = 0;
+ fp->uf_flags = flags;
fp->uf_calls = 0;
fp->uf_script_ID = current_SID;
@@ -1487,6 +1489,8 @@ list_func_head(ufunc_T *fp, int indent)
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,7 +1952,7 @@ ex_function(exarg_T *eap)
if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL)
goto errret_2;
- /* find extra arguments "range", "dict" and "abort" */
+ /* find extra arguments "range", "dict", "abort" and "closure" */
for (;;)
{
p = skipwhite(p);
@@ -1967,6 +1971,11 @@ ex_function(exarg_T *eap)
flags |= FC_ABORT;
p += 5;
}
+ else if (STRNCMP(p, "closure", 7) == 0)
+ {
+ flags |= FC_CLOSURE;
+ p += 7;
+ }
else
break;
}
@@ -2299,7 +2308,25 @@ ex_function(exarg_T *eap)
}
fp->uf_args = newargs;
fp->uf_lines = newlines;
- fp->uf_scoped = NULL;
+ 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(&current_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,6 +3563,42 @@ get_current_funccal_dict(hashtab_T *ht)
}
/*
+ * 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 *
diff --git a/src/version.c b/src/version.c
index 865ac929b..7e8ca166e 100644
--- a/src/version.c
+++ b/src/version.c
@@ -759,6 +759,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 2120,
+/**/
2119,
/**/
2118,