Merge pull request #5771 from brcolow/lambda

Lambda Support
This commit is contained in:
James McCoy 2017-02-23 07:30:20 -05:00 committed by GitHub
commit 9752a333c3
24 changed files with 2129 additions and 580 deletions

View File

@ -112,9 +112,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 expression |expr-lambda|. 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()
@ -667,6 +668,7 @@ Expression syntax summary, from least to most significant:
@r contents of register 'r'
function(expr1, ...) function call
func{ti}on(expr1, ...) function call with curly braces
{args -> expr1} lambda expression
".." indicates that the operations in this level can be concatenated.
@ -1174,6 +1176,62 @@ function(expr1, ...) function call
See below |functions|.
lambda expression *expr-lambda* *lambda*
-----------------
{args -> expr1} lambda expression
A lambda expression creates a new unnamed function which returns the result of
evaluating |expr1|. Lambda expressions are differ from |user-functions| in
the following ways:
1. The body of the lambda expression is an |expr1| and not a sequence of |Ex|
commands.
2. The prefix "a:" should not be used for arguments. E.g.: >
:let F = {arg1, arg2 -> arg1 - arg2}
:echo F(5, 2)
< 3
The arguments are optional. Example: >
:let F = {-> 'error function'}
:echo F()
< error function
*closure*
Lambda expressions can access outer scope variables and arguments. This is
often called a closure. Example where "i" a and "a:arg" are used in a lambda
while they exists in the function scope. They remain valid even after the
function returns: >
:function Foo(arg)
: let i = 3
: return {x -> x + i - a:arg}
:endfunction
:let Bar = Foo(4)
:echo Bar(6)
< 5
See also |:func-closure|. Lambda and closure support can be checked with: >
if has('lambda')
Examples for using a lambda expression with |sort()|, |map()| and |filter()|: >
:echo map([1, 2, 3], {idx, val -> val + 1})
< [2, 3, 4] >
:echo sort([3,7,2,1,4], {a, b -> a - b})
< [1, 2, 3, 4, 7]
The lambda expression is also useful for Channel, Job and timer: >
:let timer = timer_start(500,
\ {-> execute("echo 'Handler called'", "")},
\ {'repeat': 3})
< Handler called
Handler called
Handler called
Note how execute() is used to execute an Ex command. That's ugly though.
Lambda expressions have internal names like '<lambda>42'. If you get an error
for a lambda expression, you can find what it is with the following command: >
:function {'<lambda>42'}
See also: |numbered-function|
==============================================================================
3. Internal variable *internal-variables* *E461*
@ -1481,7 +1539,7 @@ v:false Special value used to put "false" in JSON and msgpack. See
|json_encode()|. This value is converted to "v:false" when used
as a String (e.g. in |expr5| with string concatenation
operator) and to zero when used as a Number (e.g. in |expr5|
or |expr7| when used with numeric operators).
or |expr7| when used with numeric operators). Read-only.
*v:fcs_reason* *fcs_reason-variable*
v:fcs_reason The reason why the |FileChangedShell| event was triggered.
@ -1631,7 +1689,7 @@ v:null Special value used to put "null" in JSON and NIL in msgpack.
See |json_encode()|. This value is converted to "v:null" when
used as a String (e.g. in |expr5| with string concatenation
operator) and to zero when used as a Number (e.g. in |expr5|
or |expr7| when used with numeric operators).
or |expr7| when used with numeric operators). Read-only.
*v:oldfiles* *oldfiles-variable*
v:oldfiles List of file names that is loaded from the |shada| file on
@ -1791,6 +1849,9 @@ v:termresponse The escape sequence returned by the terminal for the |t_RV|
always 95 or bigger). Pc is always zero.
{only when compiled with |+termresponse| feature}
*v:testing* *testing-variable*
v:testing Must be set before using `test_garbagecollect_now()`.
*v:this_session* *this_session-variable*
v:this_session Full filename of the last loaded or saved session file. See
|:mksession|. It is allowed to set this variable. When no
@ -1814,7 +1875,7 @@ v:true Special value used to put "true" in JSON and msgpack. See
|json_encode()|. This value is converted to "v:true" when used
as a String (e.g. in |expr5| with string concatenation
operator) and to one when used as a Number (e.g. in |expr5| or
|expr7| when used with numeric operators).
|expr7| when used with numeric operators). Read-only.
*v:val* *val-variable*
v:val Value of the current item of a |List| or |Dictionary|. Only
@ -1954,8 +2015,10 @@ foldlevel({lnum}) Number fold level at {lnum}
foldtext() String line displayed for closed fold
foldtextresult({lnum}) String text for closed fold at {lnum}
foreground() Number bring the Vim window to the foreground
function({name} [, {arglist}] [, {dict}])
funcref({name} [, {arglist}] [, {dict}])
Funcref reference to function {name}
function({name} [, {arglist}] [, {dict}])
Funcref named reference to function {name}
garbagecollect([{atexit}]) none free memory, breaking cyclic references
get({list}, {idx} [, {def}]) any get item {idx} from {list} or {def}
get({dict}, {key} [, {def}]) any get item {key} from {dict} or {def}
@ -2215,6 +2278,7 @@ tagfiles() List tags files used
tan({expr}) Float tangent of {expr}
tanh({expr}) Float hyperbolic tangent of {expr}
tempname() String name for a temporary file
test_garbagecollect_now() none free memory right now for testing
timer_start({time}, {callback} [, {options}])
Number create a timer
timer_stop({timer}) none stop a timer
@ -3369,31 +3433,47 @@ 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 {expr2} 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.
For a |Dictionary| |v:key| has the key of the current item.
Examples: >
:call filter(mylist, 'v:val !~ "OLD"')
call filter(mylist, 'v:val !~ "OLD"')
< Removes the items where "OLD" appears. >
:call filter(mydict, 'v:key >= 8')
call filter(mydict, 'v:key >= 8')
< Removes the items with a key below 8. >
:call filter(var, 0)
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.
If {expr2} is a |Funcref| it must take two arguments:
1. the key or the index of the current item.
2. the value of the current item.
The function must return TRUE if the item should be kept.
Example that keeps the odd items of a list: >
func Odd(idx, val)
return a:idx % 2 == 1
endfunc
call filter(mylist, function('Odd'))
<
The operation is done in-place. If you want a |List| or
|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. When {expr2} is a
Funcref errors inside a function are ignored, unless it was
defined with the "abort" flag.
finddir({name}[, {path}[, {count}]]) *finddir()*
@ -3545,12 +3625,31 @@ foreground() Move the Vim window to the foreground. Useful when sent from
|remote_foreground()| instead.
{only in the Win32 GUI and console version}
*funcref()*
funcref({name} [, {arglist}] [, {dict}])
Just like |function()|, but the returned Funcref will lookup
the function by reference, not by name. This matters when the
function {name} is redefined later.
Unlike |function()|, {name} must be an existing user function.
Also for autoloaded functions. {name} cannot be a builtin
function.
*function()* *E700* *E922* *E923*
function({name} [, {arglist}] [, {dict}])
Return a |Funcref| variable that refers to function {name}.
{name} can be a user defined function or an internal function.
{name} can also be a Funcref or a partial. When it is a
partial the dict stored in it will be used and the {dict}
argument is not allowed. E.g.: >
let FuncWithArg = function(dict.Func, [arg])
let Broken = function(dict.Func, [arg], dict)
<
When using the Funcref the function will be found by {name},
also when it was redefined later. Use |funcref()| to keep the
same function.
When {arglist} or {dict} is present this creates a partial.
That mans the argument list and/or the dictionary is stored in
the Funcref and will be used when the Funcref is called.
@ -3589,18 +3688,25 @@ function({name} [, {arglist}] [, {dict}])
garbagecollect([{atexit}]) *garbagecollect()*
Cleanup unused |Lists| and |Dictionaries| that have circular
references. There is hardly ever a need to invoke this
function, as it is automatically done when Vim runs out of
memory or is waiting for the user to press a key after
'updatetime'. Items without circular references are always
freed when they become unused.
references.
There is hardly ever a need to invoke this function, as it is
automatically done when Vim runs out of memory or is waiting
for the user to press a key after 'updatetime'. Items without
circular references are always freed when they become unused.
This is useful if you have deleted a very big |List| and/or
|Dictionary| with circular references in a script that runs
for a long time.
When the optional {atexit} argument is one, garbage
collection will also be done when exiting Vim, if it wasn't
done before. This is useful when checking for memory leaks.
The garbage collection is not done immediately but only when
it's safe to perform. This is when waiting for the user to
type a character. To force garbage collection immediately use
|test_garbagecollect_now()|.
get({list}, {idx} [, {default}]) *get()*
Get item {idx} from |List| {list}. When this item is not
available return {default}. Return zero when {default} is
@ -4937,29 +5043,43 @@ log10({expr}) *log10()*
< -2.0
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.
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.
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 {expr2} 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
If {expr2} is a |Funcref| it is called with two arguments:
1. The key or the index of the current item.
2. the value of the current item.
The function must return the new value of the item. Example
that changes each value by "key-value": >
func KeyValue(key, val)
return a:key . '-' . a:val
endfunc
call map(myDict, function('KeyValue'))
<
The operation is done in-place. If you want a |List| or
|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. When {expr2} is a
Funcref errors inside a function are ignored, unless it was
defined with the "abort" flag.
maparg({name}[, {mode} [, {abbr} [, {dict}]]]) *maparg()*
@ -6003,6 +6123,7 @@ screenrow() *screenrow()*
The result is a Number, which is the current screen row of the
cursor. The top line has number one.
This function is mainly used for testing.
Alternatively you can use |winline()|.
Note: Same restrictions as with |screencol()|.
@ -7029,6 +7150,14 @@ substitute({expr}, {pat}, {sub}, {flags}) *substitute()*
:echo substitute(s, '%\(\x\x\)',
\ '\=nr2char("0x" . submatch(1))', 'g')
< When {sub} is a Funcref that function is called, with one
optional argument. Example: >
:echo substitute(s, '%\(\x\x\)', SubNr, 'g')
< The optional argument is a list which contains the whole
matched string and up to nine submatches,like what
|submatch()| returns. Example: >
:echo substitute(s, '\(\x\x\)', {m -> '0x' . m[1]}, 'g')
synID({lnum}, {col}, {trans}) *synID()*
The result is a Number, which is the syntax ID at the position
{lnum} and {col} in the current window.
@ -7288,6 +7417,12 @@ termopen({cmd}[, {opts}]) {Nvim} *termopen()*
See |terminal-emulator| for more information.
test_garbagecollect_now() *test_garbagecollect_now()*
Like garbagecollect(), but executed right away. This must
only be called directly to avoid any structure to exist
internally, and |v:testing| must have been set before calling
any function.
tan({expr}) *tan()*
Return the tangent of {expr}, measured in radians, as a |Float|
in the range [-inf, inf].
@ -7379,16 +7514,18 @@ trunc({expr}) *trunc()*
< 4.0
type({expr}) *type()*
The result is a Number, depending on the type of {expr}:
Number: 0
String: 1
Funcref: 2
List: 3
Dictionary: 4
Float: 5
The result is a Number representing the type of {expr}.
Instead of using the number directly, it is better to use the
v:t_ variable that has the value:
Number: 0 (|v:t_number|)
String: 1 (|v:t_string|)
Funcref: 2 (|v:t_func|)
List: 3 (|v:t_list|)
Dictionary: 4 (|v:t_dict|)
Float: 5 (|v:t_float|)
Boolean: 6 (|v:true| and |v:false|)
Null: 7 (|v:null|)
To avoid the magic numbers it should be used this way: >
For backward compatibility, this method can be used: >
:if type(myvar) == type(0)
:if type(myvar) == type("")
:if type(myvar) == type(function("tr"))
@ -7399,6 +7536,8 @@ type({expr}) *type()*
< In place of checking for |v:null| type it is better to check
for |v:null| directly as it is the only value of this type: >
:if myvar is v:null
< To check if the v:t_ variables exist use this: >
:if exists('v:t_number')
undofile({name}) *undofile()*
Return the name of the undo file that would be used for a file
@ -7806,6 +7945,7 @@ insert_expand Compiled with support for CTRL-X expansion commands in
Insert mode.
jumplist Compiled with |jumplist| support.
keymap Compiled with 'keymap' support.
lambda Compiled with |lambda| support.
langmap Compiled with 'langmap' support.
libcall Compiled with |libcall()| support.
linebreak Compiled with 'linebreak', 'breakat', 'showbreak' and
@ -7957,7 +8097,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
@ -8000,6 +8140,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 "."
@ -8011,7 +8173,7 @@ See |:verbose-cmd| for more information.
:endf[unction] The end of a function definition. Must be on a line
by its own, without other commands.
*:delf* *:delfunction* *E130* *E131*
*:delf* *:delfunction* *E130* *E131* *E933*
:delf[unction] {name} Delete function {name}.
{name} can also be a |Dictionary| entry that is a
|Funcref|: >
@ -8047,10 +8209,10 @@ can be 0). "a:000" is set to a |List| that contains these arguments. Note
that "a:1" is the same as "a:000[0]".
*E742*
The a: scope and the variables in it cannot be changed, they are fixed.
However, if a |List| or |Dictionary| is used, you can change their contents.
Thus you can pass a |List| to a function and have the function add an item to
it. If you want to make sure the function cannot change a |List| or
|Dictionary| use |:lockvar|.
However, if a composite type is used, such as |List| or |Dictionary| , you can
change their contents. Thus you can pass a |List| to a function and have the
function add an item to it. If you want to make sure the function cannot
change a |List| or |Dictionary| use |:lockvar|.
When not using "...", the number of arguments in a function call must be equal
to the number of named arguments. When using "...", the number of arguments
@ -8062,9 +8224,8 @@ until the matching |:endfunction|. It is allowed to define another function
inside a function body.
*local-variables*
Inside a function variables can be used. These are local variables, which
will disappear when the function returns. Global variables need to be
accessed with "g:".
Inside a function local variables can be used. These will disappear when the
function returns. Global variables need to be accessed with "g:".
Example: >
:function Table(title, ...)

