patch 9.0.0370: cleaning up afterwards can make a function messy
Problem: Cleaning up afterwards can make a function messy. Solution: Add the :defer command.
This commit is contained in:
@ -2979,6 +2979,62 @@ is used as a method: >
|
|||||||
let y = GetList()->Filter()
|
let y = GetList()->Filter()
|
||||||
|
|
||||||
|
|
||||||
|
CLEANING UP IN A FUNCTION ~
|
||||||
|
*:defer*
|
||||||
|
:defer {func}({args}) Call {func} when the current function is done.
|
||||||
|
{args} are evaluated here.
|
||||||
|
|
||||||
|
Quite often a command in a function has a global effect, which must be undone
|
||||||
|
when the function finishes. Handling this in all kinds of situations can be a
|
||||||
|
hassle. Especially when an unexpected error is encountered. This can be done
|
||||||
|
with `try` / `finally` blocks, but this gets complicated when there is more
|
||||||
|
than one.
|
||||||
|
|
||||||
|
A much simpler solution is using `defer`. It schedules a function call when
|
||||||
|
the function is returning, no matter if there is an error. Example: >
|
||||||
|
func Filter(text)
|
||||||
|
call writefile(a:text, 'Tempfile')
|
||||||
|
call system('filter < Tempfile > Outfile')
|
||||||
|
call Handle('Outfile')
|
||||||
|
call delete('Tempfile')
|
||||||
|
call delete('Outfile')
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
Here 'Tempfile' and 'Outfile' will not be deleted if something causes the
|
||||||
|
function to abort. `:defer` can be used to avoid that: >
|
||||||
|
func Filter(text)
|
||||||
|
call writefile(a:text, 'Tempfile')
|
||||||
|
defer delete('Tempfile')
|
||||||
|
defer delete('Outfile')
|
||||||
|
call system('filter < Tempfile > Outfile')
|
||||||
|
call Handle('Outfile')
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
Note that deleting "Outfile" is scheduled before calling system(), since it
|
||||||
|
can be created even when `system()` fails.
|
||||||
|
|
||||||
|
The defered functions are called in reverse order, the last one added is
|
||||||
|
executed first. A useless example: >
|
||||||
|
func Useless()
|
||||||
|
for s in range(3)
|
||||||
|
defer execute('echomsg "number ' .. s .. '"')
|
||||||
|
endfor
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
Now `:messages` shows:
|
||||||
|
number 2
|
||||||
|
number 1
|
||||||
|
number 0
|
||||||
|
|
||||||
|
Any return value of the deferred function is discarded. The function cannot
|
||||||
|
be followed by anything, such as "->func" or ".member". Currently `:defer
|
||||||
|
GetArg()->TheFunc()` does not work, it may work in a later version.
|
||||||
|
|
||||||
|
Errors are reported but do not cause aborting execution of deferred functions.
|
||||||
|
|
||||||
|
No range is accepted.
|
||||||
|
|
||||||
|
|
||||||
AUTOMATICALLY LOADING FUNCTIONS ~
|
AUTOMATICALLY LOADING FUNCTIONS ~
|
||||||
*autoload-functions*
|
*autoload-functions*
|
||||||
When using many or large functions, it's possible to automatically define them
|
When using many or large functions, it's possible to automatically define them
|
||||||
|
|||||||
@ -9,28 +9,28 @@ static const unsigned short cmdidxs1[26] =
|
|||||||
/* b */ 21,
|
/* b */ 21,
|
||||||
/* c */ 45,
|
/* c */ 45,
|
||||||
/* d */ 112,
|
/* d */ 112,
|
||||||
/* e */ 137,
|
/* e */ 138,
|
||||||
/* f */ 166,
|
/* f */ 167,
|
||||||
/* g */ 183,
|
/* g */ 184,
|
||||||
/* h */ 189,
|
/* h */ 190,
|
||||||
/* i */ 199,
|
/* i */ 200,
|
||||||
/* j */ 219,
|
/* j */ 220,
|
||||||
/* k */ 221,
|
/* k */ 222,
|
||||||
/* l */ 226,
|
/* l */ 227,
|
||||||
/* m */ 289,
|
/* m */ 290,
|
||||||
/* n */ 307,
|
/* n */ 308,
|
||||||
/* o */ 327,
|
/* o */ 328,
|
||||||
/* p */ 339,
|
/* p */ 340,
|
||||||
/* q */ 378,
|
/* q */ 379,
|
||||||
/* r */ 381,
|
/* r */ 382,
|
||||||
/* s */ 401,
|
/* s */ 402,
|
||||||
/* t */ 471,
|
/* t */ 472,
|
||||||
/* u */ 517,
|
/* u */ 518,
|
||||||
/* v */ 528,
|
/* v */ 529,
|
||||||
/* w */ 549,
|
/* w */ 550,
|
||||||
/* x */ 563,
|
/* x */ 564,
|
||||||
/* y */ 573,
|
/* y */ 574,
|
||||||
/* z */ 574
|
/* z */ 575
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -44,7 +44,7 @@ static const unsigned char cmdidxs2[26][26] =
|
|||||||
/* a */ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 0, 0, 8, 17, 0, 18, 0, 0, 0, 0, 0 },
|
/* a */ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 0, 0, 8, 17, 0, 18, 0, 0, 0, 0, 0 },
|
||||||
/* b */ { 2, 0, 0, 5, 6, 8, 0, 0, 0, 0, 0, 9, 10, 11, 12, 13, 0, 14, 0, 0, 0, 0, 23, 0, 0, 0 },
|
/* b */ { 2, 0, 0, 5, 6, 8, 0, 0, 0, 0, 0, 9, 10, 11, 12, 13, 0, 14, 0, 0, 0, 0, 23, 0, 0, 0 },
|
||||||
/* c */ { 3, 12, 16, 18, 20, 22, 25, 0, 0, 0, 0, 33, 38, 41, 47, 57, 59, 60, 61, 0, 63, 0, 66, 0, 0, 0 },
|
/* c */ { 3, 12, 16, 18, 20, 22, 25, 0, 0, 0, 0, 33, 38, 41, 47, 57, 59, 60, 61, 0, 63, 0, 66, 0, 0, 0 },
|
||||||
/* d */ { 0, 0, 0, 0, 0, 0, 0, 0, 8, 18, 0, 19, 0, 0, 20, 0, 0, 22, 23, 0, 0, 0, 0, 0, 0, 0 },
|
/* d */ { 0, 0, 0, 0, 0, 0, 0, 0, 9, 19, 0, 20, 0, 0, 21, 0, 0, 23, 24, 0, 0, 0, 0, 0, 0, 0 },
|
||||||
/* e */ { 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 9, 11, 12, 0, 0, 0, 0, 0, 0, 0, 23, 0, 24, 0, 0 },
|
/* e */ { 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 9, 11, 12, 0, 0, 0, 0, 0, 0, 0, 23, 0, 24, 0, 0 },
|
||||||
/* f */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0 },
|
/* f */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0 },
|
||||||
/* g */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 4, 5, 0, 0, 0, 0 },
|
/* g */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 4, 5, 0, 0, 0, 0 },
|
||||||
@ -69,4 +69,4 @@ static const unsigned char cmdidxs2[26][26] =
|
|||||||
/* z */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
|
/* z */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
static const int command_count = 591;
|
static const int command_count = 592;
|
||||||
|
|||||||
@ -467,6 +467,9 @@ EXCMD(CMD_def, "def", ex_function,
|
|||||||
EXCMD(CMD_defcompile, "defcompile", ex_defcompile,
|
EXCMD(CMD_defcompile, "defcompile", ex_defcompile,
|
||||||
EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK|EX_TRLBAR|EX_EXTRA,
|
EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK|EX_TRLBAR|EX_EXTRA,
|
||||||
ADDR_NONE),
|
ADDR_NONE),
|
||||||
|
EXCMD(CMD_defer, "defer", ex_call,
|
||||||
|
EX_NEEDARG|EX_EXTRA|EX_NOTRLCOM|EX_EXPR_ARG|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK,
|
||||||
|
ADDR_NONE),
|
||||||
EXCMD(CMD_delcommand, "delcommand", ex_delcommand,
|
EXCMD(CMD_delcommand, "delcommand", ex_delcommand,
|
||||||
EX_NEEDARG|EX_WORD1|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
|
EX_NEEDARG|EX_WORD1|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
|
||||||
ADDR_NONE),
|
ADDR_NONE),
|
||||||
|
|||||||
@ -58,6 +58,7 @@ void func_ptr_unref(ufunc_T *fp);
|
|||||||
void func_ref(char_u *name);
|
void func_ref(char_u *name);
|
||||||
void func_ptr_ref(ufunc_T *fp);
|
void func_ptr_ref(ufunc_T *fp);
|
||||||
void ex_return(exarg_T *eap);
|
void ex_return(exarg_T *eap);
|
||||||
|
void handle_defer(void);
|
||||||
void ex_call(exarg_T *eap);
|
void ex_call(exarg_T *eap);
|
||||||
int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv);
|
int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv);
|
||||||
void discard_pending_return(void *rettv);
|
void discard_pending_return(void *rettv);
|
||||||
|
|||||||
@ -21,6 +21,7 @@ char_u *compile_finally(char_u *arg, cctx_T *cctx);
|
|||||||
char_u *compile_endtry(char_u *arg, cctx_T *cctx);
|
char_u *compile_endtry(char_u *arg, cctx_T *cctx);
|
||||||
char_u *compile_throw(char_u *arg, cctx_T *cctx);
|
char_u *compile_throw(char_u *arg, cctx_T *cctx);
|
||||||
char_u *compile_eval(char_u *arg, cctx_T *cctx);
|
char_u *compile_eval(char_u *arg, cctx_T *cctx);
|
||||||
|
char_u *compile_defer(char_u *arg_start, cctx_T *cctx);
|
||||||
char_u *compile_mult_expr(char_u *arg, int cmdidx, cctx_T *cctx);
|
char_u *compile_mult_expr(char_u *arg, int cmdidx, cctx_T *cctx);
|
||||||
char_u *compile_put(char_u *arg, exarg_T *eap, cctx_T *cctx);
|
char_u *compile_put(char_u *arg, exarg_T *eap, cctx_T *cctx);
|
||||||
char_u *compile_exec(char_u *line_arg, exarg_T *eap, cctx_T *cctx);
|
char_u *compile_exec(char_u *line_arg, exarg_T *eap, cctx_T *cctx);
|
||||||
|
|||||||
@ -4,6 +4,7 @@ void clear_ppconst(ppconst_T *ppconst);
|
|||||||
int compile_member(int is_slice, int *keeping_dict, cctx_T *cctx);
|
int compile_member(int is_slice, int *keeping_dict, cctx_T *cctx);
|
||||||
int compile_load_scriptvar(cctx_T *cctx, char_u *name, char_u *start, char_u **end);
|
int compile_load_scriptvar(cctx_T *cctx, char_u *name, char_u *start, char_u **end);
|
||||||
int compile_load(char_u **arg, char_u *end_arg, cctx_T *cctx, int is_expr, int error);
|
int compile_load(char_u **arg, char_u *end_arg, cctx_T *cctx, int is_expr, int error);
|
||||||
|
int compile_arguments(char_u **arg, cctx_T *cctx, int *argcount, ca_special_T special_fn);
|
||||||
char_u *to_name_end(char_u *arg, int use_namespace);
|
char_u *to_name_end(char_u *arg, int use_namespace);
|
||||||
char_u *to_name_const_end(char_u *arg);
|
char_u *to_name_const_end(char_u *arg);
|
||||||
int get_lambda_tv_and_compile(char_u **arg, typval_T *rettv, int types_optional, evalarg_T *evalarg);
|
int get_lambda_tv_and_compile(char_u **arg, typval_T *rettv, int types_optional, evalarg_T *evalarg);
|
||||||
|
|||||||
@ -23,7 +23,7 @@ int generate_PUSHS(cctx_T *cctx, char_u **str);
|
|||||||
int generate_PUSHCHANNEL(cctx_T *cctx);
|
int generate_PUSHCHANNEL(cctx_T *cctx);
|
||||||
int generate_PUSHJOB(cctx_T *cctx);
|
int generate_PUSHJOB(cctx_T *cctx);
|
||||||
int generate_PUSHBLOB(cctx_T *cctx, blob_T *blob);
|
int generate_PUSHBLOB(cctx_T *cctx, blob_T *blob);
|
||||||
int generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type);
|
int generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type, int may_prefix);
|
||||||
int generate_AUTOLOAD(cctx_T *cctx, char_u *name, type_T *type);
|
int generate_AUTOLOAD(cctx_T *cctx, char_u *name, type_T *type);
|
||||||
int generate_GETITEM(cctx_T *cctx, int index, int with_op);
|
int generate_GETITEM(cctx_T *cctx, int index, int with_op);
|
||||||
int generate_SLICE(cctx_T *cctx, int count);
|
int generate_SLICE(cctx_T *cctx, int count);
|
||||||
@ -52,6 +52,7 @@ int generate_BLOBAPPEND(cctx_T *cctx);
|
|||||||
int generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount);
|
int generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount);
|
||||||
int generate_UCALL(cctx_T *cctx, char_u *name, int argcount);
|
int generate_UCALL(cctx_T *cctx, char_u *name, int argcount);
|
||||||
int generate_PCALL(cctx_T *cctx, int argcount, char_u *name, type_T *type, int at_top);
|
int generate_PCALL(cctx_T *cctx, int argcount, char_u *name, type_T *type, int at_top);
|
||||||
|
int generate_DEFER(cctx_T *cctx, int var_idx, int argcount);
|
||||||
int generate_STRINGMEMBER(cctx_T *cctx, char_u *name, size_t len);
|
int generate_STRINGMEMBER(cctx_T *cctx, char_u *name, size_t len);
|
||||||
int generate_ECHO(cctx_T *cctx, int with_white, int count);
|
int generate_ECHO(cctx_T *cctx, int with_white, int count);
|
||||||
int generate_MULT_EXPR(cctx_T *cctx, isntype_T isn_type, int count);
|
int generate_MULT_EXPR(cctx_T *cctx, isntype_T isn_type, int count);
|
||||||
|
|||||||
@ -1753,6 +1753,7 @@ struct funccall_S
|
|||||||
linenr_T breakpoint; // next line with breakpoint or zero
|
linenr_T breakpoint; // next line with breakpoint or zero
|
||||||
int dbg_tick; // debug_tick when breakpoint was set
|
int dbg_tick; // debug_tick when breakpoint was set
|
||||||
int level; // top nesting level of executed function
|
int level; // top nesting level of executed function
|
||||||
|
garray_T fc_defer; // functions to be called on return
|
||||||
#ifdef FEAT_PROFILE
|
#ifdef FEAT_PROFILE
|
||||||
proftime_T prof_child; // time spent in a child
|
proftime_T prof_child; // time spent in a child
|
||||||
#endif
|
#endif
|
||||||
@ -1767,6 +1768,14 @@ struct funccall_S
|
|||||||
// "func"
|
// "func"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// structure used as item in "fc_defer"
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
char_u *dr_name; // function name, allocated
|
||||||
|
typval_T dr_argvars[MAX_FUNC_ARGS + 1];
|
||||||
|
int dr_argcount;
|
||||||
|
} defer_T;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Struct used by trans_function_name()
|
* Struct used by trans_function_name()
|
||||||
*/
|
*/
|
||||||
@ -2850,7 +2859,7 @@ struct file_buffer
|
|||||||
int b_u_synced; // entry lists are synced
|
int b_u_synced; // entry lists are synced
|
||||||
long b_u_seq_last; // last used undo sequence number
|
long b_u_seq_last; // last used undo sequence number
|
||||||
long b_u_save_nr_last; // counter for last file write
|
long b_u_save_nr_last; // counter for last file write
|
||||||
long b_u_seq_cur; // hu_seq of header below which we are now
|
long b_u_seq_cur; // uh_seq of header below which we are now
|
||||||
time_T b_u_time_cur; // uh_time of header below which we are now
|
time_T b_u_time_cur; // uh_time of header below which we are now
|
||||||
long b_u_save_nr_cur; // file write nr after which we are now
|
long b_u_save_nr_cur; // file write nr after which we are now
|
||||||
|
|
||||||
|
|||||||
@ -529,4 +529,36 @@ func Test_funcdef_alloc_failure()
|
|||||||
bw!
|
bw!
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
|
func AddDefer(arg)
|
||||||
|
call extend(g:deferred, [a:arg])
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
func WithDeferTwo()
|
||||||
|
call extend(g:deferred, ['in Two'])
|
||||||
|
for nr in range(3)
|
||||||
|
defer AddDefer('Two' .. nr)
|
||||||
|
endfor
|
||||||
|
call extend(g:deferred, ['end Two'])
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
func WithDeferOne()
|
||||||
|
call extend(g:deferred, ['in One'])
|
||||||
|
call writefile(['text'], 'Xfuncdefer')
|
||||||
|
defer delete('Xfuncdefer')
|
||||||
|
defer AddDefer('One')
|
||||||
|
call WithDeferTwo()
|
||||||
|
call extend(g:deferred, ['end One'])
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
func Test_defer()
|
||||||
|
let g:deferred = []
|
||||||
|
call WithDeferOne()
|
||||||
|
|
||||||
|
call assert_equal(['in One', 'in Two', 'end Two', 'Two2', 'Two1', 'Two0', 'end One', 'One'], g:deferred)
|
||||||
|
unlet g:deferred
|
||||||
|
|
||||||
|
call assert_equal('', glob('Xfuncdefer'))
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
|
||||||
" vim: shiftwidth=2 sts=2 expandtab
|
" vim: shiftwidth=2 sts=2 expandtab
|
||||||
|
|||||||
@ -2900,4 +2900,19 @@ def Test_disassemble_bitshift()
|
|||||||
'10 RETURN void', instr)
|
'10 RETURN void', instr)
|
||||||
enddef
|
enddef
|
||||||
|
|
||||||
|
def s:OneDefer()
|
||||||
|
defer delete("file")
|
||||||
|
enddef
|
||||||
|
|
||||||
|
def Test_disassemble_defer()
|
||||||
|
var instr = execute('disassemble s:OneDefer')
|
||||||
|
assert_match('OneDefer\_s*' ..
|
||||||
|
'defer delete("file")\_s*' ..
|
||||||
|
'\d PUSHFUNC "delete"\_s*' ..
|
||||||
|
'\d PUSHS "file"\_s*' ..
|
||||||
|
'\d DEFER 1 args\_s*' ..
|
||||||
|
'\d RETURN\_s*',
|
||||||
|
instr)
|
||||||
|
enddef
|
||||||
|
|
||||||
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
|
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
|
||||||
|
|||||||
@ -4272,6 +4272,36 @@ def Test_cexpr_errmsg_line_number()
|
|||||||
v9.CheckScriptFailure(lines, 'E777', 2)
|
v9.CheckScriptFailure(lines, 'E777', 2)
|
||||||
enddef
|
enddef
|
||||||
|
|
||||||
|
def AddDefer(s: string)
|
||||||
|
g:deferred->extend([s])
|
||||||
|
enddef
|
||||||
|
|
||||||
|
def DeferTwo()
|
||||||
|
g:deferred->extend(['in Two'])
|
||||||
|
for n in range(3)
|
||||||
|
defer g:AddDefer('two' .. n)
|
||||||
|
endfor
|
||||||
|
g:deferred->extend(['end Two'])
|
||||||
|
enddef
|
||||||
|
|
||||||
|
def DeferOne()
|
||||||
|
g:deferred->extend(['in One'])
|
||||||
|
defer g:AddDefer('one')
|
||||||
|
g:DeferTwo()
|
||||||
|
g:deferred->extend(['end One'])
|
||||||
|
|
||||||
|
writefile(['text'], 'XdeferFile')
|
||||||
|
defer delete('XdeferFile')
|
||||||
|
enddef
|
||||||
|
|
||||||
|
def Test_defer()
|
||||||
|
g:deferred = []
|
||||||
|
g:DeferOne()
|
||||||
|
assert_equal(['in One', 'in Two', 'end Two', 'two2', 'two1', 'two0', 'end One', 'one'], g:deferred)
|
||||||
|
unlet g:deferred
|
||||||
|
assert_equal('', glob('XdeferFile'))
|
||||||
|
enddef
|
||||||
|
|
||||||
" The following messes up syntax highlight, keep near the end.
|
" The following messes up syntax highlight, keep near the end.
|
||||||
if has('python3')
|
if has('python3')
|
||||||
def Test_python3_command()
|
def Test_python3_command()
|
||||||
|
|||||||
299
src/userfunc.c
299
src/userfunc.c
@ -1728,44 +1728,36 @@ emsg_funcname(char *ermsg, char_u *name)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Allocate a variable for the result of a function.
|
* Get function arguments at "*arg" and advance it.
|
||||||
* Return OK or FAIL.
|
* Return them in "*argvars[MAX_FUNC_ARGS + 1]" and the count in "argcount".
|
||||||
*/
|
*/
|
||||||
int
|
static int
|
||||||
get_func_tv(
|
get_func_arguments(
|
||||||
char_u *name, // name of the function
|
char_u **arg,
|
||||||
int len, // length of "name" or -1 to use strlen()
|
evalarg_T *evalarg,
|
||||||
typval_T *rettv,
|
int partial_argc,
|
||||||
char_u **arg, // argument, pointing to the '('
|
typval_T *argvars,
|
||||||
evalarg_T *evalarg, // for line continuation
|
int *argcount)
|
||||||
funcexe_T *funcexe) // various values
|
|
||||||
{
|
{
|
||||||
char_u *argp;
|
char_u *argp = *arg;
|
||||||
int ret = OK;
|
int ret = OK;
|
||||||
typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
|
|
||||||
int argcount = 0; // number of arguments found
|
|
||||||
int vim9script = in_vim9script();
|
int vim9script = in_vim9script();
|
||||||
int evaluate = evalarg == NULL
|
int evaluate = evalarg == NULL
|
||||||
? FALSE : (evalarg->eval_flags & EVAL_EVALUATE);
|
? FALSE : (evalarg->eval_flags & EVAL_EVALUATE);
|
||||||
|
|
||||||
/*
|
while (*argcount < MAX_FUNC_ARGS - partial_argc)
|
||||||
* Get the arguments.
|
|
||||||
*/
|
|
||||||
argp = *arg;
|
|
||||||
while (argcount < MAX_FUNC_ARGS - (funcexe->fe_partial == NULL ? 0
|
|
||||||
: funcexe->fe_partial->pt_argc))
|
|
||||||
{
|
{
|
||||||
// skip the '(' or ',' and possibly line breaks
|
// skip the '(' or ',' and possibly line breaks
|
||||||
argp = skipwhite_and_linebreak(argp + 1, evalarg);
|
argp = skipwhite_and_linebreak(argp + 1, evalarg);
|
||||||
|
|
||||||
if (*argp == ')' || *argp == ',' || *argp == NUL)
|
if (*argp == ')' || *argp == ',' || *argp == NUL)
|
||||||
break;
|
break;
|
||||||
if (eval1(&argp, &argvars[argcount], evalarg) == FAIL)
|
if (eval1(&argp, &argvars[*argcount], evalarg) == FAIL)
|
||||||
{
|
{
|
||||||
ret = FAIL;
|
ret = FAIL;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
++argcount;
|
++*argcount;
|
||||||
// The comma should come right after the argument, but this wasn't
|
// The comma should come right after the argument, but this wasn't
|
||||||
// checked previously, thus only enforce it in Vim9 script.
|
// checked previously, thus only enforce it in Vim9 script.
|
||||||
if (vim9script)
|
if (vim9script)
|
||||||
@ -1791,11 +1783,41 @@ get_func_tv(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
argp = skipwhite_and_linebreak(argp, evalarg);
|
argp = skipwhite_and_linebreak(argp, evalarg);
|
||||||
if (*argp == ')')
|
if (*argp == ')')
|
||||||
++argp;
|
++argp;
|
||||||
else
|
else
|
||||||
ret = FAIL;
|
ret = FAIL;
|
||||||
|
*arg = argp;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Call a function and put the result in "rettv".
|
||||||
|
* Return OK or FAIL.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
get_func_tv(
|
||||||
|
char_u *name, // name of the function
|
||||||
|
int len, // length of "name" or -1 to use strlen()
|
||||||
|
typval_T *rettv,
|
||||||
|
char_u **arg, // argument, pointing to the '('
|
||||||
|
evalarg_T *evalarg, // for line continuation
|
||||||
|
funcexe_T *funcexe) // various values
|
||||||
|
{
|
||||||
|
char_u *argp;
|
||||||
|
int ret = OK;
|
||||||
|
typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
|
||||||
|
int argcount = 0; // number of arguments found
|
||||||
|
int vim9script = in_vim9script();
|
||||||
|
int evaluate = evalarg == NULL
|
||||||
|
? FALSE : (evalarg->eval_flags & EVAL_EVALUATE);
|
||||||
|
|
||||||
|
argp = *arg;
|
||||||
|
ret = get_func_arguments(&argp, evalarg,
|
||||||
|
(funcexe->fe_partial == NULL ? 0 : funcexe->fe_partial->pt_argc),
|
||||||
|
argvars, &argcount);
|
||||||
|
|
||||||
if (ret == OK)
|
if (ret == OK)
|
||||||
{
|
{
|
||||||
@ -2884,6 +2906,9 @@ call_user_func(
|
|||||||
do_cmdline(NULL, get_func_line, (void *)fc,
|
do_cmdline(NULL, get_func_line, (void *)fc,
|
||||||
DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
|
DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
|
||||||
|
|
||||||
|
// Invoke functions added with ":defer".
|
||||||
|
handle_defer();
|
||||||
|
|
||||||
--RedrawingDisabled;
|
--RedrawingDisabled;
|
||||||
|
|
||||||
// when the function was aborted because of an error, return -1
|
// when the function was aborted because of an error, return -1
|
||||||
@ -5457,8 +5482,164 @@ ex_return(exarg_T *eap)
|
|||||||
clear_evalarg(&evalarg, eap);
|
clear_evalarg(&evalarg, eap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
ex_call_inner(
|
||||||
|
exarg_T *eap,
|
||||||
|
char_u *name,
|
||||||
|
char_u **arg,
|
||||||
|
char_u *startarg,
|
||||||
|
funcexe_T *funcexe_init,
|
||||||
|
evalarg_T *evalarg)
|
||||||
|
{
|
||||||
|
linenr_T lnum;
|
||||||
|
int doesrange;
|
||||||
|
typval_T rettv;
|
||||||
|
int failed = FALSE;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When skipping, evaluate the function once, to find the end of the
|
||||||
|
* arguments.
|
||||||
|
* When the function takes a range, this is discovered after the first
|
||||||
|
* call, and the loop is broken.
|
||||||
|
*/
|
||||||
|
if (eap->skip)
|
||||||
|
{
|
||||||
|
++emsg_skip;
|
||||||
|
lnum = eap->line2; // do it once, also with an invalid range
|
||||||
|
}
|
||||||
|
else
|
||||||
|
lnum = eap->line1;
|
||||||
|
for ( ; lnum <= eap->line2; ++lnum)
|
||||||
|
{
|
||||||
|
funcexe_T funcexe;
|
||||||
|
|
||||||
|
if (!eap->skip && eap->addr_count > 0)
|
||||||
|
{
|
||||||
|
if (lnum > curbuf->b_ml.ml_line_count)
|
||||||
|
{
|
||||||
|
// If the function deleted lines or switched to another buffer
|
||||||
|
// the line number may become invalid.
|
||||||
|
emsg(_(e_invalid_range));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
curwin->w_cursor.lnum = lnum;
|
||||||
|
curwin->w_cursor.col = 0;
|
||||||
|
curwin->w_cursor.coladd = 0;
|
||||||
|
}
|
||||||
|
*arg = startarg;
|
||||||
|
|
||||||
|
funcexe = *funcexe_init;
|
||||||
|
funcexe.fe_doesrange = &doesrange;
|
||||||
|
rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this
|
||||||
|
if (get_func_tv(name, -1, &rettv, arg, evalarg, &funcexe) == FAIL)
|
||||||
|
{
|
||||||
|
failed = TRUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (has_watchexpr())
|
||||||
|
dbg_check_breakpoint(eap);
|
||||||
|
|
||||||
|
// Handle a function returning a Funcref, Dictionary or List.
|
||||||
|
if (handle_subscript(arg, NULL, &rettv,
|
||||||
|
eap->skip ? NULL : &EVALARG_EVALUATE, TRUE) == FAIL)
|
||||||
|
{
|
||||||
|
failed = TRUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_tv(&rettv);
|
||||||
|
if (doesrange || eap->skip)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Stop when immediately aborting on error, or when an interrupt
|
||||||
|
// occurred or an exception was thrown but not caught.
|
||||||
|
// get_func_tv() returned OK, so that the check for trailing
|
||||||
|
// characters below is executed.
|
||||||
|
if (aborting())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (eap->skip)
|
||||||
|
--emsg_skip;
|
||||||
|
return failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Core part of ":defer func(arg)". "arg" points to the "(" and is advanced.
|
||||||
|
* Returns FAIL or OK.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
ex_defer_inner(char_u *name, char_u **arg, evalarg_T *evalarg)
|
||||||
|
{
|
||||||
|
typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
|
||||||
|
int argcount = 0; // number of arguments found
|
||||||
|
defer_T *dr;
|
||||||
|
int ret = FAIL;
|
||||||
|
char_u *saved_name;
|
||||||
|
|
||||||
|
if (current_funccal == NULL)
|
||||||
|
{
|
||||||
|
semsg(_(e_str_not_inside_function), "defer");
|
||||||
|
return FAIL;
|
||||||
|
}
|
||||||
|
if (get_func_arguments(arg, evalarg, FALSE, argvars, &argcount) == FAIL)
|
||||||
|
goto theend;
|
||||||
|
saved_name = vim_strsave(name);
|
||||||
|
if (saved_name == NULL)
|
||||||
|
goto theend;
|
||||||
|
|
||||||
|
if (current_funccal->fc_defer.ga_itemsize == 0)
|
||||||
|
ga_init2(¤t_funccal->fc_defer, sizeof(defer_T), 10);
|
||||||
|
if (ga_grow(¤t_funccal->fc_defer, 1) == FAIL)
|
||||||
|
goto theend;
|
||||||
|
dr = ((defer_T *)current_funccal->fc_defer.ga_data)
|
||||||
|
+ current_funccal->fc_defer.ga_len++;
|
||||||
|
dr->dr_name = saved_name;
|
||||||
|
dr->dr_argcount = argcount;
|
||||||
|
while (argcount > 0)
|
||||||
|
{
|
||||||
|
--argcount;
|
||||||
|
dr->dr_argvars[argcount] = argvars[argcount];
|
||||||
|
}
|
||||||
|
ret = OK;
|
||||||
|
|
||||||
|
theend:
|
||||||
|
while (--argcount >= 0)
|
||||||
|
clear_tv(&argvars[argcount]);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Invoked after a functions has finished: invoke ":defer" functions.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
handle_defer(void)
|
||||||
|
{
|
||||||
|
int idx;
|
||||||
|
|
||||||
|
for (idx = current_funccal->fc_defer.ga_len - 1; idx >= 0; --idx)
|
||||||
|
{
|
||||||
|
funcexe_T funcexe;
|
||||||
|
typval_T rettv;
|
||||||
|
defer_T *dr = ((defer_T *)current_funccal->fc_defer.ga_data) + idx;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
CLEAR_FIELD(funcexe);
|
||||||
|
funcexe.fe_evaluate = TRUE;
|
||||||
|
|
||||||
|
rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this
|
||||||
|
call_func(dr->dr_name, -1, &rettv,
|
||||||
|
dr->dr_argcount, dr->dr_argvars, &funcexe);
|
||||||
|
clear_tv(&rettv);
|
||||||
|
vim_free(dr->dr_name);
|
||||||
|
for (i = dr->dr_argcount - 1; i >= 0; --i)
|
||||||
|
clear_tv(&dr->dr_argvars[i]);
|
||||||
|
}
|
||||||
|
ga_clear(¤t_funccal->fc_defer);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ":1,25call func(arg1, arg2)" function call.
|
* ":1,25call func(arg1, arg2)" function call.
|
||||||
|
* ":defer func(arg1, arg2)" deferred function call.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
ex_call(exarg_T *eap)
|
ex_call(exarg_T *eap)
|
||||||
@ -5468,9 +5649,6 @@ ex_call(exarg_T *eap)
|
|||||||
char_u *name;
|
char_u *name;
|
||||||
char_u *tofree;
|
char_u *tofree;
|
||||||
int len;
|
int len;
|
||||||
typval_T rettv;
|
|
||||||
linenr_T lnum;
|
|
||||||
int doesrange;
|
|
||||||
int failed = FALSE;
|
int failed = FALSE;
|
||||||
funcdict_T fudi;
|
funcdict_T fudi;
|
||||||
partial_T *partial = NULL;
|
partial_T *partial = NULL;
|
||||||
@ -5482,6 +5660,8 @@ ex_call(exarg_T *eap)
|
|||||||
fill_evalarg_from_eap(&evalarg, eap, eap->skip);
|
fill_evalarg_from_eap(&evalarg, eap, eap->skip);
|
||||||
if (eap->skip)
|
if (eap->skip)
|
||||||
{
|
{
|
||||||
|
typval_T rettv;
|
||||||
|
|
||||||
// trans_function_name() doesn't work well when skipping, use eval0()
|
// trans_function_name() doesn't work well when skipping, use eval0()
|
||||||
// instead to skip to any following command, e.g. for:
|
// instead to skip to any following command, e.g. for:
|
||||||
// :if 0 | call dict.foo().bar() | endif
|
// :if 0 | call dict.foo().bar() | endif
|
||||||
@ -5531,82 +5711,29 @@ ex_call(exarg_T *eap)
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if (eap->cmdidx == CMD_defer)
|
||||||
* When skipping, evaluate the function once, to find the end of the
|
|
||||||
* arguments.
|
|
||||||
* When the function takes a range, this is discovered after the first
|
|
||||||
* call, and the loop is broken.
|
|
||||||
*/
|
|
||||||
if (eap->skip)
|
|
||||||
{
|
{
|
||||||
++emsg_skip;
|
arg = startarg;
|
||||||
lnum = eap->line2; // do it once, also with an invalid range
|
failed = ex_defer_inner(name, &arg, &evalarg) == FAIL;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
lnum = eap->line1;
|
|
||||||
for ( ; lnum <= eap->line2; ++lnum)
|
|
||||||
{
|
{
|
||||||
funcexe_T funcexe;
|
funcexe_T funcexe;
|
||||||
|
|
||||||
if (!eap->skip && eap->addr_count > 0)
|
|
||||||
{
|
|
||||||
if (lnum > curbuf->b_ml.ml_line_count)
|
|
||||||
{
|
|
||||||
// If the function deleted lines or switched to another buffer
|
|
||||||
// the line number may become invalid.
|
|
||||||
emsg(_(e_invalid_range));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
curwin->w_cursor.lnum = lnum;
|
|
||||||
curwin->w_cursor.col = 0;
|
|
||||||
curwin->w_cursor.coladd = 0;
|
|
||||||
}
|
|
||||||
arg = startarg;
|
|
||||||
|
|
||||||
CLEAR_FIELD(funcexe);
|
CLEAR_FIELD(funcexe);
|
||||||
funcexe.fe_firstline = eap->line1;
|
funcexe.fe_check_type = type;
|
||||||
funcexe.fe_lastline = eap->line2;
|
|
||||||
funcexe.fe_doesrange = &doesrange;
|
|
||||||
funcexe.fe_evaluate = !eap->skip;
|
|
||||||
funcexe.fe_partial = partial;
|
funcexe.fe_partial = partial;
|
||||||
funcexe.fe_selfdict = fudi.fd_dict;
|
funcexe.fe_selfdict = fudi.fd_dict;
|
||||||
funcexe.fe_check_type = type;
|
funcexe.fe_firstline = eap->line1;
|
||||||
|
funcexe.fe_lastline = eap->line2;
|
||||||
funcexe.fe_found_var = found_var;
|
funcexe.fe_found_var = found_var;
|
||||||
rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this
|
funcexe.fe_evaluate = !eap->skip;
|
||||||
if (get_func_tv(name, -1, &rettv, &arg, &evalarg, &funcexe) == FAIL)
|
failed = ex_call_inner(eap, name, &arg, startarg, &funcexe, &evalarg);
|
||||||
{
|
|
||||||
failed = TRUE;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if (has_watchexpr())
|
|
||||||
dbg_check_breakpoint(eap);
|
|
||||||
|
|
||||||
// Handle a function returning a Funcref, Dictionary or List.
|
|
||||||
if (handle_subscript(&arg, NULL, &rettv,
|
|
||||||
eap->skip ? NULL : &EVALARG_EVALUATE, TRUE) == FAIL)
|
|
||||||
{
|
|
||||||
failed = TRUE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
clear_tv(&rettv);
|
|
||||||
if (doesrange || eap->skip)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Stop when immediately aborting on error, or when an interrupt
|
|
||||||
// occurred or an exception was thrown but not caught.
|
|
||||||
// get_func_tv() returned OK, so that the check for trailing
|
|
||||||
// characters below is executed.
|
|
||||||
if (aborting())
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (eap->skip)
|
|
||||||
--emsg_skip;
|
|
||||||
|
|
||||||
// When inside :try we need to check for following "| catch" or "| endtry".
|
// When inside :try we need to check for following "| catch" or "| endtry".
|
||||||
// Not when there was an error, but do check if an exception was thrown.
|
// Not when there was an error, but do check if an exception was thrown.
|
||||||
if ((!aborting() || did_throw)
|
if ((!aborting() || did_throw) && (!failed || eap->cstack->cs_trylevel > 0))
|
||||||
&& (!failed || eap->cstack->cs_trylevel > 0))
|
|
||||||
{
|
{
|
||||||
// Check for trailing illegal characters and a following command.
|
// Check for trailing illegal characters and a following command.
|
||||||
arg = skipwhite(arg);
|
arg = skipwhite(arg);
|
||||||
|
|||||||
@ -707,6 +707,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
370,
|
||||||
/**/
|
/**/
|
||||||
369,
|
369,
|
||||||
/**/
|
/**/
|
||||||
|
|||||||
20
src/vim9.h
20
src/vim9.h
@ -113,6 +113,7 @@ typedef enum {
|
|||||||
ISN_FUNCREF, // push a function ref to dfunc isn_arg.funcref
|
ISN_FUNCREF, // push a function ref to dfunc isn_arg.funcref
|
||||||
ISN_NEWFUNC, // create a global function from a lambda function
|
ISN_NEWFUNC, // create a global function from a lambda function
|
||||||
ISN_DEF, // list functions
|
ISN_DEF, // list functions
|
||||||
|
ISN_DEFER, // :defer argument count is isn_arg.number
|
||||||
|
|
||||||
// expression operations
|
// expression operations
|
||||||
ISN_JUMP, // jump if condition is matched isn_arg.jump
|
ISN_JUMP, // jump if condition is matched isn_arg.jump
|
||||||
@ -419,6 +420,12 @@ typedef struct {
|
|||||||
int dbg_break_lnum; // first line to break after
|
int dbg_break_lnum; // first line to break after
|
||||||
} debug_T;
|
} debug_T;
|
||||||
|
|
||||||
|
// arguments to ISN_DEFER
|
||||||
|
typedef struct {
|
||||||
|
int defer_var_idx; // local variable index for defer list
|
||||||
|
int defer_argcount; // number of arguments
|
||||||
|
} deferins_T;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Instruction
|
* Instruction
|
||||||
*/
|
*/
|
||||||
@ -468,6 +475,7 @@ struct isn_S {
|
|||||||
tobool_T tobool;
|
tobool_T tobool;
|
||||||
getitem_T getitem;
|
getitem_T getitem;
|
||||||
debug_T debug;
|
debug_T debug;
|
||||||
|
deferins_T defer;
|
||||||
} isn_arg;
|
} isn_arg;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -498,6 +506,9 @@ struct dfunc_S {
|
|||||||
|
|
||||||
int df_varcount; // number of local variables
|
int df_varcount; // number of local variables
|
||||||
int df_has_closure; // one if a closure was created
|
int df_has_closure; // one if a closure was created
|
||||||
|
int df_defer_var_idx; // index of local variable that has a list
|
||||||
|
// of deferred function calls; zero if not
|
||||||
|
// set
|
||||||
};
|
};
|
||||||
|
|
||||||
// Number of entries used by stack frame for a function call.
|
// Number of entries used by stack frame for a function call.
|
||||||
@ -735,6 +746,15 @@ struct cctx_S {
|
|||||||
// lhs_name is not NULL
|
// lhs_name is not NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* List of special functions for "compile_arguments()".
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
CA_NOT_SPECIAL,
|
||||||
|
CA_SEARCHPAIR, // {skip} in searchpair() and searchpairpos()
|
||||||
|
CA_SUBSTITUTE, // {sub} in substitute(), when prefixed with \=
|
||||||
|
} ca_special_T;
|
||||||
|
|
||||||
// flags for typval2type()
|
// flags for typval2type()
|
||||||
#define TVTT_DO_MEMBER 1
|
#define TVTT_DO_MEMBER 1
|
||||||
#define TVTT_MORE_SPECIFIC 2 // get most specific type for member
|
#define TVTT_MORE_SPECIFIC 2 // get most specific type for member
|
||||||
|
|||||||
@ -1654,6 +1654,9 @@ compile_throw(char_u *arg, cctx_T *cctx UNUSED)
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compile an expression or function call.
|
||||||
|
*/
|
||||||
char_u *
|
char_u *
|
||||||
compile_eval(char_u *arg, cctx_T *cctx)
|
compile_eval(char_u *arg, cctx_T *cctx)
|
||||||
{
|
{
|
||||||
@ -1681,6 +1684,67 @@ compile_eval(char_u *arg, cctx_T *cctx)
|
|||||||
return skipwhite(p);
|
return skipwhite(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compile "defer func(arg)".
|
||||||
|
*/
|
||||||
|
char_u *
|
||||||
|
compile_defer(char_u *arg_start, cctx_T *cctx)
|
||||||
|
{
|
||||||
|
char_u *p;
|
||||||
|
char_u *arg = arg_start;
|
||||||
|
int argcount = 0;
|
||||||
|
dfunc_T *dfunc;
|
||||||
|
type_T *type;
|
||||||
|
int func_idx;
|
||||||
|
|
||||||
|
// Get a funcref for the function name.
|
||||||
|
// TODO: better way to find the "(".
|
||||||
|
p = vim_strchr(arg, '(');
|
||||||
|
if (p == NULL)
|
||||||
|
{
|
||||||
|
semsg(_(e_missing_parenthesis_str), arg);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
*p = NUL;
|
||||||
|
func_idx = find_internal_func(arg);
|
||||||
|
if (func_idx >= 0)
|
||||||
|
// TODO: better type
|
||||||
|
generate_PUSHFUNC(cctx, (char_u *)internal_func_name(func_idx),
|
||||||
|
&t_func_any, FALSE);
|
||||||
|
else if (compile_expr0(&arg, cctx) == FAIL)
|
||||||
|
return NULL;
|
||||||
|
*p = '(';
|
||||||
|
|
||||||
|
// check for function type
|
||||||
|
type = get_type_on_stack(cctx, 0);
|
||||||
|
if (type->tt_type != VAR_FUNC)
|
||||||
|
{
|
||||||
|
emsg(_(e_function_name_required));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// compile the arguments
|
||||||
|
arg = skipwhite(p + 1);
|
||||||
|
if (compile_arguments(&arg, cctx, &argcount, CA_NOT_SPECIAL) == FAIL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
// TODO: check argument count with "type"
|
||||||
|
|
||||||
|
dfunc = ((dfunc_T *)def_functions.ga_data) + cctx->ctx_ufunc->uf_dfunc_idx;
|
||||||
|
if (dfunc->df_defer_var_idx == 0)
|
||||||
|
{
|
||||||
|
lvar_T *lvar = reserve_local(cctx, (char_u *)"@defer@", 7,
|
||||||
|
TRUE, &t_list_any);
|
||||||
|
if (lvar == NULL)
|
||||||
|
return NULL;
|
||||||
|
dfunc->df_defer_var_idx = lvar->lv_idx + 1;
|
||||||
|
}
|
||||||
|
if (generate_DEFER(cctx, dfunc->df_defer_var_idx - 1, argcount) == FAIL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return skipwhite(arg);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* compile "echo expr"
|
* compile "echo expr"
|
||||||
* compile "echomsg expr"
|
* compile "echomsg expr"
|
||||||
|
|||||||
@ -2373,7 +2373,7 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
|
|||||||
r = generate_PUSHBLOB(cctx, blob_alloc());
|
r = generate_PUSHBLOB(cctx, blob_alloc());
|
||||||
break;
|
break;
|
||||||
case VAR_FUNC:
|
case VAR_FUNC:
|
||||||
r = generate_PUSHFUNC(cctx, NULL, &t_func_void);
|
r = generate_PUSHFUNC(cctx, NULL, &t_func_void, TRUE);
|
||||||
break;
|
break;
|
||||||
case VAR_LIST:
|
case VAR_LIST:
|
||||||
r = generate_NEWLIST(cctx, 0, FALSE);
|
r = generate_NEWLIST(cctx, 0, FALSE);
|
||||||
@ -2748,6 +2748,7 @@ compile_def_function(
|
|||||||
// Was compiled in this mode before: Free old instructions.
|
// Was compiled in this mode before: Free old instructions.
|
||||||
delete_def_function_contents(dfunc, FALSE);
|
delete_def_function_contents(dfunc, FALSE);
|
||||||
ga_clear_strings(&dfunc->df_var_names);
|
ga_clear_strings(&dfunc->df_var_names);
|
||||||
|
dfunc->df_defer_var_idx = 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -3249,6 +3250,10 @@ compile_def_function(
|
|||||||
line = compile_eval(p, &cctx);
|
line = compile_eval(p, &cctx);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CMD_defer:
|
||||||
|
line = compile_defer(p, &cctx);
|
||||||
|
break;
|
||||||
|
|
||||||
case CMD_echo:
|
case CMD_echo:
|
||||||
case CMD_echon:
|
case CMD_echon:
|
||||||
case CMD_echoconsole:
|
case CMD_echoconsole:
|
||||||
|
|||||||
@ -101,9 +101,15 @@ struct ectx_S {
|
|||||||
static garray_T profile_info_ga = {0, 0, sizeof(profinfo_T), 20, NULL};
|
static garray_T profile_info_ga = {0, 0, sizeof(profinfo_T), 20, NULL};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Get pointer to item in the stack.
|
||||||
|
#define STACK_TV(idx) (((typval_T *)ectx->ec_stack.ga_data) + idx)
|
||||||
|
|
||||||
// Get pointer to item relative to the bottom of the stack, -1 is the last one.
|
// Get pointer to item relative to the bottom of the stack, -1 is the last one.
|
||||||
#define STACK_TV_BOT(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_stack.ga_len + (idx))
|
#define STACK_TV_BOT(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_stack.ga_len + (idx))
|
||||||
|
|
||||||
|
// Get pointer to a local variable on the stack. Negative for arguments.
|
||||||
|
#define STACK_TV_VAR(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_frame_idx + STACK_FRAME_SIZE + idx)
|
||||||
|
|
||||||
void
|
void
|
||||||
to_string_error(vartype_T vartype)
|
to_string_error(vartype_T vartype)
|
||||||
{
|
{
|
||||||
@ -610,9 +616,6 @@ call_dfunc(
|
|||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get pointer to item in the stack.
|
|
||||||
#define STACK_TV(idx) (((typval_T *)ectx->ec_stack.ga_data) + idx)
|
|
||||||
|
|
||||||
// Double linked list of funcstack_T in use.
|
// Double linked list of funcstack_T in use.
|
||||||
static funcstack_T *first_funcstack = NULL;
|
static funcstack_T *first_funcstack = NULL;
|
||||||
|
|
||||||
@ -842,6 +845,89 @@ set_ref_in_funcstacks(int copyID)
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handle ISN_DEFER. Stack has a function reference and "argcount" arguments.
|
||||||
|
* The local variable that lists deferred functions is "var_idx".
|
||||||
|
* Returns OK or FAIL.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
add_defer_func(int var_idx, int argcount, ectx_T *ectx)
|
||||||
|
{
|
||||||
|
typval_T *defer_tv = STACK_TV_VAR(var_idx);
|
||||||
|
list_T *defer_l;
|
||||||
|
typval_T *func_tv;
|
||||||
|
list_T *l;
|
||||||
|
int i;
|
||||||
|
typval_T listval;
|
||||||
|
|
||||||
|
if (defer_tv->v_type != VAR_LIST)
|
||||||
|
{
|
||||||
|
// first one, allocate the list
|
||||||
|
if (rettv_list_alloc(defer_tv) == FAIL)
|
||||||
|
return FAIL;
|
||||||
|
}
|
||||||
|
defer_l = defer_tv->vval.v_list;
|
||||||
|
|
||||||
|
l = list_alloc_with_items(argcount + 1);
|
||||||
|
if (l == NULL)
|
||||||
|
return FAIL;
|
||||||
|
listval.v_type = VAR_LIST;
|
||||||
|
listval.vval.v_list = l;
|
||||||
|
listval.v_lock = 0;
|
||||||
|
if (list_insert_tv(defer_l, &listval,
|
||||||
|
defer_l == NULL ? NULL : defer_l->lv_first) == FAIL)
|
||||||
|
{
|
||||||
|
vim_free(l);
|
||||||
|
return FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
func_tv = STACK_TV_BOT(-argcount - 1);
|
||||||
|
// TODO: check type is a funcref
|
||||||
|
list_set_item(l, 0, func_tv);
|
||||||
|
|
||||||
|
for (i = 1; i <= argcount; ++i)
|
||||||
|
list_set_item(l, i, STACK_TV_BOT(-argcount + i - 1));
|
||||||
|
ectx->ec_stack.ga_len -= argcount + 1;
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Invoked when returning from a function: Invoke any deferred calls.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
invoke_defer_funcs(ectx_T *ectx)
|
||||||
|
{
|
||||||
|
dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
|
||||||
|
+ ectx->ec_dfunc_idx;
|
||||||
|
typval_T *defer_tv = STACK_TV_VAR(dfunc->df_defer_var_idx - 1);
|
||||||
|
listitem_T *li;
|
||||||
|
|
||||||
|
if (defer_tv->v_type != VAR_LIST)
|
||||||
|
return; // no function added
|
||||||
|
for (li = defer_tv->vval.v_list->lv_first; li != NULL; li = li->li_next)
|
||||||
|
{
|
||||||
|
list_T *l = li->li_tv.vval.v_list;
|
||||||
|
typval_T rettv;
|
||||||
|
typval_T argvars[MAX_FUNC_ARGS];
|
||||||
|
int i;
|
||||||
|
listitem_T *arg_li = l->lv_first;
|
||||||
|
funcexe_T funcexe;
|
||||||
|
|
||||||
|
for (i = 0; i < l->lv_len - 1; ++i)
|
||||||
|
{
|
||||||
|
arg_li = arg_li->li_next;
|
||||||
|
argvars[i] = arg_li->li_tv;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLEAR_FIELD(funcexe);
|
||||||
|
funcexe.fe_evaluate = TRUE;
|
||||||
|
rettv.v_type = VAR_UNKNOWN;
|
||||||
|
(void)call_func(l->lv_first->li_tv.vval.v_string, -1,
|
||||||
|
&rettv, l->lv_len - 1, argvars, &funcexe);
|
||||||
|
clear_tv(&rettv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Return from the current function.
|
* Return from the current function.
|
||||||
*/
|
*/
|
||||||
@ -876,6 +962,9 @@ func_return(ectx_T *ectx)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (dfunc->df_defer_var_idx > 0)
|
||||||
|
invoke_defer_funcs(ectx);
|
||||||
|
|
||||||
// No check for uf_refcount being zero, cannot think of a way that would
|
// No check for uf_refcount being zero, cannot think of a way that would
|
||||||
// happen.
|
// happen.
|
||||||
--dfunc->df_ufunc->uf_calls;
|
--dfunc->df_ufunc->uf_calls;
|
||||||
@ -949,8 +1038,6 @@ func_return(ectx_T *ectx)
|
|||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef STACK_TV
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prepare arguments and rettv for calling a builtin or user function.
|
* Prepare arguments and rettv for calling a builtin or user function.
|
||||||
*/
|
*/
|
||||||
@ -1732,16 +1819,6 @@ typedef struct subs_expr_S {
|
|||||||
int subs_status;
|
int subs_status;
|
||||||
} subs_expr_T;
|
} subs_expr_T;
|
||||||
|
|
||||||
// Get pointer to item in the stack.
|
|
||||||
#define STACK_TV(idx) (((typval_T *)ectx->ec_stack.ga_data) + idx)
|
|
||||||
|
|
||||||
// Get pointer to item at the bottom of the stack, -1 is the bottom.
|
|
||||||
#undef STACK_TV_BOT
|
|
||||||
#define STACK_TV_BOT(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_stack.ga_len + idx)
|
|
||||||
|
|
||||||
// Get pointer to a local variable on the stack. Negative for arguments.
|
|
||||||
#define STACK_TV_VAR(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_frame_idx + STACK_FRAME_SIZE + idx)
|
|
||||||
|
|
||||||
// Set when calling do_debug().
|
// Set when calling do_debug().
|
||||||
static ectx_T *debug_context = NULL;
|
static ectx_T *debug_context = NULL;
|
||||||
static int debug_var_count;
|
static int debug_var_count;
|
||||||
@ -3670,6 +3747,13 @@ exec_instructions(ectx_T *ectx)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// :defer func(arg)
|
||||||
|
case ISN_DEFER:
|
||||||
|
if (add_defer_func(iptr->isn_arg.defer.defer_var_idx,
|
||||||
|
iptr->isn_arg.defer.defer_argcount, ectx) == FAIL)
|
||||||
|
goto on_error;
|
||||||
|
break;
|
||||||
|
|
||||||
// return from a :def function call without a value
|
// return from a :def function call without a value
|
||||||
case ISN_RETURN_VOID:
|
case ISN_RETURN_VOID:
|
||||||
if (GA_GROW_FAILS(&ectx->ec_stack, 1))
|
if (GA_GROW_FAILS(&ectx->ec_stack, 1))
|
||||||
@ -5024,6 +5108,14 @@ on_fatal_error:
|
|||||||
done:
|
done:
|
||||||
ret = OK;
|
ret = OK;
|
||||||
theend:
|
theend:
|
||||||
|
{
|
||||||
|
dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
|
||||||
|
+ ectx->ec_dfunc_idx;
|
||||||
|
|
||||||
|
if (dfunc->df_defer_var_idx > 0)
|
||||||
|
invoke_defer_funcs(ectx);
|
||||||
|
}
|
||||||
|
|
||||||
dict_stack_clear(dict_stack_len_at_start);
|
dict_stack_clear(dict_stack_len_at_start);
|
||||||
ectx->ec_trylevel_at_start = save_trylevel_at_start;
|
ectx->ec_trylevel_at_start = save_trylevel_at_start;
|
||||||
return ret;
|
return ret;
|
||||||
@ -5903,6 +5995,10 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
|
|||||||
case ISN_PCALL_END:
|
case ISN_PCALL_END:
|
||||||
smsg("%s%4d PCALL end", pfx, current);
|
smsg("%s%4d PCALL end", pfx, current);
|
||||||
break;
|
break;
|
||||||
|
case ISN_DEFER:
|
||||||
|
smsg("%s%4d DEFER %d args", pfx, current,
|
||||||
|
(int)iptr->isn_arg.defer.defer_argcount);
|
||||||
|
break;
|
||||||
case ISN_RETURN:
|
case ISN_RETURN:
|
||||||
smsg("%s%4d RETURN", pfx, current);
|
smsg("%s%4d RETURN", pfx, current);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -313,7 +313,7 @@ compile_load_scriptvar(
|
|||||||
// name. If a '(' follows it must be a function. Otherwise we
|
// name. If a '(' follows it must be a function. Otherwise we
|
||||||
// don't know, it can be "script.Func".
|
// don't know, it can be "script.Func".
|
||||||
if (cc == '(' || paren_follows_after_expr)
|
if (cc == '(' || paren_follows_after_expr)
|
||||||
res = generate_PUSHFUNC(cctx, auto_name, &t_func_any);
|
res = generate_PUSHFUNC(cctx, auto_name, &t_func_any, TRUE);
|
||||||
else
|
else
|
||||||
res = generate_AUTOLOAD(cctx, auto_name, &t_any);
|
res = generate_AUTOLOAD(cctx, auto_name, &t_any);
|
||||||
vim_free(auto_name);
|
vim_free(auto_name);
|
||||||
@ -329,7 +329,7 @@ compile_load_scriptvar(
|
|||||||
char_u sid_name[MAX_FUNC_NAME_LEN];
|
char_u sid_name[MAX_FUNC_NAME_LEN];
|
||||||
|
|
||||||
func_name_with_sid(exp_name, import->imp_sid, sid_name);
|
func_name_with_sid(exp_name, import->imp_sid, sid_name);
|
||||||
res = generate_PUSHFUNC(cctx, sid_name, &t_func_any);
|
res = generate_PUSHFUNC(cctx, sid_name, &t_func_any, TRUE);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
res = generate_OLDSCRIPT(cctx, ISN_LOADEXPORT, exp_name,
|
res = generate_OLDSCRIPT(cctx, ISN_LOADEXPORT, exp_name,
|
||||||
@ -353,7 +353,7 @@ compile_load_scriptvar(
|
|||||||
if (ufunc != NULL)
|
if (ufunc != NULL)
|
||||||
{
|
{
|
||||||
// function call or function reference
|
// function call or function reference
|
||||||
generate_PUSHFUNC(cctx, ufunc->uf_name, NULL);
|
generate_PUSHFUNC(cctx, ufunc->uf_name, NULL, TRUE);
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
return FAIL;
|
return FAIL;
|
||||||
@ -387,7 +387,7 @@ generate_funcref(cctx_T *cctx, char_u *name, int has_g_prefix)
|
|||||||
if (func_needs_compiling(ufunc, compile_type)
|
if (func_needs_compiling(ufunc, compile_type)
|
||||||
&& compile_def_function(ufunc, TRUE, compile_type, NULL) == FAIL)
|
&& compile_def_function(ufunc, TRUE, compile_type, NULL) == FAIL)
|
||||||
return FAIL;
|
return FAIL;
|
||||||
return generate_PUSHFUNC(cctx, ufunc->uf_name, ufunc->uf_func_type);
|
return generate_PUSHFUNC(cctx, ufunc->uf_name, ufunc->uf_func_type, TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -609,20 +609,11 @@ compile_string(isn_T *isn, cctx_T *cctx, int str_offset)
|
|||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* List of special functions for "compile_arguments".
|
|
||||||
*/
|
|
||||||
typedef enum {
|
|
||||||
CA_NOT_SPECIAL,
|
|
||||||
CA_SEARCHPAIR, // {skip} in searchpair() and searchpairpos()
|
|
||||||
CA_SUBSTITUTE, // {sub} in substitute(), when prefixed with \=
|
|
||||||
} ca_special_T;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Compile the argument expressions.
|
* Compile the argument expressions.
|
||||||
* "arg" points to just after the "(" and is advanced to after the ")"
|
* "arg" points to just after the "(" and is advanced to after the ")"
|
||||||
*/
|
*/
|
||||||
static int
|
int
|
||||||
compile_arguments(
|
compile_arguments(
|
||||||
char_u **arg,
|
char_u **arg,
|
||||||
cctx_T *cctx,
|
cctx_T *cctx,
|
||||||
|
|||||||
@ -616,7 +616,7 @@ generate_tv_PUSH(cctx_T *cctx, typval_T *tv)
|
|||||||
case VAR_FUNC:
|
case VAR_FUNC:
|
||||||
if (tv->vval.v_string != NULL)
|
if (tv->vval.v_string != NULL)
|
||||||
iemsg("non-null function constant not supported");
|
iemsg("non-null function constant not supported");
|
||||||
generate_PUSHFUNC(cctx, NULL, &t_func_unknown);
|
generate_PUSHFUNC(cctx, NULL, &t_func_unknown, TRUE);
|
||||||
break;
|
break;
|
||||||
case VAR_PARTIAL:
|
case VAR_PARTIAL:
|
||||||
if (tv->vval.v_partial != NULL)
|
if (tv->vval.v_partial != NULL)
|
||||||
@ -796,9 +796,11 @@ generate_PUSHBLOB(cctx_T *cctx, blob_T *blob)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Generate an ISN_PUSHFUNC instruction with name "name".
|
* Generate an ISN_PUSHFUNC instruction with name "name".
|
||||||
|
* When "may_prefix" is TRUE prefix "g:" unless "name" is script-local or
|
||||||
|
* autoload.
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type)
|
generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type, int may_prefix)
|
||||||
{
|
{
|
||||||
isn_T *isn;
|
isn_T *isn;
|
||||||
char_u *funcname;
|
char_u *funcname;
|
||||||
@ -808,7 +810,8 @@ generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type)
|
|||||||
return FAIL;
|
return FAIL;
|
||||||
if (name == NULL)
|
if (name == NULL)
|
||||||
funcname = NULL;
|
funcname = NULL;
|
||||||
else if (*name == K_SPECIAL // script-local
|
else if (!may_prefix
|
||||||
|
|| *name == K_SPECIAL // script-local
|
||||||
|| vim_strchr(name, AUTOLOAD_CHAR) != NULL) // autoload
|
|| vim_strchr(name, AUTOLOAD_CHAR) != NULL) // autoload
|
||||||
funcname = vim_strsave(name);
|
funcname = vim_strsave(name);
|
||||||
else
|
else
|
||||||
@ -1678,6 +1681,22 @@ generate_PCALL(
|
|||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generate an ISN_DEFER instruction.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
generate_DEFER(cctx_T *cctx, int var_idx, int argcount)
|
||||||
|
{
|
||||||
|
isn_T *isn;
|
||||||
|
|
||||||
|
RETURN_OK_IF_SKIP(cctx);
|
||||||
|
if ((isn = generate_instr_drop(cctx, ISN_DEFER, argcount + 1)) == NULL)
|
||||||
|
return FAIL;
|
||||||
|
isn->isn_arg.defer.defer_var_idx = var_idx;
|
||||||
|
isn->isn_arg.defer.defer_argcount = argcount;
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Generate an ISN_STRINGMEMBER instruction.
|
* Generate an ISN_STRINGMEMBER instruction.
|
||||||
*/
|
*/
|
||||||
@ -2240,6 +2259,7 @@ delete_instr(isn_T *isn)
|
|||||||
case ISN_CONCAT:
|
case ISN_CONCAT:
|
||||||
case ISN_COND2BOOL:
|
case ISN_COND2BOOL:
|
||||||
case ISN_DEBUG:
|
case ISN_DEBUG:
|
||||||
|
case ISN_DEFER:
|
||||||
case ISN_DROP:
|
case ISN_DROP:
|
||||||
case ISN_ECHO:
|
case ISN_ECHO:
|
||||||
case ISN_ECHOCONSOLE:
|
case ISN_ECHOCONSOLE:
|
||||||
@ -2310,7 +2330,7 @@ delete_instr(isn_T *isn)
|
|||||||
case ISN_USEDICT:
|
case ISN_USEDICT:
|
||||||
// nothing allocated
|
// nothing allocated
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
Reference in New Issue
Block a user