Problem:  Vim9: no generic support yet
Solution: Add support for generic functions, funcrefs and object/class
          methods (Yegappan Lakshmanan).
closes: #17313
Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
		
	
		
			
				
	
	
		
			2794 lines
		
	
	
		
			68 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2794 lines
		
	
	
		
			68 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* 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' && !in_vim9script())
 | |
| 	    return OK;
 | |
| 	semsg(_(e_cannot_unlet_str), name);
 | |
| 	return FAIL;
 | |
|     }
 | |
|     return OK;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Callback passed to ex_unletlock().
 | |
|  */
 | |
|     static int
 | |
| compile_unlet(
 | |
|     lval_T  *lvp,
 | |
|     char_u  *name_end,
 | |
|     exarg_T *eap,
 | |
|     int	    deep UNUSED,
 | |
|     void    *coookie)
 | |
| {
 | |
|     cctx_T	*cctx = coookie;
 | |
|     char_u	*p = lvp->ll_name;
 | |
|     int		cc = *name_end;
 | |
|     int		ret = OK;
 | |
| 
 | |
|     if (cctx->ctx_skip == SKIP_YES)
 | |
| 	return OK;
 | |
| 
 | |
|     *name_end = NUL;
 | |
|     if (*p == '$')
 | |
|     {
 | |
| 	// :unlet $ENV_VAR
 | |
| 	ret = generate_UNLET(cctx, ISN_UNLETENV, p + 1, eap->forceit);
 | |
|     }
 | |
|     else if (vim_strchr(p, '.') != NULL || vim_strchr(p, '[') != NULL)
 | |
|     {
 | |
| 	lhs_T	    lhs;
 | |
| 
 | |
| 	// This is similar to assigning: lookup the list/dict, compile the
 | |
| 	// idx/key.  Then instead of storing the value unlet the item.
 | |
| 	// unlet {list}[idx]
 | |
| 	// unlet {dict}[key]  dict.key
 | |
| 	//
 | |
| 	// Figure out the LHS type and other properties.
 | |
| 	//
 | |
| 	ret = compile_lhs(p, &lhs, CMD_unlet, FALSE, FALSE, 0, cctx);
 | |
| 
 | |
| 	// Use the info in "lhs" to unlet the item at the index in the
 | |
| 	// list or dict.
 | |
| 	if (ret == OK)
 | |
| 	{
 | |
| 	    if (!lhs.lhs_has_index)
 | |
| 	    {
 | |
| 		semsg(_(e_cannot_unlet_imported_item_str), p);
 | |
| 		ret = FAIL;
 | |
| 	    }
 | |
| 	    else
 | |
| 		ret = compile_assign_unlet(p, &lhs, FALSE, &t_void, cctx);
 | |
| 	}
 | |
| 
 | |
| 	vim_free(lhs.lhs_name);
 | |
|     }
 | |
|     else if (check_vim9_unlet(p) == FAIL)
 | |
|     {
 | |
| 	ret = FAIL;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
| 	// Normal name.  Only supports g:, w:, t: and b: namespaces.
 | |
| 	ret = generate_UNLET(cctx, ISN_UNLET, p, eap->forceit);
 | |
|     }
 | |
| 
 | |
|     *name_end = cc;
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Callback passed to ex_unletlock().
 | |
|  */
 | |
|     static int
 | |
| compile_lock_unlock(
 | |
|     lval_T  *lvp,
 | |
|     char_u  *name_end,
 | |
|     exarg_T *eap,
 | |
|     int	    deep,
 | |
|     void    *coookie)
 | |
| {
 | |
|     cctx_T	*cctx = coookie;
 | |
|     int		cc = *name_end;
 | |
|     char_u	*p = lvp->ll_name;
 | |
|     int		ret = OK;
 | |
|     char_u	*buf;
 | |
|     isntype_T	isn = ISN_EXEC;
 | |
|     char	*cmd = eap->cmdidx == CMD_lockvar ? "lockvar" : "unlockvar";
 | |
|     int		is_arg = FALSE;
 | |
| 
 | |
| #ifdef LOG_LOCKVAR
 | |
|     ch_log(NULL, "LKVAR: compile_lock_unlock(): cookie %p, name %s",
 | |
| 								coookie, p);
 | |
| #endif
 | |
| 
 | |
|     if (cctx->ctx_skip == SKIP_YES)
 | |
| 	return OK;
 | |
| 
 | |
|     if (*p == NUL)
 | |
|     {
 | |
| 	semsg(_(e_argument_required_for_str), cmd);
 | |
| 	return FAIL;
 | |
|     }
 | |
| 
 | |
|     // Cannot use :lockvar and :unlockvar on local variables.
 | |
|     if (p[1] != ':')
 | |
|     {
 | |
| 	char_u *end = find_name_end(p, NULL, NULL, FNE_CHECK_START);
 | |
| 
 | |
| 	// The most important point is that something like
 | |
| 	// name[idx].member... needs to be resolved at runtime, get_lval(),
 | |
| 	// starting from the root "name".
 | |
| 
 | |
| 	// These checks are reminiscent of the variable_exists function.
 | |
| 	// But most of the matches require special handling.
 | |
| 
 | |
| 	// If bare name is is locally accessible, except for local var,
 | |
| 	// then put it on the stack to use with ISN_LOCKUNLOCK.
 | |
| 	// This could be v.memb, v[idx_key]; bare class variable,
 | |
| 	// function arg. The item on the stack, will be passed
 | |
| 	// to ex_lockvar() indirectly and be used as the root for get_lval.
 | |
| 	// A bare script variable name needs no special handling.
 | |
| 
 | |
| 	char_u	*name = NULL;
 | |
| 	int	len = end - p;
 | |
| 
 | |
| 	if (lookup_local(p, len, NULL, cctx) == OK)
 | |
| 	{
 | |
| 	    // Handle "this", "this.val", "anyvar[idx]"
 | |
| 	    if (*end != '.' && *end != '['
 | |
| 				&& (len != 4 || STRNCMP("this", p, len) != 0))
 | |
| 	    {
 | |
| 		emsg(_(e_cannot_lock_unlock_local_variable));
 | |
| 		return FAIL;
 | |
| 	    }
 | |
| 	    // Push the local on the stack, could be "this".
 | |
| 	    name = p;
 | |
| #ifdef LOG_LOCKVAR
 | |
| 	    ch_log(NULL, "LKVAR:    ... lookup_local: name %s", name);
 | |
| #endif
 | |
| 	}
 | |
| 	if (name == NULL)
 | |
| 	{
 | |
| 	    class_T *cl;
 | |
| 	    if (cctx_class_member_idx(cctx, p, len, &cl) >= 0)
 | |
| 	    {
 | |
| 		if (*end != '.' && *end != '[')
 | |
| 		{
 | |
| 		    // Push the class of the bare class variable name
 | |
| 		    name = cl->class_name;
 | |
| 		    len = (int)STRLEN(name);
 | |
| #ifdef LOG_LOCKVAR
 | |
| 		    ch_log(NULL, "LKVAR:    ... cctx_class_member: name %s",
 | |
| 			   name);
 | |
| #endif
 | |
| 		}
 | |
| 	    }
 | |
| 	}
 | |
| 	if (name == NULL)
 | |
| 	{
 | |
| 	    // Can lockvar any function arg.
 | |
| 	    if (arg_exists(p, len, NULL, NULL, NULL, cctx) == OK)
 | |
| 	    {
 | |
| 		name = p;
 | |
| 		is_arg = TRUE;
 | |
| #ifdef LOG_LOCKVAR
 | |
| 		ch_log(NULL, "LKVAR:    ... arg_exists: name %s", name);
 | |
| #endif
 | |
| 	    }
 | |
| 	}
 | |
| 	if (name == NULL)
 | |
| 	{
 | |
| 	    // No special handling for a bare script variable; but
 | |
| 	    // if followed by '[' or '.', it's a root for get_lval().
 | |
| 	    if (script_var_exists(p, len, cctx, NULL) == OK
 | |
| 		&& (*end == '.' || *end == '['))
 | |
| 	    {
 | |
| 		name = p;
 | |
| #ifdef LOG_LOCKVAR
 | |
| 		ch_log(NULL, "LKVAR:    ... script_var_exists: name %s", name);
 | |
| #endif
 | |
| 	    }
 | |
| 	}
 | |
| 	if (name != NULL)
 | |
| 	{
 | |
| #ifdef LOG_LOCKVAR
 | |
| 	    ch_log(NULL, "LKVAR:    ... INS_LOCKUNLOCK %s", name);
 | |
| #endif
 | |
| 	    if (compile_load(&name, len, name + len, cctx, FALSE, FALSE) == FAIL)
 | |
| 		return FAIL;
 | |
| 	    isn = ISN_LOCKUNLOCK;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|     // Checking is done at runtime.
 | |
|     *name_end = NUL;
 | |
|     size_t len = name_end - p + 20;
 | |
|     buf = alloc(len);
 | |
|     if (buf == NULL)
 | |
| 	ret = FAIL;
 | |
|     else
 | |
|     {
 | |
| 	if (deep < 0)
 | |
| 	    vim_snprintf((char *)buf, len, "%s! %s", cmd, p);
 | |
| 	else
 | |
| 	    vim_snprintf((char *)buf, len, "%s %d %s", cmd, deep, p);
 | |
| #ifdef LOG_LOCKVAR
 | |
| 	ch_log(NULL, "LKVAR:    ... buf %s", buf);
 | |
| #endif
 | |
| 	if (isn == ISN_LOCKUNLOCK)
 | |
| 	    ret = generate_LOCKUNLOCK(cctx, buf, is_arg);
 | |
| 	else
 | |
| 	    ret = generate_EXEC_copy(cctx, isn, buf);
 | |
| 
 | |
| 	vim_free(buf);
 | |
| 	*name_end = cc;
 | |
|     }
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * compile "unlet var", "lock var" and "unlock var"
 | |
|  * "arg" points to "var".
 | |
|  */
 | |
|     char_u *
 | |
| compile_unletlock(char_u *arg, exarg_T *eap, cctx_T *cctx)
 | |
| {
 | |
|     int	    deep = 0;
 | |
|     char_u  *p = arg;
 | |
| 
 | |
|     if (eap->cmdidx != CMD_unlet)
 | |
|     {
 | |
| 	if (eap->forceit)
 | |
| 	    deep = -1;
 | |
| 	else if (vim_isdigit(*p))
 | |
| 	{
 | |
| 	    deep = getdigits(&p);
 | |
| 	    p = skipwhite(p);
 | |
| 	}
 | |
| 	else
 | |
| 	    deep = 2;
 | |
|     }
 | |
| 
 | |
|     ex_unletlock(eap, p, deep, GLV_NO_AUTOLOAD | GLV_COMPILING,
 | |
| 	    eap->cmdidx == CMD_unlet ? compile_unlet : compile_lock_unlock,
 | |
| 	    cctx);
 | |
|     return eap->nextcmd == NULL ? (char_u *)"" : eap->nextcmd;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Generate a jump to the ":endif"/":endfor"/":endwhile"/":finally"/":endtry".
 | |
|  * "funcref_idx" is used for JUMP_WHILE_FALSE
 | |
|  */
 | |
|     static int
 | |
| compile_jump_to_end(
 | |
| 	endlabel_T  **el,
 | |
| 	jumpwhen_T  when,
 | |
| 	int	    funcref_idx,
 | |
| 	cctx_T	    *cctx)
 | |
| {
 | |
|     garray_T	*instr = &cctx->ctx_instr;
 | |
|     endlabel_T  *endlabel = ALLOC_CLEAR_ONE(endlabel_T);
 | |
| 
 | |
|     if (endlabel == NULL)
 | |
| 	return FAIL;
 | |
|     endlabel->el_next = *el;
 | |
|     *el = endlabel;
 | |
|     endlabel->el_end_label = instr->ga_len;
 | |
| 
 | |
|     if (when == JUMP_WHILE_FALSE)
 | |
| 	generate_WHILE(cctx, funcref_idx);
 | |
|     else
 | |
| 	generate_JUMP(cctx, when, 0);
 | |
|     return OK;
 | |
| }
 | |
| 
 | |
|     static void
 | |
| compile_fill_jump_to_end(endlabel_T **el, int jump_where, cctx_T *cctx)
 | |
| {
 | |
|     garray_T	*instr = &cctx->ctx_instr;
 | |
| 
 | |
|     while (*el != NULL)
 | |
|     {
 | |
| 	endlabel_T  *cur = (*el);
 | |
| 	isn_T	    *isn;
 | |
| 
 | |
| 	isn = ((isn_T *)instr->ga_data) + cur->el_end_label;
 | |
| 	isn->isn_arg.jump.jump_where = jump_where;
 | |
| 	*el = cur->el_next;
 | |
| 	vim_free(cur);
 | |
|     }
 | |
| }
 | |
| 
 | |
|     static void
 | |
| compile_free_jump_to_end(endlabel_T **el)
 | |
| {
 | |
|     while (*el != NULL)
 | |
|     {
 | |
| 	endlabel_T  *cur = (*el);
 | |
| 
 | |
| 	*el = cur->el_next;
 | |
| 	vim_free(cur);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Create a new scope and set up the generic items.
 | |
|  */
 | |
|     static scope_T *
 | |
| new_scope(cctx_T *cctx, scopetype_T type)
 | |
| {
 | |
|     scope_T *scope = ALLOC_CLEAR_ONE(scope_T);
 | |
| 
 | |
|     if (scope == NULL)
 | |
| 	return NULL;
 | |
|     scope->se_outer = cctx->ctx_scope;
 | |
|     cctx->ctx_scope = scope;
 | |
|     scope->se_type = type;
 | |
|     scope->se_local_count = cctx->ctx_locals.ga_len;
 | |
|     if (scope->se_outer != NULL)
 | |
| 	scope->se_loop_depth = scope->se_outer->se_loop_depth;
 | |
|     return scope;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Free the current scope and go back to the outer scope.
 | |
|  */
 | |
|     void
 | |
| drop_scope(cctx_T *cctx)
 | |
| {
 | |
|     scope_T *scope = cctx->ctx_scope;
 | |
| 
 | |
|     if (scope == NULL)
 | |
|     {
 | |
| 	iemsg("calling drop_scope() without a scope");
 | |
| 	return;
 | |
|     }
 | |
|     cctx->ctx_scope = scope->se_outer;
 | |
|     switch (scope->se_type)
 | |
|     {
 | |
| 	case IF_SCOPE:
 | |
| 	    compile_free_jump_to_end(&scope->se_u.se_if.is_end_label); break;
 | |
| 	case FOR_SCOPE:
 | |
| 	    compile_free_jump_to_end(&scope->se_u.se_for.fs_end_label); break;
 | |
| 	case WHILE_SCOPE:
 | |
| 	    compile_free_jump_to_end(&scope->se_u.se_while.ws_end_label); break;
 | |
| 	case TRY_SCOPE:
 | |
| 	    compile_free_jump_to_end(&scope->se_u.se_try.ts_end_label); break;
 | |
| 	case NO_SCOPE:
 | |
| 	case BLOCK_SCOPE:
 | |
| 	    break;
 | |
|     }
 | |
|     vim_free(scope);
 | |
| }
 | |
| 
 | |
|     static int
 | |
| misplaced_cmdmod(cctx_T *cctx)
 | |
| {
 | |
|     garray_T	*instr = &cctx->ctx_instr;
 | |
| 
 | |
|     if (cctx->ctx_has_cmdmod
 | |
| 	    && ((isn_T *)instr->ga_data)[instr->ga_len - 1].isn_type
 | |
| 								 == ISN_CMDMOD)
 | |
|     {
 | |
| 	emsg(_(e_misplaced_command_modifier));
 | |
| 	return TRUE;
 | |
|     }
 | |
|     return FALSE;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * compile "if expr"
 | |
|  *
 | |
|  * "if expr" Produces instructions:
 | |
|  *	EVAL expr		Push result of "expr"
 | |
|  *	JUMP_IF_FALSE end
 | |
|  *	... body ...
 | |
|  * end:
 | |
|  *
 | |
|  * "if expr | else" Produces instructions:
 | |
|  *	EVAL expr		Push result of "expr"
 | |
|  *	JUMP_IF_FALSE else
 | |
|  *	... body ...
 | |
|  *	JUMP_ALWAYS end
 | |
|  * else:
 | |
|  *	... body ...
 | |
|  * end:
 | |
|  *
 | |
|  * "if expr1 | elseif expr2 | else" Produces instructions:
 | |
|  *	EVAL expr		Push result of "expr"
 | |
|  *	JUMP_IF_FALSE elseif
 | |
|  *	... body ...
 | |
|  *	JUMP_ALWAYS end
 | |
|  * elseif:
 | |
|  *	EVAL expr		Push result of "expr"
 | |
|  *	JUMP_IF_FALSE else
 | |
|  *	... body ...
 | |
|  *	JUMP_ALWAYS end
 | |
|  * else:
 | |
|  *	... body ...
 | |
|  * end:
 | |
|  */
 | |
|     char_u *
 | |
| compile_if(char_u *arg, cctx_T *cctx)
 | |
| {
 | |
|     char_u	*p = arg;
 | |
|     garray_T	*instr = &cctx->ctx_instr;
 | |
|     int		instr_count = instr->ga_len;
 | |
|     scope_T	*scope;
 | |
|     skip_T	skip_save = cctx->ctx_skip;
 | |
|     ppconst_T	ppconst;
 | |
| 
 | |
|     CLEAR_FIELD(ppconst);
 | |
|     if (compile_expr1(&p, cctx, &ppconst) == FAIL)
 | |
|     {
 | |
| 	clear_ppconst(&ppconst);
 | |
| 	return NULL;
 | |
|     }
 | |
|     if (!ends_excmd2(arg, skipwhite(p)))
 | |
|     {
 | |
| 	semsg(_(e_trailing_characters_str), p);
 | |
| 	return NULL;
 | |
|     }
 | |
|     if (cctx->ctx_skip == SKIP_YES)
 | |
| 	clear_ppconst(&ppconst);
 | |
|     else if (instr->ga_len == instr_count && ppconst.pp_used == 1)
 | |
|     {
 | |
| 	int error = FALSE;
 | |
| 	int v;
 | |
| 
 | |
| 	// The expression results in a constant.
 | |
| 	v = tv_get_bool_chk(&ppconst.pp_tv[0], &error);
 | |
| 	clear_ppconst(&ppconst);
 | |
| 	if (error)
 | |
| 	    return NULL;
 | |
| 	cctx->ctx_skip = v ? SKIP_NOT : SKIP_YES;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
| 	// Not a constant, generate instructions for the expression.
 | |
| 	cctx->ctx_skip = SKIP_UNKNOWN;
 | |
| 	if (generate_ppconst(cctx, &ppconst) == FAIL)
 | |
| 	    return NULL;
 | |
| 	if (bool_on_stack(cctx) == FAIL)
 | |
| 	    return NULL;
 | |
|     }
 | |
| 
 | |
|     // CMDMOD_REV must come before the jump
 | |
|     generate_undo_cmdmods(cctx);
 | |
| 
 | |
|     scope = new_scope(cctx, IF_SCOPE);
 | |
|     if (scope == NULL)
 | |
| 	return NULL;
 | |
|     scope->se_skip_save = skip_save;
 | |
|     // "is_had_return" will be reset if any block does not end in :return
 | |
|     scope->se_u.se_if.is_had_return = TRUE;
 | |
| 
 | |
|     if (cctx->ctx_skip == SKIP_UNKNOWN)
 | |
|     {
 | |
| 	// "where" is set when ":elseif", "else" or ":endif" is found
 | |
| 	scope->se_u.se_if.is_if_label = instr->ga_len;
 | |
| 	generate_JUMP(cctx, JUMP_IF_FALSE, 0);
 | |
|     }
 | |
|     else
 | |
| 	scope->se_u.se_if.is_if_label = -1;
 | |
| 
 | |
| #ifdef FEAT_PROFILE
 | |
|     if (cctx->ctx_compile_type == CT_PROFILE && cctx->ctx_skip == SKIP_YES
 | |
| 						      && skip_save != SKIP_YES)
 | |
|     {
 | |
| 	// generated a profile start, need to generate a profile end, since it
 | |
| 	// won't be done after returning
 | |
| 	cctx->ctx_skip = SKIP_NOT;
 | |
| 	generate_instr(cctx, ISN_PROF_END);
 | |
| 	cctx->ctx_skip = SKIP_YES;
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     return p;
 | |
| }
 | |
| 
 | |
|     char_u *
 | |
| compile_elseif(char_u *arg, cctx_T *cctx)
 | |
| {
 | |
|     char_u	*p = arg;
 | |
|     garray_T	*instr = &cctx->ctx_instr;
 | |
|     int		instr_count;
 | |
|     isn_T	*isn;
 | |
|     scope_T	*scope = cctx->ctx_scope;
 | |
|     ppconst_T	ppconst;
 | |
|     skip_T	save_skip = cctx->ctx_skip;
 | |
| 
 | |
|     if (scope == NULL || scope->se_type != IF_SCOPE)
 | |
|     {
 | |
| 	emsg(_(e_elseif_without_if));
 | |
| 	return NULL;
 | |
|     }
 | |
|     unwind_locals(cctx, scope->se_local_count, TRUE);
 | |
|     if (!cctx->ctx_had_return && !cctx->ctx_had_throw)
 | |
| 	// the previous if block didn't end in a "return" or a "throw"
 | |
| 	// statement.
 | |
| 	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 && !cctx->ctx_had_throw)
 | |
| 	// the previous if block didn't end in a "return" or a "throw"
 | |
| 	// statement.
 | |
| 	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 && !cctx->ctx_had_throw)
 | |
| 	// the previous if block didn't end in a "return" or a "throw"
 | |
| 	// statement.
 | |
| 	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;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * When compiling a for loop to iterate over a tuple, get the type of the loop
 | |
|  * variable to use.
 | |
|  */
 | |
|     static type_T *
 | |
| compile_for_tuple_get_vartype(type_T *vartype, int var_list)
 | |
| {
 | |
|     // If this is not a variadic tuple, or all the tuple items don't have
 | |
|     // the same type, then use t_any
 | |
|     if (!(vartype->tt_flags & TTFLAG_VARARGS) || vartype->tt_argcount != 1)
 | |
| 	return &t_any;
 | |
| 
 | |
|     // variadic tuple
 | |
|     type_T *member_type = vartype->tt_args[0]->tt_member;
 | |
|     if (member_type->tt_type == VAR_ANY)
 | |
| 	return &t_any;
 | |
| 
 | |
|     if (!var_list)
 | |
| 	// for x in tuple<...list<xxx>>
 | |
| 	return member_type;
 | |
| 
 | |
|     if (member_type->tt_type == VAR_LIST
 | |
| 	    && member_type->tt_member->tt_type != VAR_ANY)
 | |
| 	// for [x, y] in tuple<...list<list<xxx>>>
 | |
| 	return member_type->tt_member;
 | |
|     else if (member_type->tt_type == VAR_TUPLE
 | |
| 				&& member_type->tt_flags & TTFLAG_VARARGS
 | |
| 				&& member_type->tt_argcount == 1)
 | |
| 	// for [x, y] in tuple<...list<tuple<...list<xxx>>>>
 | |
| 	return member_type->tt_args[0]->tt_member;
 | |
| 
 | |
|     return &t_any;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * 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_TUPLE
 | |
| 		&& 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;
 | |
| 	}
 | |
| 	else if (vartype->tt_type == VAR_TUPLE)
 | |
| 	    item_type = compile_for_tuple_get_vartype(vartype, var_list);
 | |
| 
 | |
| 	// 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, cctx->ctx_ufunc,
 | |
| 								cctx, TRUE);
 | |
| 		if (lhs_type == NULL)
 | |
| 		    goto failed;
 | |
| 	    }
 | |
| 
 | |
| 	    if (get_var_dest(name, &dest, CMD_for, &opt_flags,
 | |
| 					      &vimvaridx, &type, cctx) == FAIL)
 | |
| 		goto failed;
 | |
| 	    if (dest != dest_local)
 | |
| 	    {
 | |
| 		if (generate_store_var(cctx, dest, opt_flags, vimvaridx,
 | |
| 						     type, name, NULL) == FAIL)
 | |
| 		    goto failed;
 | |
| 	    }
 | |
| 	    else if (varlen == 1 && *arg == '_')
 | |
| 	    {
 | |
| 		// Assigning to "_": drop the value.
 | |
| 		if (generate_instr_drop(cctx, ISN_DROP, 1) == NULL)
 | |
| 		    goto failed;
 | |
| 	    }
 | |
| 	    else
 | |
| 	    {
 | |
| 		// Script var is not supported.
 | |
| 		if (STRNCMP(name, "s:", 2) == 0)
 | |
| 		{
 | |
| 		    emsg(_(e_cannot_use_script_variable_in_for_loop));
 | |
| 		    goto failed;
 | |
| 		}
 | |
| 
 | |
| 		if (!valid_varname(arg, (int)varlen, FALSE))
 | |
| 		    goto failed;
 | |
| 		if (lookup_local(arg, varlen, NULL, cctx) == OK)
 | |
| 		{
 | |
| 		    semsg(_(e_variable_already_declared_str), arg);
 | |
| 		    goto failed;
 | |
| 		}
 | |
| 
 | |
| 		// Reserve a variable to store "var".
 | |
| 		if (var_list)
 | |
| 		{
 | |
| 		    where.wt_index = idx + 1;
 | |
| 		    where.wt_kind = WT_VARIABLE;
 | |
| 		}
 | |
| 		if (lhs_type == &t_any)
 | |
| 		    lhs_type = item_type;
 | |
| 		else if (item_type != &t_unknown
 | |
| 			&& need_type_where(item_type, lhs_type, FALSE, -1,
 | |
| 					    where, cctx, FALSE, FALSE) == FAIL)
 | |
| 		    goto failed;
 | |
| 		var_lvar = reserve_local(cctx, arg, varlen, ASSIGN_FINAL,
 | |
| 								     lhs_type);
 | |
| 		if (var_lvar == NULL)
 | |
| 		    // out of memory or used as an argument
 | |
| 		    goto failed;
 | |
| 
 | |
| 		if (semicolon && idx == var_count - 1)
 | |
| 		    var_lvar->lv_type = vartype;
 | |
| 		generate_STORE(cctx, ISN_STORE, var_lvar->lv_idx, NULL);
 | |
| 	    }
 | |
| 
 | |
| 	    if (*p == ',' || *p == ';')
 | |
| 		++p;
 | |
| 	    arg = skipwhite(p);
 | |
| 	    vim_free(name);
 | |
| 	}
 | |
| 
 | |
| 	// remember the number of variables and closures, used for ENDLOOP
 | |
| 	compile_fill_loop_info(&forscope->fs_loop_info, funcref_lvar_idx, cctx);
 | |
| 	forscope->fs_loop_info.li_depth = scope->se_loop_depth - 1;
 | |
|     }
 | |
| 
 | |
|     return arg_end;
 | |
| 
 | |
| failed:
 | |
|     vim_free(name);
 | |
|     drop_scope(cctx);
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Used when ending a loop of :for and :while: Generate an ISN_ENDLOOP
 | |
|  * instruction if any variable was declared that could be used by a new
 | |
|  * closure.
 | |
|  */
 | |
|     static int
 | |
| compile_loop_end(loop_info_T *loop_info, cctx_T *cctx)
 | |
| {
 | |
|     if (cctx->ctx_locals.ga_len > loop_info->li_local_count
 | |
| 	    && cctx->ctx_closure_count > loop_info->li_closure_count)
 | |
| 	return generate_ENDLOOP(cctx, loop_info);
 | |
|     return OK;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * compile "endfor"
 | |
|  */
 | |
|     char_u *
 | |
| compile_endfor(char_u *arg, cctx_T *cctx)
 | |
| {
 | |
|     garray_T	*instr = &cctx->ctx_instr;
 | |
|     scope_T	*scope = cctx->ctx_scope;
 | |
|     forscope_T	*forscope;
 | |
|     isn_T	*isn;
 | |
| 
 | |
|     if (misplaced_cmdmod(cctx))
 | |
| 	return NULL;
 | |
| 
 | |
|     if (scope == NULL || scope->se_type != FOR_SCOPE)
 | |
|     {
 | |
| 	emsg(_(e_endfor_without_for));
 | |
| 	return NULL;
 | |
|     }
 | |
|     forscope = &scope->se_u.se_for;
 | |
|     cctx->ctx_scope = scope->se_outer;
 | |
|     if (cctx->ctx_skip != SKIP_YES)
 | |
|     {
 | |
| 	// Handle the case that any local variables were declared that might be
 | |
| 	// used in a closure.
 | |
| 	if (compile_loop_end(&forscope->fs_loop_info, cctx) == FAIL)
 | |
| 	    return NULL;
 | |
| 
 | |
| 	unwind_locals(cctx, scope->se_local_count, FALSE);
 | |
| 
 | |
| 	// At end of ":for" scope jump back to the FOR instruction.
 | |
| 	generate_JUMP(cctx, JUMP_ALWAYS, forscope->fs_top_label);
 | |
| 
 | |
| 	// Fill in the "end" label in the FOR statement so it can jump here.
 | |
| 	// In debug mode an ISN_DEBUG was inserted.
 | |
| 	isn = ((isn_T *)instr->ga_data) + forscope->fs_top_label
 | |
| 				+ (cctx->ctx_compile_type == CT_DEBUG ? 1 : 0);
 | |
| 	isn->isn_arg.forloop.for_end = instr->ga_len;
 | |
| 
 | |
| 	// Fill in the "end" label any BREAK statements
 | |
| 	compile_fill_jump_to_end(&forscope->fs_end_label, instr->ga_len, cctx);
 | |
| 
 | |
| 	// Below the ":for" scope drop the "expr" list from the stack.
 | |
| 	if (generate_instr_drop(cctx, ISN_DROP, 1) == NULL)
 | |
| 	    return NULL;
 | |
|     }
 | |
| 
 | |
|     vim_free(scope);
 | |
| 
 | |
|     return arg;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * compile "while expr"
 | |
|  *
 | |
|  * Produces instructions:
 | |
|  * top:  EVAL expr			Push result of "expr"
 | |
|  *	 WHILE funcref-idx  end		Jump if false
 | |
|  *	 ... body ...
 | |
|  *       ENDLOOP funcref-idx off count	only if closure uses local var
 | |
|  *	 JUMP top			Jump back to repeat
 | |
|  * end:
 | |
|  *
 | |
|  */
 | |
|     char_u *
 | |
| compile_while(char_u *arg, cctx_T *cctx)
 | |
| {
 | |
|     char_u	    *p = arg;
 | |
|     scope_T	    *scope;
 | |
|     whilescope_T    *whilescope;
 | |
|     lvar_T	    *funcref_lvar;
 | |
|     int		    funcref_lvar_idx;
 | |
| 
 | |
|     scope = new_scope(cctx, WHILE_SCOPE);
 | |
|     if (scope == NULL)
 | |
| 	return NULL;
 | |
|     if (scope->se_loop_depth == MAX_LOOP_DEPTH)
 | |
|     {
 | |
| 	emsg(_(e_loop_nesting_too_deep));
 | |
| 	return NULL;
 | |
|     }
 | |
|     ++scope->se_loop_depth;
 | |
|     whilescope = &scope->se_u.se_while;
 | |
| 
 | |
|     // "endwhile" jumps back here, one before when profiling or using cmdmods
 | |
|     whilescope->ws_top_label = current_instr_idx(cctx);
 | |
| 
 | |
|     // Reserve a variable to store ec_funcrefs.ga_len, used in ISN_ENDLOOP.
 | |
|     // It is not used when no closures are encountered, we don't know yet.
 | |
|     funcref_lvar = reserve_local(cctx, (char_u *)"", 0, ASSIGN_VAR, &t_number);
 | |
|     if (funcref_lvar == NULL)
 | |
|     {
 | |
| 	drop_scope(cctx);
 | |
| 	return NULL;  // out of memory
 | |
|     }
 | |
|     // get the index before a following reserve_local() makes the lval invalid
 | |
|     funcref_lvar_idx = funcref_lvar->lv_idx;
 | |
| 
 | |
|     // remember the number of variables and closures, used for ENDLOOP
 | |
|     compile_fill_loop_info(&whilescope->ws_loop_info, funcref_lvar_idx, cctx);
 | |
|     whilescope->ws_loop_info.li_depth = scope->se_loop_depth - 1;
 | |
| 
 | |
|     // compile "expr"
 | |
|     if (compile_expr0(&p, cctx) == FAIL)
 | |
| 	return NULL;
 | |
| 
 | |
|     if (!ends_excmd2(arg, skipwhite(p)))
 | |
|     {
 | |
| 	semsg(_(e_trailing_characters_str), p);
 | |
| 	return NULL;
 | |
|     }
 | |
| 
 | |
|     if (cctx->ctx_skip != SKIP_YES)
 | |
|     {
 | |
| 	if (bool_on_stack(cctx) == FAIL)
 | |
| 	    return FAIL;
 | |
| 
 | |
| 	// CMDMOD_REV must come before the jump
 | |
| 	generate_undo_cmdmods(cctx);
 | |
| 
 | |
| 	// "while_end" is set when ":endwhile" is found
 | |
| 	if (compile_jump_to_end(&whilescope->ws_end_label,
 | |
| 			     JUMP_WHILE_FALSE, funcref_lvar_idx, cctx) == FAIL)
 | |
| 	    return FAIL;
 | |
|     }
 | |
| 
 | |
|     return p;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * compile "endwhile"
 | |
|  */
 | |
|     char_u *
 | |
| compile_endwhile(char_u *arg, cctx_T *cctx)
 | |
| {
 | |
|     scope_T	*scope = cctx->ctx_scope;
 | |
|     garray_T	*instr = &cctx->ctx_instr;
 | |
| 
 | |
|     if (misplaced_cmdmod(cctx))
 | |
| 	return NULL;
 | |
|     if (scope == NULL || scope->se_type != WHILE_SCOPE)
 | |
|     {
 | |
| 	emsg(_(e_endwhile_without_while));
 | |
| 	return NULL;
 | |
|     }
 | |
|     cctx->ctx_scope = scope->se_outer;
 | |
|     if (cctx->ctx_skip != SKIP_YES)
 | |
|     {
 | |
| 	whilescope_T	*whilescope = &scope->se_u.se_while;
 | |
| 
 | |
| 	// Handle the case that any local variables were declared that might be
 | |
| 	// used in a closure.
 | |
| 	if (compile_loop_end(&whilescope->ws_loop_info, cctx) == FAIL)
 | |
| 	    return NULL;
 | |
| 
 | |
| 	unwind_locals(cctx, scope->se_local_count, FALSE);
 | |
| 
 | |
| #ifdef FEAT_PROFILE
 | |
| 	// count the endwhile before jumping
 | |
| 	may_generate_prof_end(cctx, cctx->ctx_lnum);
 | |
| #endif
 | |
| 
 | |
| 	// At end of ":for" scope jump back to the FOR instruction.
 | |
| 	generate_JUMP(cctx, JUMP_ALWAYS, scope->se_u.se_while.ws_top_label);
 | |
| 
 | |
| 	// Fill in the "end" label in the WHILE statement so it can jump here.
 | |
| 	// And in any jumps for ":break"
 | |
| 	compile_fill_jump_to_end(&scope->se_u.se_while.ws_end_label,
 | |
| 							  instr->ga_len, cctx);
 | |
|     }
 | |
| 
 | |
|     vim_free(scope);
 | |
| 
 | |
|     return arg;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Get the current information about variables declared inside a loop.
 | |
|  * Returns TRUE if there are any and fills "lvi".
 | |
|  */
 | |
|     int
 | |
| get_loop_var_info(cctx_T *cctx, loopvarinfo_T *lvi)
 | |
| {
 | |
|     scope_T	*scope = cctx->ctx_scope;
 | |
|     int		prev_local_count = 0;
 | |
| 
 | |
|     CLEAR_POINTER(lvi);
 | |
|     for (;;)
 | |
|     {
 | |
| 	loop_info_T	*loopinfo;
 | |
| 	int		cur_local_last;
 | |
| 	int		start_local_count;
 | |
| 
 | |
| 	while (scope != NULL && scope->se_type != WHILE_SCOPE
 | |
| 						&& scope->se_type != FOR_SCOPE)
 | |
| 	    scope = scope->se_outer;
 | |
| 	if (scope == NULL)
 | |
| 	    break;
 | |
| 
 | |
| 	if (scope->se_type == WHILE_SCOPE)
 | |
| 	{
 | |
| 	    loopinfo = &scope->se_u.se_while.ws_loop_info;
 | |
| 	    // :while reserves one variable for funcref count
 | |
| 	    cur_local_last = loopinfo->li_local_count - 1;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 	    loopinfo = &scope->se_u.se_for.fs_loop_info;
 | |
| 	    // :for reserves three variable: loop count, funcref count and loop
 | |
| 	    // var
 | |
| 	    cur_local_last = loopinfo->li_local_count - 3;
 | |
| 	}
 | |
| 
 | |
| 	start_local_count = loopinfo->li_local_count;
 | |
| 	if (cctx->ctx_locals.ga_len > start_local_count)
 | |
| 	{
 | |
| 	    lvi->lvi_loop[loopinfo->li_depth].var_idx =
 | |
| 						      (short)start_local_count;
 | |
| 	    lvi->lvi_loop[loopinfo->li_depth].var_count =
 | |
| 			  (short)(cctx->ctx_locals.ga_len - start_local_count
 | |
| 							   - prev_local_count);
 | |
| 	    if (lvi->lvi_depth == 0)
 | |
| 		lvi->lvi_depth = loopinfo->li_depth + 1;
 | |
| 	}
 | |
| 
 | |
| 	scope = scope->se_outer;
 | |
| 	prev_local_count = cctx->ctx_locals.ga_len - cur_local_last;
 | |
|     }
 | |
|     return lvi->lvi_depth > 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Get the index of the variable "idx" in a loop, if any.
 | |
|  */
 | |
|     void
 | |
| get_loop_var_idx(cctx_T *cctx, int idx, lvar_T *lvar)
 | |
| {
 | |
|     loopvarinfo_T lvi;
 | |
| 
 | |
|     lvar->lv_loop_depth = -1;
 | |
|     lvar->lv_loop_idx = -1;
 | |
|     if (get_loop_var_info(cctx, &lvi))
 | |
|     {
 | |
| 	int depth;
 | |
| 
 | |
| 	for (depth = lvi.lvi_depth - 1; depth >= 0; --depth)
 | |
| 	    if (idx >= lvi.lvi_loop[depth].var_idx
 | |
| 		    && idx < lvi.lvi_loop[depth].var_idx
 | |
| 					       + lvi.lvi_loop[depth].var_count)
 | |
| 	    {
 | |
| 		lvar->lv_loop_depth = depth;
 | |
| 		lvar->lv_loop_idx = lvi.lvi_loop[depth].var_idx;
 | |
| 		return;
 | |
| 	    }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Common for :break, :continue and :return
 | |
|  */
 | |
|     static int
 | |
| compile_find_scope(
 | |
| 	int	    *loop_label,    // where to jump to or NULL
 | |
| 	endlabel_T  ***el,	    // end label or NULL
 | |
| 	int	    *try_scopes,    // :try scopes encountered or NULL
 | |
| 	char	    *error,	    // error to use when no scope found
 | |
| 	cctx_T	    *cctx)
 | |
| {
 | |
|     scope_T	*scope = cctx->ctx_scope;
 | |
| 
 | |
|     for (;;)
 | |
|     {
 | |
| 	if (scope == NULL)
 | |
| 	{
 | |
| 	    if (error != NULL)
 | |
| 		emsg(_(error));
 | |
| 	    return FAIL;
 | |
| 	}
 | |
| 	if (scope->se_type == FOR_SCOPE)
 | |
| 	{
 | |
| 	    if (compile_loop_end(&scope->se_u.se_for.fs_loop_info, cctx)
 | |
| 								       == FAIL)
 | |
| 		return FAIL;
 | |
| 	    if (loop_label != NULL)
 | |
| 		*loop_label = scope->se_u.se_for.fs_top_label;
 | |
| 	    if (el != NULL)
 | |
| 		*el = &scope->se_u.se_for.fs_end_label;
 | |
| 	    break;
 | |
| 	}
 | |
| 	if (scope->se_type == WHILE_SCOPE)
 | |
| 	{
 | |
| 	    if (compile_loop_end(&scope->se_u.se_while.ws_loop_info, cctx)
 | |
| 								       == FAIL)
 | |
| 		return FAIL;
 | |
| 	    if (loop_label != NULL)
 | |
| 		*loop_label = scope->se_u.se_while.ws_top_label;
 | |
| 	    if (el != NULL)
 | |
| 		*el = &scope->se_u.se_while.ws_end_label;
 | |
| 	    break;
 | |
| 	}
 | |
| 	if (try_scopes != NULL && scope->se_type == TRY_SCOPE)
 | |
| 	    ++*try_scopes;
 | |
| 	scope = scope->se_outer;
 | |
|     }
 | |
|     return OK;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * compile "continue"
 | |
|  */
 | |
|     char_u *
 | |
| compile_continue(char_u *arg, cctx_T *cctx)
 | |
| {
 | |
|     int		try_scopes = 0;
 | |
|     int		loop_label;
 | |
| 
 | |
|     if (compile_find_scope(&loop_label, NULL, &try_scopes,
 | |
| 				e_continue_without_while_or_for, cctx) == FAIL)
 | |
| 	return NULL;
 | |
|     if (try_scopes > 0)
 | |
| 	// Inside one or more try/catch blocks we first need to jump to the
 | |
| 	// "finally" or "endtry" to cleanup.
 | |
| 	generate_TRYCONT(cctx, try_scopes, loop_label);
 | |
|     else
 | |
| 	// Jump back to the FOR or WHILE instruction.
 | |
| 	generate_JUMP(cctx, JUMP_ALWAYS, loop_label);
 | |
| 
 | |
|     return arg;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * compile "break"
 | |
|  */
 | |
|     char_u *
 | |
| compile_break(char_u *arg, cctx_T *cctx)
 | |
| {
 | |
|     int		try_scopes = 0;
 | |
|     endlabel_T	**el;
 | |
| 
 | |
|     if (compile_find_scope(NULL, &el, &try_scopes,
 | |
| 				   e_break_without_while_or_for, cctx) == FAIL)
 | |
| 	return NULL;
 | |
| 
 | |
|     if (cctx->ctx_skip == SKIP_YES)
 | |
| 	return arg;
 | |
| 
 | |
|     if (try_scopes > 0)
 | |
| 	// Inside one or more try/catch blocks we first need to jump to the
 | |
| 	// "finally" or "endtry" to cleanup.  Then come to the next JUMP
 | |
| 	// instruction, which we don't know the index of yet.
 | |
| 	generate_TRYCONT(cctx, try_scopes, cctx->ctx_instr.ga_len + 1);
 | |
| 
 | |
|     // Jump to the end of the FOR or WHILE loop.  The instruction index will be
 | |
|     // filled in later.
 | |
|     if (compile_jump_to_end(el, JUMP_ALWAYS, 0, cctx) == FAIL)
 | |
| 	return NULL;
 | |
| 
 | |
|     return arg;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * compile "{" start of block
 | |
|  */
 | |
|     char_u *
 | |
| compile_block(char_u *arg, cctx_T *cctx)
 | |
| {
 | |
|     if (new_scope(cctx, BLOCK_SCOPE) == NULL)
 | |
| 	return NULL;
 | |
|     return skipwhite(arg + 1);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * compile end of block: drop one scope
 | |
|  */
 | |
|     void
 | |
| compile_endblock(cctx_T *cctx)
 | |
| {
 | |
|     scope_T	*scope = cctx->ctx_scope;
 | |
| 
 | |
|     cctx->ctx_scope = scope->se_outer;
 | |
|     unwind_locals(cctx, scope->se_local_count, TRUE);
 | |
|     vim_free(scope);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Compile "try".
 | |
|  * Creates a new scope for the try-endtry, pointing to the first catch and
 | |
|  * finally.
 | |
|  * Creates another scope for the "try" block itself.
 | |
|  * TRY instruction sets up exception handling at runtime.
 | |
|  *
 | |
|  *	"try"
 | |
|  *	    TRY -> catch1, -> finally  push trystack entry
 | |
|  *	    ... try block
 | |
|  *	"throw {exception}"
 | |
|  *	    EVAL {exception}
 | |
|  *	    THROW		create exception
 | |
|  *	    ... try block
 | |
|  *	" catch {expr}"
 | |
|  *	    JUMP -> finally
 | |
|  * catch1:  PUSH exception
 | |
|  *	    EVAL {expr}
 | |
|  *	    MATCH
 | |
|  *	    JUMP nomatch -> catch2
 | |
|  *	    CATCH   remove exception
 | |
|  *	    ... catch block
 | |
|  *	" catch"
 | |
|  *	    JUMP -> finally
 | |
|  * catch2:  CATCH   remove exception
 | |
|  *	    ... catch block
 | |
|  *	" finally"
 | |
|  * finally:
 | |
|  *	    ... finally block
 | |
|  *	" endtry"
 | |
|  *	    ENDTRY  pop trystack entry, may rethrow
 | |
|  */
 | |
|     char_u *
 | |
| compile_try(char_u *arg, cctx_T *cctx)
 | |
| {
 | |
|     garray_T	*instr = &cctx->ctx_instr;
 | |
|     scope_T	*try_scope;
 | |
|     scope_T	*scope;
 | |
| 
 | |
|     if (misplaced_cmdmod(cctx))
 | |
| 	return NULL;
 | |
| 
 | |
|     // scope that holds the jumps that go to catch/finally/endtry
 | |
|     try_scope = new_scope(cctx, TRY_SCOPE);
 | |
|     if (try_scope == NULL)
 | |
| 	return NULL;
 | |
| 
 | |
|     if (cctx->ctx_skip != SKIP_YES)
 | |
|     {
 | |
| 	isn_T	*isn;
 | |
| 
 | |
| 	// "try_catch" is set when the first ":catch" is found or when no catch
 | |
| 	// is found and ":finally" is found.
 | |
| 	// "try_finally" is set when ":finally" is found
 | |
| 	// "try_endtry" is set when ":endtry" is found
 | |
| 	try_scope->se_u.se_try.ts_try_label = instr->ga_len;
 | |
| 	if ((isn = generate_instr(cctx, ISN_TRY)) == NULL)
 | |
| 	    return NULL;
 | |
| 	isn->isn_arg.tryref.try_ref = ALLOC_CLEAR_ONE(tryref_T);
 | |
| 	if (isn->isn_arg.tryref.try_ref == NULL)
 | |
| 	    return NULL;
 | |
|     }
 | |
| 
 | |
|     // scope for the try block itself
 | |
|     scope = new_scope(cctx, BLOCK_SCOPE);
 | |
|     if (scope == NULL)
 | |
| 	return NULL;
 | |
| 
 | |
|     return arg;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Compile "catch {expr}".
 | |
|  */
 | |
|     char_u *
 | |
| compile_catch(char_u *arg, cctx_T *cctx)
 | |
| {
 | |
|     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)
 | |
| {
 | |
|     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, TOSTRING_NONE, cctx) == FAIL)
 | |
| 	return NULL;
 | |
|     if (generate_instr_drop(cctx, ISN_THROW, 1) == NULL)
 | |
| 	return NULL;
 | |
| 
 | |
|     return p;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Compile an expression or function call.
 | |
|  */
 | |
|     char_u *
 | |
| compile_eval(char_u *arg, cctx_T *cctx)
 | |
| {
 | |
|     char_u	*p = arg;
 | |
|     int		name_only;
 | |
|     long	lnum = SOURCING_LNUM;
 | |
| 
 | |
|     // find_ex_command() will consider a variable name an expression, assuming
 | |
|     // that something follows on the next line.  Check that something actually
 | |
|     // follows, otherwise it's probably a misplaced command.
 | |
|     name_only = cmd_is_name_only(arg);
 | |
| 
 | |
|     if (compile_expr0(&p, cctx) == FAIL)
 | |
| 	return NULL;
 | |
| 
 | |
|     if (name_only && lnum == SOURCING_LNUM)
 | |
|     {
 | |
| 	semsg(_(e_expression_without_effect_str), arg);
 | |
| 	return NULL;
 | |
|     }
 | |
| 
 | |
|     // drop the result
 | |
|     generate_instr_drop(cctx, ISN_DROP, 1);
 | |
| 
 | |
|     return skipwhite(p);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Get the local variable index for deferred function calls.
 | |
|  * Reserve it when not done already.
 | |
|  * Returns zero for failure.
 | |
|  */
 | |
|     int
 | |
| get_defer_var_idx(cctx_T *cctx)
 | |
| {
 | |
|     dfunc_T	*dfunc = ((dfunc_T *)def_functions.ga_data)
 | |
| 					       + cctx->ctx_ufunc->uf_dfunc_idx;
 | |
|     if (dfunc->df_defer_var_idx == 0)
 | |
|     {
 | |
| 	lvar_T *lvar = reserve_local(cctx, (char_u *)"@defer@", 7,
 | |
| 							    TRUE, &t_list_any);
 | |
| 	if (lvar == NULL)
 | |
| 	    return 0;
 | |
| 	dfunc->df_defer_var_idx = lvar->lv_idx + 1;
 | |
|     }
 | |
|     return dfunc->df_defer_var_idx;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Compile "defer func(arg)".
 | |
|  */
 | |
|     char_u *
 | |
| compile_defer(char_u *arg_start, cctx_T *cctx)
 | |
| {
 | |
|     char_u	*paren;
 | |
|     char_u	*arg = arg_start;
 | |
|     int		argcount = 0;
 | |
|     int		defer_var_idx;
 | |
|     type_T	*type = NULL;
 | |
|     int		func_idx;
 | |
| 
 | |
|     // Get a funcref for the function name.
 | |
|     // TODO: better way to find the "(".
 | |
|     paren = vim_strchr(arg, '(');
 | |
|     if (paren == NULL)
 | |
|     {
 | |
| 	semsg(_(e_missing_parenthesis_str), arg);
 | |
| 	return NULL;
 | |
|     }
 | |
|     *paren = NUL;
 | |
|     func_idx = find_internal_func(arg);
 | |
|     if (func_idx >= 0)
 | |
| 	// TODO: better type
 | |
| 	generate_PUSHFUNC(cctx, (char_u *)internal_func_name(func_idx),
 | |
| 							   &t_func_any, FALSE);
 | |
|     else if (compile_expr0(&arg, cctx) == FAIL)
 | |
| 	return NULL;
 | |
|     *paren = '(';
 | |
| 
 | |
|     // check for function type
 | |
|     if (cctx->ctx_skip != SKIP_YES)
 | |
|     {
 | |
| 	type = get_type_on_stack(cctx, 0);
 | |
| 	if (type->tt_type != VAR_FUNC)
 | |
| 	{
 | |
| 	    emsg(_(e_function_name_required));
 | |
| 	    return NULL;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|     // compile the arguments
 | |
|     arg = skipwhite(paren + 1);
 | |
|     if (compile_arguments(&arg, cctx, &argcount, CA_NOT_SPECIAL) == FAIL)
 | |
| 	return NULL;
 | |
| 
 | |
|     if (cctx->ctx_skip != SKIP_YES)
 | |
|     {
 | |
| 	if (func_idx >= 0)
 | |
| 	{
 | |
| 	    type2_T	*argtypes = NULL;
 | |
| 	    type2_T	shuffled_argtypes[MAX_FUNC_ARGS];
 | |
| 
 | |
| 	    if (check_internal_func_args(cctx, func_idx, argcount, FALSE,
 | |
| 			&argtypes, shuffled_argtypes) == FAIL)
 | |
| 		return NULL;
 | |
| 	}
 | |
| 	else if (check_func_args_from_type(cctx, type, argcount, TRUE,
 | |
| 		    arg_start) == FAIL)
 | |
| 	    return NULL;
 | |
| 
 | |
| 	defer_var_idx = get_defer_var_idx(cctx);
 | |
| 	if (defer_var_idx == 0)
 | |
| 	    return NULL;
 | |
| 	if (generate_DEFER(cctx, defer_var_idx - 1, argcount) == FAIL)
 | |
| 	    return NULL;
 | |
|     }
 | |
| 
 | |
|     return skipwhite(arg);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * compile "echo expr"
 | |
|  * compile "echomsg expr"
 | |
|  * compile "echoerr expr"
 | |
|  * compile "echoconsole expr"
 | |
|  * compile "echowindow expr" - may have cmd_count set
 | |
|  * compile "execute expr"
 | |
|  */
 | |
|     char_u *
 | |
| compile_mult_expr(
 | |
| 	char_u	*arg,
 | |
| 	int	cmdidx,
 | |
| 	long	cmd_count UNUSED,
 | |
| 	cctx_T	*cctx)
 | |
| {
 | |
|     char_u	*p = arg;
 | |
|     char_u	*prev = arg;
 | |
|     char_u	*expr_start;
 | |
|     int		count = 0;
 | |
|     int		start_ctx_lnum = cctx->ctx_lnum;
 | |
|     type_T	*type;
 | |
|     int		r = OK;
 | |
| 
 | |
|     for (;;)
 | |
|     {
 | |
| 	if (ends_excmd2(prev, p))
 | |
| 	    break;
 | |
| 	expr_start = p;
 | |
| 	if (compile_expr0(&p, cctx) == FAIL)
 | |
| 	    return NULL;
 | |
| 
 | |
| 	if (cctx->ctx_skip != SKIP_YES)
 | |
| 	{
 | |
| 	    // check for non-void type
 | |
| 	    type = get_type_on_stack(cctx, 0);
 | |
| 	    if (type->tt_type == VAR_VOID)
 | |
| 	    {
 | |
| 		semsg(_(e_expression_does_not_result_in_value_str), expr_start);
 | |
| 		return NULL;
 | |
| 	    }
 | |
| 	}
 | |
| 
 | |
| 	++count;
 | |
| 	prev = p;
 | |
| 	p = skipwhite(p);
 | |
|     }
 | |
| 
 | |
|     if (count > 0)
 | |
|     {
 | |
| 	long save_lnum = cctx->ctx_lnum;
 | |
| 
 | |
| 	// Use the line number where the command started.
 | |
| 	cctx->ctx_lnum = start_ctx_lnum;
 | |
| 
 | |
| 	if (cmdidx == CMD_echo || cmdidx == CMD_echon)
 | |
| 	    r = generate_ECHO(cctx, cmdidx == CMD_echo, count);
 | |
| 	else if (cmdidx == CMD_execute)
 | |
| 	    r = generate_MULT_EXPR(cctx, ISN_EXECUTE, count);
 | |
| 	else if (cmdidx == CMD_echomsg)
 | |
| 	    r = generate_MULT_EXPR(cctx, ISN_ECHOMSG, count);
 | |
| #ifdef HAS_MESSAGE_WINDOW
 | |
| 	else if (cmdidx == CMD_echowindow)
 | |
| 	    r = generate_ECHOWINDOW(cctx, count, cmd_count);
 | |
| #endif
 | |
| 	else if (cmdidx == CMD_echoconsole)
 | |
| 	    r = generate_MULT_EXPR(cctx, ISN_ECHOCONSOLE, count);
 | |
| 	else
 | |
| 	    r = generate_MULT_EXPR(cctx, ISN_ECHOERR, count);
 | |
| 
 | |
| 	cctx->ctx_lnum = save_lnum;
 | |
|     }
 | |
|     return r == OK ? p : NULL;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * If "eap" has a range that is not a constant generate an ISN_RANGE
 | |
|  * instruction to compute it and return OK.
 | |
|  * Otherwise return FAIL, the caller must deal with any range.
 | |
|  */
 | |
|     static int
 | |
| compile_variable_range(exarg_T *eap, cctx_T *cctx)
 | |
| {
 | |
|     char_u *range_end = skip_range(eap->cmd, TRUE, NULL);
 | |
|     char_u *p = skipdigits(eap->cmd);
 | |
| 
 | |
|     if (p == range_end)
 | |
| 	return FAIL;
 | |
|     return generate_RANGE(cctx, vim_strnsave(eap->cmd, range_end - eap->cmd));
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * :put r
 | |
|  * :put ={expr}
 | |
|  * or if fixindent == TRUE
 | |
|  * :iput r
 | |
|  * :iput ={expr}
 | |
|  */
 | |
|     char_u *
 | |
| compile_put(char_u *arg, exarg_T *eap, cctx_T *cctx, int fixindent)
 | |
| {
 | |
|     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, fixindent);
 | |
| 
 | |
|     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, TOSTRING_TOLERANT, cctx);
 | |
| 	    ++count;
 | |
| 	    p = skipwhite(p);
 | |
| 	    if (*p != '`')
 | |
| 	    {
 | |
| 		emsg(_(e_missing_backtick));
 | |
| 		return NULL;
 | |
| 	    }
 | |
| 	    start = p + 1;
 | |
| 
 | |
| 	    p = (char_u *)strstr((char *)start, "`=");
 | |
| 	    if (p == NULL)
 | |
| 	    {
 | |
| 		if (*skipwhite(start) != NUL)
 | |
| 		{
 | |
| 		    char_u *val = vim_strsave(start);
 | |
| 
 | |
| 		    generate_PUSHS(cctx, &val);
 | |
| 		    ++count;
 | |
| 		}
 | |
| 		break;
 | |
| 	    }
 | |
| 	}
 | |
| 	generate_EXECCONCAT(cctx, count);
 | |
|     }
 | |
|     else
 | |
| 	generate_EXEC_copy(cctx, ISN_EXEC, line);
 | |
| 
 | |
| theend:
 | |
|     if (*nextcmd != NUL)
 | |
|     {
 | |
| 	// the parser expects a pointer to the bar, put it back
 | |
| 	--nextcmd;
 | |
| 	*nextcmd = '|';
 | |
|     }
 | |
|     vim_free(tofree);
 | |
| 
 | |
|     return nextcmd;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * A script command with heredoc, e.g.
 | |
|  *	ruby << EOF
 | |
|  *	   command
 | |
|  *	EOF
 | |
|  * Has been turned into one long line with NL characters by
 | |
|  * get_function_body():
 | |
|  *	ruby << EOF<NL>   command<NL>EOF
 | |
|  */
 | |
|     char_u *
 | |
| compile_script(char_u *line, cctx_T *cctx)
 | |
| {
 | |
|     if (cctx->ctx_skip != SKIP_YES)
 | |
|     {
 | |
| 	isn_T	*isn;
 | |
| 
 | |
| 	if ((isn = generate_instr(cctx, ISN_EXEC_SPLIT)) == NULL)
 | |
| 	    return NULL;
 | |
| 	isn->isn_arg.string = vim_strsave(line);
 | |
|     }
 | |
|     return (char_u *)"";
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * :s/pat/repl/
 | |
|  */
 | |
|     char_u *
 | |
| compile_substitute(char_u *arg, exarg_T *eap, cctx_T *cctx)
 | |
| {
 | |
|     char_u  *cmd = eap->arg;
 | |
|     char_u  *expr = (char_u *)strstr((char *)cmd, "\\=");
 | |
| 
 | |
|     if (expr != NULL)
 | |
|     {
 | |
| 	int delimiter = *cmd++;
 | |
| 
 | |
| 	// There is a \=expr, find it in the substitute part.
 | |
| 	cmd = skip_regexp_ex(cmd, delimiter, magic_isset(), NULL, NULL, NULL);
 | |
| 	if (cmd[0] == delimiter && cmd[1] == '\\' && cmd[2] == '=')
 | |
| 	{
 | |
| 	    garray_T	save_ga = cctx->ctx_instr;
 | |
| 	    char_u	*end;
 | |
| 	    int		expr_res;
 | |
| 	    int		trailing_error;
 | |
| 	    int		instr_count;
 | |
| 	    isn_T	*instr;
 | |
| 	    isn_T	*isn;
 | |
| 
 | |
| 	    cmd += 3;
 | |
| 	    end = skip_substitute(cmd, delimiter);
 | |
| 
 | |
| 	    // Temporarily reset the list of instructions so that the jump
 | |
| 	    // labels are correct.
 | |
| 	    cctx->ctx_instr.ga_len = 0;
 | |
| 	    cctx->ctx_instr.ga_maxlen = 0;
 | |
| 	    cctx->ctx_instr.ga_data = NULL;
 | |
| 	    expr_res = compile_expr0(&cmd, cctx);
 | |
| 	    if (end[-1] == NUL)
 | |
| 		end[-1] = delimiter;
 | |
| 	    cmd = skipwhite(cmd);
 | |
| 	    trailing_error = *cmd != delimiter && *cmd != NUL;
 | |
| 
 | |
| 	    if (expr_res == FAIL || trailing_error
 | |
| 				       || GA_GROW_FAILS(&cctx->ctx_instr, 1))
 | |
| 	    {
 | |
| 		if (trailing_error)
 | |
| 		    semsg(_(e_trailing_characters_str), cmd);
 | |
| 		clear_instr_ga(&cctx->ctx_instr);
 | |
| 		cctx->ctx_instr = save_ga;
 | |
| 		return NULL;
 | |
| 	    }
 | |
| 
 | |
| 	    // Move the generated instructions into the ISN_SUBSTITUTE
 | |
| 	    // instructions, then restore the list of instructions before
 | |
| 	    // adding the ISN_SUBSTITUTE instruction.
 | |
| 	    instr_count = cctx->ctx_instr.ga_len;
 | |
| 	    instr = cctx->ctx_instr.ga_data;
 | |
| 	    instr[instr_count].isn_type = ISN_FINISH;
 | |
| 
 | |
| 	    cctx->ctx_instr = save_ga;
 | |
| 	    if ((isn = generate_instr(cctx, ISN_SUBSTITUTE)) == NULL)
 | |
| 	    {
 | |
| 		int idx;
 | |
| 
 | |
| 		for (idx = 0; idx < instr_count; ++idx)
 | |
| 		    delete_instr(instr + idx);
 | |
| 		vim_free(instr);
 | |
| 		return NULL;
 | |
| 	    }
 | |
| 	    isn->isn_arg.subs.subs_cmd = vim_strsave(arg);
 | |
| 	    isn->isn_arg.subs.subs_instr = instr;
 | |
| 
 | |
| 	    // skip over flags
 | |
| 	    if (*end == '&')
 | |
| 		++end;
 | |
| 	    while (ASCII_ISALPHA(*end) || *end == '#')
 | |
| 		++end;
 | |
| 	    return end;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|     return compile_exec(arg, eap, cctx);
 | |
| }
 | |
| 
 | |
|     char_u *
 | |
| compile_redir(char_u *line, exarg_T *eap, cctx_T *cctx)
 | |
| {
 | |
|     char_u  *arg = eap->arg;
 | |
|     lhs_T   *lhs = &cctx->ctx_redir_lhs;
 | |
| 
 | |
|     if (lhs->lhs_name != NULL)
 | |
|     {
 | |
| 	if (STRNCMP(arg, "END", 3) == 0)
 | |
| 	{
 | |
| 	    if (cctx->ctx_skip != SKIP_YES)
 | |
| 	    {
 | |
| 		if (lhs->lhs_append)
 | |
| 		{
 | |
| 		    // First load the current variable value.
 | |
| 		    if (compile_load_lhs_with_index(lhs, lhs->lhs_whole,
 | |
| 								 cctx) == FAIL)
 | |
| 			return NULL;
 | |
| 		}
 | |
| 
 | |
| 		// Gets the redirected text and put it on the stack, then store
 | |
| 		// it in the variable.
 | |
| 		generate_instr_type(cctx, ISN_REDIREND, &t_string);
 | |
| 
 | |
| 		if (lhs->lhs_append)
 | |
| 		    generate_CONCAT(cctx, 2);
 | |
| 
 | |
| 		if (lhs->lhs_has_index)
 | |
| 		{
 | |
| 		    // Use the info in "lhs" to store the value at the index in
 | |
| 		    // the list or dict.
 | |
| 		    if (compile_assign_unlet(lhs->lhs_whole, lhs, TRUE,
 | |
| 						      &t_string, cctx) == FAIL)
 | |
| 			return NULL;
 | |
| 		}
 | |
| 		else if (generate_store_lhs(cctx, lhs, -1, FALSE) == FAIL)
 | |
| 		    return NULL;
 | |
| 
 | |
| 		VIM_CLEAR(lhs->lhs_name);
 | |
| 		VIM_CLEAR(lhs->lhs_whole);
 | |
| 	    }
 | |
| 	    return arg + 3;
 | |
| 	}
 | |
| 	emsg(_(e_cannot_nest_redir));
 | |
| 	return NULL;
 | |
|     }
 | |
| 
 | |
|     if (arg[0] == '=' && arg[1] == '>')
 | |
|     {
 | |
| 	int	    append = FALSE;
 | |
| 
 | |
| 	// redirect to a variable is compiled
 | |
| 	arg += 2;
 | |
| 	if (*arg == '>')
 | |
| 	{
 | |
| 	    ++arg;
 | |
| 	    append = TRUE;
 | |
| 	}
 | |
| 	arg = skipwhite(arg);
 | |
| 
 | |
| 	if (compile_assign_lhs(arg, lhs, CMD_redir,
 | |
| 					 FALSE, FALSE, FALSE, 1, cctx) == FAIL)
 | |
| 	    return NULL;
 | |
| 	if (need_type(&t_string, lhs->lhs_member_type, FALSE,
 | |
| 					    -1, 0, cctx, FALSE, FALSE) == FAIL)
 | |
| 	    return NULL;
 | |
| 	if (cctx->ctx_skip == SKIP_YES)
 | |
| 	{
 | |
| 	    VIM_CLEAR(lhs->lhs_name);
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 	    generate_instr(cctx, ISN_REDIRSTART);
 | |
| 	    lhs->lhs_append = append;
 | |
| 	    if (lhs->lhs_has_index)
 | |
| 	    {
 | |
| 		lhs->lhs_whole = vim_strnsave(arg, lhs->lhs_varlen_total);
 | |
| 		if (lhs->lhs_whole == NULL)
 | |
| 		    return NULL;
 | |
| 	    }
 | |
| 	}
 | |
| 
 | |
| 	return arg + lhs->lhs_varlen_total;
 | |
|     }
 | |
| 
 | |
|     // other redirects are handled like at script level
 | |
|     return compile_exec(line, eap, cctx);
 | |
| }
 | |
| 
 | |
| #if defined(FEAT_QUICKFIX) || defined(PROTO)
 | |
|     char_u *
 | |
| compile_cexpr(char_u *line, exarg_T *eap, cctx_T *cctx)
 | |
| {
 | |
|     isn_T	*isn;
 | |
|     char_u	*p;
 | |
| 
 | |
|     isn = generate_instr(cctx, ISN_CEXPR_AUCMD);
 | |
|     if (isn == NULL)
 | |
| 	return NULL;
 | |
|     isn->isn_arg.number = eap->cmdidx;
 | |
| 
 | |
|     p = eap->arg;
 | |
|     if (compile_expr0(&p, cctx) == FAIL)
 | |
| 	return NULL;
 | |
| 
 | |
|     isn = generate_instr(cctx, ISN_CEXPR_CORE);
 | |
|     if (isn == NULL)
 | |
| 	return NULL;
 | |
|     isn->isn_arg.cexpr.cexpr_ref = ALLOC_ONE(cexprref_T);
 | |
|     if (isn->isn_arg.cexpr.cexpr_ref == NULL)
 | |
| 	return NULL;
 | |
|     isn->isn_arg.cexpr.cexpr_ref->cer_cmdidx = eap->cmdidx;
 | |
|     isn->isn_arg.cexpr.cexpr_ref->cer_forceit = eap->forceit;
 | |
|     isn->isn_arg.cexpr.cexpr_ref->cer_cmdline = vim_strsave(skipwhite(line));
 | |
| 
 | |
|     return p;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /*
 | |
|  * Compile "return [expr]".
 | |
|  * When "legacy" is TRUE evaluate [expr] with legacy syntax
 | |
|  */
 | |
|     char_u *
 | |
| compile_return(char_u *arg, int check_return_type, int legacy, cctx_T *cctx)
 | |
| {
 | |
|     char_u	*p = arg;
 | |
|     type_T	*stack_type;
 | |
| 
 | |
|     if (*p != NUL && *p != '|' && *p != '\n'
 | |
| 					&& (legacy || !vim9_comment_start(p)))
 | |
|     {
 | |
| 	// For a lambda, "return expr" is always used, also when "expr" results
 | |
| 	// in a void.
 | |
| 	if (cctx->ctx_ufunc->uf_ret_type->tt_type == VAR_VOID
 | |
| 		&& (cctx->ctx_ufunc->uf_flags & FC_LAMBDA) == 0)
 | |
| 	{
 | |
| 	    emsg(_(e_returning_value_in_function_without_return_type));
 | |
| 	    return NULL;
 | |
| 	}
 | |
| 	if (legacy)
 | |
| 	{
 | |
| 	    int save_flags = cmdmod.cmod_flags;
 | |
| 
 | |
| 	    generate_LEGACY_EVAL(cctx, p);
 | |
| 	    if (need_type(&t_any, cctx->ctx_ufunc->uf_ret_type, FALSE, -1,
 | |
| 						0, cctx, FALSE, FALSE) == FAIL)
 | |
| 		return NULL;
 | |
| 	    cmdmod.cmod_flags |= CMOD_LEGACY;
 | |
| 	    (void)skip_expr(&p, NULL);
 | |
| 	    cmdmod.cmod_flags = save_flags;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 	    // compile return argument into instructions
 | |
| 	    if (compile_expr0(&p, cctx) == FAIL)
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	if (cctx->ctx_skip != SKIP_YES)
 | |
| 	{
 | |
| 	    // "check_return_type" with uf_ret_type set to &t_unknown is used
 | |
| 	    // for an inline function without a specified return type.  Set the
 | |
| 	    // return type here.
 | |
| 	    stack_type = get_type_on_stack(cctx, 0);
 | |
| 	    if (check_type_is_value(stack_type) == FAIL)
 | |
| 		return NULL;
 | |
| 	    if ((check_return_type && (cctx->ctx_ufunc->uf_ret_type == NULL
 | |
| 				|| cctx->ctx_ufunc->uf_ret_type == &t_unknown))
 | |
| 		    || (!check_return_type
 | |
| 				&& cctx->ctx_ufunc->uf_ret_type == &t_unknown))
 | |
| 	    {
 | |
| 		cctx->ctx_ufunc->uf_ret_type = stack_type;
 | |
| 	    }
 | |
| 	    else
 | |
| 	    {
 | |
| 		if (need_type(stack_type, cctx->ctx_ufunc->uf_ret_type, FALSE,
 | |
| 					    -1, 0, cctx, FALSE, FALSE) == FAIL)
 | |
| 		    return NULL;
 | |
| 	    }
 | |
| 	}
 | |
|     }
 | |
|     else
 | |
|     {
 | |
| 	// "check_return_type" cannot be TRUE, only used for a lambda which
 | |
| 	// always has an argument.
 | |
| 	if (cctx->ctx_ufunc->uf_ret_type->tt_type != VAR_VOID
 | |
| 		&& cctx->ctx_ufunc->uf_ret_type->tt_type != VAR_UNKNOWN)
 | |
| 	{
 | |
| 	    emsg(_(e_missing_return_value));
 | |
| 	    return NULL;
 | |
| 	}
 | |
| 
 | |
| 	if (IS_CONSTRUCTOR_METHOD(cctx->ctx_ufunc))
 | |
| 	{
 | |
| 	    // For a class new() constructor, return an object of the class.
 | |
| 	    generate_instr(cctx, ISN_RETURN_OBJECT);
 | |
| 	    cctx->ctx_ufunc->uf_ret_type =
 | |
| 		&cctx->ctx_ufunc->uf_class->class_object_type;
 | |
| 	}
 | |
| 	else
 | |
| 	    // No argument, return zero.
 | |
| 	    generate_PUSHNR(cctx, 0);
 | |
|     }
 | |
| 
 | |
|     // may need ENDLOOP when inside a :for or :while loop
 | |
|     if (compile_find_scope(NULL, NULL, NULL, NULL, cctx) == FAIL)
 | |
| 
 | |
|     // Undo any command modifiers.
 | |
|     generate_undo_cmdmods(cctx);
 | |
| 
 | |
|     if (cctx->ctx_skip != SKIP_YES && generate_instr(cctx, ISN_RETURN) == NULL)
 | |
| 	return NULL;
 | |
| 
 | |
|     // "return val | endif" is possible
 | |
|     return skipwhite(p);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Check if the separator for a :global or :substitute command is OK.
 | |
|  */
 | |
|     int
 | |
| check_global_and_subst(char_u *cmd, char_u *arg)
 | |
| {
 | |
|     if (arg == cmd + 1 && vim_strchr((char_u *)":-.", *arg) != NULL)
 | |
|     {
 | |
| 	semsg(_(e_separator_not_supported_str), arg);
 | |
| 	return FAIL;
 | |
|     }
 | |
|     if (VIM_ISWHITE(cmd[1]))
 | |
|     {
 | |
| 	semsg(_(e_no_white_space_allowed_before_separator_str), cmd);
 | |
| 	return FAIL;
 | |
|     }
 | |
|     return OK;
 | |
| }
 | |
| 
 | |
| 
 | |
| #endif  // defined(FEAT_EVAL)
 |