View File

@ -221,11 +221,10 @@ Object nvim_call_function(String fname, Array args, Error *err)
// Call the function
typval_T rettv;
int dummy;
int r = call_func((char_u *) fname.data, (int) fname.size,
&rettv, (int) args.size, vim_args,
int r = call_func((char_u *)fname.data, (int)fname.size,
&rettv, (int)args.size, vim_args, NULL,
curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy,
true,
NULL, NULL);
true, NULL, NULL);
if (r == FAIL) {
api_set_error(err, Exception, _("Error calling function."));
}

View File

@ -31,8 +31,6 @@ typedef struct {
#include "nvim/hashtab.h"
// for dict_T
#include "nvim/eval_defs.h"
// for proftime_T
#include "nvim/profile.h"
// for String
#include "nvim/api/private/defs.h"
// for Map(K, V)
@ -90,7 +88,6 @@ typedef struct {
typedef struct window_S win_T;
typedef struct wininfo_S wininfo_T;
typedef struct frame_S frame_T;
typedef int scid_T; /* script ID */
// for struct memline (it needs memfile_T)
#include "nvim/memline_defs.h"

File diff suppressed because it is too large Load Diff

View File

@ -12,39 +12,6 @@
// All user-defined functions are found in this hashtable.
extern hashtab_T func_hashtab;
// Structure to hold info for a user function.
typedef struct ufunc ufunc_T;
struct ufunc {
int uf_varargs; ///< variable nr of arguments
int uf_flags;
int uf_calls; ///< nr of active calls
garray_T uf_args; ///< arguments
garray_T uf_lines; ///< function lines
int uf_profiling; ///< true when func is being profiled
// Profiling the function as a whole.
int uf_tm_count; ///< nr of calls
proftime_T uf_tm_total; ///< time spent in function + children
proftime_T uf_tm_self; ///< time spent in function itself
proftime_T uf_tm_children; ///< time spent in children this call
// Profiling the function per line.
int *uf_tml_count; ///< nr of times line was executed
proftime_T *uf_tml_total; ///< time spent in a line + children
proftime_T *uf_tml_self; ///< time spent in a line itself
proftime_T uf_tml_start; ///< start time for current line
proftime_T uf_tml_children; ///< time spent in children for this line
proftime_T uf_tml_wait; ///< start wait time for current line
int uf_tml_idx; ///< index of line being timed; -1 if none
int uf_tml_execed; ///< line being timed was executed
scid_T uf_script_ID; ///< ID of script where function was defined,
// used for s: variables
int uf_refcount; ///< for numbered function: reference count
char_u uf_name[1]; ///< name of function (actually longer); can
// start with <SNR>123_ (<SNR> is K_SPECIAL
// KS_EXTRA KE_SNR)
};
// From user function to hashitem and back.
EXTERN ufunc_T dumuf;
#define UF2HIKEY(fp) ((fp)->uf_name)
@ -127,6 +94,7 @@ typedef enum {
VV__NULL_LIST, // List with NULL value. For test purposes only.
VV__NULL_DICT, // Dictionary with NULL value. For test purposes only.
VV_VIM_DID_ENTER,
VV_TESTING,
VV_TYPE_NUMBER,
VV_TYPE_STRING,
VV_TYPE_FUNC,
@ -156,8 +124,8 @@ extern const list_T *eval_msgpack_type_lists[LAST_MSGPACK_TYPE + 1];
#undef LAST_MSGPACK_TYPE
/// Maximum number of function arguments
#define MAX_FUNC_ARGS 20
typedef int (*ArgvFunc)(int current_argcount, typval_T *argv,
int called_func_argcount);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval.h.generated.h"

View File

@ -103,6 +103,7 @@ return {
foldtext={},
foldtextresult={args=1},
foreground={},
funcref={args={1, 3}},
['function']={args={1, 3}},
garbagecollect={args={0, 1}},
get={args={2, 3}},
@ -303,6 +304,7 @@ return {
tanh={args=1, func="float_op_wrapper", data="&tanh"},
tempname={},
termopen={args={1, 2}},
test_garbagecollect_now={},
timer_start={args={2,3}},
timer_stop={args=1},
tolower={args=1},

View File

@ -344,7 +344,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
case VAR_PARTIAL: {
partial_T *const pt = tv->vval.v_partial;
(void)pt;
TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : pt->pt_name));
TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : partial_name(pt)));
_mp_push(*mpstack, ((MPConvStackVal) {
.type = kMPConvPartial,
.tv = tv,

View File

@ -6,6 +6,9 @@
#include "nvim/hashtab.h"
#include "nvim/lib/queue.h"
#include "nvim/garray.h" // for garray_T
#include "nvim/profile.h" // for proftime_T
#include "nvim/pos.h" // for linenr_T
typedef int varnumber_T;
typedef double float_T;
@ -104,15 +107,19 @@ struct listvar_S {
list_T *lv_used_prev; /* previous list in used lists list */
};
/*
* Structure to hold an item of a Dictionary.
* Also used for a variable.
* The key is copied into "di_key" to avoid an extra alloc/free for it.
*/
// Static list with 10 items. Use init_static_list() to initialize.
typedef struct {
list_T sl_list; // must be first
listitem_T sl_items[10];
} staticList10_T;
// Structure to hold an item of a Dictionary.
// Also used for a variable.
// The key is copied into "di_key" to avoid an extra alloc/free for it.
struct dictitem_S {
typval_T di_tv; /* type and value of the variable */
char_u di_flags; /* flags (only used for variable) */
char_u di_key[1]; /* key (actually longer!) */
typval_T di_tv; ///< type and value of the variable
char_u di_flags; ///< flags (only used for variable)
char_u di_key[1]; ///< key (actually longer!)
};
typedef struct dictitem_S dictitem_T;
@ -147,9 +154,88 @@ struct dictvar_S {
QUEUE watchers; ///< Dictionary key watchers set by user code.
};
typedef int scid_T; // script ID
typedef struct funccall_S funccall_T;
// Structure to hold info for a user function.
typedef struct ufunc ufunc_T;
struct ufunc {
int uf_varargs; ///< variable nr of arguments
int uf_flags;
int uf_calls; ///< nr of active calls
bool uf_cleared; ///< func_clear() was already called
garray_T uf_args; ///< arguments
garray_T uf_lines; ///< function lines
int uf_profiling; ///< true when func is being profiled
// Profiling the function as a whole.
int uf_tm_count; ///< nr of calls
proftime_T uf_tm_total; ///< time spent in function + children
proftime_T uf_tm_self; ///< time spent in function itself
proftime_T uf_tm_children; ///< time spent in children this call
// Profiling the function per line.
int *uf_tml_count; ///< nr of times line was executed
proftime_T *uf_tml_total; ///< time spent in a line + children
proftime_T *uf_tml_self; ///< time spent in a line itself
proftime_T uf_tml_start; ///< start time for current line
proftime_T uf_tml_children; ///< time spent in children for this line
proftime_T uf_tml_wait; ///< start wait time for current line
int uf_tml_idx; ///< index of line being timed; -1 if none
int uf_tml_execed; ///< line being timed was executed
scid_T uf_script_ID; ///< ID of script where function was defined,
// used for s: variables
int uf_refcount; ///< reference count, see func_name_refcount()
funccall_T *uf_scoped; ///< l: local variables for closure
char_u uf_name[1]; ///< name of function (actually longer); can
// start with <SNR>123_ (<SNR> is K_SPECIAL
// KS_EXTRA KE_SNR)
};
/// Maximum number of function arguments
#define MAX_FUNC_ARGS 20
#define VAR_SHORT_LEN 20 // short variable name length
#define FIXVAR_CNT 12 // number of fixed variables
// structure to hold info for a function that is currently being executed.
struct funccall_S {
ufunc_T *func; ///< function being called
int linenr; ///< next line to be executed
int returned; ///< ":return" used
struct { ///< fixed variables for arguments
dictitem_T var; ///< variable (without room for name)
char_u room[VAR_SHORT_LEN]; ///< room for the name
} fixvar[FIXVAR_CNT];
dict_T l_vars; ///< l: local function variables
dictitem_T l_vars_var; ///< variable for l: scope
dict_T l_avars; ///< a: argument variables
dictitem_T l_avars_var; ///< variable for a: scope
list_T l_varlist; ///< list for a:000
listitem_T l_listitems[MAX_FUNC_ARGS]; ///< listitems for a:000
typval_T *rettv; ///< return value
linenr_T breakpoint; ///< next line with breakpoint or zero
int dbg_tick; ///< debug_tick when breakpoint was set
int level; ///< top nesting level of executed function
proftime_T prof_child; ///< time spent in a child
funccall_T *caller; ///< calling function or NULL
int fc_refcount; ///< number of user functions that reference
// this funccal
int fc_copyID; ///< for garbage collection
garray_T fc_funcs; ///< list of ufunc_T* which keep a reference
// to "func"
};
// structure used by trans_function_name()
typedef struct {
dict_T *fd_dict; ///< Dictionary used.
char_u *fd_newkey; ///< New key in "dict" in allocated memory.
dictitem_T *fd_di; ///< Dictionary item used.
} funcdict_T;
struct partial_S {
int pt_refcount; ///< Reference count.
char_u *pt_name; ///< Function name.
char_u *pt_name; ///< Function name; when NULL use pt_func->name.
ufunc_T *pt_func; ///< Function pointer; when NULL lookup function
///< with pt_name.
bool pt_auto; ///< when true the partial was created for using
///< dict.member in handle_subscript().
int pt_argc; ///< Number of arguments.

View File

@ -1327,8 +1327,9 @@ int using_script(void)
void before_blocking(void)
{
updatescript(0);
if (may_garbage_collect)
garbage_collect();
if (may_garbage_collect) {
garbage_collect(false);
}
}
/*
@ -1366,10 +1367,11 @@ int vgetc(void)
char_u buf[MB_MAXBYTES + 1];
int i;
/* Do garbage collection when garbagecollect() was called previously and
* we are now at the toplevel. */
if (may_garbage_collect && want_garbage_collect)
garbage_collect();
// Do garbage collection when garbagecollect() was called previously and
// we are now at the toplevel.
if (may_garbage_collect && want_garbage_collect) {
garbage_collect(false);
}
/*
* If a character was put back with vungetc, it was already processed.

View File

@ -1236,6 +1236,9 @@ EXTERN char *ignoredp;
EXTERN bool in_free_unref_items INIT(= false);
// Used for checking if local variables or arguments used in a lambda.
EXTERN int *eval_lavars_used INIT(= NULL);
// If a msgpack-rpc channel should be started over stdin/stdout
EXTERN bool embedded_mode INIT(= false);

View File

@ -624,8 +624,9 @@ void getout(int exitval)
iconv_end();
#endif
cs_end();
if (garbage_collect_at_exit)
garbage_collect();
if (garbage_collect_at_exit) {
garbage_collect(false);
}
mch_exit(exitval);
}

View File

@ -2460,11 +2460,11 @@ do_mouse (
};
typval_T rettv;
int doesrange;
(void) call_func((char_u *) tab_page_click_defs[mouse_col].func,
(int) strlen(tab_page_click_defs[mouse_col].func),
&rettv, ARRAY_SIZE(argv), argv,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&doesrange, true, NULL, NULL);
(void)call_func((char_u *)tab_page_click_defs[mouse_col].func,
(int)strlen(tab_page_click_defs[mouse_col].func),
&rettv, ARRAY_SIZE(argv), argv, NULL,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&doesrange, true, NULL, NULL);
clear_tv(&rettv);
break;
}

View File

@ -6442,32 +6442,72 @@ static linenr_T submatch_firstlnum;
static linenr_T submatch_maxline;
static int submatch_line_lbr;
/*
* vim_regsub() - perform substitutions after a vim_regexec() or
* vim_regexec_multi() match.
*
* If "copy" is TRUE really copy into "dest".
* If "copy" is FALSE nothing is copied, this is just to find out the length
* of the result.
*
* If "backslash" is TRUE, a backslash will be removed later, need to double
* them to keep them, and insert a backslash before a CR to avoid it being
* replaced with a line break later.
*
* Note: The matched text must not change between the call of
* vim_regexec()/vim_regexec_multi() and vim_regsub()! It would make the back
* references invalid!
*
* Returns the size of the replacement, including terminating NUL.
*/
int vim_regsub(regmatch_T *rmp, char_u *source, char_u *dest, int copy, int magic, int backslash)
/// Put the submatches in "argv[0]" which is a list passed into call_func() by
/// vim_regsub_both().
static int fill_submatch_list(int argc, typval_T *argv, int argcount)
{
listitem_T *li;
int i;
char_u *s;
if (argcount == 0) {
// called function doesn't take an argument
return 0;
}
// Relies on sl_list to be the first item in staticList10_T.
init_static_list((staticList10_T *)(argv->vval.v_list));
// There are always 10 list items in staticList10_T.
li = argv->vval.v_list->lv_first;
for (i = 0; i < 10; i++) {
s = submatch_match->startp[i];
if (s == NULL || submatch_match->endp[i] == NULL) {
s = NULL;
} else {
s = vim_strnsave(s, (int)(submatch_match->endp[i] - s));
}
li->li_tv.v_type = VAR_STRING;
li->li_tv.vval.v_string = s;
li = li->li_next;
}
return 1;
}
static void clear_submatch_list(staticList10_T *sl)
{
int i;
for (i = 0; i < 10; i++) {
xfree(sl->sl_items[i].li_tv.vval.v_string);
}
}
/// vim_regsub() - perform substitutions after a vim_regexec() or
/// vim_regexec_multi() match.
///
/// If "copy" is TRUE really copy into "dest".
/// If "copy" is FALSE nothing is copied, this is just to find out the length
/// of the result.
///
/// If "backslash" is TRUE, a backslash will be removed later, need to double
/// them to keep them, and insert a backslash before a CR to avoid it being
/// replaced with a line break later.
///
/// Note: The matched text must not change between the call of
/// vim_regexec()/vim_regexec_multi() and vim_regsub()! It would make the back
/// references invalid!
///
/// Returns the size of the replacement, including terminating NUL.
int vim_regsub(regmatch_T *rmp, char_u *source, typval_T *expr, char_u *dest,
int copy, int magic, int backslash)
{
reg_match = rmp;
reg_mmatch = NULL;
reg_maxline = 0;
reg_buf = curbuf;
reg_line_lbr = TRUE;
return vim_regsub_both(source, dest, copy, magic, backslash);
reg_line_lbr = true;
return vim_regsub_both(source, expr, dest, copy, magic, backslash);
}
int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *dest, int copy, int magic, int backslash)
@ -6477,11 +6517,12 @@ int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *de
reg_buf = curbuf; /* always works on the current buffer! */
reg_firstlnum = lnum;
reg_maxline = curbuf->b_ml.ml_line_count - lnum;
reg_line_lbr = FALSE;
return vim_regsub_both(source, dest, copy, magic, backslash);
reg_line_lbr = false;
return vim_regsub_both(source, NULL, dest, copy, magic, backslash);
}
static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, int backslash)
static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
int copy, int magic, int backslash)
{
char_u *src;
char_u *dst;
@ -6495,8 +6536,8 @@ static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, in
int len = 0; /* init for GCC */
static char_u *eval_result = NULL;
/* Be paranoid... */
if (source == NULL || dest == NULL) {
// Be paranoid...
if ((source == NULL && expr == NULL) || dest == NULL) {
EMSG(_(e_null));
return 0;
}
@ -6505,16 +6546,13 @@ static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, in
src = source;
dst = dest;
/*
* When the substitute part starts with "\=" evaluate it as an expression.
*/
if (source[0] == '\\' && source[1] == '='
&& !can_f_submatch /* can't do this recursively */
) {
/* To make sure that the length doesn't change between checking the
* length and copying the string, and to speed up things, the
* resulting string is saved from the call with "copy" == FALSE to the
* call with "copy" == TRUE. */
// When the substitute part starts with "\=" evaluate it as an expression.
if (expr != NULL || (source[0] == '\\' && source[1] == '='
&& !can_f_submatch)) { // can't do this recursively
// To make sure that the length doesn't change between checking the
// length and copying the string, and to speed up things, the
// resulting string is saved from the call with "copy" == FALSE to the
// call with "copy" == TRUE.
if (copy) {
if (eval_result != NULL) {
STRCPY(dest, eval_result);
@ -6525,6 +6563,7 @@ static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, in
} else {
win_T *save_reg_win;
int save_ireg_ic;
bool prev_can_f_submatch = can_f_submatch;
xfree(eval_result);
@ -6539,9 +6578,50 @@ static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, in
submatch_line_lbr = reg_line_lbr;
save_reg_win = reg_win;
save_ireg_ic = ireg_ic;
can_f_submatch = TRUE;
can_f_submatch = true;
if (expr != NULL) {
typval_T argv[2];
int dummy;
char_u buf[NUMBUFLEN];
typval_T rettv;
staticList10_T matchList;
rettv.v_type = VAR_STRING;
rettv.vval.v_string = NULL;
if (prev_can_f_submatch) {
// can't do this recursively
} else {
argv[0].v_type = VAR_LIST;
argv[0].vval.v_list = &matchList.sl_list;
matchList.sl_list.lv_len = 0;
if (expr->v_type == VAR_FUNC) {
s = expr->vval.v_string;
call_func(s, (int)STRLEN(s), &rettv, 1, argv,
fill_submatch_list, 0L, 0L, &dummy,
true, NULL, NULL);
} else if (expr->v_type == VAR_PARTIAL) {
partial_T *partial = expr->vval.v_partial;
s = partial_name(partial);
call_func(s, (int)STRLEN(s), &rettv, 1, argv,
fill_submatch_list, 0L, 0L, &dummy,
true, partial, NULL);
}
if (matchList.sl_list.lv_len > 0) {
// fill_submatch_list() was called.
clear_submatch_list(&matchList);
}
}
eval_result = get_tv_string_buf_chk(&rettv, buf);
if (eval_result != NULL) {
eval_result = vim_strsave(eval_result);
}
clear_tv(&rettv);
} else {
eval_result = eval_to_string(source + 2, NULL, true);
}
eval_result = eval_to_string(source + 2, NULL, TRUE);
if (eval_result != NULL) {
int had_backslash = FALSE;

View File

@ -34,12 +34,14 @@ NEW_TESTS ?= \
test_cscope.res \
test_digraph.res \
test_diffmode.res \
test_filter_map.res \
test_gn.res \
test_hardcopy.res \
test_help_tagjump.res \
test_history.res \
test_increment.res \
test_increment_dbcs.res \
test_lambda.res \
test_langmap.res \
test_match.res \
test_matchadd_conceal.res \

View File

@ -62,6 +62,12 @@ lang mess C
" Always use forward slashes.
set shellslash
" Make sure $HOME does not get read or written.
let $HOME = '/does/not/exist'
" Prepare for calling garbagecollect_for_testing().
let v:testing = 1
" Align with vim defaults.
set directory^=.
set nohidden

View File

@ -9,8 +9,10 @@ source test_ex_undo.vim
source test_expr.vim
source test_expr_utf8.vim
source test_feedkeys.vim
source test_filter_map.vim
source test_goto.vim
source test_jumps.vim
source test_lambda.vim
source test_match.vim
source test_matchadd_conceal_utf8.vim
source test_menu.vim

View File

@ -85,6 +85,11 @@ func Test_getcompletion()
let l = getcompletion('paint', 'function')
call assert_equal([], l)
let Flambda = {-> 'hello'}
let l = getcompletion('', 'function')
let l = filter(l, {i, v -> v =~ 'lambda'})
call assert_equal(0, len(l))
let l = getcompletion('run', 'file')
call assert_true(index(l, 'runtest.vim') >= 0)
let l = getcompletion('walk', 'file')

View File

@ -106,3 +106,68 @@ func Test_setmatches()
call setmatches(set)
call assert_equal(exp, getmatches())
endfunc
func Test_substitute_expr()
let g:val = 'XXX'
call assert_equal('XXX', substitute('yyy', 'y*', '\=g:val', ''))
call assert_equal('XXX', substitute('yyy', 'y*', {-> g:val}, ''))
call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)',
\ '\=nr2char("0x" . submatch(1))', 'g'))
call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)',
\ {-> nr2char("0x" . submatch(1))}, 'g'))
call assert_equal('231', substitute('123', '\(.\)\(.\)\(.\)',
\ {-> submatch(2) . submatch(3) . submatch(1)}, ''))
func Recurse()
return substitute('yyy', 'y*', {-> g:val}, '')
endfunc
call assert_equal('--', substitute('xxx', 'x*', {-> '-' . Recurse() . '-'}, ''))
endfunc
func Test_invalid_submatch()
" This was causing invalid memory access in Vim-7.4.2232 and older
call assert_fails("call substitute('x', '.', {-> submatch(10)}, '')", 'E935:')
endfunc
func Test_substitute_expr_arg()
call assert_equal('123456789-123456789=', substitute('123456789',
\ '\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)',
\ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, ''))
call assert_equal('123456-123456=789', substitute('123456789',
\ '\(.\)\(.\)\(.\)\(a*\)\(n*\)\(.\)\(.\)\(.\)\(x*\)',
\ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, ''))
call assert_equal('123456789-123456789x=', substitute('123456789',
\ '\(.\)\(.\)\(.*\)',
\ {m -> m[0] . '-' . m[1] . m[2] . m[3] . 'x' . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, ''))
call assert_fails("call substitute('xxx', '.', {m -> string(add(m, 'x'))}, '')", 'E742:')
call assert_fails("call substitute('xxx', '.', {m -> string(insert(m, 'x'))}, '')", 'E742:')
call assert_fails("call substitute('xxx', '.', {m -> string(extend(m, ['x']))}, '')", 'E742:')
call assert_fails("call substitute('xxx', '.', {m -> string(remove(m, 1))}, '')", 'E742:')
endfunc
func Test_function_with_funcref()
let s:f = function('type')
let s:fref = function(s:f)
call assert_equal(v:t_string, s:fref('x'))
call assert_fails("call function('s:f')", 'E700:')
endfunc
func Test_funcref()
func! One()
return 1
endfunc
let OneByName = function('One')
let OneByRef = funcref('One')
func! One()
return 2
endfunc
call assert_equal(2, OneByName())
call assert_equal(1, OneByRef())
let OneByRef = funcref('One')
call assert_equal(2, OneByRef())
call assert_fails('echo funcref("{")', 'E475:')
endfunc

View File

@ -0,0 +1,81 @@
" Test filter() and map()
" list with expression string
func Test_filter_map_list_expr_string()
" filter()
call assert_equal([2, 3, 4], filter([1, 2, 3, 4], 'v:val > 1'))
call assert_equal([3, 4], filter([1, 2, 3, 4], 'v:key > 1'))
call assert_equal([], filter([1, 2, 3, 4], 0))
" map()
call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], 'v:val * 2'))
call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], 'v:key * 2'))
call assert_equal([9, 9, 9, 9], map([1, 2, 3, 4], 9))
endfunc
" dict with expression string
func Test_filter_map_dict_expr_string()
let dict = {"foo": 1, "bar": 2, "baz": 3}
" filter()
call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), 'v:val > 1'))
call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), 'v:key > "bar"'))
call assert_equal({}, filter(copy(dict), 0))
" map()
call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), 'v:val * 2'))
call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), 'v:key[0]'))
call assert_equal({"foo": 9, "bar": 9, "baz": 9}, map(copy(dict), 9))
endfunc
" list with funcref
func Test_filter_map_list_expr_funcref()
" filter()
func! s:filter1(index, val) abort
return a:val > 1
endfunc
call assert_equal([2, 3, 4], filter([1, 2, 3, 4], function('s:filter1')))
func! s:filter2(index, val) abort
return a:index > 1
endfunc
call assert_equal([3, 4], filter([1, 2, 3, 4], function('s:filter2')))
" map()
func! s:filter3(index, val) abort
return a:val * 2
endfunc
call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], function('s:filter3')))
func! s:filter4(index, val) abort
return a:index * 2
endfunc
call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], function('s:filter4')))
endfunc
" dict with funcref
func Test_filter_map_dict_expr_funcref()
let dict = {"foo": 1, "bar": 2, "baz": 3}
" filter()
func! s:filter1(key, val) abort
return a:val > 1
endfunc
call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), function('s:filter1')))
func! s:filter2(key, val) abort
return a:key > "bar"
endfunc
call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), function('s:filter2')))
" map()
func! s:filter3(key, val) abort
return a:val * 2
endfunc
call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), function('s:filter3')))
func! s:filter4(key, val) abort
return a:key[0]
endfunc
call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4')))
endfunc

