summaryrefslogtreecommitdiffstats
path: root/src/vim9cmds.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:09:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:09:20 +0000
commit029f72b1a93430b24b88eb3a72c6114d9f149737 (patch)
tree765d5c2041967f9c6fef195fe343d9234a030e90 /src/vim9cmds.c
parentInitial commit. (diff)
downloadvim-029f72b1a93430b24b88eb3a72c6114d9f149737.tar.xz
vim-029f72b1a93430b24b88eb3a72c6114d9f149737.zip
Adding upstream version 2:9.1.0016.upstream/2%9.1.0016
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/vim9cmds.c')
-rw-r--r--src/vim9cmds.c2745
1 files changed, 2745 insertions, 0 deletions
diff --git a/src/vim9cmds.c b/src/vim9cmds.c
new file mode 100644
index 0000000..ad245b9
--- /dev/null
+++ b/src/vim9cmds.c
@@ -0,0 +1,2745 @@
+/* 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)
+ 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)
+ 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)
+ 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 UNUSED)
+{
+ 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 UNUSED)
+{
+ 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, FALSE, 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, TRUE, 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)