diff options
author | Bram Moolenaar <Bram@vim.org> | 2021-12-20 15:04:29 +0000 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2021-12-20 15:04:29 +0000 |
commit | dc7c366f3aae65ee691010b08f37acfb26e0742b (patch) | |
tree | 90d12bd3030048100a9c5084474aef484f6fc58f /src/vim9cmds.c | |
parent | a99fb23842f055c511bfe1b62de7bbd14d5a99c0 (diff) | |
download | vim-git-dc7c366f3aae65ee691010b08f37acfb26e0742b.tar.gz |
patch 8.2.3860: Vim9: codecov struggles with the file sizev8.2.3860
Problem: Vim9: codecov struggles with the file size.
Solution: Split vim9compile.c into four files.
Diffstat (limited to 'src/vim9cmds.c')
-rw-r--r-- | src/vim9cmds.c | 2274 |
1 files changed, 2274 insertions, 0 deletions
diff --git a/src/vim9cmds.c b/src/vim9cmds.c new file mode 100644 index 000000000..fe9ead275 --- /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 + * 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.try.try_ref = ALLOC_CLEAR_ONE(tryref_T); + if (isn->isn_arg.try.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)); + return NULL; + } + + if (scope->se_u.se_try.ts_caught_all) + { + emsg(_(e_catch_unreachable_after_catch_all)); + return NULL; + } + + 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, 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.try.try_ref->try_catch == 0) + isn->isn_arg.try.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 FAIL; + } + 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 FAIL; + if (generate_PUSHS(cctx, &pat) == FAIL) + return FAIL; + + 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)); + 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.try.try_ref->try_finally != 0) + { + emsg(_(e_finally_dup)); + 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.try.try_ref->try_catch == 0) + isn->isn_arg.try.try_ref->try_catch = this_instr; + isn->isn_arg.try.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; + } + 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_no_endtry)); + else if (scope->se_type == WHILE_SCOPE) + emsg(_(e_endwhile)); + else if (scope->se_type == FOR_SCOPE) + emsg(_(e_endfor)); + else + emsg(_(e_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.try.try_ref->try_catch == 0 + && try_isn->isn_arg.try.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; + } + } + + 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.try.try_ref->try_endtry = instr->ga_len; + if (cctx->ctx_skip != SKIP_YES + && 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; +} + + 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); +} + +/* + * compile "echo expr" + * compile "echomsg expr" + * compile "echoerr expr" + * compile "echoconsole expr" + * compile "execute expr" + */ + char_u * +compile_mult_expr(char_u *arg, int cmdidx, 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; + garray_T *stack = &cctx->ctx_type_stack; + type_T *type; + + 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 = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + 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) + generate_ECHO(cctx, cmdidx == CMD_echo, count); + else if (cmdidx == CMD_execute) + generate_MULT_EXPR(cctx, ISN_EXECUTE, count); + else if (cmdidx == CMD_echomsg) + generate_MULT_EXPR(cctx, ISN_ECHOMSG, count); + else if (cmdidx == CMD_echoconsole) + generate_MULT_EXPR(cctx, ISN_ECHOCONSOLE, count); + else + generate_MULT_EXPR(cctx, ISN_ECHOERR, count); + + cctx->ctx_lnum = save_lnum; + } + return p; +} + +/* + * 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 = 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); + 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; // 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_arg), 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 (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_instr_drop(cctx, ISN_CONCAT, 1); + + 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) == 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, 1, cctx) == FAIL) + return NULL; + if (need_type(&t_string, lhs->lhs_member_type, + -1, 0, cctx, FALSE, FALSE) == FAIL) + return NULL; + 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; + garray_T *stack = &cctx->ctx_type_stack; + type_T *stack_type; + + if (*p != NUL && *p != '|' && *p != '\n') + { + if (legacy) + { + int save_flags = cmdmod.cmod_flags; + + generate_LEGACY_EVAL(cctx, p); + if (need_type(&t_any, cctx->ctx_ufunc->uf_ret_type, -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 = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + if ((check_return_type && (cctx->ctx_ufunc->uf_ret_type == NULL + || cctx->ctx_ufunc->uf_ret_type == &t_unknown + || cctx->ctx_ufunc->uf_ret_type == &t_any)) + || (!check_return_type + && cctx->ctx_ufunc->uf_ret_type == &t_unknown)) + { + cctx->ctx_ufunc->uf_ret_type = stack_type; + } + else + { + if (cctx->ctx_ufunc->uf_ret_type->tt_type == VAR_VOID + && stack_type->tt_type != VAR_VOID + && stack_type->tt_type != VAR_UNKNOWN) + { + emsg(_(e_returning_value_in_function_without_return_type)); + return NULL; + } + if (need_type(stack_type, cctx->ctx_ufunc->uf_ret_type, -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; + } + + // No argument, return zero. + generate_PUSHNR(cctx, 0); + } + + // 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) |