View File

@ -0,0 +1,287 @@
" Test for lambda and closure
function! Test_lambda_feature()
call assert_equal(1, has('lambda'))
endfunction
function! Test_lambda_with_filter()
let s:x = 2
call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x}))
endfunction
function! Test_lambda_with_map()
let s:x = 1
call assert_equal([2, 3, 4], map([1, 2, 3], {i, v -> v + s:x}))
endfunction
function! Test_lambda_with_sort()
call assert_equal([1, 2, 3, 4, 7], sort([3,7,2,1,4], {a, b -> a - b}))
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, {-> execute("let s:n += 1 | echo s:n", "")}, {"repeat": -1})
endfunction
call s:Foo()
sleep 200ms
" do not collect lambda
call garbagecollect()
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({... -> ['zero', a:1, a:2, a:3]}, ['one', 'two'])
call assert_equal(['zero', 'one', 'two', 'three'], l:Cb('three'))
endfunction
function Test_lambda_fails()
call assert_equal(3, {a, b -> a + b}(1, 2))
call assert_fails('echo {a, a -> a + a}(1, 2)', 'E15:')
call assert_fails('echo {a, b -> a + b)}(1, 2)', 'E15:')
endfunc
func Test_not_lambda()
let x = {'>' : 'foo'}
call assert_equal('foo', x['>'])
endfunc
function! Test_lambda_capture_by_reference()
let v = 1
let l:F = {x -> x + 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 {-> 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({-> x})
endfunction
call assert_equal(123, s:bar())
endfunction
function! Test_lambda_do_not_share_local_variable()
function! s:define_funcs()
let l:One = {-> split(execute("let a = 'abc' | echo a"))[0]}
let l:Two = {-> 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('abc', l:F[0]())
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]}
endfunction
let l:F = s:foo()
call garbagecollect()
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 {... -> a:000 + [x]}
endfunction
function! s:bar()
return s:foo()(1)
endfunction
call assert_equal([1, 2], s:bar())
endfunction
function! Test_lambda_call_lambda_from_lambda()
function! s:foo(x)
let l:F1 = {-> {-> a:x}}
return {-> l:F1()}
endfunction
let l:F = s:foo(1)
call assert_equal(1, l:F()())
endfunction
function! Test_lambda_delfunc()
function! s:gen()
let pl = l:
let l:Foo = {-> get(pl, "Foo", get(pl, "Bar", {-> 0}))}
let l:Bar = l:Foo
delfunction l:Foo
return l:Bar
endfunction
let l:F = s:gen()
call assert_fails(':call l:F()', 'E933:')
endfunction
function! Test_lambda_scope()
function! s:NewCounter()
let c = 0
return {-> [execute('let c += 1'), c][-1]}
endfunction
function! s:NewCounter2()
return {-> [execute('let c += 100'), c][-1]}
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_lambda_share_scope()
function! s:New()
let c = 0
let l:Inc0 = {-> [execute('let c += 1'), c][-1]}
let l:Dec0 = {-> [execute('let c -= 1'), c][-1]}
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_circular_reference()
function! s:Foo()
let d = {}
let d.f = {-> d}
return d.f
endfunction
call s:Foo()
call garbagecollect()
let i = 0 | while i < 10000 | call s:Foo() | let i+= 1 | endwhile
call garbagecollect()
endfunction
function! Test_lambda_combination()
call assert_equal(2, {x -> {x -> x}}(1)(2))
call assert_equal(10, {y -> {x -> x(y)(10)}({y -> y})}({z -> z}))
call assert_equal(5.0, {x -> {y -> x / y}}(10)(2.0))
call assert_equal(6, {x -> {y -> {z -> x + y + z}}}(1)(2)(3))
call assert_equal(6, {x -> {f -> f(x)}}(3)({x -> x * 2}))
call assert_equal(6, {f -> {x -> f(x)}}({x -> x * 2})(3))
" Z combinator
let Z = {f -> {x -> f({y -> x(x)(y)})}({x -> f({y -> x(x)(y)})})}
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 garbagecollect()
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 garbagecollect()
endfunction
function! LambdaFoo()
let x = 0
function! LambdaBar() closure
let x += 1
return x
endfunction
return function('LambdaBar')
endfunction
func Test_closure_refcount()
let g:Count = LambdaFoo()
call test_garbagecollect_now()
call assert_equal(1, g:Count())
let g:Count2 = LambdaFoo()
call test_garbagecollect_now()
call assert_equal(1, g:Count2())
call assert_equal(2, g:Count())
call assert_equal(3, g:Count2())
delfunc LambdaFoo
delfunc LambdaBar
endfunc
" This test is causing a use-after-free on shutdown.
func Test_named_function_closure()
func! Afoo()
let x = 14
func! s:Abar() closure
return x
endfunc
call assert_equal(14, s:Abar())
endfunc
call Afoo()
call assert_equal(14, s:Abar())
call garbagecollect()
call assert_equal(14, s:Abar())
endfunc

View File

@ -14,6 +14,14 @@ func MySort(up, one, two)
return a:one < a:two ? 1 : -1
endfunc
func MyMap(sub, index, val)
return a:val - a:sub
endfunc
func MyFilter(threshold, index, val)
return a:val > a:threshold
endfunc
func Test_partial_args()
let Cb = function('MyFunc', ["foo", "bar"])
@ -36,6 +44,16 @@ func Test_partial_args()
call assert_equal([1, 2, 3], sort([3, 1, 2], Sort))
let Sort = function('MySort', [0])
call assert_equal([3, 2, 1], sort([3, 1, 2], Sort))
let Map = function('MyMap', [2])
call assert_equal([-1, 0, 1], map([1, 2, 3], Map))
let Map = function('MyMap', [3])
call assert_equal([-2, -1, 0], map([1, 2, 3], Map))
let Filter = function('MyFilter', [1])
call assert_equal([2, 3], filter([1, 2, 3], Filter))
let Filter = function('MyFilter', [2])
call assert_equal([3], filter([1, 2, 3], Filter))
endfunc
func MyDictFunc(arg1, arg2) dict
@ -59,6 +77,9 @@ func Test_partial_dict()
call assert_equal("hello/xxx/yyy", Cb("xxx", "yyy"))
call assert_fails('Cb("fff")', 'E492:')
let Cb = function('MyDictFunc', dict)
call assert_equal({"foo": "hello/foo/1", "bar": "hello/bar/2"}, map({"foo": 1, "bar": 2}, Cb))
let dict = {"tr": function('tr', ['hello', 'h', 'H'])}
call assert_equal("Hello", dict.tr())
endfunc

View File

@ -205,9 +205,9 @@ static int included_patches[] = {
// 2238 NA
2237,
// 2236,
// 2235,
2235,
// 2234 NA
// 2233,
2233,
// 2232 NA
// 2231,
// 2230,
@ -243,7 +243,7 @@ static int included_patches[] = {
// 2200,
// 2199 NA
// 2198 NA
// 2197,
2197,
// 2196,
// 2195 NA
2194,
@ -297,16 +297,16 @@ static int included_patches[] = {
2146,
// 2145 NA
// 2144,
// 2143,
// 2142,
// 2141,
2143,
2142,
2141,
// 2140 NA
// 2139,
2139,
// 2138 NA
// 2137,
// 2136,
2137,
2136,
// 2135,
// 2134,
2134,
// 2133 NA
// 2132 NA
// 2131 NA
@ -319,9 +319,9 @@ static int included_patches[] = {
2124,
2123,
// 2122 NA
// 2121,
// 2120,
// 2119,
2121,
2120,
2119,
// 2118 NA
2117,
// 2116 NA
@ -344,13 +344,13 @@ static int included_patches[] = {
2099,
// 2098,
// 2097,
// 2096,
2096,
// 2095,
// 2094 NA
// 2093 NA
// 2092 NA
// 2091 NA
// 2090,
2090,
// 2089 NA
2088,
2087,
@ -364,11 +364,11 @@ static int included_patches[] = {
// 2079 NA
// 2078 NA
2077,
// 2076,
2076,
2075,
2074,
// 2073 NA
// 2072,
2072,
2071,
// 2070 NA
// 2069,
@ -396,7 +396,7 @@ static int included_patches[] = {
// 2047,
// 2046,
// 2045 NA
// 2044,
2044,
2043,
// 2042 NA
// 2041 NA
@ -438,7 +438,7 @@ static int included_patches[] = {
2005,
// 2004 NA
// 2003 NA
// 2002,
2002,
// 2001 NA
2000,
1999,
@ -451,7 +451,7 @@ static int included_patches[] = {
// 1992,
// 1991,
1990,
// 1989,
1989,
// 1988 NA
// 1987 NA
// 1986,
@ -714,7 +714,7 @@ static int included_patches[] = {
1730,
// 1729 NA
1728,
// 1727 NA
1727,
// 1726 NA
// 1725 NA
// 1724 NA

View File

@ -391,6 +391,27 @@ describe('jobs', function()
eq({'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, next_msg())
end)
it('jobstart() works with closures', function()
source([[
fun! MkFun()
let a1 = 'foo'
let a2 = 'bar'
return {id, data, event -> rpcnotify(g:channel, '1', a1, a2, Normalize(data), event)}
endfun
let g:job_opts = {'on_stdout': MkFun()}
call jobstart('echo "some text"', g:job_opts)
]])
eq({'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, next_msg())
end)
it('jobstart() works when closure passed directly to `jobstart`', function()
source([[
let g:job_opts = {'on_stdout': {id, data, event -> rpcnotify(g:channel, '1', 'foo', 'bar', Normalize(data), event)}}
call jobstart('echo "some text"', g:job_opts)
]])
eq({'notification', '1', {'foo', 'bar', {'some text', ''}, 'stdout'}}, next_msg())
end)
describe('jobwait', function()
it('returns a list of status codes', function()
source([[

View File

@ -254,23 +254,33 @@ describe('dictionary change notifications', function()
command('call g:ReplaceWatcher2()')
command('let g:key = "value"')
eq({'notification', '2b', {'key', {old = 'v2', new = 'value'}}}, next_msg())
end)
it('does not crash when freeing a watched dictionary', function()
source([[
function! Watcher(dict, key, value)
echo a:key string(a:value)
endfunction
function! Watcher(dict, key, value)
echo a:key string(a:value)
endfunction
function! MakeWatch()
let d = {'foo': 'bar'}
call dictwatcheradd(d, 'foo', function('Watcher'))
endfunction
function! MakeWatch()
let d = {'foo': 'bar'}
call dictwatcheradd(d, 'foo', function('Watcher'))
endfunction
]])
command('call MakeWatch()')
eq(2, eval('1+1')) -- Still alive?
end)
end)
describe('with lambdas', function()
it('works correctly', function()
source([[
let d = {'foo': 'baz'}
call dictwatcheradd(d, 'foo', {dict, key, value -> rpcnotify(g:channel, '2', key, value)})
let d.foo = 'bar'
]])
eq({'notification', '2', {'foo', {old = 'baz', new = 'bar'}}}, next_msg())
end)
end)
end)