summaryrefslogtreecommitdiffstats
path: root/src/vim9cmds.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/vim9cmds.c')
-rw-r--r--src/vim9cmds.c2274
1 files changed, 2274 insertions, 0 deletions
diff --git a/src/vim9cmds.c b/src/vim9cmds.c
new file mode 100644
index 0000000000..fe9ead2750
--- /dev/null
+++ b/src/vim9cmds.c
@@ -0,0 +1,2274 @@
+/* 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".
+ */
+ static void
+unwind_locals(cctx_T *cctx, int new_top)
+{
+ if (cctx->ctx_locals.ga_len > new_top)
+ {
+ int idx;
+ lvar_T *lvar;
+
+ for (idx = new_top; idx < cctx->ctx_locals.ga_len; ++idx)
+ {
+ lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx;
+ vim_free(lvar->lv_name);
+ }
+ }
+ cctx->ctx_locals.ga_len = new_top;
+}
+
+/*
+ * Free all local variables.
+ */
+ void
+free_locals(cctx_T *cctx)
+{
+ unwind_locals(cctx, 0);
+ ga_clear(&cctx->ctx_locals);
+}
+
+
+/*
+ * Check if "name" can be "unlet".
+ */
+ int
+check_vim9_unlet(char_u *name)
+{
+ 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, 0, cctx);
+
+ // : unlet an indexed item
+ if (!lhs.lhs_has_index)
+ {
+ iemsg("called compile_lhs() without an index");
+ ret = FAIL;
+ }
+ else
+ {
+ // Use the info in "lhs" to unlet the item at the index in the
+ // list or dict.
+ 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 UNUSED,
+ void *coookie)
+{
+ cctx_T *cctx = coookie;
+ int cc = *name_end;
+ char_u *p = lvp->ll_name;
+ int ret = OK;
+ size_t len;
+ char_u *buf;
+ isntype_T isn = ISN_EXEC;
+
+ if (cctx->ctx_skip == SKIP_YES)
+ return OK;
+
+ // Cannot use :lockvar and :unlockvar on local variables.
+ if (p[1] != ':')
+ {
+ char_u *end = find_name_end(p, NULL, NULL, FNE_CHECK_START);
+
+ if (lookup_local(p, end - p, NULL, cctx) == OK)
+ {
+ char_u *s = p;
+
+ if (*end != '.' && *end != '[')
+ {
+ emsg(_(e_cannot_lock_unlock_local_variable));
+ return FAIL;
+ }
+
+ // For "d.member" put the local variable on the stack, it will be
+ // passed to ex_lockvar() indirectly.
+ if (compile_load(&s, end, cctx, FALSE, FALSE) == FAIL)
+ return FAIL;
+ isn = ISN_LOCKUNLOCK;
+ }
+ }
+
+ // Checking is done at runtime.
+ *name_end = NUL;
+ len = name_end - p + 20;
+ buf = alloc(len);
+ if (buf == NULL)
+ ret = FAIL;
+ else
+ {
+ vim_snprintf((char *)buf, len, "%s %s",
+ eap->cmdidx == CMD_lockvar ? "lockvar" : "unlockvar",
+ p);
+ 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)
+{
+ ex_unletlock(eap, arg, 0, 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".
+ */
+ static int
+compile_jump_to_end(endlabel_T **el, jumpwhen_T when, 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;
+
+ 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;
+ 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_arg), 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);
+ 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, 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_arg), 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);
+ 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, 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);
+ 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;
+}
+
+/*
+ * Compile "for var in expr":
+ *
+ * Produces instructions:
+ * PUSHNR -1
+ * STORE 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 var Store item in "var"
+ * ... body ...
+ * 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 *stack = &cctx->ctx_type_stack;
+ garray_T *instr = &cctx->ctx_instr;
+ scope_T *scope;
+ lvar_T *loop_lvar; // loop iteration variable
+ 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));
+ return NULL;
+ }
+ wp = p + 2;
+ if (may_get_next_line_error(wp, &p, cctx) == FAIL)
+ return NULL;
+
+ // 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;
+ prev_lnum = ((isn_T *)instr->ga_data)[instr->ga_len]
+ .isn_arg.debug.dbg_break_lnum;
+ }
+
+ scope = new_scope(cctx, FOR_SCOPE);
+ if (scope == NULL)
+ return NULL;
+
+ // Reserve a variable to store the loop iteration counter and initialize it
+ // to -1.
+ loop_lvar = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number);
+ if (loop_lvar == NULL)
+ {
+ // out of memory
+ drop_scope(cctx);
+ return NULL;
+ }
+ generate_STORENR(cctx, loop_lvar->lv_idx, -1);
+
+ // 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 a not a supported type we can
+ // give an error now.
+ vartype = ((type_T **)stack->ga_data)[stack->ga_len - 1];
+ if (vartype->tt_type != VAR_LIST && vartype->tt_type != VAR_STRING
+ && vartype->tt_type != VAR_BLOB && vartype->tt_type != VAR_ANY)
+ {
+ 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
+ scope->se_u.se_for.fs_top_label = current_instr_idx(cctx);
+
+ generate_FOR(cctx, loop_lvar->lv_idx);
+
+ arg = arg_start;
+ if (var_list)
+ {
+ generate_UNPACK(cctx, var_count, semicolon);
+ arg = skipwhite(arg + 1); // skip white after '['
+
+ // the list item is replaced by a number of items
+ if (GA_GROW_FAILS(stack, var_count - 1))
+ {
+ drop_scope(cctx);
+ return NULL;
+ }
+ --stack->ga_len;
+ for (idx = 0; idx < var_count; ++idx)
+ {
+ ((type_T **)stack->ga_data)[stack->ga_len] =
+ (semicolon && idx == 0) ? vartype : item_type;
+ ++stack->ga_len;
+ }
+ }
+
+ 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 (*p == ':')
+ {
+ p = skipwhite(p + 1);
+ lhs_type = parse_type(&p, cctx->ctx_type_list, TRUE);
+ }
+
+ 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,
+ 0, 0, type, name) == 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), arg);
+ goto failed;
+ }
+
+ // Reserve a variable to store "var".
+ where.wt_index = var_list ? idx + 1 : 0;
+ where.wt_variable = TRUE;
+ if (lhs_type == &t_any)
+ lhs_type = item_type;
+ else if (item_type != &t_unknown
+ && (item_type == &t_any
+ ? need_type(item_type, lhs_type,
+ -1, 0, cctx, FALSE, FALSE)
+ : check_type(lhs_type, item_type, TRUE, where))
+ == FAIL)
+ goto failed;
+ var_lvar = reserve_local(cctx, arg, varlen, TRUE, 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;
+ else
+ var_lvar->lv_type = item_type;
+ generate_STORE(cctx, ISN_STORE, var_lvar->lv_idx, NULL);
+ }
+
+ if (*p == ',' || *p == ';')
+ ++p;
+ arg = skipwhite(p);
+ vim_free(name);
+ }
+
+ if (cctx->ctx_compile_type == CT_DEBUG)
+ {
+ int save_prev_lnum = cctx->ctx_prev_lnum;
+
+ // Add ISN_DEBUG here, so that the loop variables can be inspected.
+ // Use the prev_lnum from the ISN_DEBUG instruction removed above.
+ cctx->ctx_prev_lnum = prev_lnum;
+ generate_instr_debug(cctx);
+ cctx->ctx_prev_lnum = save_prev_lnum;
+ }
+ }
+
+ return arg_end;
+
+failed:
+ vim_free(name);
+ drop_scope(cctx);
+ return NULL;
+}
+
+/*
+ * 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_for));
+ return NULL;
+ }
+ forscope = &scope->se_u.se_for;
+ cctx->ctx_scope = scope->se_outer;
+ if (cctx->ctx_skip != SKIP_YES)
+ {
+ unwind_locals(cctx, scope->se_local_count);
+
+ // 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.
+ isn = ((isn_T *)instr->ga_data) + forscope->fs_top_label;
+ 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"
+ * JUMP_IF_FALSE end jump if false
+ * ... body ...
+ * JUMP top Jump back to repeat
+ * end:
+ *
+ */
+ char_u *
+compile_while(char_u *arg, cctx_T *cctx)
+{
+ char_u *p = arg;
+ scope_T *scope;
+
+ scope = new_scope(cctx, WHILE_SCOPE);
+ if (scope == NULL)
+ return NULL;
+
+ // "endwhile" jumps back here, one before when profiling or using cmdmods
+ scope->se_u.se_while.ws_top_label = current_instr_idx(cctx);
+
+ // compile "expr"
+ if (compile_expr0(&p, cctx) == FAIL)
+ return NULL;
+
+ if (!ends_excmd2(arg, skipwhite(p)))
+ {
+ semsg(_(e_trailing_arg), 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(&scope->se_u.se_while.ws_end_label,
+ JUMP_IF_FALSE, 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_while));
+ return NULL;
+ }
+ cctx->ctx_scope = scope->se_outer;
+ if (cctx->ctx_skip != SKIP_YES)
+ {
+ unwind_locals(cctx, scope->se_local_count);
+
+#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;
+}
+
+/*
+ * compile "continue"
+ */
+ char_u *
+compile_continue(char_u *arg, cctx_T *cctx)
+{
+ scope_T *scope = cctx->ctx_scope;
+ int try_scopes = 0;
+ int loop_label;
+
+ for (;;)
+ {
+ if (scope == NULL)
+ {
+ emsg(_(e_continue));
+ return NULL;
+ }
+ if (scope->se_type == FOR_SCOPE)
+ {
+ loop_label = scope->se_u.se_for.fs_top_label;
+ break;
+ }
+ if (scope->se_type == WHILE_SCOPE)
+ {
+ loop_label = scope->se_u.se_while.ws_top_label;
+ break;
+ }
+ if (scope->se_type == TRY_SCOPE)
+ ++try_scopes;
+ scope = scope->se_outer;
+ }
+
+ 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)
+{
+ scope_T *scope = cctx->ctx_scope;
+ endlabel_T **el;
+
+ for (;;)
+ {
+ if (scope == NULL)
+ {
+ emsg(_(e_break));
+ return NULL;
+ }
+ if (scope->se_type == FOR_SCOPE || scope->se_type == WHILE_SCOPE)
+ break;
+ scope = scope->se_outer;
+ }
+
+ // Jump to the end of the FOR or WHILE loop.
+ if (scope->se_type == FOR_SCOPE)
+ el = &scope->se_u.se_for.fs_end_label;
+ else
+ el = &scope->se_u.se_while.ws_end_label;
+ if (compile_jump_to_end(el, JUMP_ALWAYS, cctx) == FAIL)
+ return FAIL;
+
+ 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);
+ 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