diff src/vim9cmds.c @ 32670:695b50472e85

Fix line endings issue
author Christian Brabandt <cb@256bit.org>
date Mon, 26 Jun 2023 13:13:12 +0200
parents 448aef880252
children a39314fa9495
line wrap: on
line diff
--- a/src/vim9cmds.c
+++ b/src/vim9cmds.c
@@ -1,2654 +1,2654 @@
-/* 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;
-    size_t	len;
-    char_u	*buf;
-    isntype_T	isn = ISN_EXEC;
-    char	*cmd = eap->cmdidx == CMD_lockvar ? "lockvar" : "unlockvar";
-
-    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);
-
-	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
-    {
-	if (deep < 0)
-	    vim_snprintf((char *)buf, len, "%s! %s", cmd, p);
-	else
-	    vim_snprintf((char *)buf, len, "%s %d %s", cmd, deep, 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)
-{
-    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 (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".
-		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
-			&& 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;
-    int		func_idx;
-    int		obj_method = 0;
-
-    // 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
-    {
-	int typecount = cctx->ctx_type_stack.ga_len;
-	if (compile_expr0(&arg, cctx) == FAIL)
-	    return NULL;
-	if (cctx->ctx_type_stack.ga_len >= typecount + 2)
-	    // must have seen "obj.Func", pushed an object and a function
-	    obj_method = 1;
-    }
-    *paren = '(';
-
-    // check for function type
-    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 (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, obj_method, 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_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;
-	}
-
-	// 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)
+/* 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;
+    size_t	len;
+    char_u	*buf;
+    isntype_T	isn = ISN_EXEC;
+    char	*cmd = eap->cmdidx == CMD_lockvar ? "lockvar" : "unlockvar";
+
+    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);
+
+	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
+    {
+	if (deep < 0)
+	    vim_snprintf((char *)buf, len, "%s! %s", cmd, p);
+	else
+	    vim_snprintf((char *)buf, len, "%s %d %s", cmd, deep, 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)
+{
+    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 (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".
+		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
+			&& 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;
+    int		func_idx;
+    int		obj_method = 0;
+
+    // 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
+    {
+	int typecount = cctx->ctx_type_stack.ga_len;
+	if (compile_expr0(&arg, cctx) == FAIL)
+	    return NULL;
+	if (cctx->ctx_type_stack.ga_len >= typecount + 2)
+	    // must have seen "obj.Func", pushed an object and a function
+	    obj_method = 1;
+    }
+    *paren = '(';
+
+    // check for function type
+    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 (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, obj_method, 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_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;
+	}
+
+	// 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)