mirror of
https://github.com/vim/vim
synced 2025-03-16 14:57:52 +01:00
Problem: Vim9: missing return statement with throw (atitcreate) Solution: Treat a throw statement at the end of an if-else block as a return statement (Yegappan Lakshmanan) fixes: #16312 closes: #16338 Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
2751 lines
67 KiB
C
2751 lines
67 KiB
C
/* vi:set ts=8 sts=4 sw=4 noet:
|
|
*
|
|
* VIM - Vi IMproved by Bram Moolenaar
|
|
*
|
|
* Do ":help uganda" in Vim to read copying and usage conditions.
|
|
* Do ":help credits" in Vim to see a list of people who contributed.
|
|
* See README.txt for an overview of the Vim source code.
|
|
*/
|
|
|
|
/*
|
|
* vim9cmds.c: Dealing with commands of a compiled function
|
|
*/
|
|
|
|
#define USING_FLOAT_STUFF
|
|
#include "vim.h"
|
|
|
|
#if defined(FEAT_EVAL) || defined(PROTO)
|
|
|
|
// When not generating protos this is included in proto.h
|
|
#ifdef PROTO
|
|
# include "vim9.h"
|
|
#endif
|
|
|
|
/*
|
|
* Get the index of the current instruction.
|
|
* This compensates for a preceding ISN_CMDMOD and ISN_PROF_START.
|
|
*/
|
|
static int
|
|
current_instr_idx(cctx_T *cctx)
|
|
{
|
|
garray_T *instr = &cctx->ctx_instr;
|
|
int idx = instr->ga_len;
|
|
|
|
while (idx > 0)
|
|
{
|
|
if (cctx->ctx_has_cmdmod && ((isn_T *)instr->ga_data)[idx - 1]
|
|
.isn_type == ISN_CMDMOD)
|
|
{
|
|
--idx;
|
|
continue;
|
|
}
|
|
#ifdef FEAT_PROFILE
|
|
if (((isn_T *)instr->ga_data)[idx - 1].isn_type == ISN_PROF_START)
|
|
{
|
|
--idx;
|
|
continue;
|
|
}
|
|
#endif
|
|
if (((isn_T *)instr->ga_data)[idx - 1].isn_type == ISN_DEBUG)
|
|
{
|
|
--idx;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return idx;
|
|
}
|
|
/*
|
|
* Remove local variables above "new_top".
|
|
* Do this by clearing the name. If "keep" is TRUE do not reset the length, a
|
|
* closure may still need location of the variable.
|
|
*/
|
|
static void
|
|
unwind_locals(cctx_T *cctx, int new_top, int keep)
|
|
{
|
|
if (cctx->ctx_locals.ga_len > new_top)
|
|
for (int idx = new_top; idx < cctx->ctx_locals.ga_len; ++idx)
|
|
{
|
|
lvar_T *lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx;
|
|
VIM_CLEAR(lvar->lv_name);
|
|
}
|
|
if (!keep)
|
|
cctx->ctx_locals.ga_len = new_top;
|
|
}
|
|
|
|
/*
|
|
* Free all local variables.
|
|
*/
|
|
void
|
|
free_locals(cctx_T *cctx)
|
|
{
|
|
unwind_locals(cctx, 0, FALSE);
|
|
ga_clear(&cctx->ctx_locals);
|
|
}
|
|
|
|
|
|
/*
|
|
* Check if "name" can be "unlet".
|
|
*/
|
|
int
|
|
check_vim9_unlet(char_u *name)
|
|
{
|
|
if (*name == NUL)
|
|
{
|
|
semsg(_(e_argument_required_for_str), "unlet");
|
|
return FAIL;
|
|
}
|
|
|
|
if (name[1] != ':' || vim_strchr((char_u *)"gwtb", *name) == NULL)
|
|
{
|
|
// "unlet s:var" is allowed in legacy script.
|
|
if (*name == 's' && !script_is_vim9())
|
|
return OK;
|
|
semsg(_(e_cannot_unlet_str), name);
|
|
return FAIL;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* Callback passed to ex_unletlock().
|
|
*/
|
|
static int
|
|
compile_unlet(
|
|
lval_T *lvp,
|
|
char_u *name_end,
|
|
exarg_T *eap,
|
|
int deep UNUSED,
|
|
void *coookie)
|
|
{
|
|
cctx_T *cctx = coookie;
|
|
char_u *p = lvp->ll_name;
|
|
int cc = *name_end;
|
|
int ret = OK;
|
|
|
|
if (cctx->ctx_skip == SKIP_YES)
|
|
return OK;
|
|
|
|
*name_end = NUL;
|
|
if (*p == '$')
|
|
{
|
|
// :unlet $ENV_VAR
|
|
ret = generate_UNLET(cctx, ISN_UNLETENV, p + 1, eap->forceit);
|
|
}
|
|
else if (vim_strchr(p, '.') != NULL || vim_strchr(p, '[') != NULL)
|
|
{
|
|
lhs_T lhs;
|
|
|
|
// This is similar to assigning: lookup the list/dict, compile the
|
|
// idx/key. Then instead of storing the value unlet the item.
|
|
// unlet {list}[idx]
|
|
// unlet {dict}[key] dict.key
|
|
//
|
|
// Figure out the LHS type and other properties.
|
|
//
|
|
ret = compile_lhs(p, &lhs, CMD_unlet, FALSE, FALSE, 0, cctx);
|
|
|
|
// Use the info in "lhs" to unlet the item at the index in the
|
|
// list or dict.
|
|
if (ret == OK)
|
|
{
|
|
if (!lhs.lhs_has_index)
|
|
{
|
|
semsg(_(e_cannot_unlet_imported_item_str), p);
|
|
ret = FAIL;
|
|
}
|
|
else
|
|
ret = compile_assign_unlet(p, &lhs, FALSE, &t_void, cctx);
|
|
}
|
|
|
|
vim_free(lhs.lhs_name);
|
|
}
|
|
else if (check_vim9_unlet(p) == FAIL)
|
|
{
|
|
ret = FAIL;
|
|
}
|
|
else
|
|
{
|
|
// Normal name. Only supports g:, w:, t: and b: namespaces.
|
|
ret = generate_UNLET(cctx, ISN_UNLET, p, eap->forceit);
|
|
}
|
|
|
|
*name_end = cc;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Callback passed to ex_unletlock().
|
|
*/
|
|
static int
|
|
compile_lock_unlock(
|
|
lval_T *lvp,
|
|
char_u *name_end,
|
|
exarg_T *eap,
|
|
int deep,
|
|
void *coookie)
|
|
{
|
|
cctx_T *cctx = coookie;
|
|
int cc = *name_end;
|
|
char_u *p = lvp->ll_name;
|
|
int ret = OK;
|
|
char_u *buf;
|
|
isntype_T isn = ISN_EXEC;
|
|
char *cmd = eap->cmdidx == CMD_lockvar ? "lockvar" : "unlockvar";
|
|
int is_arg = FALSE;
|
|
|
|
#ifdef LOG_LOCKVAR
|
|
ch_log(NULL, "LKVAR: compile_lock_unlock(): cookie %p, name %s",
|
|
coookie, p);
|
|
#endif
|
|
|
|
if (cctx->ctx_skip == SKIP_YES)
|
|
return OK;
|
|
|
|
if (*p == NUL)
|
|
{
|
|
semsg(_(e_argument_required_for_str), cmd);
|
|
return FAIL;
|
|
}
|
|
|
|
// Cannot use :lockvar and :unlockvar on local variables.
|
|
if (p[1] != ':')
|
|
{
|
|
char_u *end = find_name_end(p, NULL, NULL, FNE_CHECK_START);
|
|
|
|
// The most important point is that something like
|
|
// name[idx].member... needs to be resolved at runtime, get_lval(),
|
|
// starting from the root "name".
|
|
|
|
// These checks are reminiscent of the variable_exists function.
|
|
// But most of the matches require special handling.
|
|
|
|
// If bare name is is locally accessible, except for local var,
|
|
// then put it on the stack to use with ISN_LOCKUNLOCK.
|
|
// This could be v.memb, v[idx_key]; bare class variable,
|
|
// function arg. The item on the stack, will be passed
|
|
// to ex_lockvar() indirectly and be used as the root for get_lval.
|
|
// A bare script variable name needs no special handling.
|
|
|
|
char_u *name = NULL;
|
|
int len = end - p;
|
|
|
|
if (lookup_local(p, len, NULL, cctx) == OK)
|
|
{
|
|
// Handle "this", "this.val", "anyvar[idx]"
|
|
if (*end != '.' && *end != '['
|
|
&& (len != 4 || STRNCMP("this", p, len) != 0))
|
|
{
|
|
emsg(_(e_cannot_lock_unlock_local_variable));
|
|
return FAIL;
|
|
}
|
|
// Push the local on the stack, could be "this".
|
|
name = p;
|
|
#ifdef LOG_LOCKVAR
|
|
ch_log(NULL, "LKVAR: ... lookup_local: name %s", name);
|
|
#endif
|
|
}
|
|
if (name == NULL)
|
|
{
|
|
class_T *cl;
|
|
if (cctx_class_member_idx(cctx, p, len, &cl) >= 0)
|
|
{
|
|
if (*end != '.' && *end != '[')
|
|
{
|
|
// Push the class of the bare class variable name
|
|
name = cl->class_name;
|
|
len = (int)STRLEN(name);
|
|
#ifdef LOG_LOCKVAR
|
|
ch_log(NULL, "LKVAR: ... cctx_class_member: name %s",
|
|
name);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
if (name == NULL)
|
|
{
|
|
// Can lockvar any function arg.
|
|
if (arg_exists(p, len, NULL, NULL, NULL, cctx) == OK)
|
|
{
|
|
name = p;
|
|
is_arg = TRUE;
|
|
#ifdef LOG_LOCKVAR
|
|
ch_log(NULL, "LKVAR: ... arg_exists: name %s", name);
|
|
#endif
|
|
}
|
|
}
|
|
if (name == NULL)
|
|
{
|
|
// No special handling for a bare script variable; but
|
|
// if followed by '[' or '.', it's a root for get_lval().
|
|
if (script_var_exists(p, len, cctx, NULL) == OK
|
|
&& (*end == '.' || *end == '['))
|
|
{
|
|
name = p;
|
|
#ifdef LOG_LOCKVAR
|
|
ch_log(NULL, "LKVAR: ... script_var_exists: name %s", name);
|
|
#endif
|
|
}
|
|
}
|
|
if (name != NULL)
|
|
{
|
|
#ifdef LOG_LOCKVAR
|
|
ch_log(NULL, "LKVAR: ... INS_LOCKUNLOCK %s", name);
|
|
#endif
|
|
if (compile_load(&name, name + len, cctx, FALSE, FALSE) == FAIL)
|
|
return FAIL;
|
|
isn = ISN_LOCKUNLOCK;
|
|
}
|
|
}
|
|
|
|
// Checking is done at runtime.
|
|
*name_end = NUL;
|
|
size_t len = name_end - p + 20;
|
|
buf = alloc(len);
|
|
if (buf == NULL)
|
|
ret = FAIL;
|
|
else
|
|
{
|
|
if (deep < 0)
|
|
vim_snprintf((char *)buf, len, "%s! %s", cmd, p);
|
|
else
|
|
vim_snprintf((char *)buf, len, "%s %d %s", cmd, deep, p);
|
|
#ifdef LOG_LOCKVAR
|
|
ch_log(NULL, "LKVAR: ... buf %s", buf);
|
|
#endif
|
|
if (isn == ISN_LOCKUNLOCK)
|
|
ret = generate_LOCKUNLOCK(cctx, buf, is_arg);
|
|
else
|
|
ret = generate_EXEC_copy(cctx, isn, buf);
|
|
|
|
vim_free(buf);
|
|
*name_end = cc;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* compile "unlet var", "lock var" and "unlock var"
|
|
* "arg" points to "var".
|
|
*/
|
|
char_u *
|
|
compile_unletlock(char_u *arg, exarg_T *eap, cctx_T *cctx)
|
|
{
|
|
int deep = 0;
|
|
char_u *p = arg;
|
|
|
|
if (eap->cmdidx != CMD_unlet)
|
|
{
|
|
if (eap->forceit)
|
|
deep = -1;
|
|
else if (vim_isdigit(*p))
|
|
{
|
|
deep = getdigits(&p);
|
|
p = skipwhite(p);
|
|
}
|
|
else
|
|
deep = 2;
|
|
}
|
|
|
|
ex_unletlock(eap, p, deep, GLV_NO_AUTOLOAD | GLV_COMPILING,
|
|
eap->cmdidx == CMD_unlet ? compile_unlet : compile_lock_unlock,
|
|
cctx);
|
|
return eap->nextcmd == NULL ? (char_u *)"" : eap->nextcmd;
|
|
}
|
|
|
|
/*
|
|
* Generate a jump to the ":endif"/":endfor"/":endwhile"/":finally"/":endtry".
|
|
* "funcref_idx" is used for JUMP_WHILE_FALSE
|
|
*/
|
|
static int
|
|
compile_jump_to_end(
|
|
endlabel_T **el,
|
|
jumpwhen_T when,
|
|
int funcref_idx,
|
|
cctx_T *cctx)
|
|
{
|
|
garray_T *instr = &cctx->ctx_instr;
|
|
endlabel_T *endlabel = ALLOC_CLEAR_ONE(endlabel_T);
|
|
|
|
if (endlabel == NULL)
|
|
return FAIL;
|
|
endlabel->el_next = *el;
|
|
*el = endlabel;
|
|
endlabel->el_end_label = instr->ga_len;
|
|
|
|
if (when == JUMP_WHILE_FALSE)
|
|
generate_WHILE(cctx, funcref_idx);
|
|
else
|
|
generate_JUMP(cctx, when, 0);
|
|
return OK;
|
|
}
|
|
|
|
static void
|
|
compile_fill_jump_to_end(endlabel_T **el, int jump_where, cctx_T *cctx)
|
|
{
|
|
garray_T *instr = &cctx->ctx_instr;
|
|
|
|
while (*el != NULL)
|
|
{
|
|
endlabel_T *cur = (*el);
|
|
isn_T *isn;
|
|
|
|
isn = ((isn_T *)instr->ga_data) + cur->el_end_label;
|
|
isn->isn_arg.jump.jump_where = jump_where;
|
|
*el = cur->el_next;
|
|
vim_free(cur);
|
|
}
|
|
}
|
|
|
|
static void
|
|
compile_free_jump_to_end(endlabel_T **el)
|
|
{
|
|
while (*el != NULL)
|
|
{
|
|
endlabel_T *cur = (*el);
|
|
|
|
*el = cur->el_next;
|
|
vim_free(cur);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create a new scope and set up the generic items.
|
|
*/
|
|
static scope_T *
|
|
new_scope(cctx_T *cctx, scopetype_T type)
|
|
{
|
|
scope_T *scope = ALLOC_CLEAR_ONE(scope_T);
|
|
|
|
if (scope == NULL)
|
|
return NULL;
|
|
scope->se_outer = cctx->ctx_scope;
|
|
cctx->ctx_scope = scope;
|
|
scope->se_type = type;
|
|
scope->se_local_count = cctx->ctx_locals.ga_len;
|
|
if (scope->se_outer != NULL)
|
|
scope->se_loop_depth = scope->se_outer->se_loop_depth;
|
|
return scope;
|
|
}
|
|
|
|
/*
|
|
* Free the current scope and go back to the outer scope.
|
|
*/
|
|
void
|
|
drop_scope(cctx_T *cctx)
|
|
{
|
|
scope_T *scope = cctx->ctx_scope;
|
|
|
|
if (scope == NULL)
|
|
{
|
|
iemsg("calling drop_scope() without a scope");
|
|
return;
|
|
}
|
|
cctx->ctx_scope = scope->se_outer;
|
|
switch (scope->se_type)
|
|
{
|
|
case IF_SCOPE:
|
|
compile_free_jump_to_end(&scope->se_u.se_if.is_end_label); break;
|
|
case FOR_SCOPE:
|
|
compile_free_jump_to_end(&scope->se_u.se_for.fs_end_label); break;
|
|
case WHILE_SCOPE:
|
|
compile_free_jump_to_end(&scope->se_u.se_while.ws_end_label); break;
|
|
case TRY_SCOPE:
|
|
compile_free_jump_to_end(&scope->se_u.se_try.ts_end_label); break;
|
|
case NO_SCOPE:
|
|
case BLOCK_SCOPE:
|
|
break;
|
|
}
|
|
vim_free(scope);
|
|
}
|
|
|
|
static int
|
|
misplaced_cmdmod(cctx_T *cctx)
|
|
{
|
|
garray_T *instr = &cctx->ctx_instr;
|
|
|
|
if (cctx->ctx_has_cmdmod
|
|
&& ((isn_T *)instr->ga_data)[instr->ga_len - 1].isn_type
|
|
== ISN_CMDMOD)
|
|
{
|
|
emsg(_(e_misplaced_command_modifier));
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* compile "if expr"
|
|
*
|
|
* "if expr" Produces instructions:
|
|
* EVAL expr Push result of "expr"
|
|
* JUMP_IF_FALSE end
|
|
* ... body ...
|
|
* end:
|
|
*
|
|
* "if expr | else" Produces instructions:
|
|
* EVAL expr Push result of "expr"
|
|
* JUMP_IF_FALSE else
|
|
* ... body ...
|
|
* JUMP_ALWAYS end
|
|
* else:
|
|
* ... body ...
|
|
* end:
|
|
*
|
|
* "if expr1 | elseif expr2 | else" Produces instructions:
|
|
* EVAL expr Push result of "expr"
|
|
* JUMP_IF_FALSE elseif
|
|
* ... body ...
|
|
* JUMP_ALWAYS end
|
|
* elseif:
|
|
* EVAL expr Push result of "expr"
|
|
* JUMP_IF_FALSE else
|
|
* ... body ...
|
|
* JUMP_ALWAYS end
|
|
* else:
|
|
* ... body ...
|
|
* end:
|
|
*/
|
|
char_u *
|
|
compile_if(char_u *arg, cctx_T *cctx)
|
|
{
|
|
char_u *p = arg;
|
|
garray_T *instr = &cctx->ctx_instr;
|
|
int instr_count = instr->ga_len;
|
|
scope_T *scope;
|
|
skip_T skip_save = cctx->ctx_skip;
|
|
ppconst_T ppconst;
|
|
|
|
CLEAR_FIELD(ppconst);
|
|
if (compile_expr1(&p, cctx, &ppconst) == FAIL)
|
|
{
|
|
clear_ppconst(&ppconst);
|
|
return NULL;
|
|
}
|
|
if (!ends_excmd2(arg, skipwhite(p)))
|
|
{
|
|
semsg(_(e_trailing_characters_str), p);
|
|
return NULL;
|
|
}
|
|
if (cctx->ctx_skip == SKIP_YES)
|
|
clear_ppconst(&ppconst);
|
|
else if (instr->ga_len == instr_count && ppconst.pp_used == 1)
|
|
{
|
|
int error = FALSE;
|
|
int v;
|
|
|
|
// The expression results in a constant.
|
|
v = tv_get_bool_chk(&ppconst.pp_tv[0], &error);
|
|
clear_ppconst(&ppconst);
|
|
if (error)
|
|
return NULL;
|
|
cctx->ctx_skip = v ? SKIP_NOT : SKIP_YES;
|
|
}
|
|
else
|
|
{
|
|
// Not a constant, generate instructions for the expression.
|
|
cctx->ctx_skip = SKIP_UNKNOWN;
|
|
if (generate_ppconst(cctx, &ppconst) == FAIL)
|
|
return NULL;
|
|
if (bool_on_stack(cctx) == FAIL)
|
|
return NULL;
|
|
}
|
|
|
|
// CMDMOD_REV must come before the jump
|
|
generate_undo_cmdmods(cctx);
|
|
|
|
scope = new_scope(cctx, IF_SCOPE);
|
|
if (scope == NULL)
|
|
return NULL;
|
|
scope->se_skip_save = skip_save;
|
|
// "is_had_return" will be reset if any block does not end in :return
|
|
scope->se_u.se_if.is_had_return = TRUE;
|
|
|
|
if (cctx->ctx_skip == SKIP_UNKNOWN)
|
|
{
|
|
// "where" is set when ":elseif", "else" or ":endif" is found
|
|
scope->se_u.se_if.is_if_label = instr->ga_len;
|
|
generate_JUMP(cctx, JUMP_IF_FALSE, 0);
|
|
}
|
|
else
|
|
scope->se_u.se_if.is_if_label = -1;
|
|
|
|
#ifdef FEAT_PROFILE
|
|
if (cctx->ctx_compile_type == CT_PROFILE && cctx->ctx_skip == SKIP_YES
|
|
&& skip_save != SKIP_YES)
|
|
{
|
|
// generated a profile start, need to generate a profile end, since it
|
|
// won't be done after returning
|
|
cctx->ctx_skip = SKIP_NOT;
|
|
generate_instr(cctx, ISN_PROF_END);
|
|
cctx->ctx_skip = SKIP_YES;
|
|
}
|
|
#endif
|
|
|
|
return p;
|
|
}
|
|
|
|
char_u *
|
|
compile_elseif(char_u *arg, cctx_T *cctx)
|
|
{
|
|
char_u *p = arg;
|
|
garray_T *instr = &cctx->ctx_instr;
|
|
int instr_count;
|
|
isn_T *isn;
|
|
scope_T *scope = cctx->ctx_scope;
|
|
ppconst_T ppconst;
|
|
skip_T save_skip = cctx->ctx_skip;
|
|
|
|
if (scope == NULL || scope->se_type != IF_SCOPE)
|
|
{
|
|
emsg(_(e_elseif_without_if));
|
|
return NULL;
|
|
}
|
|
unwind_locals(cctx, scope->se_local_count, TRUE);
|
|
if (!cctx->ctx_had_return && !cctx->ctx_had_throw)
|
|
// the previous if block didn't end in a "return" or a "throw"
|
|
// statement.
|
|
scope->se_u.se_if.is_had_return = FALSE;
|
|
|
|
if (cctx->ctx_skip == SKIP_NOT)
|
|
{
|
|
// previous block was executed, this one and following will not
|
|
cctx->ctx_skip = SKIP_YES;
|
|
scope->se_u.se_if.is_seen_skip_not = TRUE;
|
|
}
|
|
if (scope->se_u.se_if.is_seen_skip_not)
|
|
{
|
|
// A previous block was executed, skip over expression and bail out.
|
|
// Do not count the "elseif" for profiling and cmdmod
|
|
instr->ga_len = current_instr_idx(cctx);
|
|
|
|
skip_expr_cctx(&p, cctx);
|
|
return p;
|
|
}
|
|
|
|
if (cctx->ctx_skip == SKIP_UNKNOWN)
|
|
{
|
|
int moved_cmdmod = FALSE;
|
|
int saved_debug = FALSE;
|
|
isn_T debug_isn;
|
|
|
|
// Move any CMDMOD instruction to after the jump
|
|
if (((isn_T *)instr->ga_data)[instr->ga_len - 1].isn_type == ISN_CMDMOD)
|
|
{
|
|
if (GA_GROW_FAILS(instr, 1))
|
|
return NULL;
|
|
((isn_T *)instr->ga_data)[instr->ga_len] =
|
|
((isn_T *)instr->ga_data)[instr->ga_len - 1];
|
|
--instr->ga_len;
|
|
moved_cmdmod = TRUE;
|
|
}
|
|
|
|
// Remove the already generated ISN_DEBUG, it is written below the
|
|
// ISN_FOR instruction.
|
|
if (cctx->ctx_compile_type == CT_DEBUG && instr->ga_len > 0
|
|
&& ((isn_T *)instr->ga_data)[instr->ga_len - 1]
|
|
.isn_type == ISN_DEBUG)
|
|
{
|
|
--instr->ga_len;
|
|
debug_isn = ((isn_T *)instr->ga_data)[instr->ga_len];
|
|
saved_debug = TRUE;
|
|
}
|
|
|
|
if (compile_jump_to_end(&scope->se_u.se_if.is_end_label,
|
|
JUMP_ALWAYS, 0, cctx) == FAIL)
|
|
return NULL;
|
|
// previous "if" or "elseif" jumps here
|
|
isn = ((isn_T *)instr->ga_data) + scope->se_u.se_if.is_if_label;
|
|
isn->isn_arg.jump.jump_where = instr->ga_len;
|
|
|
|
if (moved_cmdmod)
|
|
++instr->ga_len;
|
|
|
|
if (saved_debug)
|
|
{
|
|
// move the debug instruction here
|
|
if (GA_GROW_FAILS(instr, 1))
|
|
return NULL;
|
|
((isn_T *)instr->ga_data)[instr->ga_len] = debug_isn;
|
|
++instr->ga_len;
|
|
}
|
|
}
|
|
|
|
// compile "expr"; if we know it evaluates to FALSE skip the block
|
|
CLEAR_FIELD(ppconst);
|
|
if (cctx->ctx_skip == SKIP_YES)
|
|
{
|
|
cctx->ctx_skip = SKIP_UNKNOWN;
|
|
#ifdef FEAT_PROFILE
|
|
if (cctx->ctx_compile_type == CT_PROFILE)
|
|
// the previous block was skipped, need to profile this line
|
|
generate_instr(cctx, ISN_PROF_START);
|
|
#endif
|
|
if (cctx->ctx_compile_type == CT_DEBUG)
|
|
// the previous block was skipped, may want to debug this line
|
|
generate_instr_debug(cctx);
|
|
}
|
|
|
|
instr_count = instr->ga_len;
|
|
if (compile_expr1(&p, cctx, &ppconst) == FAIL)
|
|
{
|
|
clear_ppconst(&ppconst);
|
|
return NULL;
|
|
}
|
|
cctx->ctx_skip = save_skip;
|
|
if (!ends_excmd2(arg, skipwhite(p)))
|
|
{
|
|
clear_ppconst(&ppconst);
|
|
semsg(_(e_trailing_characters_str), p);
|
|
return NULL;
|
|
}
|
|
if (scope->se_skip_save == SKIP_YES)
|
|
clear_ppconst(&ppconst);
|
|
else if (instr->ga_len == instr_count && ppconst.pp_used == 1)
|
|
{
|
|
int error = FALSE;
|
|
int v;
|
|
|
|
// The expression result is a constant.
|
|
v = tv_get_bool_chk(&ppconst.pp_tv[0], &error);
|
|
if (error)
|
|
{
|
|
clear_ppconst(&ppconst);
|
|
return NULL;
|
|
}
|
|
cctx->ctx_skip = v ? SKIP_NOT : SKIP_YES;
|
|
clear_ppconst(&ppconst);
|
|
scope->se_u.se_if.is_if_label = -1;
|
|
}
|
|
else
|
|
{
|
|
// Not a constant, generate instructions for the expression.
|
|
cctx->ctx_skip = SKIP_UNKNOWN;
|
|
if (generate_ppconst(cctx, &ppconst) == FAIL)
|
|
return NULL;
|
|
if (bool_on_stack(cctx) == FAIL)
|
|
return NULL;
|
|
|
|
// CMDMOD_REV must come before the jump
|
|
generate_undo_cmdmods(cctx);
|
|
|
|
// "where" is set when ":elseif", "else" or ":endif" is found
|
|
scope->se_u.se_if.is_if_label = instr->ga_len;
|
|
generate_JUMP(cctx, JUMP_IF_FALSE, 0);
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
char_u *
|
|
compile_else(char_u *arg, cctx_T *cctx)
|
|
{
|
|
char_u *p = arg;
|
|
garray_T *instr = &cctx->ctx_instr;
|
|
isn_T *isn;
|
|
scope_T *scope = cctx->ctx_scope;
|
|
|
|
if (scope == NULL || scope->se_type != IF_SCOPE)
|
|
{
|
|
emsg(_(e_else_without_if));
|
|
return NULL;
|
|
}
|
|
unwind_locals(cctx, scope->se_local_count, TRUE);
|
|
if (!cctx->ctx_had_return && !cctx->ctx_had_throw)
|
|
// the previous if block didn't end in a "return" or a "throw"
|
|
// statement.
|
|
scope->se_u.se_if.is_had_return = FALSE;
|
|
scope->se_u.se_if.is_seen_else = TRUE;
|
|
|
|
#ifdef FEAT_PROFILE
|
|
if (cctx->ctx_compile_type == CT_PROFILE)
|
|
{
|
|
if (cctx->ctx_skip == SKIP_NOT
|
|
&& ((isn_T *)instr->ga_data)[instr->ga_len - 1]
|
|
.isn_type == ISN_PROF_START)
|
|
// the previous block was executed, do not count "else" for
|
|
// profiling
|
|
--instr->ga_len;
|
|
if (cctx->ctx_skip == SKIP_YES && !scope->se_u.se_if.is_seen_skip_not)
|
|
{
|
|
// the previous block was not executed, this one will, do count the
|
|
// "else" for profiling
|
|
cctx->ctx_skip = SKIP_NOT;
|
|
generate_instr(cctx, ISN_PROF_END);
|
|
generate_instr(cctx, ISN_PROF_START);
|
|
cctx->ctx_skip = SKIP_YES;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!scope->se_u.se_if.is_seen_skip_not && scope->se_skip_save != SKIP_YES)
|
|
{
|
|
// jump from previous block to the end, unless the else block is empty
|
|
if (cctx->ctx_skip == SKIP_UNKNOWN)
|
|
{
|
|
if (!cctx->ctx_had_return
|
|
&& compile_jump_to_end(&scope->se_u.se_if.is_end_label,
|
|
JUMP_ALWAYS, 0, cctx) == FAIL)
|
|
return NULL;
|
|
}
|
|
|
|
if (cctx->ctx_skip == SKIP_UNKNOWN)
|
|
{
|
|
if (scope->se_u.se_if.is_if_label >= 0)
|
|
{
|
|
// previous "if" or "elseif" jumps here
|
|
isn = ((isn_T *)instr->ga_data) + scope->se_u.se_if.is_if_label;
|
|
isn->isn_arg.jump.jump_where = instr->ga_len;
|
|
scope->se_u.se_if.is_if_label = -1;
|
|
}
|
|
}
|
|
|
|
if (cctx->ctx_skip != SKIP_UNKNOWN)
|
|
cctx->ctx_skip = cctx->ctx_skip == SKIP_YES ? SKIP_NOT : SKIP_YES;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
char_u *
|
|
compile_endif(char_u *arg, cctx_T *cctx)
|
|
{
|
|
scope_T *scope = cctx->ctx_scope;
|
|
ifscope_T *ifscope;
|
|
garray_T *instr = &cctx->ctx_instr;
|
|
isn_T *isn;
|
|
|
|
if (misplaced_cmdmod(cctx))
|
|
return NULL;
|
|
|
|
if (scope == NULL || scope->se_type != IF_SCOPE)
|
|
{
|
|
emsg(_(e_endif_without_if));
|
|
return NULL;
|
|
}
|
|
ifscope = &scope->se_u.se_if;
|
|
unwind_locals(cctx, scope->se_local_count, TRUE);
|
|
if (!cctx->ctx_had_return && !cctx->ctx_had_throw)
|
|
// the previous if block didn't end in a "return" or a "throw"
|
|
// statement.
|
|
ifscope->is_had_return = FALSE;
|
|
|
|
if (scope->se_u.se_if.is_if_label >= 0)
|
|
{
|
|
// previous "if" or "elseif" jumps here
|
|
isn = ((isn_T *)instr->ga_data) + scope->se_u.se_if.is_if_label;
|
|
isn->isn_arg.jump.jump_where = instr->ga_len;
|
|
}
|
|
// Fill in the "end" label in jumps at the end of the blocks.
|
|
compile_fill_jump_to_end(&ifscope->is_end_label, instr->ga_len, cctx);
|
|
|
|
#ifdef FEAT_PROFILE
|
|
// even when skipping we count the endif as executed, unless the block it's
|
|
// in is skipped
|
|
if (cctx->ctx_compile_type == CT_PROFILE && cctx->ctx_skip == SKIP_YES
|
|
&& scope->se_skip_save != SKIP_YES)
|
|
{
|
|
cctx->ctx_skip = SKIP_NOT;
|
|
generate_instr(cctx, ISN_PROF_START);
|
|
}
|
|
#endif
|
|
cctx->ctx_skip = scope->se_skip_save;
|
|
|
|
// If all the blocks end in :return and there is an :else then the
|
|
// had_return flag is set.
|
|
cctx->ctx_had_return = ifscope->is_had_return && ifscope->is_seen_else;
|
|
|
|
drop_scope(cctx);
|
|
return arg;
|
|
}
|
|
|
|
/*
|
|
* Save the info needed for ENDLOOP. Used by :for and :while.
|
|
*/
|
|
static void
|
|
compile_fill_loop_info(loop_info_T *loop_info, int funcref_idx, cctx_T *cctx)
|
|
{
|
|
loop_info->li_funcref_idx = funcref_idx;
|
|
loop_info->li_local_count = cctx->ctx_locals.ga_len;
|
|
loop_info->li_closure_count = cctx->ctx_closure_count;
|
|
}
|
|
|
|
/*
|
|
* Compile "for var in expr":
|
|
*
|
|
* Produces instructions:
|
|
* STORE -1 in loop-idx Set index to -1
|
|
* EVAL expr Result of "expr" on top of stack
|
|
* top: FOR loop-idx, end Increment index, use list on bottom of stack
|
|
* - if beyond end, jump to "end"
|
|
* - otherwise get item from list and push it
|
|
* - store ec_funcrefs in var "loop-idx" + 1
|
|
* STORE var Store item in "var"
|
|
* ... body ...
|
|
* ENDLOOP funcref-idx off count Only if closure uses local var
|
|
* JUMP top Jump back to repeat
|
|
* end: DROP Drop the result of "expr"
|
|
*
|
|
* Compile "for [var1, var2] in expr" - as above, but instead of "STORE var":
|
|
* UNPACK 2 Split item in 2
|
|
* STORE var1 Store item in "var1"
|
|
* STORE var2 Store item in "var2"
|
|
*/
|
|
char_u *
|
|
compile_for(char_u *arg_start, cctx_T *cctx)
|
|
{
|
|
char_u *arg;
|
|
char_u *arg_end;
|
|
char_u *name = NULL;
|
|
char_u *p;
|
|
char_u *wp;
|
|
int var_count = 0;
|
|
int var_list = FALSE;
|
|
int semicolon = FALSE;
|
|
size_t varlen;
|
|
garray_T *instr = &cctx->ctx_instr;
|
|
scope_T *scope;
|
|
forscope_T *forscope;
|
|
lvar_T *loop_lvar; // loop iteration variable
|
|
int loop_lvar_idx;
|
|
lvar_T *funcref_lvar;
|
|
int funcref_lvar_idx;
|
|
lvar_T *var_lvar; // variable for "var"
|
|
type_T *vartype;
|
|
type_T *item_type = &t_any;
|
|
int idx;
|
|
int prev_lnum = cctx->ctx_prev_lnum;
|
|
|
|
p = skip_var_list(arg_start, TRUE, &var_count, &semicolon, FALSE);
|
|
if (p == NULL)
|
|
return NULL;
|
|
if (var_count == 0)
|
|
var_count = 1;
|
|
else
|
|
var_list = TRUE; // can also be a list of one variable
|
|
|
|
// consume "in"
|
|
wp = p;
|
|
if (may_get_next_line_error(wp, &p, cctx) == FAIL)
|
|
return NULL;
|
|
if (STRNCMP(p, "in", 2) != 0 || !IS_WHITE_OR_NUL(p[2]))
|
|
{
|
|
if (*p == ':' && wp != p)
|
|
semsg(_(e_no_white_space_allowed_before_colon_str), p);
|
|
else
|
|
emsg(_(e_missing_in_after_for));
|
|
return NULL;
|
|
}
|
|
wp = p + 2;
|
|
if (may_get_next_line_error(wp, &p, cctx) == FAIL)
|
|
return NULL;
|
|
|
|
// Find the already generated ISN_DEBUG to get the line number for the
|
|
// instruction written below the ISN_FOR instruction.
|
|
if (cctx->ctx_compile_type == CT_DEBUG && instr->ga_len > 0
|
|
&& ((isn_T *)instr->ga_data)[instr->ga_len - 1]
|
|
.isn_type == ISN_DEBUG)
|
|
{
|
|
prev_lnum = ((isn_T *)instr->ga_data)[instr->ga_len - 1]
|
|
.isn_arg.debug.dbg_break_lnum;
|
|
}
|
|
|
|
scope = new_scope(cctx, FOR_SCOPE);
|
|
if (scope == NULL)
|
|
return NULL;
|
|
if (scope->se_loop_depth == MAX_LOOP_DEPTH)
|
|
{
|
|
emsg(_(e_loop_nesting_too_deep));
|
|
return NULL;
|
|
}
|
|
++scope->se_loop_depth;
|
|
forscope = &scope->se_u.se_for;
|
|
|
|
// Reserve a variable to store the loop iteration counter and initialize it
|
|
// to -1.
|
|
loop_lvar = reserve_local(cctx, (char_u *)"", 0, ASSIGN_VAR, &t_number);
|
|
if (loop_lvar == NULL)
|
|
{
|
|
drop_scope(cctx);
|
|
return NULL; // out of memory
|
|
}
|
|
// get the index before a following reserve_local() makes the lval invalid
|
|
loop_lvar_idx = loop_lvar->lv_idx;
|
|
generate_STORENR(cctx, loop_lvar_idx, -1);
|
|
|
|
// Reserve a variable to store ec_funcrefs.ga_len, used in ISN_ENDLOOP.
|
|
// The variable index is always the loop var index plus one.
|
|
// It is not used when no closures are encountered, we don't know yet.
|
|
funcref_lvar = reserve_local(cctx, (char_u *)"", 0, ASSIGN_VAR, &t_number);
|
|
if (funcref_lvar == NULL)
|
|
{
|
|
drop_scope(cctx);
|
|
return NULL; // out of memory
|
|
}
|
|
// get the index before a following reserve_local() makes the lval invalid
|
|
funcref_lvar_idx = funcref_lvar->lv_idx;
|
|
|
|
// compile "expr", it remains on the stack until "endfor"
|
|
arg = p;
|
|
if (compile_expr0(&arg, cctx) == FAIL)
|
|
{
|
|
drop_scope(cctx);
|
|
return NULL;
|
|
}
|
|
arg_end = arg;
|
|
|
|
if (cctx->ctx_skip != SKIP_YES)
|
|
{
|
|
// If we know the type of "var" and it is not a supported type we can
|
|
// give an error now.
|
|
vartype = get_type_on_stack(cctx, 0);
|
|
if (vartype->tt_type != VAR_LIST
|
|
&& vartype->tt_type != VAR_STRING
|
|
&& vartype->tt_type != VAR_BLOB
|
|
&& vartype->tt_type != VAR_ANY
|
|
&& vartype->tt_type != VAR_UNKNOWN)
|
|
{
|
|
semsg(_(e_for_loop_on_str_not_supported),
|
|
vartype_name(vartype->tt_type));
|
|
drop_scope(cctx);
|
|
return NULL;
|
|
}
|
|
|
|
if (vartype->tt_type == VAR_STRING)
|
|
item_type = &t_string;
|
|
else if (vartype->tt_type == VAR_BLOB)
|
|
item_type = &t_number;
|
|
else if (vartype->tt_type == VAR_LIST
|
|
&& vartype->tt_member->tt_type != VAR_ANY)
|
|
{
|
|
if (!var_list)
|
|
item_type = vartype->tt_member;
|
|
else if (vartype->tt_member->tt_type == VAR_LIST
|
|
&& vartype->tt_member->tt_member->tt_type != VAR_ANY)
|
|
item_type = vartype->tt_member->tt_member;
|
|
}
|
|
|
|
// CMDMOD_REV must come before the FOR instruction.
|
|
generate_undo_cmdmods(cctx);
|
|
|
|
// "for_end" is set when ":endfor" is found
|
|
forscope->fs_top_label = current_instr_idx(cctx);
|
|
|
|
if (cctx->ctx_compile_type == CT_DEBUG)
|
|
{
|
|
int save_prev_lnum = cctx->ctx_prev_lnum;
|
|
isn_T *isn;
|
|
|
|
// Add ISN_DEBUG here, before deciding to end the loop. There will
|
|
// be another ISN_DEBUG before the next instruction.
|
|
// Use the prev_lnum from the ISN_DEBUG instruction removed above.
|
|
// Increment the variable count so that the loop variable can be
|
|
// inspected.
|
|
cctx->ctx_prev_lnum = prev_lnum;
|
|
isn = generate_instr_debug(cctx);
|
|
++isn->isn_arg.debug.dbg_var_names_len;
|
|
cctx->ctx_prev_lnum = save_prev_lnum;
|
|
}
|
|
|
|
generate_FOR(cctx, loop_lvar_idx);
|
|
|
|
arg = arg_start;
|
|
if (var_list)
|
|
{
|
|
generate_UNPACK(cctx, var_count, semicolon);
|
|
arg = skipwhite(arg + 1); // skip white after '['
|
|
|
|
// drop the list item
|
|
--cctx->ctx_type_stack.ga_len;
|
|
|
|
// add type of the items
|
|
for (idx = 0; idx < var_count; ++idx)
|
|
{
|
|
type_T *type = (semicolon && idx == 0) ? vartype : item_type;
|
|
|
|
if (push_type_stack(cctx, type) == FAIL)
|
|
{
|
|
drop_scope(cctx);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (idx = 0; idx < var_count; ++idx)
|
|
{
|
|
assign_dest_T dest = dest_local;
|
|
int opt_flags = 0;
|
|
int vimvaridx = -1;
|
|
type_T *type = &t_any;
|
|
type_T *lhs_type = &t_any;
|
|
where_T where = WHERE_INIT;
|
|
|
|
p = skip_var_one(arg, FALSE);
|
|
varlen = p - arg;
|
|
name = vim_strnsave(arg, varlen);
|
|
if (name == NULL)
|
|
goto failed;
|
|
if (*skipwhite(p) == ':')
|
|
{
|
|
if (VIM_ISWHITE(*p))
|
|
{
|
|
semsg(_(e_no_white_space_allowed_before_colon_str), p);
|
|
goto failed;
|
|
}
|
|
p = skipwhite(p + 1);
|
|
lhs_type = parse_type(&p, cctx->ctx_type_list, TRUE);
|
|
if (lhs_type == NULL)
|
|
goto failed;
|
|
}
|
|
|
|
if (get_var_dest(name, &dest, CMD_for, &opt_flags,
|
|
&vimvaridx, &type, cctx) == FAIL)
|
|
goto failed;
|
|
if (dest != dest_local)
|
|
{
|
|
if (generate_store_var(cctx, dest, opt_flags, vimvaridx,
|
|
type, name, NULL) == FAIL)
|
|
goto failed;
|
|
}
|
|
else if (varlen == 1 && *arg == '_')
|
|
{
|
|
// Assigning to "_": drop the value.
|
|
if (generate_instr_drop(cctx, ISN_DROP, 1) == NULL)
|
|
goto failed;
|
|
}
|
|
else
|
|
{
|
|
// Script var is not supported.
|
|
if (STRNCMP(name, "s:", 2) == 0)
|
|
{
|
|
emsg(_(e_cannot_use_script_variable_in_for_loop));
|
|
goto failed;
|
|
}
|
|
|
|
if (!valid_varname(arg, (int)varlen, FALSE))
|
|
goto failed;
|
|
if (lookup_local(arg, varlen, NULL, cctx) == OK)
|
|
{
|
|
semsg(_(e_variable_already_declared_str), arg);
|
|
goto failed;
|
|
}
|
|
|
|
// Reserve a variable to store "var".
|
|
if (var_list)
|
|
{
|
|
where.wt_index = idx + 1;
|
|
where.wt_kind = WT_VARIABLE;
|
|
}
|
|
if (lhs_type == &t_any)
|
|
lhs_type = item_type;
|
|
else if (item_type != &t_unknown
|
|
&& need_type_where(item_type, lhs_type, FALSE, -1,
|
|
where, cctx, FALSE, FALSE) == FAIL)
|
|
goto failed;
|
|
var_lvar = reserve_local(cctx, arg, varlen, ASSIGN_FINAL,
|
|
lhs_type);
|
|
if (var_lvar == NULL)
|
|
// out of memory or used as an argument
|
|
goto failed;
|
|
|
|
if (semicolon && idx == var_count - 1)
|
|
var_lvar->lv_type = vartype;
|
|
generate_STORE(cctx, ISN_STORE, var_lvar->lv_idx, NULL);
|
|
}
|
|
|
|
if (*p == ',' || *p == ';')
|
|
++p;
|
|
arg = skipwhite(p);
|
|
vim_free(name);
|
|
}
|
|
|
|
// remember the number of variables and closures, used for ENDLOOP
|
|
compile_fill_loop_info(&forscope->fs_loop_info, funcref_lvar_idx, cctx);
|
|
forscope->fs_loop_info.li_depth = scope->se_loop_depth - 1;
|
|
}
|
|
|
|
return arg_end;
|
|
|
|
failed:
|
|
vim_free(name);
|
|
drop_scope(cctx);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Used when ending a loop of :for and :while: Generate an ISN_ENDLOOP
|
|
* instruction if any variable was declared that could be used by a new
|
|
* closure.
|
|
*/
|
|
static int
|
|
compile_loop_end(loop_info_T *loop_info, cctx_T *cctx)
|
|
{
|
|
if (cctx->ctx_locals.ga_len > loop_info->li_local_count
|
|
&& cctx->ctx_closure_count > loop_info->li_closure_count)
|
|
return generate_ENDLOOP(cctx, loop_info);
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* compile "endfor"
|
|
*/
|
|
char_u *
|
|
compile_endfor(char_u *arg, cctx_T *cctx)
|
|
{
|
|
garray_T *instr = &cctx->ctx_instr;
|
|
scope_T *scope = cctx->ctx_scope;
|
|
forscope_T *forscope;
|
|
isn_T *isn;
|
|
|
|
if (misplaced_cmdmod(cctx))
|
|
return NULL;
|
|
|
|
if (scope == NULL || scope->se_type != FOR_SCOPE)
|
|
{
|
|
emsg(_(e_endfor_without_for));
|
|
return NULL;
|
|
}
|
|
forscope = &scope->se_u.se_for;
|
|
cctx->ctx_scope = scope->se_outer;
|
|
if (cctx->ctx_skip != SKIP_YES)
|
|
{
|
|
// Handle the case that any local variables were declared that might be
|
|
// used in a closure.
|
|
if (compile_loop_end(&forscope->fs_loop_info, cctx) == FAIL)
|
|
return NULL;
|
|
|
|
unwind_locals(cctx, scope->se_local_count, FALSE);
|
|
|
|
// At end of ":for" scope jump back to the FOR instruction.
|
|
generate_JUMP(cctx, JUMP_ALWAYS, forscope->fs_top_label);
|
|
|
|
// Fill in the "end" label in the FOR statement so it can jump here.
|
|
// In debug mode an ISN_DEBUG was inserted.
|
|
isn = ((isn_T *)instr->ga_data) + forscope->fs_top_label
|
|
+ (cctx->ctx_compile_type == CT_DEBUG ? 1 : 0);
|
|
isn->isn_arg.forloop.for_end = instr->ga_len;
|
|
|
|
// Fill in the "end" label any BREAK statements
|
|
compile_fill_jump_to_end(&forscope->fs_end_label, instr->ga_len, cctx);
|
|
|
|
// Below the ":for" scope drop the "expr" list from the stack.
|
|
if (generate_instr_drop(cctx, ISN_DROP, 1) == NULL)
|
|
return NULL;
|
|
}
|
|
|
|
vim_free(scope);
|
|
|
|
return arg;
|
|
}
|
|
|
|
/*
|
|
* compile "while expr"
|
|
*
|
|
* Produces instructions:
|
|
* top: EVAL expr Push result of "expr"
|
|
* WHILE funcref-idx end Jump if false
|
|
* ... body ...
|
|
* ENDLOOP funcref-idx off count only if closure uses local var
|
|
* JUMP top Jump back to repeat
|
|
* end:
|
|
*
|
|
*/
|
|
char_u *
|
|
compile_while(char_u *arg, cctx_T *cctx)
|
|
{
|
|
char_u *p = arg;
|
|
scope_T *scope;
|
|
whilescope_T *whilescope;
|
|
lvar_T *funcref_lvar;
|
|
int funcref_lvar_idx;
|
|
|
|
scope = new_scope(cctx, WHILE_SCOPE);
|
|
if (scope == NULL)
|
|
return NULL;
|
|
if (scope->se_loop_depth == MAX_LOOP_DEPTH)
|
|
{
|
|
emsg(_(e_loop_nesting_too_deep));
|
|
return NULL;
|
|
}
|
|
++scope->se_loop_depth;
|
|
whilescope = &scope->se_u.se_while;
|
|
|
|
// "endwhile" jumps back here, one before when profiling or using cmdmods
|
|
whilescope->ws_top_label = current_instr_idx(cctx);
|
|
|
|
// Reserve a variable to store ec_funcrefs.ga_len, used in ISN_ENDLOOP.
|
|
// It is not used when no closures are encountered, we don't know yet.
|
|
funcref_lvar = reserve_local(cctx, (char_u *)"", 0, ASSIGN_VAR, &t_number);
|
|
if (funcref_lvar == NULL)
|
|
{
|
|
drop_scope(cctx);
|
|
return NULL; // out of memory
|
|
}
|
|
// get the index before a following reserve_local() makes the lval invalid
|
|
funcref_lvar_idx = funcref_lvar->lv_idx;
|
|
|
|
// remember the number of variables and closures, used for ENDLOOP
|
|
compile_fill_loop_info(&whilescope->ws_loop_info, funcref_lvar_idx, cctx);
|
|
whilescope->ws_loop_info.li_depth = scope->se_loop_depth - 1;
|
|
|
|
// compile "expr"
|
|
if (compile_expr0(&p, cctx) == FAIL)
|
|
return NULL;
|
|
|
|
if (!ends_excmd2(arg, skipwhite(p)))
|
|
{
|
|
semsg(_(e_trailing_characters_str), p);
|
|
return NULL;
|
|
}
|
|
|
|
if (cctx->ctx_skip != SKIP_YES)
|
|
{
|
|
if (bool_on_stack(cctx) == FAIL)
|
|
return FAIL;
|
|
|
|
// CMDMOD_REV must come before the jump
|
|
generate_undo_cmdmods(cctx);
|
|
|
|
// "while_end" is set when ":endwhile" is found
|
|
if (compile_jump_to_end(&whilescope->ws_end_label,
|
|
JUMP_WHILE_FALSE, funcref_lvar_idx, cctx) == FAIL)
|
|
return FAIL;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* compile "endwhile"
|
|
*/
|
|
char_u *
|
|
compile_endwhile(char_u *arg, cctx_T *cctx)
|
|
{
|
|
scope_T *scope = cctx->ctx_scope;
|
|
garray_T *instr = &cctx->ctx_instr;
|
|
|
|
if (misplaced_cmdmod(cctx))
|
|
return NULL;
|
|
if (scope == NULL || scope->se_type != WHILE_SCOPE)
|
|
{
|
|
emsg(_(e_endwhile_without_while));
|
|
return NULL;
|
|
}
|
|
cctx->ctx_scope = scope->se_outer;
|
|
if (cctx->ctx_skip != SKIP_YES)
|
|
{
|
|
whilescope_T *whilescope = &scope->se_u.se_while;
|
|
|
|
// Handle the case that any local variables were declared that might be
|
|
// used in a closure.
|
|
if (compile_loop_end(&whilescope->ws_loop_info, cctx) == FAIL)
|
|
return NULL;
|
|
|
|
unwind_locals(cctx, scope->se_local_count, FALSE);
|
|
|
|
#ifdef FEAT_PROFILE
|
|
// count the endwhile before jumping
|
|
may_generate_prof_end(cctx, cctx->ctx_lnum);
|
|
#endif
|
|
|
|
// At end of ":for" scope jump back to the FOR instruction.
|
|
generate_JUMP(cctx, JUMP_ALWAYS, scope->se_u.se_while.ws_top_label);
|
|
|
|
// Fill in the "end" label in the WHILE statement so it can jump here.
|
|
// And in any jumps for ":break"
|
|
compile_fill_jump_to_end(&scope->se_u.se_while.ws_end_label,
|
|
instr->ga_len, cctx);
|
|
}
|
|
|
|
vim_free(scope);
|
|
|
|
return arg;
|
|
}
|
|
|
|
/*
|
|
* Get the current information about variables declared inside a loop.
|
|
* Returns TRUE if there are any and fills "lvi".
|
|
*/
|
|
int
|
|
get_loop_var_info(cctx_T *cctx, loopvarinfo_T *lvi)
|
|
{
|
|
scope_T *scope = cctx->ctx_scope;
|
|
int prev_local_count = 0;
|
|
|
|
CLEAR_POINTER(lvi);
|
|
for (;;)
|
|
{
|
|
loop_info_T *loopinfo;
|
|
int cur_local_last;
|
|
int start_local_count;
|
|
|
|
while (scope != NULL && scope->se_type != WHILE_SCOPE
|
|
&& scope->se_type != FOR_SCOPE)
|
|
scope = scope->se_outer;
|
|
if (scope == NULL)
|
|
break;
|
|
|
|
if (scope->se_type == WHILE_SCOPE)
|
|
{
|
|
loopinfo = &scope->se_u.se_while.ws_loop_info;
|
|
// :while reserves one variable for funcref count
|
|
cur_local_last = loopinfo->li_local_count - 1;
|
|
}
|
|
else
|
|
{
|
|
loopinfo = &scope->se_u.se_for.fs_loop_info;
|
|
// :for reserves three variable: loop count, funcref count and loop
|
|
// var
|
|
cur_local_last = loopinfo->li_local_count - 3;
|
|
}
|
|
|
|
start_local_count = loopinfo->li_local_count;
|
|
if (cctx->ctx_locals.ga_len > start_local_count)
|
|
{
|
|
lvi->lvi_loop[loopinfo->li_depth].var_idx =
|
|
(short)start_local_count;
|
|
lvi->lvi_loop[loopinfo->li_depth].var_count =
|
|
(short)(cctx->ctx_locals.ga_len - start_local_count
|
|
- prev_local_count);
|
|
if (lvi->lvi_depth == 0)
|
|
lvi->lvi_depth = loopinfo->li_depth + 1;
|
|
}
|
|
|
|
scope = scope->se_outer;
|
|
prev_local_count = cctx->ctx_locals.ga_len - cur_local_last;
|
|
}
|
|
return lvi->lvi_depth > 0;
|
|
}
|
|
|
|
/*
|
|
* Get the index of the variable "idx" in a loop, if any.
|
|
*/
|
|
void
|
|
get_loop_var_idx(cctx_T *cctx, int idx, lvar_T *lvar)
|
|
{
|
|
loopvarinfo_T lvi;
|
|
|
|
lvar->lv_loop_depth = -1;
|
|
lvar->lv_loop_idx = -1;
|
|
if (get_loop_var_info(cctx, &lvi))
|
|
{
|
|
int depth;
|
|
|
|
for (depth = lvi.lvi_depth - 1; depth >= 0; --depth)
|
|
if (idx >= lvi.lvi_loop[depth].var_idx
|
|
&& idx < lvi.lvi_loop[depth].var_idx
|
|
+ lvi.lvi_loop[depth].var_count)
|
|
{
|
|
lvar->lv_loop_depth = depth;
|
|
lvar->lv_loop_idx = lvi.lvi_loop[depth].var_idx;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Common for :break, :continue and :return
|
|
*/
|
|
static int
|
|
compile_find_scope(
|
|
int *loop_label, // where to jump to or NULL
|
|
endlabel_T ***el, // end label or NULL
|
|
int *try_scopes, // :try scopes encountered or NULL
|
|
char *error, // error to use when no scope found
|
|
cctx_T *cctx)
|
|
{
|
|
scope_T *scope = cctx->ctx_scope;
|
|
|
|
for (;;)
|
|
{
|
|
if (scope == NULL)
|
|
{
|
|
if (error != NULL)
|
|
emsg(_(error));
|
|
return FAIL;
|
|
}
|
|
if (scope->se_type == FOR_SCOPE)
|
|
{
|
|
if (compile_loop_end(&scope->se_u.se_for.fs_loop_info, cctx)
|
|
== FAIL)
|
|
return FAIL;
|
|
if (loop_label != NULL)
|
|
*loop_label = scope->se_u.se_for.fs_top_label;
|
|
if (el != NULL)
|
|
*el = &scope->se_u.se_for.fs_end_label;
|
|
break;
|
|
}
|
|
if (scope->se_type == WHILE_SCOPE)
|
|
{
|
|
if (compile_loop_end(&scope->se_u.se_while.ws_loop_info, cctx)
|
|
== FAIL)
|
|
return FAIL;
|
|
if (loop_label != NULL)
|
|
*loop_label = scope->se_u.se_while.ws_top_label;
|
|
if (el != NULL)
|
|
*el = &scope->se_u.se_while.ws_end_label;
|
|
break;
|
|
}
|
|
if (try_scopes != NULL && scope->se_type == TRY_SCOPE)
|
|
++*try_scopes;
|
|
scope = scope->se_outer;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* compile "continue"
|
|
*/
|
|
char_u *
|
|
compile_continue(char_u *arg, cctx_T *cctx)
|
|
{
|
|
int try_scopes = 0;
|
|
int loop_label;
|
|
|
|
if (compile_find_scope(&loop_label, NULL, &try_scopes,
|
|
e_continue_without_while_or_for, cctx) == FAIL)
|
|
return NULL;
|
|
if (try_scopes > 0)
|
|
// Inside one or more try/catch blocks we first need to jump to the
|
|
// "finally" or "endtry" to cleanup.
|
|
generate_TRYCONT(cctx, try_scopes, loop_label);
|
|
else
|
|
// Jump back to the FOR or WHILE instruction.
|
|
generate_JUMP(cctx, JUMP_ALWAYS, loop_label);
|
|
|
|
return arg;
|
|
}
|
|
|
|
/*
|
|
* compile "break"
|
|
*/
|
|
char_u *
|
|
compile_break(char_u *arg, cctx_T *cctx)
|
|
{
|
|
int try_scopes = 0;
|
|
endlabel_T **el;
|
|
|
|
if (compile_find_scope(NULL, &el, &try_scopes,
|
|
e_break_without_while_or_for, cctx) == FAIL)
|
|
return NULL;
|
|
|
|
if (cctx->ctx_skip == SKIP_YES)
|
|
return arg;
|
|
|
|
if (try_scopes > 0)
|
|
// Inside one or more try/catch blocks we first need to jump to the
|
|
// "finally" or "endtry" to cleanup. Then come to the next JUMP
|
|
// instruction, which we don't know the index of yet.
|
|
generate_TRYCONT(cctx, try_scopes, cctx->ctx_instr.ga_len + 1);
|
|
|
|
// Jump to the end of the FOR or WHILE loop. The instruction index will be
|
|
// filled in later.
|
|
if (compile_jump_to_end(el, JUMP_ALWAYS, 0, cctx) == FAIL)
|
|
return NULL;
|
|
|
|
return arg;
|
|
}
|
|
|
|
/*
|
|
* compile "{" start of block
|
|
*/
|
|
char_u *
|
|
compile_block(char_u *arg, cctx_T *cctx)
|
|
{
|
|
if (new_scope(cctx, BLOCK_SCOPE) == NULL)
|
|
return NULL;
|
|
return skipwhite(arg + 1);
|
|
}
|
|
|
|
/*
|
|
* compile end of block: drop one scope
|
|
*/
|
|
void
|
|
compile_endblock(cctx_T *cctx)
|
|
{
|
|
scope_T *scope = cctx->ctx_scope;
|
|
|
|
cctx->ctx_scope = scope->se_outer;
|
|
unwind_locals(cctx, scope->se_local_count, TRUE);
|
|
vim_free(scope);
|
|
}
|
|
|
|
/*
|
|
* Compile "try".
|
|
* Creates a new scope for the try-endtry, pointing to the first catch and
|
|
* finally.
|
|
* Creates another scope for the "try" block itself.
|
|
* TRY instruction sets up exception handling at runtime.
|
|
*
|
|
* "try"
|
|
* TRY -> catch1, -> finally push trystack entry
|
|
* ... try block
|
|
* "throw {exception}"
|
|
* EVAL {exception}
|
|
* THROW create exception
|
|
* ... try block
|
|
* " catch {expr}"
|
|
* JUMP -> finally
|
|
* catch1: PUSH exception
|
|
* EVAL {expr}
|
|
* MATCH
|
|
* JUMP nomatch -> catch2
|
|
* CATCH remove exception
|
|
* ... catch block
|
|
* " catch"
|
|
* JUMP -> finally
|
|
* catch2: CATCH remove exception
|
|
* ... catch block
|
|
* " finally"
|
|
* finally:
|
|
* ... finally block
|
|
* " endtry"
|
|
* ENDTRY pop trystack entry, may rethrow
|
|
*/
|
|
char_u *
|
|
compile_try(char_u *arg, cctx_T *cctx)
|
|
{
|
|
garray_T *instr = &cctx->ctx_instr;
|
|
scope_T *try_scope;
|
|
scope_T *scope;
|
|
|
|
if (misplaced_cmdmod(cctx))
|
|
return NULL;
|
|
|
|
// scope that holds the jumps that go to catch/finally/endtry
|
|
try_scope = new_scope(cctx, TRY_SCOPE);
|
|
if (try_scope == NULL)
|
|
return NULL;
|
|
|
|
if (cctx->ctx_skip != SKIP_YES)
|
|
{
|
|
isn_T *isn;
|
|
|
|
// "try_catch" is set when the first ":catch" is found or when no catch
|
|
// is found and ":finally" is found.
|
|
// "try_finally" is set when ":finally" is found
|
|
// "try_endtry" is set when ":endtry" is found
|
|
try_scope->se_u.se_try.ts_try_label = instr->ga_len;
|
|
if ((isn = generate_instr(cctx, ISN_TRY)) == NULL)
|
|
return NULL;
|
|
isn->isn_arg.tryref.try_ref = ALLOC_CLEAR_ONE(tryref_T);
|
|
if (isn->isn_arg.tryref.try_ref == NULL)
|
|
return NULL;
|
|
}
|
|
|
|
// scope for the try block itself
|
|
scope = new_scope(cctx, BLOCK_SCOPE);
|
|
if (scope == NULL)
|
|
return NULL;
|
|
|
|
return arg;
|
|
}
|
|
|
|
/*
|
|
* Compile "catch {expr}".
|
|
*/
|
|
char_u *
|
|
compile_catch(char_u *arg, cctx_T *cctx)
|
|
{
|
|
scope_T *scope = cctx->ctx_scope;
|
|
garray_T *instr = &cctx->ctx_instr;
|
|
char_u *p;
|
|
isn_T *isn;
|
|
|
|
if (misplaced_cmdmod(cctx))
|
|
return NULL;
|
|
|
|
// end block scope from :try or :catch
|
|
if (scope != NULL && scope->se_type == BLOCK_SCOPE)
|
|
compile_endblock(cctx);
|
|
scope = cctx->ctx_scope;
|
|
|
|
// Error if not in a :try scope
|
|
if (scope == NULL || scope->se_type != TRY_SCOPE)
|
|
{
|
|
emsg(_(e_catch_without_try));
|
|
return NULL;
|
|
}
|
|
|
|
if (scope->se_u.se_try.ts_caught_all
|
|
&& !ignore_unreachable_code_for_testing)
|
|
{
|
|
emsg(_(e_catch_unreachable_after_catch_all));
|
|
return NULL;
|
|
}
|
|
if (!cctx->ctx_had_return)
|
|
scope->se_u.se_try.ts_no_return = TRUE;
|
|
|
|
if (cctx->ctx_skip != SKIP_YES)
|
|
{
|
|
#ifdef FEAT_PROFILE
|
|
// the profile-start should be after the jump
|
|
if (cctx->ctx_compile_type == CT_PROFILE
|
|
&& instr->ga_len > 0
|
|
&& ((isn_T *)instr->ga_data)[instr->ga_len - 1]
|
|
.isn_type == ISN_PROF_START)
|
|
--instr->ga_len;
|
|
#endif
|
|
// Jump from end of previous block to :finally or :endtry
|
|
if (compile_jump_to_end(&scope->se_u.se_try.ts_end_label,
|
|
JUMP_ALWAYS, 0, cctx) == FAIL)
|
|
return NULL;
|
|
|
|
// End :try or :catch scope: set value in ISN_TRY instruction
|
|
isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_try_label;
|
|
if (isn->isn_arg.tryref.try_ref->try_catch == 0)
|
|
isn->isn_arg.tryref.try_ref->try_catch = instr->ga_len;
|
|
if (scope->se_u.se_try.ts_catch_label != 0)
|
|
{
|
|
// Previous catch without match jumps here
|
|
isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_catch_label;
|
|
isn->isn_arg.jump.jump_where = instr->ga_len;
|
|
}
|
|
#ifdef FEAT_PROFILE
|
|
if (cctx->ctx_compile_type == CT_PROFILE)
|
|
{
|
|
// a "throw" that jumps here needs to be counted
|
|
generate_instr(cctx, ISN_PROF_END);
|
|
// the "catch" is also counted
|
|
generate_instr(cctx, ISN_PROF_START);
|
|
}
|
|
#endif
|
|
if (cctx->ctx_compile_type == CT_DEBUG)
|
|
generate_instr_debug(cctx);
|
|
}
|
|
|
|
p = skipwhite(arg);
|
|
if (ends_excmd2(arg, p))
|
|
{
|
|
scope->se_u.se_try.ts_caught_all = TRUE;
|
|
scope->se_u.se_try.ts_catch_label = 0;
|
|
}
|
|
else
|
|
{
|
|
char_u *end;
|
|
char_u *pat;
|
|
char_u *tofree = NULL;
|
|
int dropped = 0;
|
|
int len;
|
|
|
|
// Push v:exception, push {expr} and MATCH
|
|
generate_instr_type(cctx, ISN_PUSHEXC, &t_string);
|
|
|
|
end = skip_regexp_ex(p + 1, *p, TRUE, &tofree, &dropped, NULL);
|
|
if (*end != *p)
|
|
{
|
|
semsg(_(e_separator_mismatch_str), p);
|
|
vim_free(tofree);
|
|
return NULL;
|
|
}
|
|
if (tofree == NULL)
|
|
len = (int)(end - (p + 1));
|
|
else
|
|
len = (int)(end - tofree);
|
|
pat = vim_strnsave(tofree == NULL ? p + 1 : tofree, len);
|
|
vim_free(tofree);
|
|
p += len + 2 + dropped;
|
|
if (pat == NULL)
|
|
return NULL;
|
|
if (generate_PUSHS(cctx, &pat) == FAIL)
|
|
return NULL;
|
|
|
|
if (generate_COMPARE(cctx, EXPR_MATCH, FALSE) == FAIL)
|
|
return NULL;
|
|
|
|
scope->se_u.se_try.ts_catch_label = instr->ga_len;
|
|
if (generate_JUMP(cctx, JUMP_IF_FALSE, 0) == FAIL)
|
|
return NULL;
|
|
}
|
|
|
|
if (cctx->ctx_skip != SKIP_YES && generate_instr(cctx, ISN_CATCH) == NULL)
|
|
return NULL;
|
|
|
|
if (new_scope(cctx, BLOCK_SCOPE) == NULL)
|
|
return NULL;
|
|
return p;
|
|
}
|
|
|
|
char_u *
|
|
compile_finally(char_u *arg, cctx_T *cctx)
|
|
{
|
|
scope_T *scope = cctx->ctx_scope;
|
|
garray_T *instr = &cctx->ctx_instr;
|
|
isn_T *isn;
|
|
int this_instr;
|
|
|
|
if (misplaced_cmdmod(cctx))
|
|
return NULL;
|
|
|
|
// end block scope from :try or :catch
|
|
if (scope != NULL && scope->se_type == BLOCK_SCOPE)
|
|
compile_endblock(cctx);
|
|
scope = cctx->ctx_scope;
|
|
|
|
// Error if not in a :try scope
|
|
if (scope == NULL || scope->se_type != TRY_SCOPE)
|
|
{
|
|
emsg(_(e_finally_without_try));
|
|
return NULL;
|
|
}
|
|
|
|
if (cctx->ctx_skip != SKIP_YES)
|
|
{
|
|
// End :catch or :finally scope: set value in ISN_TRY instruction
|
|
isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_try_label;
|
|
if (isn->isn_arg.tryref.try_ref->try_finally != 0)
|
|
{
|
|
emsg(_(e_multiple_finally));
|
|
return NULL;
|
|
}
|
|
|
|
this_instr = instr->ga_len;
|
|
#ifdef FEAT_PROFILE
|
|
if (cctx->ctx_compile_type == CT_PROFILE
|
|
&& ((isn_T *)instr->ga_data)[this_instr - 1]
|
|
.isn_type == ISN_PROF_START)
|
|
{
|
|
// jump to the profile start of the "finally"
|
|
--this_instr;
|
|
|
|
// jump to the profile end above it
|
|
if (this_instr > 0 && ((isn_T *)instr->ga_data)[this_instr - 1]
|
|
.isn_type == ISN_PROF_END)
|
|
--this_instr;
|
|
}
|
|
#endif
|
|
|
|
// Fill in the "end" label in jumps at the end of the blocks.
|
|
compile_fill_jump_to_end(&scope->se_u.se_try.ts_end_label,
|
|
this_instr, cctx);
|
|
|
|
// If there is no :catch then an exception jumps to :finally.
|
|
if (isn->isn_arg.tryref.try_ref->try_catch == 0)
|
|
isn->isn_arg.tryref.try_ref->try_catch = this_instr;
|
|
isn->isn_arg.tryref.try_ref->try_finally = this_instr;
|
|
if (scope->se_u.se_try.ts_catch_label != 0)
|
|
{
|
|
// Previous catch without match jumps here
|
|
isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_catch_label;
|
|
isn->isn_arg.jump.jump_where = this_instr;
|
|
scope->se_u.se_try.ts_catch_label = 0;
|
|
}
|
|
scope->se_u.se_try.ts_has_finally = TRUE;
|
|
if (generate_instr(cctx, ISN_FINALLY) == NULL)
|
|
return NULL;
|
|
}
|
|
|
|
return arg;
|
|
}
|
|
|
|
char_u *
|
|
compile_endtry(char_u *arg, cctx_T *cctx)
|
|
{
|
|
scope_T *scope = cctx->ctx_scope;
|
|
garray_T *instr = &cctx->ctx_instr;
|
|
isn_T *try_isn;
|
|
|
|
if (misplaced_cmdmod(cctx))
|
|
return NULL;
|
|
|
|
// end block scope from :catch or :finally
|
|
if (scope != NULL && scope->se_type == BLOCK_SCOPE)
|
|
compile_endblock(cctx);
|
|
scope = cctx->ctx_scope;
|
|
|
|
// Error if not in a :try scope
|
|
if (scope == NULL || scope->se_type != TRY_SCOPE)
|
|
{
|
|
if (scope == NULL)
|
|
emsg(_(e_endtry_without_try));
|
|
else if (scope->se_type == WHILE_SCOPE)
|
|
emsg(_(e_missing_endwhile));
|
|
else if (scope->se_type == FOR_SCOPE)
|
|
emsg(_(e_missing_endfor));
|
|
else
|
|
emsg(_(e_missing_endif));
|
|
return NULL;
|
|
}
|
|
|
|
try_isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_try_label;
|
|
if (cctx->ctx_skip != SKIP_YES)
|
|
{
|
|
if (try_isn->isn_arg.tryref.try_ref->try_catch == 0
|
|
&& try_isn->isn_arg.tryref.try_ref->try_finally == 0)
|
|
{
|
|
emsg(_(e_missing_catch_or_finally));
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef FEAT_PROFILE
|
|
if (cctx->ctx_compile_type == CT_PROFILE
|
|
&& ((isn_T *)instr->ga_data)[instr->ga_len - 1]
|
|
.isn_type == ISN_PROF_START)
|
|
// move the profile start after "endtry" so that it's not counted when
|
|
// the exception is rethrown.
|
|
--instr->ga_len;
|
|
#endif
|
|
|
|
// Fill in the "end" label in jumps at the end of the blocks, if not
|
|
// done by ":finally".
|
|
compile_fill_jump_to_end(&scope->se_u.se_try.ts_end_label,
|
|
instr->ga_len, cctx);
|
|
|
|
if (scope->se_u.se_try.ts_catch_label != 0)
|
|
{
|
|
// Last catch without match jumps here
|
|
isn_T *isn = ((isn_T *)instr->ga_data)
|
|
+ scope->se_u.se_try.ts_catch_label;
|
|
isn->isn_arg.jump.jump_where = instr->ga_len;
|
|
}
|
|
}
|
|
|
|
// If there is a finally clause that ends in return then we will return.
|
|
// If one of the blocks didn't end in "return" or we did not catch all
|
|
// exceptions reset the had_return flag.
|
|
if (!(scope->se_u.se_try.ts_has_finally && cctx->ctx_had_return)
|
|
&& (scope->se_u.se_try.ts_no_return
|
|
|| !scope->se_u.se_try.ts_caught_all))
|
|
cctx->ctx_had_return = FALSE;
|
|
|
|
compile_endblock(cctx);
|
|
|
|
if (cctx->ctx_skip != SKIP_YES)
|
|
{
|
|
// End :catch or :finally scope: set instruction index in ISN_TRY
|
|
// instruction
|
|
try_isn->isn_arg.tryref.try_ref->try_endtry = instr->ga_len;
|
|
if (generate_instr(cctx, ISN_ENDTRY) == NULL)
|
|
return NULL;
|
|
#ifdef FEAT_PROFILE
|
|
if (cctx->ctx_compile_type == CT_PROFILE)
|
|
generate_instr(cctx, ISN_PROF_START);
|
|
#endif
|
|
}
|
|
return arg;
|
|
}
|
|
|
|
/*
|
|
* compile "throw {expr}"
|
|
*/
|
|
char_u *
|
|
compile_throw(char_u *arg, cctx_T *cctx)
|
|
{
|
|
char_u *p = skipwhite(arg);
|
|
|
|
if (compile_expr0(&p, cctx) == FAIL)
|
|
return NULL;
|
|
if (cctx->ctx_skip == SKIP_YES)
|
|
return p;
|
|
if (may_generate_2STRING(-1, TOSTRING_NONE, cctx) == FAIL)
|
|
return NULL;
|
|
if (generate_instr_drop(cctx, ISN_THROW, 1) == NULL)
|
|
return NULL;
|
|
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* Compile an expression or function call.
|
|
*/
|
|
char_u *
|
|
compile_eval(char_u *arg, cctx_T *cctx)
|
|
{
|
|
char_u *p = arg;
|
|
int name_only;
|
|
long lnum = SOURCING_LNUM;
|
|
|
|
// find_ex_command() will consider a variable name an expression, assuming
|
|
// that something follows on the next line. Check that something actually
|
|
// follows, otherwise it's probably a misplaced command.
|
|
name_only = cmd_is_name_only(arg);
|
|
|
|
if (compile_expr0(&p, cctx) == FAIL)
|
|
return NULL;
|
|
|
|
if (name_only && lnum == SOURCING_LNUM)
|
|
{
|
|
semsg(_(e_expression_without_effect_str), arg);
|
|
return NULL;
|
|
}
|
|
|
|
// drop the result
|
|
generate_instr_drop(cctx, ISN_DROP, 1);
|
|
|
|
return skipwhite(p);
|
|
}
|
|
|
|
/*
|
|
* Get the local variable index for deferred function calls.
|
|
* Reserve it when not done already.
|
|
* Returns zero for failure.
|
|
*/
|
|
int
|
|
get_defer_var_idx(cctx_T *cctx)
|
|
{
|
|
dfunc_T *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 0;
|
|
dfunc->df_defer_var_idx = lvar->lv_idx + 1;
|
|
}
|
|
return dfunc->df_defer_var_idx;
|
|
}
|
|
|
|
/*
|
|
* Compile "defer func(arg)".
|
|
*/
|
|
char_u *
|
|
compile_defer(char_u *arg_start, cctx_T *cctx)
|
|
{
|
|
char_u *paren;
|
|
char_u *arg = arg_start;
|
|
int argcount = 0;
|
|
int defer_var_idx;
|
|
type_T *type = NULL;
|
|
int func_idx;
|
|
|
|
// Get a funcref for the function name.
|
|
// TODO: better way to find the "(".
|
|
paren = vim_strchr(arg, '(');
|
|
if (paren == NULL)
|
|
{
|
|
semsg(_(e_missing_parenthesis_str), arg);
|
|
return NULL;
|
|
}
|
|
*paren = 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;
|
|
*paren = '(';
|
|
|
|
// check for function type
|
|
if (cctx->ctx_skip != SKIP_YES)
|
|
{
|
|
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(paren + 1);
|
|
if (compile_arguments(&arg, cctx, &argcount, CA_NOT_SPECIAL) == FAIL)
|
|
return NULL;
|
|
|
|
if (cctx->ctx_skip != SKIP_YES)
|
|
{
|
|
if (func_idx >= 0)
|
|
{
|
|
type2_T *argtypes = NULL;
|
|
type2_T shuffled_argtypes[MAX_FUNC_ARGS];
|
|
|
|
if (check_internal_func_args(cctx, func_idx, argcount, FALSE,
|
|
&argtypes, shuffled_argtypes) == FAIL)
|
|
return NULL;
|
|
}
|
|
else if (check_func_args_from_type(cctx, type, argcount, TRUE,
|
|
arg_start) == FAIL)
|
|
return NULL;
|
|
|
|
defer_var_idx = get_defer_var_idx(cctx);
|
|
if (defer_var_idx == 0)
|
|
return NULL;
|
|
if (generate_DEFER(cctx, defer_var_idx - 1, argcount) == FAIL)
|
|
return NULL;
|
|
}
|
|
|
|
return skipwhite(arg);
|
|
}
|
|
|
|
/*
|
|
* compile "echo expr"
|
|
* compile "echomsg expr"
|
|
* compile "echoerr expr"
|
|
* compile "echoconsole expr"
|
|
* compile "echowindow expr" - may have cmd_count set
|
|
* compile "execute expr"
|
|
*/
|
|
char_u *
|
|
compile_mult_expr(
|
|
char_u *arg,
|
|
int cmdidx,
|
|
long cmd_count UNUSED,
|
|
cctx_T *cctx)
|
|
{
|
|
char_u *p = arg;
|
|
char_u *prev = arg;
|
|
char_u *expr_start;
|
|
int count = 0;
|
|
int start_ctx_lnum = cctx->ctx_lnum;
|
|
type_T *type;
|
|
int r = OK;
|
|
|
|
for (;;)
|
|
{
|
|
if (ends_excmd2(prev, p))
|
|
break;
|
|
expr_start = p;
|
|
if (compile_expr0(&p, cctx) == FAIL)
|
|
return NULL;
|
|
|
|
if (cctx->ctx_skip != SKIP_YES)
|
|
{
|
|
// check for non-void type
|
|
type = get_type_on_stack(cctx, 0);
|
|
if (type->tt_type == VAR_VOID)
|
|
{
|
|
semsg(_(e_expression_does_not_result_in_value_str), expr_start);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
++count;
|
|
prev = p;
|
|
p = skipwhite(p);
|
|
}
|
|
|
|
if (count > 0)
|
|
{
|
|
long save_lnum = cctx->ctx_lnum;
|
|
|
|
// Use the line number where the command started.
|
|
cctx->ctx_lnum = start_ctx_lnum;
|
|
|
|
if (cmdidx == CMD_echo || cmdidx == CMD_echon)
|
|
r = generate_ECHO(cctx, cmdidx == CMD_echo, count);
|
|
else if (cmdidx == CMD_execute)
|
|
r = generate_MULT_EXPR(cctx, ISN_EXECUTE, count);
|
|
else if (cmdidx == CMD_echomsg)
|
|
r = generate_MULT_EXPR(cctx, ISN_ECHOMSG, count);
|
|
#ifdef HAS_MESSAGE_WINDOW
|
|
else if (cmdidx == CMD_echowindow)
|
|
r = generate_ECHOWINDOW(cctx, count, cmd_count);
|
|
#endif
|
|
else if (cmdidx == CMD_echoconsole)
|
|
r = generate_MULT_EXPR(cctx, ISN_ECHOCONSOLE, count);
|
|
else
|
|
r = generate_MULT_EXPR(cctx, ISN_ECHOERR, count);
|
|
|
|
cctx->ctx_lnum = save_lnum;
|
|
}
|
|
return r == OK ? p : NULL;
|
|
}
|
|
|
|
/*
|
|
* If "eap" has a range that is not a constant generate an ISN_RANGE
|
|
* instruction to compute it and return OK.
|
|
* Otherwise return FAIL, the caller must deal with any range.
|
|
*/
|
|
static int
|
|
compile_variable_range(exarg_T *eap, cctx_T *cctx)
|
|
{
|
|
char_u *range_end = skip_range(eap->cmd, TRUE, NULL);
|
|
char_u *p = skipdigits(eap->cmd);
|
|
|
|
if (p == range_end)
|
|
return FAIL;
|
|
return generate_RANGE(cctx, vim_strnsave(eap->cmd, range_end - eap->cmd));
|
|
}
|
|
|
|
/*
|
|
* :put r
|
|
* :put ={expr}
|
|
*/
|
|
char_u *
|
|
compile_put(char_u *arg, exarg_T *eap, cctx_T *cctx)
|
|
{
|
|
char_u *line = arg;
|
|
linenr_T lnum;
|
|
char *errormsg;
|
|
int above = eap->forceit;
|
|
|
|
eap->regname = *line;
|
|
|
|
if (eap->regname == '=')
|
|
{
|
|
char_u *p = skipwhite(line + 1);
|
|
|
|
if (compile_expr0(&p, cctx) == FAIL)
|
|
return NULL;
|
|
line = p;
|
|
}
|
|
else if (eap->regname != NUL)
|
|
++line;
|
|
|
|
if (compile_variable_range(eap, cctx) == OK)
|
|
{
|
|
lnum = above ? LNUM_VARIABLE_RANGE_ABOVE : LNUM_VARIABLE_RANGE;
|
|
}
|
|
else
|
|
{
|
|
// Either no range or a number.
|
|
// "errormsg" will not be set because the range is ADDR_LINES.
|
|
if (parse_cmd_address(eap, &errormsg, FALSE) == FAIL)
|
|
// cannot happen
|
|
return NULL;
|
|
if (eap->addr_count == 0)
|
|
lnum = -1;
|
|
else
|
|
lnum = eap->line2;
|
|
if (above)
|
|
--lnum;
|
|
}
|
|
|
|
generate_PUT(cctx, eap->regname, lnum);
|
|
return line;
|
|
}
|
|
|
|
/*
|
|
* A command that is not compiled, execute with legacy code.
|
|
*/
|
|
char_u *
|
|
compile_exec(char_u *line_arg, exarg_T *eap, cctx_T *cctx)
|
|
{
|
|
char_u *line = line_arg;
|
|
char_u *p;
|
|
int has_expr = FALSE;
|
|
char_u *nextcmd = (char_u *)"";
|
|
char_u *tofree = NULL;
|
|
char_u *cmd_arg = NULL;
|
|
|
|
if (cctx->ctx_skip == SKIP_YES)
|
|
goto theend;
|
|
|
|
// If there was a prececing command modifier, drop it and include it in the
|
|
// EXEC command.
|
|
if (cctx->ctx_has_cmdmod)
|
|
{
|
|
garray_T *instr = &cctx->ctx_instr;
|
|
isn_T *isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1;
|
|
|
|
if (isn->isn_type == ISN_CMDMOD)
|
|
{
|
|
vim_regfree(isn->isn_arg.cmdmod.cf_cmdmod
|
|
->cmod_filter_regmatch.regprog);
|
|
vim_free(isn->isn_arg.cmdmod.cf_cmdmod);
|
|
--instr->ga_len;
|
|
cctx->ctx_has_cmdmod = FALSE;
|
|
}
|
|
}
|
|
|
|
if (eap->cmdidx >= 0 && eap->cmdidx < CMD_SIZE)
|
|
{
|
|
long argt = eap->argt;
|
|
int usefilter = FALSE;
|
|
|
|
has_expr = argt & (EX_XFILE | EX_EXPAND);
|
|
|
|
// If the command can be followed by a bar, find the bar and truncate
|
|
// it, so that the following command can be compiled.
|
|
// The '|' is overwritten with a NUL, it is put back below.
|
|
if ((eap->cmdidx == CMD_write || eap->cmdidx == CMD_read)
|
|
&& *eap->arg == '!')
|
|
// :w !filter or :r !filter or :r! filter
|
|
usefilter = TRUE;
|
|
if ((argt & EX_TRLBAR) && !usefilter)
|
|
{
|
|
eap->argt = argt;
|
|
separate_nextcmd(eap, TRUE);
|
|
if (eap->nextcmd != NULL)
|
|
nextcmd = eap->nextcmd;
|
|
}
|
|
else if (eap->cmdidx == CMD_wincmd)
|
|
{
|
|
p = eap->arg;
|
|
if (*p != NUL)
|
|
++p;
|
|
if (*p == 'g' || *p == Ctrl_G)
|
|
++p;
|
|
p = skipwhite(p);
|
|
if (*p == '|')
|
|
{
|
|
*p = NUL;
|
|
nextcmd = p + 1;
|
|
}
|
|
}
|
|
else if (eap->cmdidx == CMD_command || eap->cmdidx == CMD_autocmd)
|
|
{
|
|
// If there is a trailing '{' read lines until the '}'
|
|
p = eap->arg + STRLEN(eap->arg) - 1;
|
|
while (p > eap->arg && VIM_ISWHITE(*p))
|
|
--p;
|
|
if (*p == '{')
|
|
{
|
|
exarg_T ea;
|
|
int flags = 0; // unused
|
|
int start_lnum = SOURCING_LNUM;
|
|
|
|
CLEAR_FIELD(ea);
|
|
ea.arg = eap->arg;
|
|
fill_exarg_from_cctx(&ea, cctx);
|
|
(void)may_get_cmd_block(&ea, p, &tofree, &flags);
|
|
if (tofree != NULL)
|
|
{
|
|
*p = NUL;
|
|
line = concat_str(line, tofree);
|
|
if (line == NULL)
|
|
goto theend;
|
|
vim_free(tofree);
|
|
tofree = line;
|
|
SOURCING_LNUM = start_lnum;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (eap->cmdidx == CMD_syntax && STRNCMP(eap->arg, "include ", 8) == 0)
|
|
{
|
|
// expand filename in "syntax include [@group] filename"
|
|
has_expr = TRUE;
|
|
eap->arg = skipwhite(eap->arg + 7);
|
|
if (*eap->arg == '@')
|
|
eap->arg = skiptowhite(eap->arg);
|
|
}
|
|
|
|
if ((eap->cmdidx == CMD_global || eap->cmdidx == CMD_vglobal)
|
|
&& STRLEN(eap->arg) > 4)
|
|
{
|
|
int delim = *eap->arg;
|
|
|
|
p = skip_regexp_ex(eap->arg + 1, delim, TRUE, NULL, NULL, NULL);
|
|
if (*p == delim)
|
|
cmd_arg = p + 1;
|
|
}
|
|
|
|
if (eap->cmdidx == CMD_folddoopen || eap->cmdidx == CMD_folddoclosed)
|
|
cmd_arg = eap->arg;
|
|
|
|
if (cmd_arg != NULL)
|
|
{
|
|
exarg_T nea;
|
|
|
|
CLEAR_FIELD(nea);
|
|
nea.cmd = cmd_arg;
|
|
p = find_ex_command(&nea, NULL, lookup_scriptitem, NULL);
|
|
if (nea.cmdidx < CMD_SIZE)
|
|
{
|
|
has_expr = excmd_get_argt(nea.cmdidx) & (EX_XFILE | EX_EXPAND);
|
|
if (has_expr)
|
|
eap->arg = skiptowhite(eap->arg);
|
|
}
|
|
}
|
|
|
|
if (has_expr && (p = (char_u *)strstr((char *)eap->arg, "`=")) != NULL)
|
|
{
|
|
int count = 0;
|
|
char_u *start = skipwhite(line);
|
|
|
|
// :cmd xxx`=expr1`yyy`=expr2`zzz
|
|
// PUSHS ":cmd xxx"
|
|
// eval expr1
|
|
// PUSHS "yyy"
|
|
// eval expr2
|
|
// PUSHS "zzz"
|
|
// EXECCONCAT 5
|
|
for (;;)
|
|
{
|
|
if (p > start)
|
|
{
|
|
char_u *val = vim_strnsave(start, p - start);
|
|
|
|
generate_PUSHS(cctx, &val);
|
|
++count;
|
|
}
|
|
p += 2;
|
|
if (compile_expr0(&p, cctx) == FAIL)
|
|
return NULL;
|
|
may_generate_2STRING(-1, TOSTRING_TOLERANT, cctx);
|
|
++count;
|
|
p = skipwhite(p);
|
|
if (*p != '`')
|
|
{
|
|
emsg(_(e_missing_backtick));
|
|
return NULL;
|
|
}
|
|
start = p + 1;
|
|
|
|
p = (char_u *)strstr((char *)start, "`=");
|
|
if (p == NULL)
|
|
{
|
|
if (*skipwhite(start) != NUL)
|
|
{
|
|
char_u *val = vim_strsave(start);
|
|
|
|
generate_PUSHS(cctx, &val);
|
|
++count;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
generate_EXECCONCAT(cctx, count);
|
|
}
|
|
else
|
|
generate_EXEC_copy(cctx, ISN_EXEC, line);
|
|
|
|
theend:
|
|
if (*nextcmd != NUL)
|
|
{
|
|
// the parser expects a pointer to the bar, put it back
|
|
--nextcmd;
|
|
*nextcmd = '|';
|
|
}
|
|
vim_free(tofree);
|
|
|
|
return nextcmd;
|
|
}
|
|
|
|
/*
|
|
* A script command with heredoc, e.g.
|
|
* ruby << EOF
|
|
* command
|
|
* EOF
|
|
* Has been turned into one long line with NL characters by
|
|
* get_function_body():
|
|
* ruby << EOF<NL> command<NL>EOF
|
|
*/
|
|
char_u *
|
|
compile_script(char_u *line, cctx_T *cctx)
|
|
{
|
|
if (cctx->ctx_skip != SKIP_YES)
|
|
{
|
|
isn_T *isn;
|
|
|
|
if ((isn = generate_instr(cctx, ISN_EXEC_SPLIT)) == NULL)
|
|
return NULL;
|
|
isn->isn_arg.string = vim_strsave(line);
|
|
}
|
|
return (char_u *)"";
|
|
}
|
|
|
|
|
|
/*
|
|
* :s/pat/repl/
|
|
*/
|
|
char_u *
|
|
compile_substitute(char_u *arg, exarg_T *eap, cctx_T *cctx)
|
|
{
|
|
char_u *cmd = eap->arg;
|
|
char_u *expr = (char_u *)strstr((char *)cmd, "\\=");
|
|
|
|
if (expr != NULL)
|
|
{
|
|
int delimiter = *cmd++;
|
|
|
|
// There is a \=expr, find it in the substitute part.
|
|
cmd = skip_regexp_ex(cmd, delimiter, magic_isset(), NULL, NULL, NULL);
|
|
if (cmd[0] == delimiter && cmd[1] == '\\' && cmd[2] == '=')
|
|
{
|
|
garray_T save_ga = cctx->ctx_instr;
|
|
char_u *end;
|
|
int expr_res;
|
|
int trailing_error;
|
|
int instr_count;
|
|
isn_T *instr;
|
|
isn_T *isn;
|
|
|
|
cmd += 3;
|
|
end = skip_substitute(cmd, delimiter);
|
|
|
|
// Temporarily reset the list of instructions so that the jump
|
|
// labels are correct.
|
|
cctx->ctx_instr.ga_len = 0;
|
|
cctx->ctx_instr.ga_maxlen = 0;
|
|
cctx->ctx_instr.ga_data = NULL;
|
|
expr_res = compile_expr0(&cmd, cctx);
|
|
if (end[-1] == NUL)
|
|
end[-1] = delimiter;
|
|
cmd = skipwhite(cmd);
|
|
trailing_error = *cmd != delimiter && *cmd != NUL;
|
|
|
|
if (expr_res == FAIL || trailing_error
|
|
|| GA_GROW_FAILS(&cctx->ctx_instr, 1))
|
|
{
|
|
if (trailing_error)
|
|
semsg(_(e_trailing_characters_str), cmd);
|
|
clear_instr_ga(&cctx->ctx_instr);
|
|
cctx->ctx_instr = save_ga;
|
|
return NULL;
|
|
}
|
|
|
|
// Move the generated instructions into the ISN_SUBSTITUTE
|
|
// instructions, then restore the list of instructions before
|
|
// adding the ISN_SUBSTITUTE instruction.
|
|
instr_count = cctx->ctx_instr.ga_len;
|
|
instr = cctx->ctx_instr.ga_data;
|
|
instr[instr_count].isn_type = ISN_FINISH;
|
|
|
|
cctx->ctx_instr = save_ga;
|
|
if ((isn = generate_instr(cctx, ISN_SUBSTITUTE)) == NULL)
|
|
{
|
|
int idx;
|
|
|
|
for (idx = 0; idx < instr_count; ++idx)
|
|
delete_instr(instr + idx);
|
|
vim_free(instr);
|
|
return NULL;
|
|
}
|
|
isn->isn_arg.subs.subs_cmd = vim_strsave(arg);
|
|
isn->isn_arg.subs.subs_instr = instr;
|
|
|
|
// skip over flags
|
|
if (*end == '&')
|
|
++end;
|
|
while (ASCII_ISALPHA(*end) || *end == '#')
|
|
++end;
|
|
return end;
|
|
}
|
|
}
|
|
|
|
return compile_exec(arg, eap, cctx);
|
|
}
|
|
|
|
char_u *
|
|
compile_redir(char_u *line, exarg_T *eap, cctx_T *cctx)
|
|
{
|
|
char_u *arg = eap->arg;
|
|
lhs_T *lhs = &cctx->ctx_redir_lhs;
|
|
|
|
if (lhs->lhs_name != NULL)
|
|
{
|
|
if (STRNCMP(arg, "END", 3) == 0)
|
|
{
|
|
if (cctx->ctx_skip != SKIP_YES)
|
|
{
|
|
if (lhs->lhs_append)
|
|
{
|
|
// First load the current variable value.
|
|
if (compile_load_lhs_with_index(lhs, lhs->lhs_whole,
|
|
cctx) == FAIL)
|
|
return NULL;
|
|
}
|
|
|
|
// Gets the redirected text and put it on the stack, then store
|
|
// it in the variable.
|
|
generate_instr_type(cctx, ISN_REDIREND, &t_string);
|
|
|
|
if (lhs->lhs_append)
|
|
generate_CONCAT(cctx, 2);
|
|
|
|
if (lhs->lhs_has_index)
|
|
{
|
|
// Use the info in "lhs" to store the value at the index in
|
|
// the list or dict.
|
|
if (compile_assign_unlet(lhs->lhs_whole, lhs, TRUE,
|
|
&t_string, cctx) == FAIL)
|
|
return NULL;
|
|
}
|
|
else if (generate_store_lhs(cctx, lhs, -1, FALSE) == FAIL)
|
|
return NULL;
|
|
|
|
VIM_CLEAR(lhs->lhs_name);
|
|
VIM_CLEAR(lhs->lhs_whole);
|
|
}
|
|
return arg + 3;
|
|
}
|
|
emsg(_(e_cannot_nest_redir));
|
|
return NULL;
|
|
}
|
|
|
|
if (arg[0] == '=' && arg[1] == '>')
|
|
{
|
|
int append = FALSE;
|
|
|
|
// redirect to a variable is compiled
|
|
arg += 2;
|
|
if (*arg == '>')
|
|
{
|
|
++arg;
|
|
append = TRUE;
|
|
}
|
|
arg = skipwhite(arg);
|
|
|
|
if (compile_assign_lhs(arg, lhs, CMD_redir,
|
|
FALSE, FALSE, FALSE, 1, cctx) == FAIL)
|
|
return NULL;
|
|
if (need_type(&t_string, lhs->lhs_member_type, FALSE,
|
|
-1, 0, cctx, FALSE, FALSE) == FAIL)
|
|
return NULL;
|
|
if (cctx->ctx_skip == SKIP_YES)
|
|
{
|
|
VIM_CLEAR(lhs->lhs_name);
|
|
}
|
|
else
|
|
{
|
|
generate_instr(cctx, ISN_REDIRSTART);
|
|
lhs->lhs_append = append;
|
|
if (lhs->lhs_has_index)
|
|
{
|
|
lhs->lhs_whole = vim_strnsave(arg, lhs->lhs_varlen_total);
|
|
if (lhs->lhs_whole == NULL)
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return arg + lhs->lhs_varlen_total;
|
|
}
|
|
|
|
// other redirects are handled like at script level
|
|
return compile_exec(line, eap, cctx);
|
|
}
|
|
|
|
#if defined(FEAT_QUICKFIX) || defined(PROTO)
|
|
char_u *
|
|
compile_cexpr(char_u *line, exarg_T *eap, cctx_T *cctx)
|
|
{
|
|
isn_T *isn;
|
|
char_u *p;
|
|
|
|
isn = generate_instr(cctx, ISN_CEXPR_AUCMD);
|
|
if (isn == NULL)
|
|
return NULL;
|
|
isn->isn_arg.number = eap->cmdidx;
|
|
|
|
p = eap->arg;
|
|
if (compile_expr0(&p, cctx) == FAIL)
|
|
return NULL;
|
|
|
|
isn = generate_instr(cctx, ISN_CEXPR_CORE);
|
|
if (isn == NULL)
|
|
return NULL;
|
|
isn->isn_arg.cexpr.cexpr_ref = ALLOC_ONE(cexprref_T);
|
|
if (isn->isn_arg.cexpr.cexpr_ref == NULL)
|
|
return NULL;
|
|
isn->isn_arg.cexpr.cexpr_ref->cer_cmdidx = eap->cmdidx;
|
|
isn->isn_arg.cexpr.cexpr_ref->cer_forceit = eap->forceit;
|
|
isn->isn_arg.cexpr.cexpr_ref->cer_cmdline = vim_strsave(skipwhite(line));
|
|
|
|
return p;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Compile "return [expr]".
|
|
* When "legacy" is TRUE evaluate [expr] with legacy syntax
|
|
*/
|
|
char_u *
|
|
compile_return(char_u *arg, int check_return_type, int legacy, cctx_T *cctx)
|
|
{
|
|
char_u *p = arg;
|
|
type_T *stack_type;
|
|
|
|
if (*p != NUL && *p != '|' && *p != '\n'
|
|
&& (legacy || !vim9_comment_start(p)))
|
|
{
|
|
// For a lambda, "return expr" is always used, also when "expr" results
|
|
// in a void.
|
|
if (cctx->ctx_ufunc->uf_ret_type->tt_type == VAR_VOID
|
|
&& (cctx->ctx_ufunc->uf_flags & FC_LAMBDA) == 0)
|
|
{
|
|
emsg(_(e_returning_value_in_function_without_return_type));
|
|
return NULL;
|
|
}
|
|
if (legacy)
|
|
{
|
|
int save_flags = cmdmod.cmod_flags;
|
|
|
|
generate_LEGACY_EVAL(cctx, p);
|
|
if (need_type(&t_any, cctx->ctx_ufunc->uf_ret_type, FALSE, -1,
|
|
0, cctx, FALSE, FALSE) == FAIL)
|
|
return NULL;
|
|
cmdmod.cmod_flags |= CMOD_LEGACY;
|
|
(void)skip_expr(&p, NULL);
|
|
cmdmod.cmod_flags = save_flags;
|
|
}
|
|
else
|
|
{
|
|
// compile return argument into instructions
|
|
if (compile_expr0(&p, cctx) == FAIL)
|
|
return NULL;
|
|
}
|
|
|
|
if (cctx->ctx_skip != SKIP_YES)
|
|
{
|
|
// "check_return_type" with uf_ret_type set to &t_unknown is used
|
|
// for an inline function without a specified return type. Set the
|
|
// return type here.
|
|
stack_type = get_type_on_stack(cctx, 0);
|
|
if (check_type_is_value(stack_type) == FAIL)
|
|
return NULL;
|
|
if ((check_return_type && (cctx->ctx_ufunc->uf_ret_type == NULL
|
|
|| cctx->ctx_ufunc->uf_ret_type == &t_unknown))
|
|
|| (!check_return_type
|
|
&& cctx->ctx_ufunc->uf_ret_type == &t_unknown))
|
|
{
|
|
cctx->ctx_ufunc->uf_ret_type = stack_type;
|
|
}
|
|
else
|
|
{
|
|
if (need_type(stack_type, cctx->ctx_ufunc->uf_ret_type, FALSE,
|
|
-1, 0, cctx, FALSE, FALSE) == FAIL)
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// "check_return_type" cannot be TRUE, only used for a lambda which
|
|
// always has an argument.
|
|
if (cctx->ctx_ufunc->uf_ret_type->tt_type != VAR_VOID
|
|
&& cctx->ctx_ufunc->uf_ret_type->tt_type != VAR_UNKNOWN)
|
|
{
|
|
emsg(_(e_missing_return_value));
|
|
return NULL;
|
|
}
|
|
|
|
if (IS_CONSTRUCTOR_METHOD(cctx->ctx_ufunc))
|
|
{
|
|
// For a class new() constructor, return an object of the class.
|
|
generate_instr(cctx, ISN_RETURN_OBJECT);
|
|
cctx->ctx_ufunc->uf_ret_type =
|
|
&cctx->ctx_ufunc->uf_class->class_object_type;
|
|
}
|
|
else
|
|
// No argument, return zero.
|
|
generate_PUSHNR(cctx, 0);
|
|
}
|
|
|
|
// may need ENDLOOP when inside a :for or :while loop
|
|
if (compile_find_scope(NULL, NULL, NULL, NULL, cctx) == FAIL)
|
|
|
|
// Undo any command modifiers.
|
|
generate_undo_cmdmods(cctx);
|
|
|
|
if (cctx->ctx_skip != SKIP_YES && generate_instr(cctx, ISN_RETURN) == NULL)
|
|
return NULL;
|
|
|
|
// "return val | endif" is possible
|
|
return skipwhite(p);
|
|
}
|
|
|
|
/*
|
|
* Check if the separator for a :global or :substitute command is OK.
|
|
*/
|
|
int
|
|
check_global_and_subst(char_u *cmd, char_u *arg)
|
|
{
|
|
if (arg == cmd + 1 && vim_strchr((char_u *)":-.", *arg) != NULL)
|
|
{
|
|
semsg(_(e_separator_not_supported_str), arg);
|
|
return FAIL;
|
|
}
|
|
if (VIM_ISWHITE(cmd[1]))
|
|
{
|
|
semsg(_(e_no_white_space_allowed_before_separator_str), cmd);
|
|
return FAIL;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
|
|
#endif // defined(FEAT_EVAL)
|