Skip to content

Instantly share code, notes, and snippets.

@mattn
Last active January 17, 2022 08:46
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattn/50fa1dc854911d5cb797 to your computer and use it in GitHub Desktop.
Save mattn/50fa1dc854911d5cb797 to your computer and use it in GitHub Desktop.
Vim script に lambda 関数を追加するパッチ
diff -r 1b74025a66e7 runtime/doc/eval.txt
--- a/runtime/doc/eval.txt Sat Sep 27 11:18:20 2014 +0200
+++ b/runtime/doc/eval.txt Sun Sep 28 23:39:50 2014 +0900
@@ -1869,6 +1869,7 @@
join( {list} [, {sep}]) String join {list} items into one String
keys( {dict}) List keys in {dict}
len( {expr}) Number the length of {expr}
+lambda( {expr}) Create lambda function constructed with {expr}
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
@@ -2996,11 +2997,12 @@
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 {string} 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's |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"')
@@ -3010,7 +3012,7 @@
: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.
@@ -3018,9 +3020,13 @@
|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()*
@@ -4038,6 +4044,60 @@
|Dictionary| is returned.
Otherwise an error is given.
+ *lambda()*
+lambda({expr})
+ Create new lambda function with constructed with {expr}.
+ The result is function reference. So you can call it like.
+ Example:
+ :let F = lambda("return 1+2")
+ :echo F()
+
+ Lambda function allow to be given variadic arguments.
+ Example:
+ :let F = lambda("return a:1+2")
+ :echo F(2)
+
+ |sort()|, |map()|, |filter()| can be used with |lambda()|
+ Example:
+ :echo map([1, 2, 3], lambda("return a:1 + 1"))
+ :echo sort([3,7,2,1,4], lambda("return a:1 - a:2"))
+
+ Lambda function is possible to reference the variables in the
+ defined scope.
+ Example:
+ :let s:x = 2
+ :echo filter([1, 2, 3], lambda("return a:1 >= s:x"))
+
+ :function! Foo()
+ : let x = 1
+ : return lambda("return x")
+ :endfunction
+ :echo Foo()()
+
+ 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 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}
@@ -4184,18 +4244,19 @@
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's |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
@@ -4204,9 +4265,12 @@
|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()*
diff -r 1b74025a66e7 src/Makefile
--- a/src/Makefile Sat Sep 27 11:18:20 2014 +0200
+++ b/src/Makefile Sun Sep 28 23:39:50 2014 +0900
@@ -1898,6 +1898,7 @@
test_listlbr_utf8 \
test_options \
test_qf_title \
+ test_lambda \
test10 test11 test12 test13 test14 test15 test16 test17 test18 test19 \
test20 test21 test22 test23 test24 test25 test26 test27 test28 test29 \
test30 test31 test32 test33 test34 test35 test36 test37 test38 test39 \
diff -r 1b74025a66e7 src/eval.c
--- a/src/eval.c Sat Sep 27 11:18:20 2014 +0200
+++ b/src/eval.c Sun Sep 28 23:39:50 2014 +0900
@@ -166,6 +166,7 @@
* 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 @@
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) */
@@ -230,8 +232,6 @@
#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
{
ufunc_T *func; /* function being called */
@@ -256,6 +256,10 @@
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 */
};
/*
@@ -603,6 +607,7 @@
static void f_items __ARGS((typval_T *argvars, typval_T *rettv));
static void f_join __ARGS((typval_T *argvars, typval_T *rettv));
static void f_keys __ARGS((typval_T *argvars, typval_T *rettv));
+static void f_lambda __ARGS((typval_T *argvars, typval_T *rettv));
static void f_last_buffer_nr __ARGS((typval_T *argvars, typval_T *rettv));
static void f_len __ARGS((typval_T *argvars, typval_T *rettv));
static void f_libcall __ARGS((typval_T *argvars, typval_T *rettv));
@@ -3710,11 +3715,32 @@
hashitem_T *hi;
char_u *varname;
dictitem_T *di;
+ funccall_T *old_current_funccal;
ht = find_var_ht(name, &varname);
if (ht != NULL && *varname != NUL)
{
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);
@@ -6840,6 +6866,7 @@
* 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;
set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1);
set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1);
}
@@ -6912,6 +6939,7 @@
else
pfc = &(*pfc)->caller;
}
+
if (did_free_funccal)
/* When a funccal was freed some more items might be garbage
* collected, so run again. */
@@ -7017,6 +7045,8 @@
{
dict_T *dd;
list_T *ll;
+ ufunc_T *fp;
+ funccall_T *fc;
switch (tv->v_type)
{
@@ -7039,6 +7069,21 @@
set_ref_in_list(ll, copyID);
}
break;
+ case VAR_FUNC:
+ 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);
+ set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID);
+ }
+ }
+ }
+ break;
}
return;
}
@@ -8065,6 +8110,7 @@
{"items", 1, 1, f_items},
{"join", 1, 2, f_join},
{"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},
@@ -8549,7 +8595,7 @@
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.
@@ -10647,7 +10693,7 @@
}
static void filter_map __ARGS((typval_T *argvars, typval_T *rettv, int map));
-static int filter_map_one __ARGS((typval_T *tv, char_u *expr, int map, int *remp));
+static int filter_map_one __ARGS((typval_T *tv, typval_T *expr, int map, int *remp));
/*
* Implementation of map() and filter().
@@ -10658,8 +10704,7 @@
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;
@@ -10694,14 +10739,13 @@
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. */
@@ -10767,22 +10811,32 @@
static int
filter_map_one(tv, expr, map, remp)
typval_T *tv;
- char_u *expr;
+ typval_T *expr;
int map;
int *remp;
{
typval_T rettv;
+ int retval = FAIL;
+ int dummy;
char_u *s;
- int retval = FAIL;
copy_tv(tv, &vimvars[VV_VAL].vv_tv);
- s = expr;
- if (eval1(&s, &rettv, TRUE) == FAIL)
+ 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) == FALSE)
goto theend;
- if (*s != NUL) /* check for trailing chars after expr */
- {
- EMSG2(_(e_invexpr2), s);
- 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)
{
@@ -13817,6 +13871,83 @@
}
/*
+ * "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;
+ funccall_T *fc;
+
+ 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(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
@@ -20880,6 +21011,37 @@
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;
+ }
+ 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.
@@ -20895,13 +21057,23 @@
{
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;
}
/*
@@ -21252,6 +21424,24 @@
}
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 && var_check_func_name(name, v == NULL))
return;
@@ -22498,6 +22688,7 @@
fp->uf_flags = flags;
fp->uf_calls = 0;
fp->uf_script_ID = current_SID;
+ fp->uf_scoped = NULL;
goto ret_free;
erret:
@@ -23329,7 +23520,9 @@
{
ufunc_T *fp;
- if (name != NULL && isdigit(*name))
+ if (name == NULL)
+ return;
+ else if (isdigit(*name))
{
fp = find_func(name);
if (fp == NULL)
@@ -23342,6 +23535,18 @@
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);
+ }
+ }
}
/*
@@ -23353,7 +23558,9 @@
{
ufunc_T *fp;
- if (name != NULL && isdigit(*name))
+ if (name == NULL)
+ return;
+ else if (isdigit(*name))
{
fp = find_func(name);
if (fp == NULL)
@@ -23361,6 +23568,13 @@
else
++fp->uf_refcount;
}
+ else if (!STRNICMP(name, "<LAMBDA>", 8))
+ {
+ /* fail silently, when lambda function isn't be found. */
+ fp = find_func(name);
+ if (fp != NULL)
+ ++fp->uf_refcount;
+ }
}
/*
@@ -23417,6 +23631,9 @@
/* 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
@@ -23697,7 +23914,8 @@
* 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);
}
@@ -23742,7 +23960,8 @@
{
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->lambda_copyID != copyID);
}
/*
@koron
Copy link

koron commented Sep 10, 2014

gist だから ?w=1 ができないのか (´・ω・`)

@UncleBill
Copy link

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment