8000 vim/src/ex_eval.c at master · somini/vim · GitHub
[go: up one dir, main page]

Skip to content

Latest commit

 

History

History
2300 lines (2120 loc) · 67.6 KB

ex_eval.c

File metadata and controls

2300 lines (2120 loc) · 67.6 KB
/* 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.
*/
/*
* ex_eval.c: functions for Ex command line for the +eval feature.
*/
#include "vim.h"
#if defined(FEAT_EVAL) || defined(PROTO)
static void free_msglist(struct msglist *l);
static int throw_exception(void *, except_type_T, char_u *);
static char_u *get_end_emsg(struct condstack *cstack);
/*
* Exception handling terms:
*
* :try ":try" command \
* ... try block |
* :catch RE ":catch" command |
* ... catch clause |- try conditional
* :finally ":finally" command |
* ... finally clause |
* :endtry ":endtry" command /
*
* The try conditional may have any number of catch clauses and at most one
* finally clause. A ":throw" command can be inside the try block, a catch
* clause, the finally clause, or in a function called or script sourced from
* there or even outside the try conditional. Try conditionals may be nested.
*/
/*
* Configuration whether an exception is thrown on error or interrupt. When
* the preprocessor macros below evaluate to FALSE, an error (did_emsg) or
* interrupt (got_int) under an active try conditional terminates the script
* after the non-active finally clauses of all active try conditionals have been
* executed. Otherwise, errors and/or interrupts are converted into catchable
* exceptions (did_throw additionally set), which terminate the script only if
* not caught. For user exceptions, only did_throw is set. (Note: got_int can
* be set asynchronously afterwards by a SIGINT, so did_throw && got_int is not
* a reliant test that the exception currently being thrown is an interrupt
* exception. Similarly, did_emsg can be set afterwards on an error in an
* (unskipped) conditional command inside an inactive conditional, so did_throw
* && did_emsg is not a reliant test that the exception currently being thrown
* is an error exception.) - The macros can be defined as expressions checking
* for a variable that is allowed to be changed during execution of a script.
*/
#if 0
/* Expressions used for testing during the development phase. */
# define THROW_ON_ERROR (!eval_to_number("$VIMNOERRTHROW"))
# define THROW_ON_INTERRUPT (!eval_to_number("$VIMNOINTTHROW"))
# define THROW_TEST
#else
/* Values used for the Vim release. */
# define THROW_ON_ERROR TRUE
# define THROW_ON_ERROR_TRUE
# define THROW_ON_INTERRUPT TRUE
# define THROW_ON_INTERRUPT_TRUE
#endif
static void catch_exception(except_T *excp);
static void finish_exception(except_T *excp);
static void discard_exception(except_T *excp, int was_finished);
static void report_pending(int action, int pending, void *value);
/*
* When several errors appear in a row, setting "force_abort" is delayed until
* the failing command returned. "cause_abort" is set to TRUE meanwhile, in
* order to indicate that situation. This is useful when "force_abort" was set
* during execution of a function call from an expression: the aborting of the
* expression evaluation is done without producing any error messages, but all
* error messages on parsing errors during the expression evaluation are given
* (even if a try conditional is active).
*/
static int cause_abort = FALSE;
/*
* Return TRUE when immediately aborting on error, or when an interrupt
* occurred or an exception was thrown but not caught. Use for ":{range}call"
* to check whether an aborted function that does not handle a range itself
* should be called again for the next line in the range. Also used for
* cancelling expression evaluation after a function call caused an immediate
* abort. Note that the first emsg() call temporarily resets "force_abort"
* until the throw point for error messages has been reached. That is, during
* cancellation of an expression evaluation after an aborting function call or
* due to a parsing error, aborting() always returns the same value.
*/
int
aborting(void)
{
return (did_emsg && force_abort) || got_int || did_throw;
}
/*
* The value of "force_abort" is temporarily reset by the first emsg() call
* during an expression evaluation, and "cause_abort" is used instead. It might
* be necessary to restore "force_abort" even before the throw point for the
* error message has been reached. update_force_abort() should be called then.
*/
void
update_force_abort(void)
{
if (cause_abort)
force_abort = TRUE;
}
/*
* Return TRUE if a command with a subcommand resulting in "retcode" should
* abort the script processing. Can be used to suppress an autocommand after
* execution of a failing subcommand as long as the error message has not been
* displayed and actually caused the abortion.
*/
int
should_abort(int retcode)
{
return ((retcode == FAIL && trylevel != 0 && !emsg_silent) || aborting());
}
/*
* Return TRUE if a function with the "abort" flag should not be considered
* ended on an error. This means that parsing commands is continued in order
* to find finally clauses to be executed, and that some errors in skipped
* commands are still reported.
*/
int
aborted_in_try(void)
{
/* This function is only called after an error. In this case, "force_abort"
* determines wheth 8BD0 er searching for finally clauses is necessary. */
return force_abort;
}
/*
* cause_errthrow(): Cause a throw of an error exception if appropriate.
* Return TRUE if the error message should not be displayed by emsg().
* Sets "ignore", if the emsg() call should be ignored completely.
*
* When several messages appear in the same command, the first is usually the
* most specific one and used as the exception value. The "severe" flag can be
* set to TRUE, if a later but severer message should be used instead.
*/
int
cause_errthrow(
char_u *mesg,
int severe,
int *ignore)
{
struct msglist *elem;
struct msglist **plist;
/*
* Do nothing when displaying the interrupt message or reporting an
* uncaught exception (which has already been discarded then) at the top
* level. Also when no exception can be thrown. The message will be
* displayed by emsg().
*/
if (suppress_errthrow)
return FALSE;
/*
* If emsg() has not been called previously, temporarily reset
* "force_abort" until the throw point for error messages has been
* reached. This ensures that aborting() returns the same value for all
* errors that appear in the same command. This means particularly that
* for parsing errors during expression evaluation emsg() will be called
* multiply, even when the expression is evaluated from a finally clause
* that was activated due to an aborting error, interrupt, or exception.
*/
if (!did_emsg)
{
cause_abort = force_abort;
force_abort = FALSE;
}
/*
* If no try conditional is active and no exception is being thrown and
* there has not been an error in a try conditional or a throw so far, do
* nothing (for compatibility of non-EH scripts). The message will then
* be displayed by emsg(). When ":silent!" was used and we are not
* currently throwing an exception, do nothing. The message text will
* then be stored to v:errmsg by emsg() without displaying it.
*/
if (((trylevel == 0 && !cause_abort) || emsg_silent) && !did_throw)
return FALSE;
/*
* Ignore an interrupt message when inside a try conditional or when an
* exception is being thrown or when an error in a try conditional or
* throw has been detected previously. This is important in order that an
* interrupt exception is catchable by the innermost try conditional and
* not replaced by an interrupt message error exception.
*/
if (mesg == (char_u *)_(e_interr))
{
*ignore = TRUE;
return TRUE;
}
/*
* Ensure that all commands in nested function calls and sourced files
* are aborted immediately.
*/
cause_abort = TRUE;
/*
* When an exception is being thrown, some commands (like conditionals) are
* not skipped. Errors in those commands may affect what of the subsequent
* commands are regarded part of catch and finally clauses. Catching the
* exception would then cause execution of commands not intended by the
* user, who wouldn't even get aware of the problem. Therefor, discard the
* exception currently being thrown to prevent it from being caught. Just
* execute finally clauses and terminate.
*/
if (did_throw)
{
/* When discarding an interrupt exception, reset got_int to prevent the
* same interrupt being converted to an exception again and discarding
* the error exception we are about to throw here. */
if (current_exception->type == ET_INTERRUPT)
got_int = FALSE;
discard_current_exception();
}
#ifdef THROW_TEST
if (!THROW_ON_ERROR)
{
/*
* Print error message immediately without searching for a matching
* catch clause; just finally clauses are executed before the script
* is terminated.
*/
return FALSE;
}
else
#endif
{
/*
* Prepare the throw of an error exception, so that everything will
* be aborted (except for executing finally clauses), until the error
* exception is caught; if still uncaught at the top level, the error
* message will be displayed and the script processing terminated
* then. - This function has no access to the conditional stack.
* Thus, the actual throw is made after the failing command has
* returned. - Throw only the first of several errors in a row, except
* a severe error is following.
*/
if (msg_list != NULL)
{
plist = msg_list;
while (*plist != NULL)
plist = &(*plist)->next;
elem = (struct msglist *)alloc((unsigned)sizeof(struct msglist));
if (elem == NULL)
{
suppress_errthrow = TRUE;
EMSG(_(e_outofmem));
}
else
{
elem->msg = vim_strsave(mesg);
if (elem->msg == NULL)
{
vim_free(elem);
suppress_errthrow = TRUE;
EMSG(_(e_outofmem));
}
else
{
elem->next = NULL;
elem->throw_msg = NULL;
*plist = elem;
if (plist == msg_list || severe)
{
char_u *tmsg;
/* Skip the extra "Vim " prefix for message "E458". */
tmsg = elem->msg;
if (STRNCMP(tmsg, "Vim E", 5) == 0
&& VIM_ISDIGIT(tmsg[5])
&& VIM_ISDIGIT(tmsg[6])
&& VIM_ISDIGIT(tmsg[7])
&& tmsg[8] == ':'
&& tmsg[9] == ' ')
(*msg_list)->throw_msg = &tmsg[4];
else
(*msg_list)->throw_msg = tmsg;
}
}
}
}
return TRUE;
}
}
/*
* Free a "msg_list" and the messages it contains.
*/
static void
free_msglist(struct msglist *l)
{
struct msglist *messages, *next;
messages = l;
while (messages != NULL)
{
next = messages->next;
vim_free(messages->msg);
vim_free(messages);
messages = next;
}
}
/*
* Free global "*msg_list" and the messages it contains, then set "*msg_list"
* to NULL.
*/
void
free_global_msglist(void)
{
free_msglist(*msg_list);
*msg_list = NULL;
}
/*
* Throw the message specified in the call to cause_errthrow() above as an
* error exception. If cstack is NULL, postpone the throw until do_cmdline()
* has returned (see do_one_cmd()).
*/
void
do_errthrow(struct condstack *cstack, char_u *cmdname)
{
/*
* Ensure that all commands in nested function calls and sourced files
* are aborted immediately.
*/
if (cause_abort)
{
cause_abort = FALSE;
force_abort = TRUE;
}
/* If no exception is to be thrown or the conversion should be done after
* returning to a previous invocation of do_one_cmd(), do nothing. */
if (msg_list == NULL || *msg_list == NULL)
return;
if (throw_exception(*msg_list, ET_ERROR, cmdname) == FAIL)
free_msglist(*msg_list);
else
{
if (cstack != NULL)
do_throw(cstack);
else
need_rethrow = TRUE;
}
*msg_list = NULL;
}
/*
* do_intthrow(): Replace the current exception by an interrupt or interrupt
* exception if appropriate. Return TRUE if the current exception is discarded,
* FALSE otherwise.
*/
int
do_intthrow(struct condstack *cstack)
{
/*
* If no interrupt occurred or no try conditional is active and no exception
* is being thrown, do nothing (for compatibility of non-EH scripts).
*/
if (!got_int || (trylevel == 0 && !did_throw))
return FALSE;
#ifdef THROW_TEST /* avoid warning for condition always true */
if (!THROW_ON_INTERRUPT)
{
/*
* The interrupt aborts everything except for executing finally clauses.
* Discard any user or error or interrupt exception currently being
* thrown.
*/
if (did_throw)
discard_current_exception();
}
else
#endif
{
/*
* Throw an interrupt exception, so that everything will be aborted
* (except for executing finally clauses), until the interrupt exception
* is caught; if still uncaught at the top level, the script processing
* will be terminated then. - If an interrupt exception is already
* being thrown, do nothing.
*
*/
if (did_throw)
{
if (current_exception->type == ET_INTERRUPT)
return FALSE;
/* An interrupt exception replaces any user or error exception. */
discard_current_exception();
}
if (throw_exception("Vim:Interrupt", ET_INTERRUPT, NULL) != FAIL)
do_throw(cstack);
}
return TRUE;
}
/*
* Get an exception message that is to be stored in current_exception->value.
*/
char_u *
get_exception_string(
void *value,
except_type_T type,
char_u *cmdname,
int *should_free)
{
char_u *ret, *mesg;
int cmdlen;
char_u *p, *val;
if (type == ET_ERROR)
{
*should_free = TRUE;
mesg = ((struct msglist *)value)->throw_msg;
if (cmdname != NULL && *cmdname != NUL)
{
cmdlen = (int)STRLEN(cmdname);
ret = vim_strnsave((char_u *)"Vim(",
4 + cmdlen + 2 + (int)STRLEN(mesg));
if (ret == NULL)
return ret;
STRCPY(&ret[4], cmdname);
STRCPY(&ret[4 + cmdlen], "):");
val = ret + 4 + cmdlen + 2;
}
else
{
ret = vim_strnsave((char_u *)"Vim:", 4 + (int)STRLEN(mesg));
if (ret == NULL)
return ret;
val = ret + 4;
}
/* msg_add_fname may have been used to prefix the message with a file
* name in quotes. In the exception value, put the file name in
* parentheses and move it to the end. */
for (p = mesg; ; p++)
{
if (*p == NUL
|| (*p == 'E'
&& VIM_ISDIGIT(p[1])
&& (p[2] == ':'
|| (VIM_ISDIGIT(p[2])
&& (p[3] == ':'
|| (VIM_ISDIGIT(p[3])
&& p[4] == ':'))))))
{
if (*p == NUL || p == mesg)
STRCAT(val, mesg); /* 'E123' missing or at beginning */
else
{
/* '"filename" E123: message text' */
if (mesg[0] != '"' || p-2 < &mesg[1] ||
p[-2] != '"' || p[-1] != ' ')
/* "E123:" is part of the file name. */
continue;
STRCAT(val, p);
p[-2] = NUL;
sprintf((char *)(val + STRLEN(p)), " (%s)", &mesg[1]);
p[-2] = '"';
}
break;
}
}
}
else
{
*should_free = FALSE;
ret = (char_u *)value;
}
return ret;
}
/*
* Throw a new exception. Return FAIL when out of memory or it was tried to
* throw an illegal user exception. "value" is the exception string for a
* user or interrupt exception, or points to a message list in case of an
* error exception.
*/
static int
throw_exception(void *value, except_type_T type, char_u *cmdname)
{
except_T *excp;
int should_free;
/*
* Disallow faking Interrupt or error exceptions as user exceptions. They
* would be treated differently from real interrupt or error exceptions
* when no active try block is found, see do_cmdline().
*/
if (type == ET_USER)
{
if (STRNCMP((char_u *)value, "Vim", 3) == 0
&& (((char_u *)value)[3] == NUL || ((char_u *)value)[3] == ':'
|| ((char_u *)value)[3] == '('))
{
EMSG(_("E608: Cannot :throw exceptions with 'Vim' prefix"));
goto fail;
}
}
excp = (except_T *)alloc((unsigned)sizeof(except_T));
if (excp == NULL)
goto nomem;
if (type == ET_ERROR)
/* Store the original message and prefix the exception value with
* "Vim:" or, if a command name is given, "Vim(cmdname):". */
excp->messages = (struct msglist *)value;
excp->value = get_exception_string(value, type, cmdname, &should_free);
if (excp->value == NULL && should_free)
goto nomem;
10BC8
excp->type = type;
excp->throw_name = vim_strsave(sourcing_name == NULL
? (char_u *)"" : sourcing_name);
if (excp->throw_name == NULL)
{
if (should_free)
vim_free(excp->value);
goto nomem;
}
excp->throw_lnum = sourcing_lnum;
if (p_verbose >= 13 || debug_break_level > 0)
{
int save_msg_silent = msg_silent;
if (debug_break_level > 0)
msg_silent = FALSE; /* display messages */
else
verbose_enter();
++no_wait_return;
if (debug_break_level > 0 || *p_vfile == NUL)
msg_scroll = TRUE; /* always scroll up, don't overwrite */
smsg((char_u *)_("Exception thrown: %s"), excp->value);
msg_puts((char_u *)"\n"); /* don't overwrite this either */
if (debug_break_level > 0 || *p_vfile == NUL)
cmdline_row = msg_row;
--no_wait_return;
if (debug_break_level > 0)
msg_silent = save_msg_silent;
else
verbose_leave();
}
current_exception = excp;
return OK;
nomem:
vim_free(excp);
suppress_errthrow = TRUE;
EMSG(_(e_outofmem));
fail:
current_exception = NULL;
return FAIL;
}
/*
* Discard an exception. "was_finished" is set when the exception has been
* caught and the catch clause has been ended normally.
*/
static void
discard_exception(except_T *excp, int was_finished)
{
char_u *saved_IObuff;
if (excp == NULL)
{
internal_error("discard_exception()");
return;
}
if (p_verbose >= 13 || debug_break_level > 0)
{
int save_msg_silent = msg_silent;
saved_IObuff = vim_strsave(IObuff);
if (debug_break_level > 0)
msg_silent = FALSE; /* display messages */
else
verbose_enter();
++no_wait_return;
if (debug_break_level > 0 || *p_vfile == NUL)
msg_scroll = TRUE; /* always scroll up, don't overwrite */
smsg(was_finished
? (char_u *)_("Exception finished: %s")
: (char_u *)_("Exception discarded: %s"),
excp->value);
msg_puts((char_u *)"\n"); /* don't overwrite this either */
if (debug_break_level > 0 || *p_vfile == NUL)
cmdline_row = msg_row;
--no_wait_return;
if (debug_break_level > 0)
msg_silent = save_msg_silent;
else
verbose_leave();
STRCPY(IObuff, saved_IObuff);
vim_free(saved_IObuff);
}
if (excp->type != ET_INTERRUPT)
vim_free(excp->value);
if (excp->type == ET_ERROR)
free_msglist(excp->messages);
vim_free(excp->throw_name);
vim_free(excp);
}
/*
* Discard the exception currently being thrown.
*/
void
discard_current_exception(void)
{
if (current_exception != NULL)
{
discard_exception(current_exception, FALSE);
current_exception = NULL;
}
did_throw = FALSE;
need_rethrow = FALSE;
}
/*
* Put an exception on the caught stack.
*/
static void
catch_exception(except_T *excp)
{
excp->caught = caught_stack;
caught_stack = excp;
set_vim_var_string(VV_EXCEPTION, excp->value, -1);
if (*excp->throw_name != NUL)
{
if (excp->throw_lnum != 0)
vim_snprintf((char *)IObuff, IOSIZE, _("%s, line %ld"),
excp->throw_name, (long)excp->throw_lnum);
else
vim_snprintf((char *)IObuff, IOSIZE, "%s", excp->throw_name);
set_vim_var_string(VV_THROWPOINT, IObuff, -1);
}
else
/* throw_name not set on an exception from a command that was typed. */
set_vim_var_string(VV_THROWPOINT, NULL, -1);
if (p_verbose >= 13 || debug_break_level > 0)
{
int save_msg_silent = msg_silent;
if (debug_break_level > 0)
msg_silent = FALSE; /* display messages */
else
verbose_enter();
++no_wait_return;
if (debug_break_level > 0 || *p_vfile == NUL)
msg_scroll = TRUE; /* always scroll up, don't overwrite */
smsg((char_u *)_("Exception caught: %s"), excp->value);
msg_puts((char_u *)"\n"); /* don't overwrite this either */
if (debug_break_level > 0 || *p_vfile == NUL)
cmdline_row = msg_row;
--no_wait_return;
if (debug_break_level > 0)
msg_silent = save_msg_silent;
else
verbose_leave();
}
}
/*
* Remove an exception from the caught stack.
*/
static void
finish_exception(except_T *excp)
{
if (excp != caught_stack)
internal_error("finish_exception()");
caught_stack = caught_stack->caught;
if (caught_stack != NULL)
{
set_vim_var_string(VV_EXCEPTION, caught_stack->value, -1);
if (*caught_stack->throw_name != NUL)
{
if (caught_stack->throw_lnum != 0)
vim_snprintf((char *)IObuff, IOSIZE,
_("%s, line %ld"), caught_stack->throw_name,
(long)caught_stack->throw_lnum);
else
vim_snprintf((char *)IObuff, IOSIZE, "%s",
caught_stack->throw_name);
set_vim_var_string(VV_THROWPOINT, IObuff, -1);
}
else
/* throw_name not set on an exception from a command that was
* typed. */
set_vim_var_string(VV_THROWPOINT, NULL, -1);
}
else
{
set_vim_var_string(VV_EXCEPTION, NULL, -1);
set_vim_var_string(VV_THROWPOINT, NULL, -1);
}
/* Discard the exception, but use the finish message for 'verbose'. */
discard_exception(excp, TRUE);
}
/*
* Flags specifying the message displayed by report_pending.
*/
#define RP_MAKE 0
#define RP_RESUME 1
#define RP_DISCARD 2
/*
* Report information about something pending in a finally clause if required by
* the 'verbose' option or when debugging. "action" tells whether something is
* made pending or something pending is resumed or discarded. "pending" tells
* what is pending. "value" specifies the return value for a pending ":return"
* or the exception value for a pending exception.
*/
static void
report_pending(int action, int pending, void *value)
{
char_u *mesg;
char *s;
int save_msg_silent;
switch (action)
{
case RP_MAKE:
mesg = (char_u *)_("%s made pending");
break;
case RP_RESUME:
mesg = (char_u *)_("%s resumed");
break;
/* case RP_DISCARD: */
default:
mesg = (char_u *)_("%s discarded");
break;
}
switch (pending)
{
case CSTP_NONE:
return;
case CSTP_CONTINUE:
s = ":continue";
break;
case CSTP_BREAK:
s = ":break";
break;
case CSTP_FINISH:
s = ":finish";
break;
case CSTP_RETURN:
/* ":return" command producing value, allocated */
s = (char *)get_return_cmd(value);
break;
default:
if (pending & CSTP_THROW)
{
vim_snprintf((char *)IObuff, IOSIZE,
(char *)mesg, _("Exception"));
mesg = vim_strnsave(IObuff, (int)STRLEN(IObuff) + 4);
STRCAT(mesg, ": %s");
s = (char *)((except_T *)value)->value;
}
else if ((pending & CSTP_ERROR) && (pending & CSTP_INTERRUPT))
s = _("Error and interrupt");
else if (pending & CSTP_ERROR)
s = _("Error");
else /* if (pending & CSTP_INTERRUPT) */
s = _("Interrupt");
}
save_msg_silent = msg_silent;
if (debug_break_level > 0)
msg_silent = FALSE; /* display messages */
++no_wait_return;
msg_scroll = TRUE; /* always scroll up, don't overwrite */
smsg(mesg, (char_u *)s);
msg_puts((char_u *)"\n"); /* don't overwrite this either */
cmdline_row = msg_row;
--no_wait_return;
if (debug_break_level > 0)
msg_silent = save_msg_silent;
if (pending == CSTP_RETURN)
vim_free(s);
else if (pending & CSTP_THROW)
vim_free(mesg);
}
/*
* If something is made pending in a finally clause, report it if required by
* the 'verbose' option or when debugging.
*/
void
report_make_pending(int pending, void *value)
{
if (p_verbose >= 14 || debug_break_level > 0)
{
if (debug_break_level <= 0)
verbose_enter();
report_pending(RP_MAKE, pending, value);
if (debug_break_level <= 0)
verbose_leave();
}
}
/*
* If something pending in a finally clause is resumed at the ":endtry", report
* it if required by the 'verbose' option or when debugging.
*/
void
report_resume_pending(int pending, void *value)
{
if (p_verbose >= 14 || debug_break_level > 0)
{
if (debug_break_level <= 0)
verbose_enter();
report_pending(RP_RESUME, pending, value);
if (debug_break_level <= 0)
verbose_leave();
}
}
/*
* If something pending in a finally clause is discarded, report it if required
* by the 'verbose' option or when debugging.
*/
void
report_discard_pending(int pending, void *value)
{
if (p_verbose >= 14 || debug_break_level > 0)
{
if (debug_break_level <= 0)
verbose_enter();
report_pending(RP_DISCARD, pending, value);
if (debug_break_level <= 0)
verbose_leave();
}
}
/*
* ":if".
*/
void
ex_if(exarg_T *eap)
{
int error;
int skip;
int result;
struct condstack *cstack = eap->cstack;
if (cstack->cs_idx == CSTACK_LEN - 1)
eap->errmsg = (char_u *)N_("E579: :if nesting too deep");
else
{
++cstack->cs_idx;
cstack->cs_flags[cstack->cs_idx] = 0;
/*
* Don't do something after an error, interrupt, or throw, or when there
* is a surrounding conditional and it was not active.
*/
skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0
&& !(cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE));
result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip);
if (!skip && !error)
{
if (result)
cstack->cs_flags[cstack->cs_idx] = CSF_ACTIVE | CSF_TRUE;
}
else
/* set TRUE, so this conditional will never get active */
cstack->cs_flags[cstack->cs_idx] = CSF_TRUE;
}
}
/*
* ":endif".
*/
void
ex_endif(exarg_T *eap)
{
did_endif = TRUE;
if (eap->cstack->cs_idx < 0
|| (eap->cstack->cs_flags[eap->cstack->cs_idx]
& (CSF_WHILE | CSF_FOR | CSF_TRY)))
eap->errmsg = (char_u *)N_("E580: :endif without :if");
else
{
/*
* When debugging or a breakpoint was encountered, display the debug
* prompt (if not already done). This shows the user that an ":endif"
* is executed when the ":if" or a previous ":elseif" was not TRUE.
* Handle a ">quit" debug command as if an interrupt had occurred before
* the ":endif". That is, throw an interrupt exception if appropriate.
* Doing this here prevents an exception for a parsing error being
* discarded by throwing the interrupt exception later on.
*/
if (!(eap->cstack->cs_flags[eap->cstack->cs_idx] & CSF_TRUE)
&& dbg_check_skipped(eap))
(void)do_intthrow(eap->cstack);
--eap->cstack->cs_idx;
}
}
/*
* ":else" and ":elseif".
*/
void
ex_else(exarg_T *eap)
{
int error;
int skip;
int result;
struct condstack *cstack = eap->cstack;
/*
* Don't do something after an error, interrupt, or throw, or when there is
* a surrounding conditional and it was not active.
*/
skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0
&& !(cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE));
if (cstack->cs_idx < 0
|| (cstack->cs_flags[cstack->cs_idx]
& (CSF_WHILE | CSF_FOR | CSF_TRY)))
{
if (eap->cmdidx == CMD_else)
{
eap->errmsg = (char_u *)N_("E581: :else without :if");
return;
}
eap->errmsg = (char_u *)N_("E582: :elseif without :if");
skip = TRUE;
}
else if (cstack->cs_flags[cstack->cs_idx] & CSF_ELSE)
{
if (eap->cmdidx == CMD_else)
{
eap->errmsg = (char_u *)N_("E583: multiple :else");
return;
}
eap->errmsg = (char_u *)N_("E584: :elseif after :else");
skip = TRUE;
}
/* if skipping or the ":if" was TRUE, reset ACTIVE, otherwise set it */
if (skip || cstack->cs_flags[cstack->cs_idx] & CSF_TRUE)
{
if (eap->errmsg == NULL)
cstack->cs_flags[cstack->cs_idx] = CSF_TRUE;
skip = TRUE; /* don't evaluate an ":elseif" */
}
else
cstack->cs_flags[cstack->cs_idx] = CSF_ACTIVE;
/*
* When debugging or a breakpoint was encountered, display the debug prompt
* (if not already done). This shows the user that an ":else" or ":elseif"
0