/* 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.
 */

/*
 * evalfunc.c: Builtin functions
 */
#define USING_FLOAT_STUFF

#include "vim.h"

#if defined(FEAT_EVAL)

#ifdef VMS
# include <float.h>
#endif

static void f_and(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_BEVAL
static void f_balloon_gettext(typval_T *argvars, typval_T *rettv);
static void f_balloon_show(typval_T *argvars, typval_T *rettv);
# if defined(FEAT_BEVAL_TERM)
static void f_balloon_split(typval_T *argvars, typval_T *rettv);
# endif
#endif
static void f_base64_encode(typval_T *argvars, typval_T *rettv);
static void f_base64_decode(typval_T *argvars, typval_T *rettv);
static void f_bindtextdomain(typval_T *argvars, typval_T *rettv);
static void f_byte2line(typval_T *argvars, typval_T *rettv);
static void f_call(typval_T *argvars, typval_T *rettv);
static void f_changenr(typval_T *argvars, typval_T *rettv);
static void f_char2nr(typval_T *argvars, typval_T *rettv);
static void f_charcol(typval_T *argvars, typval_T *rettv);
static void f_col(typval_T *argvars, typval_T *rettv);
static void f_confirm(typval_T *argvars, typval_T *rettv);
static void f_copy(typval_T *argvars, typval_T *rettv);
static void f_cursor(typval_T *argsvars, typval_T *rettv);
#ifdef MSWIN
static void f_debugbreak(typval_T *argvars, typval_T *rettv);
#endif
static void f_deepcopy(typval_T *argvars, typval_T *rettv);
static void f_did_filetype(typval_T *argvars, typval_T *rettv);
static void f_echoraw(typval_T *argvars, typval_T *rettv);
static void f_empty(typval_T *argvars, typval_T *rettv);
static void f_environ(typval_T *argvars, typval_T *rettv);
static void f_err_teapot(typval_T *argvars, typval_T *rettv);
static void f_escape(typval_T *argvars, typval_T *rettv);
static void f_eval(typval_T *argvars, typval_T *rettv);
static void f_eventhandler(typval_T *argvars, typval_T *rettv);
static void f_execute(typval_T *argvars, typval_T *rettv);
static void f_exists_compiled(typval_T *argvars, typval_T *rettv);
static void f_expand(typval_T *argvars, typval_T *rettv);
static void f_expandcmd(typval_T *argvars, typval_T *rettv);
static void f_feedkeys(typval_T *argvars, typval_T *rettv);
static void f_fnameescape(typval_T *argvars, typval_T *rettv);
static void f_foreground(typval_T *argvars, typval_T *rettv);
static void f_funcref(typval_T *argvars, typval_T *rettv);
static void f_function(typval_T *argvars, typval_T *rettv);
static void f_garbagecollect(typval_T *argvars, typval_T *rettv);
static void f_get(typval_T *argvars, typval_T *rettv);
static void f_getcellpixels(typval_T *argvars, typval_T *rettv);
static void f_getchangelist(typval_T *argvars, typval_T *rettv);
static void f_getcharpos(typval_T *argvars, typval_T *rettv);
static void f_getcharsearch(typval_T *argvars, typval_T *rettv);
static void f_getcurpos(typval_T *argvars, typval_T *rettv);
static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv);
static void f_getenv(typval_T *argvars, typval_T *rettv);
static void f_getfontname(typval_T *argvars, typval_T *rettv);
static void f_getjumplist(typval_T *argvars, typval_T *rettv);
static void f_getpid(typval_T *argvars, typval_T *rettv);
static void f_getpos(typval_T *argvars, typval_T *rettv);
static void f_getreg(typval_T *argvars, typval_T *rettv);
static void f_getreginfo(typval_T *argvars, typval_T *rettv);
static void f_getregion(typval_T *argvars, typval_T *rettv);
static void f_getregionpos(typval_T *argvars, typval_T *rettv);
static void f_getregtype(typval_T *argvars, typval_T *rettv);
static void f_gettagstack(typval_T *argvars, typval_T *rettv);
static void f_gettext(typval_T *argvars, typval_T *rettv);
static void f_haslocaldir(typval_T *argvars, typval_T *rettv);
static void f_hlID(typval_T *argvars, typval_T *rettv);
static void f_hlexists(typval_T *argvars, typval_T *rettv);
static void f_hostname(typval_T *argvars, typval_T *rettv);
static void f_id(typval_T *argvars, typval_T *rettv);
static void f_index(typval_T *argvars, typval_T *rettv);
static void f_indexof(typval_T *argvars, typval_T *rettv);
static void f_input(typval_T *argvars, typval_T *rettv);
static void f_inputdialog(typval_T *argvars, typval_T *rettv);
static void f_inputlist(typval_T *argvars, typval_T *rettv);
static void f_inputrestore(typval_T *argvars, typval_T *rettv);
static void f_inputsave(typval_T *argvars, typval_T *rettv);
static void f_inputsecret(typval_T *argvars, typval_T *rettv);
static void f_interrupt(typval_T *argvars, typval_T *rettv);
static void f_invert(typval_T *argvars, typval_T *rettv);
static void f_islocked(typval_T *argvars, typval_T *rettv);
static void f_items(typval_T *argvars, typval_T *rettv);
static void f_keytrans(typval_T *argvars, typval_T *rettv);
static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv);
static void f_libcall(typval_T *argvars, typval_T *rettv);
static void f_libcallnr(typval_T *argvars, typval_T *rettv);
static void f_line(typval_T *argvars, typval_T *rettv);
static void f_line2byte(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_LUA
static void f_luaeval(typval_T *argvars, typval_T *rettv);
#endif
static void f_match(typval_T *argvars, typval_T *rettv);
static void f_matchbufline(typval_T *argvars, typval_T *rettv);
static void f_matchend(typval_T *argvars, typval_T *rettv);
static void f_matchlist(typval_T *argvars, typval_T *rettv);
static void f_matchstr(typval_T *argvars, typval_T *rettv);
static void f_matchstrlist(typval_T *argvars, typval_T *rettv);
static void f_matchstrpos(typval_T *argvars, typval_T *rettv);
static void f_max(typval_T *argvars, typval_T *rettv);
static void f_min(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_MZSCHEME
static void f_mzeval(typval_T *argvars, typval_T *rettv);
#endif
static void f_nextnonblank(typval_T *argvars, typval_T *rettv);
static void f_ngettext(typval_T *argvars, typval_T *rettv);
static void f_nr2char(typval_T *argvars, typval_T *rettv);
static void f_or(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_PERL
static void f_perleval(typval_T *argvars, typval_T *rettv);
#endif
static void f_prevnonblank(typval_T *argvars, typval_T *rettv);
static void f_printf(typval_T *argvars, typval_T *rettv);
static void f_pum_getpos(typval_T *argvars, typval_T *rettv);
static void f_pumvisible(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_PYTHON3
static void f_py3eval(typval_T *argvars, typval_T *rettv);
#endif
#ifdef FEAT_PYTHON
static void f_pyeval(typval_T *argvars, typval_T *rettv);
#endif
#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3)
static void f_pyxeval(typval_T *argvars, typval_T *rettv);
#endif
static void f_test_srand_seed(typval_T *argvars, typval_T *rettv);
static void f_rand(typval_T *argvars, typval_T *rettv);
static void f_range(typval_T *argvars, typval_T *rettv);
static void f_reg_executing(typval_T *argvars, typval_T *rettv);
static void f_reg_recording(typval_T *argvars, typval_T *rettv);
static void f_rename(typval_T *argvars, typval_T *rettv);
static void f_repeat(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_RUBY
static void f_rubyeval(typval_T *argvars, typval_T *rettv);
#endif
static void f_screenattr(typval_T *argvars, typval_T *rettv);
static void f_screenchar(typval_T *argvars, typval_T *rettv);
static void f_screenchars(typval_T *argvars, typval_T *rettv);
static void f_screencol(typval_T *argvars, typval_T *rettv);
static void f_screenrow(typval_T *argvars, typval_T *rettv);
static void f_screenstring(typval_T *argvars, typval_T *rettv);
static void f_search(typval_T *argvars, typval_T *rettv);
static void f_searchdecl(typval_T *argvars, typval_T *rettv);
static void f_searchpair(typval_T *argvars, typval_T *rettv);
static void f_searchpairpos(typval_T *argvars, typval_T *rettv);
static void f_searchpos(typval_T *argvars, typval_T *rettv);
static void f_setcharpos(typval_T *argvars, typval_T *rettv);
static void f_setcharsearch(typval_T *argvars, typval_T *rettv);
static void f_setcursorcharpos(typval_T *argvars, typval_T *rettv);
static void f_setenv(typval_T *argvars, typval_T *rettv);
static void f_setfperm(typval_T *argvars, typval_T *rettv);
static void f_setpos(typval_T *argvars, typval_T *rettv);
static void f_setreg(typval_T *argvars, typval_T *rettv);
static void f_settagstack(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_CRYPT
static void f_sha256(typval_T *argvars, typval_T *rettv);
#endif
static void f_shellescape(typval_T *argvars, typval_T *rettv);
static void f_shiftwidth(typval_T *argvars, typval_T *rettv);
static void f_soundfold(typval_T *argvars, typval_T *rettv);
static void f_spellbadword(typval_T *argvars, typval_T *rettv);
static void f_spellsuggest(typval_T *argvars, typval_T *rettv);
static void f_split(typval_T *argvars, typval_T *rettv);
static void f_srand(typval_T *argvars, typval_T *rettv);
static void f_submatch(typval_T *argvars, typval_T *rettv);
static void f_substitute(typval_T *argvars, typval_T *rettv);
static void f_swapfilelist(typval_T *argvars, typval_T *rettv);
static void f_swapinfo(typval_T *argvars, typval_T *rettv);
static void f_swapname(typval_T *argvars, typval_T *rettv);
static void f_synID(typval_T *argvars, typval_T *rettv);
static void f_synIDattr(typval_T *argvars, typval_T *rettv);
static void f_synIDtrans(typval_T *argvars, typval_T *rettv);
static void f_synstack(typval_T *argvars, typval_T *rettv);
static void f_synconcealed(typval_T *argvars, typval_T *rettv);
static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv);
static void f_taglist(typval_T *argvars, typval_T *rettv);
static void f_tagfiles(typval_T *argvars, typval_T *rettv);
static void f_type(typval_T *argvars, typval_T *rettv);
static void f_virtcol(typval_T *argvars, typval_T *rettv);
static void f_visualmode(typval_T *argvars, typval_T *rettv);
static void f_wildmenumode(typval_T *argvars, typval_T *rettv);
static void f_windowsversion(typval_T *argvars, typval_T *rettv);
static void f_wordcount(typval_T *argvars, typval_T *rettv);
static void f_xor(typval_T *argvars, typval_T *rettv);


/*
 * Functions that check the argument type of a builtin function.
 * Each function returns FAIL and gives an error message if the type is wrong.
 */

// Context passed to an arg_ function.
typedef struct {
    int		arg_count;	// actual argument count
    type2_T	*arg_types;	// list of argument types
    int		arg_idx;	// current argument index (first arg is zero)
    cctx_T	*arg_cctx;
} argcontext_T;

// A function to check one argument type.  The first argument is the type to
// check.  If needed, other argument types can be obtained with the context.
// E.g. if "arg_idx" is 1, then (type - 1) is the first argument type.
// NOTE:    Use "arg_any", not NULL, in funcentry_T.f_argcheck array
//	    to accept an argument of any type.
typedef int (*argcheck_T)(type_T *, type_T *, argcontext_T *);

/*
 * Call need_type() to check an argument type.
 */
    static int
check_arg_type(
	type_T		*expected,
	type_T		*actual,
	argcontext_T	*context)
{
    return need_type(actual, expected, 0,
	    context->arg_idx - context->arg_count, context->arg_idx + 1,
	    context->arg_cctx, FALSE, FALSE);
}

/*
 * Call need_type() to check an argument type and that it is modifiable
 */
    static int
check_arg_type_mod(
	type_T		*expected,
	type_T		*actual,
	argcontext_T	*context)
{
    if (need_type(actual, expected, 0,
	    context->arg_idx - context->arg_count, context->arg_idx + 1,
	    context->arg_cctx, FALSE, FALSE) == FAIL)
	return FAIL;
    return arg_type_modifiable(actual, context->arg_idx + 1);
}

/*
 * Give an error if "type" is a constant.
 */
    int
arg_type_modifiable(type_T *type, int arg_idx)
{
    char *tofree;

    if ((type->tt_flags & TTFLAG_CONST) == 0)
	return OK;
    semsg(_(e_argument_nr_trying_to_modify_const_str),
	    arg_idx, type_name(type, &tofree));
    vim_free(tofree);
    return FAIL;
}

/*
 *  Return OK for any type unconditionally.
 */
    static int
arg_any(type_T *type UNUSED,
	type_T *decl_type UNUSED,
	argcontext_T *context UNUSED)
{
    return OK;
}

/*
 * Check "type" is a float or a number.
 */
    static int
arg_float_or_nr(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    if (type->tt_type == VAR_FLOAT
	    || type->tt_type == VAR_NUMBER
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_number, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a number.
 */
    static int
arg_number(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    return check_arg_type(&t_number, type, context);
}

/*
 * Check "type" is an object.
 */
    static int
arg_object(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    if (type->tt_type == VAR_OBJECT
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_object_any, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a dict of 'any'.
 */
    static int
arg_dict_any(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    return check_arg_type(&t_dict_any, type, context);
}

/*
 * Check "type" is a list of 'any'.
 */
    static int
arg_list_any(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    return check_arg_type(&t_list_any, type, context);
}

/*
 * Check "type" is a list of 'any' and modifiable
 */
    static int
arg_list_any_mod(
	type_T	     *type,
	type_T	     *decl_type UNUSED,
	argcontext_T *context)
{
    return check_arg_type_mod(&t_list_any, type, context);
}

/*
 * Check "type" is a list of numbers.
 */
    static int
arg_list_number(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    return check_arg_type(&t_list_number, type, context);
}

/*
 * Check "type" is a list of strings.
 */
    static int
arg_list_string(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    return check_arg_type(&t_list_string, type, context);
}

/*
 * Check "type" is a tuple of 'any'.
 */
    static int
arg_tuple_any(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    return check_arg_type(&t_tuple_any, type, context);
}

/*
 * Check "type" is a string.
 */
    static int
arg_string(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    return check_arg_type(&t_string, type, context);
}

/*
 * Check "type" is a blob
 */
    static int
arg_blob(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    return check_arg_type(&t_blob, type, context);
}

/*
 * Check "type" is a bool or number 0 or 1.
 */
    static int
arg_bool(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    return check_arg_type(&t_bool, type, context);
}

/*
 * Check "type" is a bool or a number.
 */
    static int
arg_bool_or_nr(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    if (type->tt_type == VAR_BOOL
	    || type->tt_type == VAR_NUMBER
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_number, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a list of 'any' or a blob.
 */
    static int
arg_list_or_blob(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    if (type->tt_type == VAR_LIST
	    || type->tt_type == VAR_BLOB
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_list_any, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a modifiable list of 'any' or a blob.
 */
    static int
arg_list_or_blob_mod(
	type_T	     *type,
	type_T	     *decl_type,
	argcontext_T *context)
{
    if (arg_list_or_blob(type, decl_type, context) == FAIL)
	return FAIL;
    return arg_type_modifiable(type, context->arg_idx + 1);
}

/*
 * Check "type" is a list of 'any' or a tuple.
 */
    static int
arg_list_or_tuple(
    type_T		*type,
    type_T		*decl_type UNUSED,
    argcontext_T	*context)
{
    if (type->tt_type == VAR_LIST
	    || type->tt_type == VAR_TUPLE
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_list_any, type, context->arg_idx + 1);
    return FAIL;
}


/*
 * Check "type" is a list of 'any', a tuple or a blob.
 */
    static int
arg_list_or_tuple_or_blob(
    type_T		*type,
    type_T		*decl_type UNUSED,
    argcontext_T	*context)
{
    if (type->tt_type == VAR_LIST
	    || type->tt_type == VAR_TUPLE
	    || type->tt_type == VAR_BLOB
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_list_any, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a string or a number
 */
    static int
arg_string_or_nr(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    if (type->tt_type == VAR_STRING
	    || type->tt_type == VAR_NUMBER
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_string, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a buffer (string or a number)
 */
    static int
arg_buffer(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    if (type->tt_type == VAR_STRING
	    || type->tt_type == VAR_NUMBER
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_string, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a buffer or a dict of any
 */
    static int
arg_buffer_or_dict_any(
    type_T		*type,
    type_T		*decl_type UNUSED,
    argcontext_T	*context)
{
    if (type->tt_type == VAR_STRING
	    || type->tt_type == VAR_NUMBER
	    || type->tt_type == VAR_DICT
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_string, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a line (string or a number)
 */
    static int
arg_lnum(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    if (type->tt_type == VAR_STRING
	    || type->tt_type == VAR_NUMBER
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_string, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a string or a list of strings.
 */
    static int
arg_string_or_list_string(
    type_T		*type,
    type_T		*decl_type UNUSED,
    argcontext_T	*context)
{
    if (type->tt_type == VAR_STRING
	    || type_any_or_unknown(type))
	return OK;
    if (type->tt_type != VAR_LIST)
    {
	arg_type_mismatch(&t_string, type, context->arg_idx + 1);
	return FAIL;
    }
    if (type->tt_member->tt_type == VAR_ANY
		    || type->tt_member->tt_type == VAR_STRING)
	return OK;

    arg_type_mismatch(&t_list_string, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a string or a list of 'any'
 */
    static int
arg_string_or_list_any(
    type_T		*type,
    type_T		*decl_type UNUSED,
    argcontext_T	*context)
{
    if (type->tt_type == VAR_STRING
	    || type->tt_type == VAR_LIST
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_string, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a string or a dict of 'any'
 */
    static int
arg_string_or_dict_any(
    type_T		*type,
    type_T		*decl_type UNUSED,
    argcontext_T	*context)
{
    if (type->tt_type == VAR_STRING
	    || type->tt_type == VAR_DICT
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_string, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a string or a blob
 */
    static int
arg_string_or_blob(
    type_T		*type,
    type_T		*decl_type UNUSED,
    argcontext_T	*context)
{
    if (type->tt_type == VAR_STRING
	    || type->tt_type == VAR_BLOB
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_string, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a list of 'any' or a dict of 'any'.
 */
    static int
arg_list_or_dict(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    if (type->tt_type == VAR_LIST
	    || type->tt_type == VAR_DICT
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_list_any, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a list of 'any' or a dict of 'any'.  And modifiable.
 */
    static int
arg_list_or_dict_mod(
	type_T	     *type,
	type_T	     *decl_type,
	argcontext_T *context)
{
    if (arg_list_or_dict(type, decl_type, context) == FAIL)
	return FAIL;
    return arg_type_modifiable(type, context->arg_idx + 1);
}

/*
 * Check "type" is a list of 'any', a tuple of 'any' or dict of 'any'.
 */
    static int
arg_list_or_tuple_or_dict(
    type_T		*type,
    type_T		*decl_type UNUSED,
    argcontext_T	*context)
{
    if (type->tt_type == VAR_LIST
	    || type->tt_type == VAR_TUPLE
	    || type->tt_type == VAR_DICT
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_list_any, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a list of 'any', a dict of 'any' or a blob.
 * Also check if "type" is modifiable.
 */
    static int
arg_list_or_dict_or_blob_mod(
	type_T	     *type,
	type_T	     *decl_type UNUSED,
	argcontext_T *context)
{
    if (type->tt_type == VAR_LIST
	    || type->tt_type == VAR_DICT
	    || type->tt_type == VAR_BLOB
	    || type_any_or_unknown(type))
	return arg_type_modifiable(type, context->arg_idx + 1);
    arg_type_mismatch(&t_list_any, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a list of 'any' or a dict of 'any' or a blob or a string.
 */
    static int
arg_list_or_dict_or_blob_or_string(
    type_T		*type,
    type_T		*decl_type UNUSED,
    argcontext_T	*context)
{
    if (type->tt_type == VAR_LIST
	    || type->tt_type == VAR_DICT
	    || type->tt_type == VAR_BLOB
	    || type->tt_type == VAR_STRING
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_list_any, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a list of 'any' or a dict of 'any' or a blob or a string.
 * Also check the value is modifiable.
 */
    static int
arg_list_or_dict_or_blob_or_string_mod(
	type_T	     *type,
	type_T	     *decl_type,
	argcontext_T *context)
{
    if (arg_list_or_dict_or_blob_or_string(type, decl_type, context) == FAIL)
	return FAIL;
    return arg_type_modifiable(type, context->arg_idx + 1);
}

/*
 * Check "type" is a list of 'any', a tuple of 'any', a dict of 'any', a blob
 * or a string.
 */
    static int
arg_list_tuple_dict_blob_or_string(
    type_T		*type,
    type_T		*decl_type UNUSED,
    argcontext_T	*context)
{
    if (type->tt_type == VAR_LIST
	    || type->tt_type == VAR_TUPLE
	    || type->tt_type == VAR_DICT
	    || type->tt_type == VAR_BLOB
	    || type->tt_type == VAR_STRING
	    || type_any_or_unknown(type))
	return OK;

    semsg(_(e_list_tuple_dict_blob_or_string_required_for_argument_nr),
						 context->arg_idx + 1);
    return FAIL;
}

/*
 * Check second argument of map(), filter(), foreach().
 */
    static int
check_map_filter_arg2(
    type_T		*type,
    argcontext_T	*context,
    filtermap_T		filtermap)
{
    type_T *expected_member = NULL;
    type_T *(args[2]);
    type_T t_func_exp = {VAR_FUNC, 2, 0, 0, NULL, NULL, args};

    if (context->arg_types[0].type_curr->tt_type == VAR_LIST
	    || context->arg_types[0].type_curr->tt_type == VAR_DICT)
    {
	// Use the declared type if possible, so that an error is given if
	// a declared list changes type, but not if a constant list changes
	// type.
	if (context->arg_types[0].type_decl->tt_type == VAR_LIST
		|| context->arg_types[0].type_decl->tt_type == VAR_DICT)
	    expected_member = context->arg_types[0].type_decl->tt_member;
	else
	    expected_member = context->arg_types[0].type_curr->tt_member;
    }
    else if (context->arg_types[0].type_curr->tt_type == VAR_STRING)
	expected_member = &t_string;
    else if (context->arg_types[0].type_curr->tt_type == VAR_BLOB)
	expected_member = &t_number;

    args[0] = NULL;
    args[1] = &t_unknown;
    if (type->tt_argcount != -1)
    {
	if (!(type->tt_argcount == 2 || (type->tt_argcount == 1
				    && (type->tt_flags & TTFLAG_VARARGS))))
	{
	    emsg(_(e_invalid_number_of_arguments));
	    return FAIL;
	}
	if (type->tt_flags & TTFLAG_VARARGS)
	    // check the argument types at runtime
	    t_func_exp.tt_argcount = -1;
	else
	{
	    if (context->arg_types[0].type_curr->tt_type == VAR_STRING
		    || context->arg_types[0].type_curr->tt_type == VAR_BLOB
		    || context->arg_types[0].type_curr->tt_type == VAR_LIST)
		args[0] = &t_number;
	    else if (context->arg_types[0].type_decl->tt_type == VAR_DICT)
		args[0] = &t_string;
	    if (args[0] != NULL)
		args[1] = expected_member;
	}
    }

    if (!type_any_or_unknown(type->tt_member) || args[0] != NULL)
    {
	where_T where = WHERE_INIT;

	if (filtermap == FILTERMAP_MAP)
	    t_func_exp.tt_member = expected_member == NULL
					|| type_any_or_unknown(type->tt_member)
				? &t_any : expected_member;
	else if (filtermap == FILTERMAP_FILTER)
	    t_func_exp.tt_member = &t_bool;
	else // filtermap == FILTERMAP_FOREACH
	    t_func_exp.tt_member = &t_unknown;
	if (args[0] == NULL)
	    args[0] = &t_unknown;
	if (type->tt_argcount == -1)
	    t_func_exp.tt_argcount = -1;

	where.wt_index = 2;
	where.wt_kind = WT_ARGUMENT;
	return check_type(&t_func_exp, type, TRUE, where);
    }
    return OK;
}

/*
 * Check second argument of filter(): func must return a bool.
 */
    static int
arg_filter_func(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    if (type->tt_type == VAR_STRING
	    || type->tt_type == VAR_PARTIAL
	    || type_any_or_unknown(type))
	return OK;

    if (type->tt_type == VAR_FUNC)
	return check_map_filter_arg2(type, context, FILTERMAP_FILTER);
    semsg(_(e_string_or_function_required_for_argument_nr), 2);
    return FAIL;
}

/*
 * Check second argument of map(), the function.
 */
    static int
arg_map_func(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    if (type->tt_type == VAR_STRING
	    || type->tt_type == VAR_PARTIAL
	    || type_any_or_unknown(type))
	return OK;

    if (type->tt_type == VAR_FUNC)
	return check_map_filter_arg2(type, context, FILTERMAP_MAP);
    semsg(_(e_string_or_function_required_for_argument_nr), 2);
    return FAIL;
}

/*
 * Check second argument of foreach(), the function.
 */
    static int
arg_foreach_func(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    if (type->tt_type == VAR_STRING
	    || type->tt_type == VAR_PARTIAL
	    || type_any_or_unknown(type))
	return OK;

    if (type->tt_type == VAR_FUNC)
	return check_map_filter_arg2(type, context, FILTERMAP_FOREACH);
    semsg(_(e_string_or_function_required_for_argument_nr), 2);
    return FAIL;
}

/*
 * Check second argument of sort() and uniq(), the "how" argument.
 */
    static int
arg_sort_how(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    if (type->tt_type == VAR_STRING
	    || type->tt_type == VAR_PARTIAL
	    || type_any_or_unknown(type))
	return OK;

    if (type->tt_type == VAR_FUNC)
    {
	type_T *(args[2]);
	type_T t_func_exp = {VAR_FUNC, 2, 0, 0, &t_number, NULL, args};

	if (context->arg_types[0].type_curr->tt_type == VAR_LIST)
	    args[0] = context->arg_types[0].type_curr->tt_member;
	else
	    args[0] = &t_unknown;
	if (!type_any_or_unknown(type->tt_member) || args[0] != &t_unknown)
	{
	    where_T where = WHERE_INIT;

	    args[1] = args[0];
	    if (type->tt_argcount == -1)
		t_func_exp.tt_argcount = -1;
	    where.wt_index = 2;
	    where.wt_kind = WT_ARGUMENT;
	    return check_type(&t_func_exp, type, TRUE, where);
	}

	return OK;
    }
    semsg(_(e_string_or_function_required_for_argument_nr), 2);
    return FAIL;
}

/*
 * Check an expression argument, can be a string, funcref or partial.
 * Also accept a bool, a constant resulting from compiling a string argument.
 * Also accept a number, one and zero are accepted.
 */
    static int
arg_string_or_func(
    type_T		*type,
    type_T		*decl_type UNUSED,
    argcontext_T	*context)
{
    if (type->tt_type == VAR_STRING
	    || type->tt_type == VAR_PARTIAL
	    || type->tt_type == VAR_FUNC
	    || type->tt_type == VAR_BOOL
	    || type->tt_type == VAR_NUMBER
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_func_any, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check varargs' "type" are class.
 */
    static int
varargs_class(type_T *type UNUSED,
	      type_T *decl_type UNUSED,
	      argcontext_T *context)
{
    for (int i = context->arg_idx; i < context->arg_count; ++i)
    {
	type2_T *types = &context->arg_types[i];
	if (types->type_curr->tt_type != VAR_CLASS)
	{
	    semsg(_(e_class_or_typealias_required_for_argument_nr), i + 1);
	    return FAIL;
	}
    }
    return OK;
}

/*
 * Check "type" is a list of 'any', a tuple, a blob or a string.
 */
    static int
arg_string_list_tuple_or_blob(
    type_T		*type,
    type_T		*decl_type UNUSED,
    argcontext_T	*context)
{
    if (type->tt_type == VAR_LIST
	    || type->tt_type == VAR_TUPLE
	    || type->tt_type == VAR_BLOB
	    || type->tt_type == VAR_STRING
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_list_any, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a tuple or a modifiable list of 'any' or a blob or a string.
 */
    static int
arg_reverse(type_T *type, type_T *decl_type, argcontext_T *context)
{
    if (arg_string_list_tuple_or_blob(type, decl_type, context) == FAIL)
	return FAIL;
    return arg_type_modifiable(type, context->arg_idx + 1);
}

/*
 * Check "type" is a job.
 */
    static int
arg_job(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    return check_arg_type(&t_job, type, context);
}

/*
 * Check "type" is a channel or a job.
 */
    static int
arg_chan_or_job(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    if (type->tt_type == VAR_CHANNEL
	    || type->tt_type == VAR_JOB
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_channel, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" can be used as the type_decl of the previous argument.
 * Must not be used for the first argcheck_T entry.
 */
    static int
arg_same_as_prev(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    type_T *prev_type = context->arg_types[context->arg_idx - 1].type_decl;

    return check_arg_type(prev_type, type, context);
}

/*
 * Check "type" is the same basic type as the previous argument, checks list or
 * dict vs other type, but not member type.
 * Must not be used for the first argcheck_T entry.
 */
    static int
arg_same_struct_as_prev(
    type_T		*type,
    type_T		*decl_type UNUSED,
    argcontext_T	*context)
{
    type_T *prev_type = context->arg_types[context->arg_idx - 1].type_curr;

    if (prev_type->tt_type != context->arg_types[context->arg_idx].type_curr->tt_type)
	return check_arg_type(prev_type, type, context);
    return OK;
}

/*
 * Check "type" is an item of the list or blob of the previous arg.
 * Must not be used for the first argcheck_T entry.
 */
    static int
arg_item_of_prev(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    type_T *prev_type = context->arg_types[context->arg_idx - 1].type_curr;
    type_T *expected;

    if (prev_type->tt_type == VAR_LIST)
	expected = prev_type->tt_member;
    else if (prev_type->tt_type == VAR_BLOB)
	expected = &t_number;
    else
	// probably VAR_ANY, can't check
	return OK;

    return check_arg_type(expected, type, context);
}

/*
 * Check "type" is a string or a number or a list
 */
    static int
arg_str_or_nr_or_list(
    type_T		*type,
    type_T		*decl_type UNUSED,
    argcontext_T	*context)
{
    if (type->tt_type == VAR_STRING
	    || type->tt_type == VAR_NUMBER
	    || type->tt_type == VAR_LIST
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_string, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" is a dict of 'any' or a string
 */
    static int
arg_dict_any_or_string(
    type_T		*type,
    type_T		*decl_type UNUSED,
    argcontext_T	*context)
{
    if (type->tt_type == VAR_DICT
	    || type->tt_type == VAR_STRING
	    || type_any_or_unknown(type))
	return OK;
    arg_type_mismatch(&t_string, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" which is the third argument of extend() (number or string or
 * any)
 */
    static int
arg_extend3(type_T *type, type_T *decl_type, argcontext_T *context)
{
    type_T *first_type = context->arg_types[context->arg_idx - 2].type_curr;

    if (first_type->tt_type == VAR_LIST)
	return arg_number(type, decl_type, context);
    if (first_type->tt_type == VAR_DICT)
	return arg_string(type, decl_type, context);
    return OK;
}

/*
 * Check "type" which is the first argument of get() (a blob, a list, a tuple,
 * a dict or a funcref)
 */
    static int
arg_get1(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    if (type->tt_type == VAR_BLOB
	    || type->tt_type == VAR_LIST
	    || type->tt_type == VAR_TUPLE
	    || type->tt_type == VAR_DICT
	    || type->tt_type == VAR_FUNC
	    || type->tt_type == VAR_PARTIAL
	    || type_any_or_unknown(type))
	return OK;

    arg_type_mismatch(&t_list_any, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" which is the first argument of len() (a string, a number, a
 * blob, a list, a tuple, a dict or an object)
 */
    static int
arg_len1(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    if (type->tt_type == VAR_STRING
	    || type->tt_type == VAR_NUMBER
	    || type->tt_type == VAR_BLOB
	    || type->tt_type == VAR_LIST
	    || type->tt_type == VAR_TUPLE
	    || type->tt_type == VAR_DICT
	    || type->tt_type == VAR_OBJECT
	    || type_any_or_unknown(type))
	return OK;

    arg_type_mismatch(&t_list_any, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" which is the second argument of remove() (number or string or
 * any)
 */
    static int
arg_remove2(type_T *type, type_T *decl_type, argcontext_T *context)
{
    type_T *first_type = context->arg_types[context->arg_idx - 1].type_curr;

    if (first_type->tt_type == VAR_LIST || first_type->tt_type == VAR_BLOB)
	return arg_number(type, decl_type, context);
    if (first_type->tt_type == VAR_DICT)
	return arg_string_or_nr(type, decl_type, context);
    return OK;
}

/*
 * Check "type" which is the first argument of repeat() (a string, a number, a
 * blob, a list, a tuple or any)
 */
    static int
arg_repeat1(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    if (type->tt_type == VAR_STRING
	    || type->tt_type == VAR_NUMBER
	    || type->tt_type == VAR_BLOB
	    || type->tt_type == VAR_LIST
	    || type->tt_type == VAR_TUPLE
	    || type_any_or_unknown(type))
	return OK;

    arg_type_mismatch(&t_string, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" which is the first argument of slice() (a list, a tuple, a
 * blob, a string or any)
 */
    static int
arg_slice1(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    if (type->tt_type == VAR_LIST
	    || type->tt_type == VAR_TUPLE
	    || type->tt_type == VAR_BLOB
	    || type->tt_type == VAR_STRING
	    || type_any_or_unknown(type))
	return OK;

    arg_type_mismatch(&t_list_any, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" which is the first argument of count() (a string, a list, a
 * tuple, a dict or any)
 */
    static int
arg_string_list_tuple_or_dict(
    type_T		*type,
    type_T		*decl_type UNUSED,
    argcontext_T	*context)
{
    if (type->tt_type == VAR_STRING
	    || type->tt_type == VAR_LIST
	    || type->tt_type == VAR_TUPLE
	    || type->tt_type == VAR_DICT
	    || type_any_or_unknown(type))
	return OK;

    semsg(_(e_string_list_tuple_or_dict_required_for_argument_nr),
							 context->arg_idx + 1);
    return FAIL;
}

/*
 * Check "type" which is the first argument of cursor() (number or string or
 * list or any)
 */
    static int
arg_cursor1(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
{
    if (type->tt_type == VAR_NUMBER
	    || type->tt_type == VAR_STRING
	    || type->tt_type == VAR_LIST
	    || type_any_or_unknown(type))
	return OK;

    arg_type_mismatch(&t_number, type, context->arg_idx + 1);
    return FAIL;
}

/*
 * Lists of functions that check the argument types of a builtin function.
 */
static argcheck_T arg1_blob[] = {arg_blob};
static argcheck_T arg1_bool[] = {arg_bool};
static argcheck_T arg1_buffer[] = {arg_buffer};
static argcheck_T arg1_buffer_or_dict_any[] = {arg_buffer_or_dict_any};
static argcheck_T arg1_chan_or_job[] = {arg_chan_or_job};
static argcheck_T arg1_dict_any[] = {arg_dict_any};
static argcheck_T arg1_dict_or_string[] = {arg_dict_any_or_string};
static argcheck_T arg1_float_or_nr[] = {arg_float_or_nr};
static argcheck_T arg1_job[] = {arg_job};
static argcheck_T arg1_list_any[] = {arg_list_any};
static argcheck_T arg1_tuple_any[] = {arg_tuple_any};
static argcheck_T arg1_list_number[] = {arg_list_number};
static argcheck_T arg1_reverse[] = {arg_reverse};
static argcheck_T arg1_list_or_tuple_or_dict[] = {arg_list_or_tuple_or_dict};
static argcheck_T arg1_list_string[] = {arg_list_string};
static argcheck_T arg1_list_tuple_dict_blob_or_string[] = {arg_list_tuple_dict_blob_or_string};
static argcheck_T arg1_lnum[] = {arg_lnum};
static argcheck_T arg1_number[] = {arg_number};
static argcheck_T arg1_string[] = {arg_string};
static argcheck_T arg1_string_or_list_any[] = {arg_string_or_list_any};
static argcheck_T arg1_string_or_list_string[] = {arg_string_or_list_string};
static argcheck_T arg1_string_or_nr[] = {arg_string_or_nr};
static argcheck_T arg1_string_or_blob[] = {arg_string_or_blob};
static argcheck_T arg2_buffer_any[] = {arg_buffer, arg_any};
static argcheck_T arg2_buffer_bool[] = {arg_buffer, arg_bool};
static argcheck_T arg2_buffer_list_any[] = {arg_buffer, arg_list_any};
static argcheck_T arg2_buffer_lnum[] = {arg_buffer, arg_lnum};
static argcheck_T arg2_buffer_number[] = {arg_buffer, arg_number};
static argcheck_T arg2_buffer_string[] = {arg_buffer, arg_string};
static argcheck_T arg2_chan_or_job_dict[] = {arg_chan_or_job, arg_dict_any};
static argcheck_T arg2_chan_or_job_string[] = {arg_chan_or_job, arg_string};
static argcheck_T arg2_dict_any_list_any[] = {arg_dict_any, arg_list_any};
static argcheck_T arg2_dict_any_string_or_nr[] = {arg_dict_any, arg_string_or_nr};
static argcheck_T arg2_dict_string[] = {arg_dict_any, arg_string};
static argcheck_T arg2_float_or_nr[] = {arg_float_or_nr, arg_float_or_nr};
static argcheck_T arg2_job_dict[] = {arg_job, arg_dict_any};
static argcheck_T arg2_job_string_or_number[] = {arg_job, arg_string_or_nr};
static argcheck_T arg2_list_any_number[] = {arg_list_any, arg_number};
static argcheck_T arg2_list_number[] = {arg_list_number, arg_list_number};
static argcheck_T arg2_list_number_bool[] = {arg_list_number, arg_bool};
static argcheck_T arg2_list_string_dict[] = {arg_list_string, arg_dict_any};
static argcheck_T arg2_listblobmod_item[] = {arg_list_or_blob_mod, arg_item_of_prev};
static argcheck_T arg2_lnum[] = {arg_lnum, arg_lnum};
static argcheck_T arg2_lnum_number[] = {arg_lnum, arg_number};
static argcheck_T arg2_number[] = {arg_number, arg_number};
static argcheck_T arg2_number_any[] = {arg_number, arg_any};
static argcheck_T arg2_number_bool[] = {arg_number, arg_bool};
static argcheck_T arg2_number_dict_any[] = {arg_number, arg_dict_any};
static argcheck_T arg2_number_list[] = {arg_number, arg_list_any};
static argcheck_T arg2_number_string[] = {arg_number, arg_string};
static argcheck_T arg2_number_buffer[] = {arg_number, arg_buffer};
static argcheck_T arg2_number_string_or_list[] = {arg_number, arg_string_or_list_any};
static argcheck_T arg2_str_or_nr_or_list_dict[] = {arg_str_or_nr_or_list, arg_dict_any};
static argcheck_T arg2_string[] = {arg_string, arg_string};
static argcheck_T arg2_string_any[] = {arg_string, arg_any};
static argcheck_T arg2_string_bool[] = {arg_string, arg_bool};
static argcheck_T arg2_string_chan_or_job[] = {arg_string, arg_chan_or_job};
static argcheck_T arg2_string_dict[] = {arg_string, arg_dict_any};
static argcheck_T arg2_string_list_number[] = {arg_string, arg_list_number};
static argcheck_T arg2_string_number[] = {arg_string, arg_number};
static argcheck_T arg2_string_or_list_dict[] = {arg_string_or_list_any, arg_dict_any};
static argcheck_T arg2_string_or_list_number[] = {arg_string_or_list_any, arg_number};
static argcheck_T arg2_string_string_or_number[] = {arg_string, arg_string_or_nr};
static argcheck_T arg2_blob_dict[] = {arg_blob, arg_dict_any};
static argcheck_T arg2_list_or_tuple_string[] = {arg_list_or_tuple, arg_string};
static argcheck_T arg3_any_buffer_bool[] = {arg_any, arg_buffer, arg_bool};
static argcheck_T arg3_any_list_dict[] = {arg_any, arg_list_any, arg_dict_any};
static argcheck_T arg3_buffer_lnum_lnum[] = {arg_buffer, arg_lnum, arg_lnum};
static argcheck_T arg3_buffer_number_number[] = {arg_buffer, arg_number, arg_number};
static argcheck_T arg3_buffer_string_any[] = {arg_buffer, arg_string, arg_any};
static argcheck_T arg3_buffer_string_dict[] = {arg_buffer, arg_string, arg_dict_any};
static argcheck_T arg3_dict_number_number[] = {arg_dict_any, arg_number, arg_number};
static argcheck_T arg3_diff[] = {arg_list_string, arg_list_string, arg_dict_any};
static argcheck_T arg3_list_string_dict[] = {arg_list_any, arg_string, arg_dict_any};
static argcheck_T arg3_list_list_dict[] = {arg_list_any, arg_list_any, arg_dict_any};
static argcheck_T arg3_lnum_number_bool[] = {arg_lnum, arg_number, arg_bool};
static argcheck_T arg3_number[] = {arg_number, arg_number, arg_number};
static argcheck_T arg3_number_any_dict[] = {arg_number, arg_any, arg_dict_any};
static argcheck_T arg3_number_number_dict[] = {arg_number, arg_number, arg_dict_any};
static argcheck_T arg3_number_string_any[] = {arg_number, arg_string, arg_any};
static argcheck_T arg3_number_string_buffer[] = {arg_number, arg_string, arg_buffer};
static argcheck_T arg3_number_string_string[] = {arg_number, arg_string, arg_string};
static argcheck_T arg3_string[] = {arg_string, arg_string, arg_string};
static argcheck_T arg3_string_any_dict[] = {arg_string, arg_any, arg_dict_any};
static argcheck_T arg3_string_any_string[] = {arg_string, arg_any, arg_string};
static argcheck_T arg3_string_bool_bool[] = {arg_string, arg_bool, arg_bool};
static argcheck_T arg3_string_number_bool[] = {arg_string, arg_number, arg_bool};
static argcheck_T arg3_string_number_number[] = {arg_string, arg_number, arg_number};
static argcheck_T arg3_string_or_dict_bool_dict[] = {arg_string_or_dict_any, arg_bool, arg_dict_any};
static argcheck_T arg3_string_or_list_bool_number[] = {arg_string_or_list_any, arg_bool, arg_number};
static argcheck_T arg3_string_string_bool[] = {arg_string, arg_string, arg_bool};
static argcheck_T arg3_string_string_dict[] = {arg_string, arg_string, arg_dict_any};
static argcheck_T arg3_string_string_number[] = {arg_string, arg_string, arg_number};
static argcheck_T arg4_number_number_string_any[] = {arg_number, arg_number, arg_string, arg_any};
static argcheck_T arg4_string_string_any_string[] = {arg_string, arg_string, arg_any, arg_string};
static argcheck_T arg4_string_string_number_string[] = {arg_string, arg_string, arg_number, arg_string};
static argcheck_T arg4_string_number_bool_bool[] = {arg_string, arg_number, arg_bool, arg_bool};
/* Function specific argument types (not covered by the above) */
static argcheck_T arg15_assert_fails[] = {arg_string_or_nr, arg_string_or_list_any, arg_any, arg_number, arg_string};
static argcheck_T arg34_assert_inrange[] = {arg_float_or_nr, arg_float_or_nr, arg_float_or_nr, arg_string};
static argcheck_T arg4_browse[] = {arg_bool, arg_string, arg_string, arg_string};
static argcheck_T arg23_chanexpr[] = {arg_chan_or_job, arg_any, arg_dict_any};
static argcheck_T arg23_chanraw[] = {arg_chan_or_job, arg_string_or_blob, arg_dict_any};
static argcheck_T arg24_count[] = {arg_string_list_tuple_or_dict, arg_any, arg_bool, arg_number};
static argcheck_T arg13_cursor[] = {arg_cursor1, arg_number, arg_number};
static argcheck_T arg12_deepcopy[] = {arg_any, arg_bool};
static argcheck_T arg12_execute[] = {arg_string_or_list_string, arg_string};
static argcheck_T arg12_getchar[] = {arg_bool_or_nr, arg_dict_any};
static argcheck_T arg23_extend[] = {arg_list_or_dict_mod, arg_same_as_prev, arg_extend3};
static argcheck_T arg23_extendnew[] = {arg_list_or_dict, arg_same_struct_as_prev, arg_extend3};
static argcheck_T arg23_get[] = {arg_get1, arg_string_or_nr, arg_any};
static argcheck_T arg14_glob[] = {arg_string, arg_bool, arg_bool, arg_bool};
static argcheck_T arg25_globpath[] = {arg_string, arg_string, arg_bool, arg_bool, arg_bool};
static argcheck_T arg24_index[] = {arg_list_or_tuple_or_blob, arg_item_of_prev, arg_number, arg_bool};
static argcheck_T arg23_index[] = {arg_list_or_tuple_or_blob, arg_filter_func, arg_dict_any};
static argcheck_T arg23_insert[] = {arg_list_or_blob, arg_item_of_prev, arg_number};
static argcheck_T arg1_len[] = {arg_len1};
static argcheck_T arg3_libcall[] = {arg_string, arg_string, arg_string_or_nr};
static argcheck_T arg14_maparg[] = {arg_string, arg_string, arg_bool, arg_bool};
static argcheck_T arg2_filter[] = {arg_list_or_dict_or_blob_or_string_mod, arg_filter_func};
static argcheck_T arg2_foreach[] = {arg_list_tuple_dict_blob_or_string, arg_foreach_func};
static argcheck_T arg2_instanceof[] = {arg_object, varargs_class, NULL };
static argcheck_T arg2_map[] = {arg_list_or_dict_or_blob_or_string_mod, arg_map_func};
static argcheck_T arg2_mapnew[] = {arg_list_or_dict_or_blob_or_string, arg_any};
static argcheck_T arg25_matchadd[] = {arg_string, arg_string, arg_number, arg_number, arg_dict_any};
static argcheck_T arg25_matchaddpos[] = {arg_string, arg_list_any, arg_number, arg_number, arg_dict_any};
static argcheck_T arg23_matchstrlist[] = {arg_list_string, arg_string, arg_dict_any};
static argcheck_T arg45_matchbufline[] = {arg_buffer, arg_string, arg_lnum, arg_lnum, arg_dict_any};
static argcheck_T arg119_printf[] = {arg_string_or_nr, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any};
static argcheck_T arg23_reduce[] = {arg_string_list_tuple_or_blob, arg_any, arg_any};
static argcheck_T arg24_remote_expr[] = {arg_string, arg_string, arg_string, arg_number};
static argcheck_T arg23_remove[] = {arg_list_or_dict_or_blob_mod, arg_remove2, arg_number};
static argcheck_T arg2_repeat[] = {arg_repeat1, arg_number};
static argcheck_T arg15_search[] = {arg_string, arg_string, arg_number, arg_number, arg_string_or_func};
static argcheck_T arg37_searchpair[] = {arg_string, arg_string, arg_string, arg_string, arg_string_or_func, arg_number, arg_number};
static argcheck_T arg3_setbufline[] = {arg_buffer, arg_lnum, arg_str_or_nr_or_list};
static argcheck_T arg2_setline[] = {arg_lnum, arg_any};
static argcheck_T arg24_setloclist[] = {arg_number, arg_list_any, arg_string, arg_dict_any};
static argcheck_T arg13_setqflist[] = {arg_list_any, arg_string, arg_dict_any};
static argcheck_T arg23_settagstack[] = {arg_number, arg_dict_any, arg_string};
static argcheck_T arg02_sign_getplaced[] = {arg_buffer, arg_dict_any};
static argcheck_T arg45_sign_place[] = {arg_number, arg_string, arg_string, arg_buffer, arg_dict_any};
static argcheck_T arg23_slice[] = {arg_slice1, arg_number, arg_number};
static argcheck_T arg13_sortuniq[] = {arg_list_any_mod, arg_sort_how, arg_dict_any};
static argcheck_T arg24_strpart[] = {arg_string, arg_number, arg_number, arg_bool};
static argcheck_T arg12_system[] = {arg_string, arg_str_or_nr_or_list};
static argcheck_T arg23_win_execute[] = {arg_number, arg_string_or_list_string, arg_string};
static argcheck_T arg23_writefile[] = {arg_list_or_blob, arg_string, arg_string};
static argcheck_T arg24_match_func[] = {arg_string_or_list_any, arg_string, arg_number, arg_number};

// Can be used by functions called through "f_retfunc" to create new types.
static garray_T *current_type_gap = NULL;

/*
 * Functions that return the return type of a builtin function.
 * Note that "argtypes" is NULL if "argcount" is zero.
 */
    static type_T *
ret_void(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    return &t_void;
}
    static type_T *
ret_any(int	argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    return &t_any;
}
    static type_T *
ret_bool(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    return &t_bool;
}
    static type_T *
ret_number_bool(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    return &t_number_bool;
}
    static type_T *
ret_number(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    return &t_number;
}
    static type_T *
ret_float(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    return &t_float;
}
    static type_T *
ret_string(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    return &t_string;
}
    static type_T *
ret_list_any(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    return &t_list_any;
}
    static type_T *
ret_list_number(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type)
{
    *decl_type = &t_list_any;
    return &t_list_number;
}
    static type_T *
ret_list_string(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type)
{
    *decl_type = &t_list_any;
    return &t_list_string;
}
    static type_T *
ret_list_dict_any(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type)
{
    *decl_type = &t_list_any;
    return &t_list_dict_any;
}
    static type_T *
ret_list_items(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type)
{
    *decl_type = &t_list_any;
    return &t_list_list_any;
}
    static type_T *
ret_list_string_items(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type)
{
    *decl_type = &t_list_any;
    return &t_list_list_string;
}
    static type_T *
ret_list_regionpos(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type)
{
    *decl_type = &t_list_any;
    return &t_list_list_list_number;
}
    static type_T *
ret_tuple_any(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    return &t_tuple_any;
}
    static type_T *
ret_dict_any(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    return &t_dict_any;
}
    static type_T *
ret_job_info(int argcount,
	type2_T *argtypes UNUSED,
	type_T	**decl_type)
{
    if (argcount == 0)
    {
	*decl_type = &t_list_any;
	return &t_list_job;
    }
    return &t_dict_any;
}
    static type_T *
ret_dict_number(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    return &t_dict_number;
}
    static type_T *
ret_dict_string(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    return &t_dict_string;
}
    static type_T *
ret_blob(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    return &t_blob;
}
    static type_T *
ret_func_any(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    return &t_func_any;
}
    static type_T *
ret_func_unknown(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    return &t_func_unknown;
}
    static type_T *
ret_channel(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    return &t_channel;
}
    static type_T *
ret_job(int argcount UNUSED,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    return &t_job;
}
    static type_T *
ret_first_arg(int argcount,
	type2_T *argtypes,
	type_T	**decl_type)
{
    if (argcount > 0)
    {
	*decl_type = argtypes[0].type_decl;
	return argtypes[0].type_curr;
    }
    return &t_void;
}
    static type_T *
ret_slice(int argcount,
	type2_T *argtypes,
	type_T	**decl_type)
{
    if (argcount > 0)
    {
	if (argtypes[0].type_decl != NULL)
	{
	    switch (argtypes[0].type_decl->tt_type)
	    {
		case VAR_STRING: *decl_type = &t_string; break;
		case VAR_BLOB: *decl_type = &t_blob; break;
		case VAR_LIST: *decl_type = &t_list_any; break;
		case VAR_TUPLE: *decl_type = &t_tuple_any; break;
		default: break;
	    }
	}
	return argtypes[0].type_curr;
    }
    return &t_void;
}
    static type_T *
ret_copy(int argcount,
	type2_T *argtypes,
	type_T	**decl_type)
{
    if (argcount > 0)
    {
	if (argtypes[0].type_decl != NULL)
	{
	    if (argtypes[0].type_decl->tt_type == VAR_LIST)
		*decl_type = &t_list_any;
	    else if (argtypes[0].type_decl->tt_type == VAR_DICT)
		*decl_type = &t_dict_any;
	    else
		*decl_type = argtypes[0].type_decl;
	}
	if (argtypes[0].type_curr != NULL)
	{
	    if (argtypes[0].type_curr->tt_type == VAR_LIST)
		return &t_list_any;
	    else if (argtypes[0].type_curr->tt_type == VAR_DICT)
		return &t_dict_any;
	}
	return argtypes[0].type_curr;
    }
    return &t_void;
}
    static type_T *
ret_extend(int argcount,
	type2_T *argtypes,
	type_T	**decl_type)
{
    if (argcount > 0)
    {
	*decl_type = argtypes[0].type_decl;
	// if the second argument has a different current type then the current
	// type is "any"
	if (argcount > 1 && !equal_type(argtypes[0].type_curr,
						     argtypes[1].type_curr, 0))
	{
	    if (argtypes[0].type_curr->tt_type == VAR_LIST)
		return &t_list_any;
	    if (argtypes[0].type_curr->tt_type == VAR_DICT)
		return &t_dict_any;
	}
	return argtypes[0].type_curr;
    }
    return &t_void;
}
    static type_T *
ret_repeat(int argcount,
	type2_T *argtypes,
	type_T	**decl_type UNUSED)
{
    if (argcount == 0)
	return &t_any;
    if (argtypes[0].type_curr == &t_number)
	return &t_string;
    return argtypes[0].type_curr;
}
// for map(): returns first argument but item type may differ
    static type_T *
ret_first_cont(int argcount,
	type2_T *argtypes,
	type_T	**decl_type UNUSED)
{
    if (argcount > 0)
    {
	if (argtypes[0].type_curr->tt_type == VAR_LIST)
	    return &t_list_any;
	if (argtypes[0].type_curr->tt_type == VAR_DICT)
	    return &t_dict_any;
	if (argtypes[0].type_curr->tt_type == VAR_BLOB)
	    return argtypes[0].type_curr;
    }
    return &t_any;
}
// for getline()
    static type_T *
ret_getline(int argcount,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    if (argcount == 1)
	return &t_string;
    *decl_type = &t_list_any;
    return &t_list_string;
}
// for finddir()
    static type_T *
ret_finddir(int argcount,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    if (argcount < 3)
	return &t_string;
    // Depending on the count would be a string or a list of strings.
    return &t_any;
}
// for values(): list of member of first argument
    static type_T *
ret_list_member(int argcount,
	type2_T *argtypes,
	type_T	**decl_type)
{
    if (argcount > 0)
    {
	type_T *t = argtypes[0].type_decl;
	if (current_type_gap != NULL
		&& (t->tt_type == VAR_DICT || t->tt_type == VAR_LIST))
	    t = get_list_type(t->tt_member, current_type_gap);
	else
	    t = &t_list_any;
	*decl_type = t;

	t = argtypes[0].type_curr;
	if (current_type_gap != NULL
		&& (t->tt_type == VAR_DICT || t->tt_type == VAR_LIST))
	    return get_list_type(t->tt_member, current_type_gap);
    }
    return &t_list_any;
}

/*
 * Used for getqflist(): returns list if there is no argument, dict if there is
 * one.
 */
    static type_T *
ret_list_or_dict_0(int argcount,
	type2_T *argtypes UNUSED,
	type_T	**decl_type)
{
    if (argcount > 0)
	return &t_dict_any;
    *decl_type = &t_list_any;
    return &t_list_dict_any;
}

/*
 * Used for getloclist(): returns list if there is one argument, dict if there
 * are two.
 */
    static type_T *
ret_list_or_dict_1(int argcount,
	type2_T *argtypes UNUSED,
	type_T	**decl_type)
{
    if (argcount > 1)
	return &t_dict_any;
    *decl_type = &t_list_any;
    return &t_list_dict_any;
}

    static type_T *
ret_argv(int argcount,
	type2_T *argtypes UNUSED,
	type_T	**decl_type)
{
    // argv() returns list of strings
    if (argcount == 0)
    {
	*decl_type = &t_list_any;
	return &t_list_string;
    }

    // argv(0) returns a string, but argv(-1] returns a list
    return &t_any;
}

    static type_T *
ret_remove(int argcount,
	type2_T *argtypes,
	type_T	**decl_type)
{
    if (argcount > 0)
    {
	if (argtypes[0].type_curr->tt_type == VAR_LIST
		|| argtypes[0].type_curr->tt_type == VAR_DICT)
	{
	    if (argcount == 3)
	    {
		*decl_type = argtypes[0].type_decl;
		return argtypes[0].type_curr;
	    }
	    if (argtypes[0].type_curr->tt_type
					     == argtypes[0].type_decl->tt_type)
		*decl_type = argtypes[0].type_decl->tt_member;
	    return argtypes[0].type_curr->tt_member;
	}
	if (argtypes[0].type_curr->tt_type == VAR_BLOB)
	    return &t_number;
    }
    return &t_any;
}

    static type_T *
ret_max_min(int argcount,
	type2_T *argtypes,
	type_T	**decl_type)
{
    if (argcount > 0)
    {
	type_T *t = argtypes[0].type_decl;
	if (t->tt_type == VAR_LIST || t->tt_type == VAR_DICT)
	    t = t->tt_member;
	else
	    t = &t_any;
	*decl_type = t;

	t = argtypes[0].type_curr;
	if (t->tt_type == VAR_LIST || t->tt_type == VAR_DICT)
	    t = t->tt_member;
	else
	    t = &t_any;
	return t;
    }
    return &t_any;
}

    static type_T *
ret_getreg(int argcount,
	type2_T *argtypes UNUSED,
	type_T	**decl_type)
{
    // Assume that if the third argument is passed it's non-zero
    if (argcount == 3)
    {
	*decl_type = &t_list_any;
	return &t_list_string;
    }
    return &t_string;
}

    static type_T *
ret_virtcol(int argcount,
	type2_T *argtypes UNUSED,
	type_T	**decl_type)
{
    // Assume that if the second argument is passed it's non-zero
    if (argcount > 1)
    {
	*decl_type = &t_list_any;
	return &t_list_number;
    }
    return &t_number;
}

    static type_T *
ret_maparg(int argcount,
	type2_T *argtypes UNUSED,
	type_T	**decl_type UNUSED)
{
    // Assume that if the fourth argument is passed it's non-zero
    if (argcount == 4)
	return &t_dict_any;
    return &t_string;
}

/*
 * Array with names and number of arguments of all internal functions
 * MUST BE KEPT SORTED IN strcmp() ORDER FOR BINARY SEARCH!
 *
 * The builtin function may be varargs. In that case
 *	- f_max_argc == VARGS
 *	- For varargs, f_argcheck must be NULL terminated. The last non-null
 *	  entry in f_argcheck should validate all the remaining args.
 */
typedef struct
{
    char	*f_name;	// function name
    char	f_min_argc;	// minimal number of arguments
    char	f_max_argc;	// maximal number of arguments
    char	f_argtype;	// for method: FEARG_ values; bits FE_
    argcheck_T	*f_argcheck;	// list of functions to check argument types;
				// use "arg_any" (not NULL) to accept an
				// argument of any type
    type_T	*(*f_retfunc)(int argcount, type2_T *argtypes,
							   type_T **decl_type);
				// return type function
    void	(*f_func)(typval_T *args, typval_T *rvar);
				// implementation of function
} funcentry_T;

// Set f_max_argc to VARGS for varargs.
#define VARGS    CHAR_MAX

// values for f_argtype; zero means it cannot be used as a method
#define FEARG_1	    0x01    // base is the first argument
#define FEARG_2     0x02    // base is the second argument
#define FEARG_3     0x03    // base is the third argument
#define FEARG_4     0x04    // base is the fourth argument
#define FEARG_MASK  0x0F    // bits in f_argtype used as argument index
#define FE_X	    0x10    // builtin accepts a non-value (class, typealias)

#if defined(HAVE_MATH_H)
# define MATH_FUNC(name) name
#else
# define MATH_FUNC(name) NULL
#endif
#ifdef FEAT_TIMERS
# define TIMER_FUNC(name) name
#else
# define TIMER_FUNC(name) NULL
#endif
#ifdef FEAT_JOB_CHANNEL
# define JOB_FUNC(name) name
#else
# define JOB_FUNC(name) NULL
#endif
#ifdef FEAT_PROP_POPUP
# define PROP_FUNC(name) name
#else
# define PROP_FUNC(name) NULL
#endif
#ifdef FEAT_SIGNS
# define SIGN_FUNC(name) name
#else
# define SIGN_FUNC(name) NULL
#endif
#ifdef FEAT_SOUND
# define SOUND_FUNC(name) name
#else
# define SOUND_FUNC(name) NULL
#endif
#ifdef FEAT_TERMINAL
# define TERM_FUNC(name) name
#else
# define TERM_FUNC(name) NULL
#endif

static const funcentry_T global_functions[] =
{
    {"abs",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_any,	    f_abs},
    {"acos",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_float,	    f_acos},
    {"add",		2, 2, FEARG_1,	    arg2_listblobmod_item,
			ret_first_arg,	    f_add},
    {"and",		2, 2, FEARG_1,	    arg2_number,
			ret_number,	    f_and},
    {"append",		2, 2, FEARG_2,	    arg2_setline,
			ret_number_bool,    f_append},
    {"appendbufline",	3, 3, FEARG_3,	    arg3_setbufline,
			ret_number_bool,    f_appendbufline},
    {"argc",		0, 1, 0,	    arg1_number,
			ret_number,	    f_argc},
    {"argidx",		0, 0, 0,	    NULL,
			ret_number,	    f_argidx},
    {"arglistid",	0, 2, 0,	    arg2_number,
			ret_number,	    f_arglistid},
    {"argv",		0, 2, 0,	    arg2_number,
			ret_argv,	    f_argv},
    {"asin",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_float,	    f_asin},
    {"assert_beeps",	1, 1, FEARG_1,	    arg1_string,
			ret_number_bool,    f_assert_beeps},
    {"assert_equal",	2, 3, FEARG_2,	    NULL,
			ret_number_bool,    f_assert_equal},
    {"assert_equalfile", 2, 3, FEARG_1,	    arg3_string,
			ret_number_bool,    f_assert_equalfile},
    {"assert_exception", 1, 2, 0,	    arg2_string,
			ret_number_bool,    f_assert_exception},
    {"assert_fails",	1, 5, FEARG_1,	    arg15_assert_fails,
			ret_number_bool,    f_assert_fails},
    {"assert_false",	1, 2, FEARG_1,	    NULL,
			ret_number_bool,    f_assert_false},
    {"assert_inrange",	3, 4, FEARG_3,	    arg34_assert_inrange,
			ret_number_bool,    f_assert_inrange},
    {"assert_match",	2, 3, FEARG_2,	    arg3_string,
			ret_number_bool,    f_assert_match},
    {"assert_nobeep",	1, 1, FEARG_1,	    arg1_string,
			ret_number_bool,    f_assert_nobeep},
    {"assert_notequal",	2, 3, FEARG_2,	    NULL,
			ret_number_bool,    f_assert_notequal},
    {"assert_notmatch",	2, 3, FEARG_2,	    arg3_string,
			ret_number_bool,    f_assert_notmatch},
    {"assert_report",	1, 1, FEARG_1,	    arg1_string,
			ret_number_bool,    f_assert_report},
    {"assert_true",	1, 2, FEARG_1,	    NULL,
			ret_number_bool,    f_assert_true},
    {"atan",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_float,	    f_atan},
    {"atan2",		2, 2, FEARG_1,	    arg2_float_or_nr,
			ret_float,	    f_atan2},
    {"autocmd_add",	1, 1, FEARG_1,	    arg1_list_any,
			ret_number_bool,    f_autocmd_add},
    {"autocmd_delete",	1, 1, FEARG_1,	    arg1_list_any,
			ret_number_bool,    f_autocmd_delete},
    {"autocmd_get",	0, 1, FEARG_1,	    arg1_dict_any,
			ret_list_dict_any,  f_autocmd_get},
    {"balloon_gettext",	0, 0, 0,	    NULL,
			ret_string,
#ifdef FEAT_BEVAL
	    f_balloon_gettext
#else
	    NULL
#endif
			},
    {"balloon_show",	1, 1, FEARG_1,	    arg1_string_or_list_any,
			ret_void,
#ifdef FEAT_BEVAL
	    f_balloon_show
#else
	    NULL
#endif
			},
    {"balloon_split",	1, 1, FEARG_1,	    arg1_string,
			ret_list_string,
#if defined(FEAT_BEVAL_TERM)
	    f_balloon_split
#else
	    NULL
#endif
			},
    {"base64_decode",	1, 1, FEARG_1,	    arg1_string,
			ret_blob,	    f_base64_decode},
    {"base64_encode",	1, 1, FEARG_1,	    arg1_blob,
			ret_string,	    f_base64_encode},
    {"bindtextdomain",	2, 2, 0,	    arg2_string,
			ret_bool,	    f_bindtextdomain},
    {"blob2list",	1, 1, FEARG_1,	    arg1_blob,
			ret_list_number,    f_blob2list},
    {"blob2str",	1, 2, FEARG_1,	    arg2_blob_dict,
			ret_list_string,    f_blob2str},
    {"browse",		4, 4, 0,	    arg4_browse,
			ret_string,	    f_browse},
    {"browsedir",	2, 2, 0,	    arg2_string,
			ret_string,	    f_browsedir},
    {"bufadd",		1, 1, FEARG_1,	    arg1_string,
			ret_number,	    f_bufadd},
    {"bufexists",	1, 1, FEARG_1,	    arg1_buffer,
			ret_number_bool,    f_bufexists},
    {"buffer_exists",	1, 1, FEARG_1,	    arg1_buffer,	// obsolete
			ret_number_bool,    f_bufexists},
    {"buffer_name",	0, 1, FEARG_1,	    arg1_buffer,	// obsolete
			ret_string,	    f_bufname},
    {"buffer_number",	0, 1, FEARG_1,	    arg1_buffer,	// obsolete
			ret_number,	    f_bufnr},
    {"buflisted",	1, 1, FEARG_1,	    arg1_buffer,
			ret_number_bool,    f_buflisted},
    {"bufload",		1, 1, FEARG_1,	    arg1_buffer,
			ret_void,	    f_bufload},
    {"bufloaded",	1, 1, FEARG_1,	    arg1_buffer,
			ret_number_bool,    f_bufloaded},
    {"bufname",		0, 1, FEARG_1,	    arg1_buffer,
			ret_string,	    f_bufname},
    {"bufnr",		0, 2, FEARG_1,	    arg2_buffer_bool,
			ret_number,	    f_bufnr},
    {"bufwinid",	1, 1, FEARG_1,	    arg1_buffer,
			ret_number,	    f_bufwinid},
    {"bufwinnr",	1, 1, FEARG_1,	    arg1_buffer,
			ret_number,	    f_bufwinnr},
    {"byte2line",	1, 1, FEARG_1,	    arg1_number,
			ret_number,	    f_byte2line},
    {"byteidx",		2, 3, FEARG_1,	    arg3_string_number_bool,
			ret_number,	    f_byteidx},
    {"byteidxcomp",	2, 3, FEARG_1,	    arg3_string_number_bool,
			ret_number,	    f_byteidxcomp},
    {"call",		2, 3, FEARG_1,	    arg3_any_list_dict,
			ret_any,	    f_call},
    {"ceil",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_float,	    f_ceil},
    {"ch_canread",	1, 1, FEARG_1,	    arg1_chan_or_job,
			ret_number_bool,    JOB_FUNC(f_ch_canread)},
    {"ch_close",	1, 1, FEARG_1,	    arg1_chan_or_job,
			ret_void,	    JOB_FUNC(f_ch_close)},
    {"ch_close_in",	1, 1, FEARG_1,	    arg1_chan_or_job,
			ret_void,	    JOB_FUNC(f_ch_close_in)},
    {"ch_evalexpr",	2, 3, FEARG_1,	    arg23_chanexpr,
			ret_any,	    JOB_FUNC(f_ch_evalexpr)},
    {"ch_evalraw",	2, 3, FEARG_1,	    arg23_chanraw,
			ret_any,	    JOB_FUNC(f_ch_evalraw)},
    {"ch_getbufnr",	2, 2, FEARG_1,	    arg2_chan_or_job_string,
			ret_number,	    JOB_FUNC(f_ch_getbufnr)},
    {"ch_getjob",	1, 1, FEARG_1,	    arg1_chan_or_job,
			ret_job,	    JOB_FUNC(f_ch_getjob)},
    {"ch_info",		1, 1, FEARG_1,	    arg1_chan_or_job,
			ret_dict_any,	    JOB_FUNC(f_ch_info)},
    {"ch_log",		1, 2, FEARG_1,	    arg2_string_chan_or_job,
			ret_void,	    f_ch_log},
    {"ch_logfile",	1, 2, FEARG_1,	    arg2_string,
			ret_void,	    f_ch_logfile},
    {"ch_open",		1, 2, FEARG_1,	    arg2_string_dict,
			ret_channel,	    JOB_FUNC(f_ch_open)},
    {"ch_read",		1, 2, FEARG_1,	    arg2_chan_or_job_dict,
			ret_string,	    JOB_FUNC(f_ch_read)},
    {"ch_readblob",	1, 2, FEARG_1,	    arg2_chan_or_job_dict,
			ret_blob,	    JOB_FUNC(f_ch_readblob)},
    {"ch_readraw",	1, 2, FEARG_1,	    arg2_chan_or_job_dict,
			ret_string,	    JOB_FUNC(f_ch_readraw)},
    {"ch_sendexpr",	2, 3, FEARG_1,	    arg23_chanexpr,
			ret_any,	    JOB_FUNC(f_ch_sendexpr)},
    {"ch_sendraw",	2, 3, FEARG_1,	    arg23_chanraw,
			ret_void,	    JOB_FUNC(f_ch_sendraw)},
    {"ch_setoptions",	2, 2, FEARG_1,	    arg2_chan_or_job_dict,
			ret_void,	    JOB_FUNC(f_ch_setoptions)},
    {"ch_status",	1, 2, FEARG_1,	    arg2_chan_or_job_dict,
			ret_string,	    JOB_FUNC(f_ch_status)},
    {"changenr",	0, 0, 0,	    NULL,
			ret_number,	    f_changenr},
    {"char2nr",		1, 2, FEARG_1,	    arg2_string_bool,
			ret_number,	    f_char2nr},
    {"charclass",	1, 1, FEARG_1,	    arg1_string,
			ret_number,	    f_charclass},
    {"charcol",		1, 2, FEARG_1,	    arg2_string_or_list_number,
			ret_number,	    f_charcol},
    {"charidx",		2, 4, FEARG_1,	    arg4_string_number_bool_bool,
			ret_number,	    f_charidx},
    {"chdir",		1, 2, FEARG_1,	    arg2_string,
			ret_string,	    f_chdir},
    {"cindent",		1, 1, FEARG_1,	    arg1_lnum,
			ret_number,	    f_cindent},
    {"clearmatches",	0, 1, FEARG_1,	    arg1_number,
			ret_void,	    f_clearmatches},
    {"cmdcomplete_info",0, 0, 0,	    NULL,
			ret_dict_any,	    f_cmdcomplete_info},
    {"col",		1, 2, FEARG_1,	    arg2_string_or_list_number,
			ret_number,	    f_col},
    {"complete",	2, 2, FEARG_2,	    arg2_number_list,
			ret_void,	    f_complete},
    {"complete_add",	1, 1, FEARG_1,	    arg1_dict_or_string,
			ret_number,	    f_complete_add},
    {"complete_check",	0, 0, 0,	    NULL,
			ret_number_bool,    f_complete_check},
    {"complete_info",	0, 1, FEARG_1,	    arg1_list_string,
			ret_dict_any,	    f_complete_info},
    {"confirm",		1, 4, FEARG_1,	    arg4_string_string_number_string,
			ret_number,	    f_confirm},
    {"copy",		1, 1, FEARG_1,	    NULL,
			ret_copy,	    f_copy},
    {"cos",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_float,	    f_cos},
    {"cosh",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_float,	    f_cosh},
    {"count",		2, 4, FEARG_1,	    arg24_count,
			ret_number,	    f_count},
    {"cscope_connection",0,3, 0,	    arg3_number_string_string,
			ret_number,	    f_cscope_connection},
    {"cursor",		1, 3, FEARG_1,	    arg13_cursor,
			ret_number,	    f_cursor},
    {"debugbreak",	1, 1, FEARG_1,	    arg1_number,
			ret_number,
#ifdef MSWIN
	    f_debugbreak
#else
	    NULL
#endif
			},
    {"deepcopy",	1, 2, FEARG_1,	    arg12_deepcopy,
			ret_copy,	    f_deepcopy},
    {"delete",		1, 2, FEARG_1,	    arg2_string,
			ret_number_bool,    f_delete},
    {"deletebufline",	2, 3, FEARG_1,	    arg3_buffer_lnum_lnum,
			ret_number_bool,    f_deletebufline},
    {"did_filetype",	0, 0, 0,	    NULL,
			ret_number_bool,    f_did_filetype},
    {"diff",		2, 3, FEARG_1,	    arg3_diff,
			ret_any,  f_diff},
    {"diff_filler",	1, 1, FEARG_1,	    arg1_lnum,
			ret_number,	    f_diff_filler},
    {"diff_hlID",	2, 2, FEARG_1,	    arg2_lnum_number,
			ret_number,	    f_diff_hlID},
    {"digraph_get",	1, 1, FEARG_1,	    arg1_string,
			ret_string,	    f_digraph_get},
    {"digraph_getlist",	0, 1, FEARG_1,	    arg1_bool,
			ret_list_string_items, f_digraph_getlist},
    {"digraph_set",	2, 2, FEARG_1,	    arg2_string,
			ret_bool,	f_digraph_set},
    {"digraph_setlist",	1, 1, FEARG_1,	    arg1_list_string,
			ret_bool,	    f_digraph_setlist},
    {"echoraw",		1, 1, FEARG_1,	    arg1_string,
			ret_void,	    f_echoraw},
    {"empty",		1, 1, FEARG_1,	    NULL,
			ret_number_bool,    f_empty},
    {"environ",		0, 0, 0,	    NULL,
			ret_dict_string,    f_environ},
    {"err_teapot",	0, 1, 0,	    NULL,
			ret_number_bool,    f_err_teapot},
    {"escape",		2, 2, FEARG_1,	    arg2_string,
			ret_string,	    f_escape},
    {"eval",		1, 1, FEARG_1,	    arg1_string,
			ret_any,	    f_eval},
    {"eventhandler",	0, 0, 0,	    NULL,
			ret_number_bool,    f_eventhandler},
    {"executable",	1, 1, FEARG_1,	    arg1_string,
			ret_number,	    f_executable},
    {"execute",		1, 2, FEARG_1,	    arg12_execute,
			ret_string,	    f_execute},
    {"exepath",		1, 1, FEARG_1,	    arg1_string,
			ret_string,	    f_exepath},
    {"exists",		1, 1, FEARG_1,	    arg1_string,
			ret_number_bool,    f_exists},
    {"exists_compiled",	1, 1, FEARG_1,	    arg1_string,
			ret_number_bool,    f_exists_compiled},
    {"exp",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_float,	    f_exp},
    {"expand",		1, 3, FEARG_1,	    arg3_string_bool_bool,
			ret_any,	    f_expand},
    {"expandcmd",	1, 2, FEARG_1,	    arg2_string_dict,
			ret_string,	    f_expandcmd},
    {"extend",		2, 3, FEARG_1,	    arg23_extend,
			ret_extend,	    f_extend},
    {"extendnew",	2, 3, FEARG_1,	    arg23_extendnew,
			ret_first_cont,	    f_extendnew},
    {"feedkeys",	1, 2, FEARG_1,	    arg2_string,
			ret_void,	    f_feedkeys},
    {"file_readable",	1, 1, FEARG_1,	    arg1_string,	// obsolete
			ret_number_bool,    f_filereadable},
    {"filecopy",	2, 2, FEARG_1,	    arg2_string,
			ret_number_bool,    f_filecopy},
    {"filereadable",	1, 1, FEARG_1,	    arg1_string,
			ret_number_bool,    f_filereadable},
    {"filewritable",	1, 1, FEARG_1,	    arg1_string,
			ret_number,	    f_filewritable},
    {"filter",		2, 2, FEARG_1,	    arg2_filter,
			ret_first_arg,	    f_filter},
    {"finddir",		1, 3, FEARG_1,	    arg3_string_string_number,
			ret_finddir,	    f_finddir},
    {"findfile",	1, 3, FEARG_1,	    arg3_string_string_number,
			ret_any,	    f_findfile},
    {"flatten",		1, 2, FEARG_1,	    arg2_list_any_number,
			ret_list_any,	    f_flatten},
    {"flattennew",	1, 2, FEARG_1,	    arg2_list_any_number,
			ret_list_any,	    f_flattennew},
    {"float2nr",	1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_number,	    f_float2nr},
    {"floor",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_float,	    f_floor},
    {"fmod",		2, 2, FEARG_1,	    arg2_float_or_nr,
			ret_float,	    f_fmod},
    {"fnameescape",	1, 1, FEARG_1,	    arg1_string,
			ret_string,	    f_fnameescape},
    {"fnamemodify",	2, 2, FEARG_1,	    arg2_string,
			ret_string,	    f_fnamemodify},
    {"foldclosed",	1, 1, FEARG_1,	    arg1_lnum,
			ret_number,	    f_foldclosed},
    {"foldclosedend",	1, 1, FEARG_1,	    arg1_lnum,
			ret_number,	    f_foldclosedend},
    {"foldlevel",	1, 1, FEARG_1,	    arg1_lnum,
			ret_number,	    f_foldlevel},
    {"foldtext",	0, 0, 0,	    NULL,
			ret_string,	    f_foldtext},
    {"foldtextresult",	1, 1, FEARG_1,	    arg1_lnum,
			ret_string,	    f_foldtextresult},
    {"foreach",		2, 2, FEARG_1,	    arg2_foreach,
			ret_first_arg,	    f_foreach},
    {"foreground",	0, 0, 0,	    NULL,
			ret_void,	    f_foreground},
    {"fullcommand",	1, 2, FEARG_1,	    arg2_string_bool,
			ret_string,	    f_fullcommand},
    {"funcref",		1, 3, FEARG_1,	    arg3_any_list_dict,
			ret_func_unknown,   f_funcref},
    {"function",	1, 3, FEARG_1,	    arg3_any_list_dict,
			ret_func_unknown,   f_function},
    {"garbagecollect",	0, 1, 0,	    arg1_bool,
			ret_void,	    f_garbagecollect},
    {"get",		2, 3, FEARG_1,	    arg23_get,
			ret_any,	    f_get},
    {"getbufinfo",	0, 1, FEARG_1,	    arg1_buffer_or_dict_any,
			ret_list_dict_any,  f_getbufinfo},
    {"getbufline",	2, 3, FEARG_1,	    arg3_buffer_lnum_lnum,
			ret_list_string,    f_getbufline},
    {"getbufoneline",	2, 2, FEARG_1,	    arg2_buffer_lnum,
			ret_string,	    f_getbufoneline},
    {"getbufvar",	2, 3, FEARG_1,	    arg3_buffer_string_any,
			ret_any,	    f_getbufvar},
    {"getcellpixels",	0, 0, 0,	    NULL,
			ret_list_any,	    f_getcellpixels},
    {"getcellwidths",	0, 0, 0,	    NULL,
			ret_list_any,	    f_getcellwidths},
    {"getchangelist",	0, 1, FEARG_1,	    arg1_buffer,
			ret_list_any,	    f_getchangelist},
    {"getchar",		0, 2, 0,	    arg12_getchar,
			ret_any,	    f_getchar},
    {"getcharmod",	0, 0, 0,	    NULL,
			ret_number,	    f_getcharmod},
    {"getcharpos",	1, 1, FEARG_1,	    arg1_string,
			ret_list_number,    f_getcharpos},
    {"getcharsearch",	0, 0, 0,	    NULL,
			ret_dict_any,	    f_getcharsearch},
    {"getcharstr",	0, 2, 0,	    arg12_getchar,
			ret_string,	    f_getcharstr},
    {"getcmdcomplpat",	0, 0, 0,	    NULL,
			ret_string,	    f_getcmdcomplpat},
    {"getcmdcompltype",	0, 0, 0,	    NULL,
			ret_string,	    f_getcmdcompltype},
    {"getcmdline",	0, 0, 0,	    NULL,
			ret_string,	    f_getcmdline},
    {"getcmdpos",	0, 0, 0,	    NULL,
			ret_number,	    f_getcmdpos},
    {"getcmdprompt",	0, 0, 0,	    NULL,
			ret_string,	    f_getcmdprompt},
    {"getcmdscreenpos",	0, 0, 0,	    NULL,
			ret_number,	    f_getcmdscreenpos},
    {"getcmdtype",	0, 0, 0,	    NULL,
			ret_string,	    f_getcmdtype},
    {"getcmdwintype",	0, 0, 0,	    NULL,
			ret_string,	    f_getcmdwintype},
    {"getcompletion",	2, 3, FEARG_1,	    arg3_string_string_bool,
			ret_list_string,    f_getcompletion},
    {"getcompletiontype", 1, 1, FEARG_1,    arg1_string,
			ret_string,	    f_getcompletiontype},
    {"getcurpos",	0, 1, FEARG_1,	    arg1_number,
			ret_list_number,    f_getcurpos},
    {"getcursorcharpos", 0, 1, FEARG_1,	    arg1_number,
			ret_list_number,    f_getcursorcharpos},
    {"getcwd",		0, 2, FEARG_1,	    arg2_number,
			ret_string,	    f_getcwd},
    {"getenv",		1, 1, FEARG_1,	    arg1_string,
			ret_any,	    f_getenv},
    {"getfontname",	0, 1, 0,	    arg1_string,
			ret_string,	    f_getfontname},
    {"getfperm",	1, 1, FEARG_1,	    arg1_string,
			ret_string,	    f_getfperm},
    {"getfsize",	1, 1, FEARG_1,	    arg1_string,
			ret_number,	    f_getfsize},
    {"getftime",	1, 1, FEARG_1,	    arg1_string,
			ret_number,	    f_getftime},
    {"getftype",	1, 1, FEARG_1,	    arg1_string,
			ret_string,	    f_getftype},
    {"getimstatus",	0, 0, 0,	    NULL,
			ret_number_bool,    f_getimstatus},
    {"getjumplist",	0, 2, FEARG_1,	    arg2_number,
			ret_list_any,	    f_getjumplist},
    {"getline",		1, 2, FEARG_1,	    arg2_lnum,
			ret_getline,	    f_getline},
    {"getloclist",	1, 2, 0,	    arg2_number_dict_any,
			ret_list_or_dict_1, f_getloclist},
    {"getmarklist",	0, 1, FEARG_1,	    arg1_buffer,
			ret_list_dict_any,  f_getmarklist},
    {"getmatches",	0, 1, 0,	    arg1_number,
			ret_list_dict_any,  f_getmatches},
    {"getmousepos",	0, 0, 0,	    NULL,
			ret_dict_number,    f_getmousepos},
    {"getmouseshape",	0, 0, 0,	    NULL,
			ret_string,	    f_getmouseshape},
    {"getpid",		0, 0, 0,	    NULL,
			ret_number,	    f_getpid},
    {"getpos",		1, 1, FEARG_1,	    arg1_string,
			ret_list_number,    f_getpos},
    {"getqflist",	0, 1, 0,	    arg1_dict_any,
			ret_list_or_dict_0, f_getqflist},
    {"getreg",		0, 3, FEARG_1,	    arg3_string_bool_bool,
			ret_getreg,	    f_getreg},
    {"getreginfo",	0, 1, FEARG_1,	    arg1_string,
			ret_dict_any,	    f_getreginfo},
    {"getregion",	2, 3, FEARG_1,	    arg3_list_list_dict,
			ret_list_string,    f_getregion},
    {"getregionpos",	2, 3, FEARG_1,      arg3_list_list_dict,
			ret_list_regionpos, f_getregionpos},
    {"getregtype",	0, 1, FEARG_1,	    arg1_string,
			ret_string,	    f_getregtype},
    {"getscriptinfo",	0, 1, 0,	    arg1_dict_any,
			ret_list_dict_any,  f_getscriptinfo},
    {"getstacktrace",	0, 0, 0,	    NULL,
			ret_list_dict_any,  f_getstacktrace},
    {"gettabinfo",	0, 1, FEARG_1,	    arg1_number,
			ret_list_dict_any,  f_gettabinfo},
    {"gettabvar",	2, 3, FEARG_1,	    arg3_number_string_any,
			ret_any,	    f_gettabvar},
    {"gettabwinvar",	3, 4, FEARG_1,	    arg4_number_number_string_any,
			ret_any,	    f_gettabwinvar},
    {"gettagstack",	0, 1, FEARG_1,	    arg1_number,
			ret_dict_any,	    f_gettagstack},
    {"gettext",		1, 2, FEARG_1,	    arg2_string,
			ret_string,	    f_gettext},
    {"getwininfo",	0, 1, FEARG_1,	    arg1_number,
			ret_list_dict_any,  f_getwininfo},
    {"getwinpos",	0, 1, FEARG_1,	    arg1_number,
			ret_list_number,    f_getwinpos},
    {"getwinposx",	0, 0, 0,	    NULL,
			ret_number,	    f_getwinposx},
    {"getwinposy",	0, 0, 0,	    NULL,
			ret_number,	    f_getwinposy},
    {"getwinvar",	2, 3, FEARG_1,	    arg3_number_string_any,
			ret_any,	    f_getwinvar},
    {"glob",		1, 4, FEARG_1,	    arg14_glob,
			ret_any,	    f_glob},
    {"glob2regpat",	1, 1, FEARG_1,	    arg1_string,
			ret_string,	    f_glob2regpat},
    {"globpath",	2, 5, FEARG_2,	    arg25_globpath,
			ret_any,	    f_globpath},
    {"has",		1, 2, 0,	    arg2_string_bool,
			ret_number_bool,    f_has},
    {"has_key",		2, 2, FEARG_1,	    arg2_dict_any_string_or_nr,
			ret_number_bool,    f_has_key},
    {"haslocaldir",	0, 2, FEARG_1,	    arg2_number,
			ret_number,	    f_haslocaldir},
    {"hasmapto",	1, 3, FEARG_1,	    arg3_string_string_bool,
			ret_number_bool,    f_hasmapto},
    {"highlightID",	1, 1, FEARG_1,	    arg1_string,	// obsolete
			ret_number,	    f_hlID},
    {"highlight_exists",1, 1, FEARG_1,	    arg1_string,	// obsolete
			ret_number_bool,    f_hlexists},
    {"histadd",		2, 2, FEARG_2,	    arg2_string,
			ret_number_bool,    f_histadd},
    {"histdel",		1, 2, FEARG_1,	    arg2_string_string_or_number,
			ret_number_bool,    f_histdel},
    {"histget",		1, 2, FEARG_1,	    arg2_string_number,
			ret_string,	    f_histget},
    {"histnr",		1, 1, FEARG_1,	    arg1_string,
			ret_number,	    f_histnr},
    {"hlID",		1, 1, FEARG_1,	    arg1_string,
			ret_number,	    f_hlID},
    {"hlexists",	1, 1, FEARG_1,	    arg1_string,
			ret_number_bool,    f_hlexists},
    {"hlget",		0, 2, FEARG_1,	    arg2_string_bool,
			ret_list_dict_any,  f_hlget},
    {"hlset",		1, 1, FEARG_1,	    arg1_list_any,
			ret_number_bool,    f_hlset},
    {"hostname",	0, 0, 0,	    NULL,
			ret_string,	    f_hostname},
    {"iconv",		3, 3, FEARG_1,	    arg3_string,
			ret_string,	    f_iconv},
    {"id",		1, 1, FEARG_1,	    NULL,
			ret_string,	    f_id},
    {"indent",		1, 1, FEARG_1,	    arg1_lnum,
			ret_number,	    f_indent},
    {"index",		2, 4, FEARG_1,	    arg24_index,
			ret_number,	    f_index},
    {"indexof",		2, 3, FEARG_1,	    arg23_index,
			ret_number,	    f_indexof},
    {"input",		1, 3, FEARG_1,	    arg3_string,
			ret_string,	    f_input},
    {"inputdialog",	1, 3, FEARG_1,	    arg3_string,
			ret_string,	    f_inputdialog},
    {"inputlist",	1, 1, FEARG_1,	    arg1_list_string,
			ret_number,	    f_inputlist},
    {"inputrestore",	0, 0, 0,	    NULL,
			ret_number_bool,    f_inputrestore},
    {"inputsave",	0, 0, 0,	    NULL,
			ret_number_bool,    f_inputsave},
    {"inputsecret",	1, 2, FEARG_1,	    arg2_string,
			ret_string,	    f_inputsecret},
    {"insert",		2, 3, FEARG_1,	    arg23_insert,
			ret_first_arg,	    f_insert},
    {"instanceof",	2, VARGS, FEARG_1|FE_X,	arg2_instanceof,
			ret_bool,	    f_instanceof},
    {"interrupt",	0, 0, 0,	    NULL,
			ret_void,	    f_interrupt},
    {"invert",		1, 1, FEARG_1,	    arg1_number,
			ret_number,	    f_invert},
    {"isabsolutepath",	1, 1, FEARG_1,	    arg1_string,
			ret_number_bool,    f_isabsolutepath},
    {"isdirectory",	1, 1, FEARG_1,	    arg1_string,
			ret_number_bool,    f_isdirectory},
    {"isinf",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_number,	    MATH_FUNC(f_isinf)},
    {"islocked",	1, 1, FEARG_1,	    arg1_string,
			ret_number_bool,    f_islocked},
    {"isnan",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_number_bool,    MATH_FUNC(f_isnan)},
    {"items",		1, 1, FEARG_1,	    arg1_list_tuple_dict_blob_or_string,
			ret_list_items,	    f_items},
    {"job_getchannel",	1, 1, FEARG_1,	    arg1_job,
			ret_channel,	    JOB_FUNC(f_job_getchannel)},
    {"job_info",	0, 1, FEARG_1,	    arg1_job,
			ret_job_info,	    JOB_FUNC(f_job_info)},
    {"job_setoptions",	2, 2, FEARG_1,	    arg2_job_dict,
			ret_void,	    JOB_FUNC(f_job_setoptions)},
    {"job_start",	1, 2, FEARG_1,	    arg2_string_or_list_dict,
			ret_job,	    JOB_FUNC(f_job_start)},
    {"job_status",	1, 1, FEARG_1,	    arg1_job,
			ret_string,	    JOB_FUNC(f_job_status)},
    {"job_stop",	1, 2, FEARG_1,	    arg2_job_string_or_number,
			ret_number_bool,    JOB_FUNC(f_job_stop)},
    {"join",		1, 2, FEARG_1,	    arg2_list_or_tuple_string,
			ret_string,	    f_join},
    {"js_decode",	1, 1, FEARG_1,	    arg1_string,
			ret_any,	    f_js_decode},
    {"js_encode",	1, 1, FEARG_1,	    NULL,
			ret_string,	    f_js_encode},
    {"json_decode",	1, 1, FEARG_1,	    arg1_string,
			ret_any,	    f_json_decode},
    {"json_encode",	1, 1, FEARG_1,	    NULL,
			ret_string,	    f_json_encode},
    {"keys",		1, 1, FEARG_1,	    arg1_dict_any,
			ret_list_string,    f_keys},
    {"keytrans",	1, 1, FEARG_1,	    arg1_string,
			ret_string,	    f_keytrans},
    {"last_buffer_nr",	0, 0, 0,	    NULL,	// obsolete
			ret_number,	    f_last_buffer_nr},
    {"len",		1, 1, FEARG_1,	    arg1_len,
			ret_number,	    f_len},
    {"libcall",		3, 3, FEARG_3,	    arg3_libcall,
			ret_string,	    f_libcall},
    {"libcallnr",	3, 3, FEARG_3,	    arg3_libcall,
			ret_number,	    f_libcallnr},
    {"line",		1, 2, FEARG_1,	    arg2_string_number,
			ret_number,	    f_line},
    {"line2byte",	1, 1, FEARG_1,	    arg1_lnum,
			ret_number,	    f_line2byte},
    {"lispindent",	1, 1, FEARG_1,	    arg1_lnum,
			ret_number,	    f_lispindent},
    {"list2blob",	1, 1, FEARG_1,	    arg1_list_number,
			ret_blob,	    f_list2blob},
    {"list2str",	1, 2, FEARG_1,	    arg2_list_number_bool,
			ret_string,	    f_list2str},
    {"list2tuple",	1, 1, FEARG_1,	    arg1_list_any,
			ret_tuple_any,	    f_list2tuple},
    {"listener_add",	1, 3, FEARG_2,	    arg3_any_buffer_bool,
			ret_number,	    f_listener_add},
    {"listener_flush",	0, 1, FEARG_1,	    arg1_buffer,
			ret_void,	    f_listener_flush},
    {"listener_remove",	1, 1, FEARG_1,	    arg1_number,
			ret_number_bool,    f_listener_remove},
    {"localtime",	0, 0, 0,	    NULL,
			ret_number,	    f_localtime},
    {"log",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_float,	    f_log},
    {"log10",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_float,	    f_log10},
    {"luaeval",		1, 2, FEARG_1,	    arg2_string_any,
			ret_any,
#ifdef FEAT_LUA
		f_luaeval
#else
		NULL
#endif
			},
    {"map",		2, 2, FEARG_1,	    arg2_map,
			ret_first_cont,	    f_map},
    {"maparg",		1, 4, FEARG_1,	    arg14_maparg,
			ret_maparg,	    f_maparg},
    {"mapcheck",	1, 3, FEARG_1,	    arg3_string_string_bool,
			ret_string,	    f_mapcheck},
    {"maplist",		0, 1, 0,	    arg1_bool,
			ret_list_dict_any,  f_maplist},
    {"mapnew",		2, 2, FEARG_1,	    arg2_mapnew,
			ret_first_cont,	    f_mapnew},
    {"mapset",		1, 3, FEARG_1,	    arg3_string_or_dict_bool_dict,
			ret_void,	    f_mapset},
    {"match",		2, 4, FEARG_1,	    arg24_match_func,
			ret_any,	    f_match},
    {"matchadd",	2, 5, FEARG_1,	    arg25_matchadd,
			ret_number,	    f_matchadd},
    {"matchaddpos",	2, 5, FEARG_1,	    arg25_matchaddpos,
			ret_number,	    f_matchaddpos},
    {"matcharg",	1, 1, FEARG_1,	    arg1_number,
			ret_list_string,    f_matcharg},
    {"matchbufline",	4, 5, FEARG_1,	    arg45_matchbufline,
			ret_list_any,	    f_matchbufline},
    {"matchdelete",	1, 2, FEARG_1,	    arg2_number,
			ret_number_bool,    f_matchdelete},
    {"matchend",	2, 4, FEARG_1,	    arg24_match_func,
			ret_number,	    f_matchend},
    {"matchfuzzy",	2, 3, FEARG_1,	    arg3_list_string_dict,
			ret_list_any,	    f_matchfuzzy},
    {"matchfuzzypos",	2, 3, FEARG_1,	    arg3_list_string_dict,
			ret_list_any,	    f_matchfuzzypos},
    {"matchlist",	2, 4, FEARG_1,	    arg24_match_func,
			ret_list_string,    f_matchlist},
    {"matchstr",	2, 4, FEARG_1,	    arg24_match_func,
			ret_string,	    f_matchstr},
    {"matchstrlist",	2, 3, FEARG_1,	    arg23_matchstrlist,
			ret_list_any,	    f_matchstrlist},
    {"matchstrpos",	2, 4, FEARG_1,	    arg24_match_func,
			ret_list_any,	    f_matchstrpos},
    {"max",		1, 1, FEARG_1,	    arg1_list_or_tuple_or_dict,
			ret_max_min,	    f_max},
    {"menu_info",	1, 2, FEARG_1,	    arg2_string,
			ret_dict_any,
#ifdef FEAT_MENU
	    f_menu_info
#else
	    NULL
#endif
			},
    {"min",		1, 1, FEARG_1,	    arg1_list_or_tuple_or_dict,
			ret_max_min,	    f_min},
    {"mkdir",		1, 3, FEARG_1,	    arg3_string_string_number,
			ret_number_bool,    f_mkdir},
    {"mode",		0, 1, FEARG_1,	    arg1_bool,
			ret_string,	    f_mode},
    {"mzeval",		1, 1, FEARG_1,	    arg1_string,
			ret_any,
#ifdef FEAT_MZSCHEME
	    f_mzeval
#else
	    NULL
#endif
			},
    {"nextnonblank",	1, 1, FEARG_1,	    arg1_lnum,
			ret_number,	    f_nextnonblank},
    {"ngettext",	3, 4, FEARG_3,	    arg4_string_string_number_string,
			ret_string,	    f_ngettext},
    {"nr2char",		1, 2, FEARG_1,	    arg2_number_bool,
			ret_string,	    f_nr2char},
    {"or",		2, 2, FEARG_1,	    arg2_number,
			ret_number,	    f_or},
    {"pathshorten",	1, 2, FEARG_1,	    arg2_string_number,
			ret_string,	    f_pathshorten},
    {"perleval",	1, 1, FEARG_1,	    arg1_string,
			ret_any,
#ifdef FEAT_PERL
	    f_perleval
#else
	    NULL
#endif
			},
    {"popup_atcursor",	2, 2, FEARG_1,	    arg2_str_or_nr_or_list_dict,
			ret_number,	    PROP_FUNC(f_popup_atcursor)},
    {"popup_beval",	2, 2, FEARG_1,	    arg2_str_or_nr_or_list_dict,
			ret_number,	    PROP_FUNC(f_popup_beval)},
    {"popup_clear",	0, 1, 0,	    arg1_bool,
			ret_void,	    PROP_FUNC(f_popup_clear)},
    {"popup_close",	1, 2, FEARG_1,	    arg2_number_any,
			ret_void,	    PROP_FUNC(f_popup_close)},
    {"popup_create",	2, 2, FEARG_1,	    arg2_str_or_nr_or_list_dict,
			ret_number,	    PROP_FUNC(f_popup_create)},
    {"popup_dialog",	2, 2, FEARG_1,	    arg2_str_or_nr_or_list_dict,
			ret_number,	    PROP_FUNC(f_popup_dialog)},
    {"popup_filter_menu", 2, 2, 0,	    arg2_number_string,
			ret_bool,	    PROP_FUNC(f_popup_filter_menu)},
    {"popup_filter_yesno", 2, 2, 0,	    arg2_number_string,
			ret_bool,	    PROP_FUNC(f_popup_filter_yesno)},
    {"popup_findecho",	0, 0, 0,	    NULL,
			ret_number,	    PROP_FUNC(f_popup_findecho)},
    {"popup_findinfo",	0, 0, 0,	    NULL,
			ret_number,	    PROP_FUNC(f_popup_findinfo)},
    {"popup_findpreview", 0, 0, 0,	    NULL,
			ret_number,	    PROP_FUNC(f_popup_findpreview)},
    {"popup_getoptions", 1, 1, FEARG_1,	    arg1_number,
			ret_dict_any,	    PROP_FUNC(f_popup_getoptions)},
    {"popup_getpos",	1, 1, FEARG_1,	    arg1_number,
			ret_dict_any,	    PROP_FUNC(f_popup_getpos)},
    {"popup_hide",	1, 1, FEARG_1,	    arg1_number,
			ret_void,	    PROP_FUNC(f_popup_hide)},
    {"popup_list",	0, 0, 0,	    NULL,
			ret_list_number,    PROP_FUNC(f_popup_list)},
    {"popup_locate",	2, 2, 0,	    arg2_number,
			ret_number,	    PROP_FUNC(f_popup_locate)},
    {"popup_menu",	2, 2, FEARG_1,	    arg2_str_or_nr_or_list_dict,
			ret_number,	    PROP_FUNC(f_popup_menu)},
    {"popup_move",	2, 2, FEARG_1,	    arg2_number_dict_any,
			ret_void,	    PROP_FUNC(f_popup_move)},
    {"popup_notification", 2, 2, FEARG_1,   arg2_str_or_nr_or_list_dict,
			ret_number,	    PROP_FUNC(f_popup_notification)},
    {"popup_setbuf",	2, 2, FEARG_1,	    arg2_number_buffer,
			ret_number_bool,    PROP_FUNC(f_popup_setbuf)},
    {"popup_setoptions", 2, 2, FEARG_1,	    arg2_number_dict_any,
			ret_void,	    PROP_FUNC(f_popup_setoptions)},
    {"popup_settext",	2, 2, FEARG_1,	    arg2_number_string_or_list,
			ret_void,	    PROP_FUNC(f_popup_settext)},
    {"popup_show",	1, 1, FEARG_1,	    arg1_number,
			ret_number,	    PROP_FUNC(f_popup_show)},
    {"pow",		2, 2, FEARG_1,	    arg2_float_or_nr,
			ret_float,	    f_pow},
    {"preinserted",	0, 0, 0,	    NULL,
			ret_number_bool,    f_preinserted},
    {"prevnonblank",	1, 1, FEARG_1,	    arg1_lnum,
			ret_number,	    f_prevnonblank},
    {"printf",		1, 19, FEARG_2,	    arg119_printf,
			ret_string,	    f_printf},
    {"prompt_getprompt", 1, 1, FEARG_1,	    arg1_buffer,
			ret_string,	    JOB_FUNC(f_prompt_getprompt)},
    {"prompt_setcallback", 2, 2, FEARG_1,   arg2_buffer_any,
			ret_void,	    JOB_FUNC(f_prompt_setcallback)},
    {"prompt_setinterrupt", 2, 2, FEARG_1,  arg2_buffer_any,
			ret_void,	    JOB_FUNC(f_prompt_setinterrupt)},
    {"prompt_setprompt", 2, 2, FEARG_1,	    arg2_buffer_string,
			ret_void,	    JOB_FUNC(f_prompt_setprompt)},
    {"prop_add",	3, 3, FEARG_1,	    arg3_number_number_dict,
			ret_number,	    PROP_FUNC(f_prop_add)},
    {"prop_add_list",	2, 2, FEARG_1,	    arg2_dict_any_list_any,
			ret_void,	    PROP_FUNC(f_prop_add_list)},
    {"prop_clear",	1, 3, FEARG_1,	    arg3_number_number_dict,
			ret_void,	    PROP_FUNC(f_prop_clear)},
    {"prop_find",	1, 2, FEARG_1,	    arg2_dict_string,
			ret_dict_any,	    PROP_FUNC(f_prop_find)},
    {"prop_list",	1, 2, FEARG_1,	    arg2_number_dict_any,
			ret_list_dict_any,  PROP_FUNC(f_prop_list)},
    {"prop_remove",	1, 3, FEARG_1,	    arg3_dict_number_number,
			ret_number,	    PROP_FUNC(f_prop_remove)},
    {"prop_type_add",	2, 2, FEARG_1,	    arg2_string_dict,
			ret_void,	    PROP_FUNC(f_prop_type_add)},
    {"prop_type_change", 2, 2, FEARG_1,	    arg2_string_dict,
			ret_void,	    PROP_FUNC(f_prop_type_change)},
    {"prop_type_delete", 1, 2, FEARG_1,	    arg2_string_dict,
			ret_void,	    PROP_FUNC(f_prop_type_delete)},
    {"prop_type_get",	1, 2, FEARG_1,	    arg2_string_dict,
			ret_dict_any,	    PROP_FUNC(f_prop_type_get)},
    {"prop_type_list",	0, 1, FEARG_1,	    arg1_dict_any,
			ret_list_string,    PROP_FUNC(f_prop_type_list)},
    {"pum_getpos",	0, 0, 0,	    NULL,
			ret_dict_number,    f_pum_getpos},
    {"pumvisible",	0, 0, 0,	    NULL,
			ret_number_bool,    f_pumvisible},
    {"py3eval",		1, 2, FEARG_1,	    arg2_string_dict,
			ret_any,
#ifdef FEAT_PYTHON3
	    f_py3eval
#else
	    NULL
#endif
	    },
    {"pyeval",		1, 2, FEARG_1,	    arg2_string_dict,
			ret_any,
#ifdef FEAT_PYTHON
	    f_pyeval
#else
	    NULL
#endif
			},
    {"pyxeval",		1, 2, FEARG_1,	    arg2_string_dict,
			ret_any,
#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3)
	    f_pyxeval
#else
	    NULL
#endif
			},
    {"rand",		0, 1, FEARG_1,	    arg1_list_number,
			ret_number,	    f_rand},
    {"range",		1, 3, FEARG_1,	    arg3_number,
			ret_list_number,    f_range},
    {"readblob",	1, 3, FEARG_1,	    arg3_string_number_number,
			ret_blob,	    f_readblob},
    {"readdir",		1, 3, FEARG_1,	    arg3_string_any_dict,
			ret_list_string,    f_readdir},
    {"readdirex",	1, 3, FEARG_1,	    arg3_string_any_dict,
			ret_list_dict_any,  f_readdirex},
    {"readfile",	1, 3, FEARG_1,	    arg3_string_string_number,
			ret_list_string,    f_readfile},
    {"redraw_listener_add", 1, 1, FEARG_1,  arg1_dict_any,
			ret_number,	    f_redraw_listener_add},
    {"redraw_listener_remove", 1, 1, FEARG_1, arg1_number,
			ret_void,	    f_redraw_listener_remove},
    {"reduce",		2, 3, FEARG_1,	    arg23_reduce,
			ret_any,	    f_reduce},
    {"reg_executing",	0, 0, 0,	    NULL,
			ret_string,	    f_reg_executing},
    {"reg_recording",	0, 0, 0,	    NULL,
			ret_string,	    f_reg_recording},
    {"reltime",		0, 2, FEARG_1,	    arg2_list_number,
			ret_list_any,	    f_reltime},
    {"reltimefloat",	1, 1, FEARG_1,	    arg1_list_number,
			ret_float,	    f_reltimefloat},
    {"reltimestr",	1, 1, FEARG_1,	    arg1_list_number,
			ret_string,	    f_reltimestr},
    {"remote_expr",	2, 4, FEARG_1,	    arg24_remote_expr,
			ret_string,	    f_remote_expr},
    {"remote_foreground", 1, 1, FEARG_1,    arg1_string,
			ret_string,	    f_remote_foreground},
    {"remote_peek",	1, 2, FEARG_1,	    arg2_string,
			ret_number,	    f_remote_peek},
    {"remote_read",	1, 2, FEARG_1,	    arg2_string_number,
			ret_string,	    f_remote_read},
    {"remote_send",	2, 3, FEARG_1,	    arg3_string,
			ret_string,	    f_remote_send},
    {"remote_startserver", 1, 1, FEARG_1,   arg1_string,
			ret_void,	    f_remote_startserver},
    {"remove",		2, 3, FEARG_1,	    arg23_remove,
			ret_remove,	    f_remove},
    {"rename",		2, 2, FEARG_1,	    arg2_string,
			ret_number_bool,    f_rename},
    {"repeat",		2, 2, FEARG_1,	    arg2_repeat,
			ret_repeat,	    f_repeat},
    {"resolve",		1, 1, FEARG_1,	    arg1_string,
			ret_string,	    f_resolve},
    {"reverse",		1, 1, FEARG_1,	    arg1_reverse,
			ret_first_arg,	    f_reverse},
    {"round",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_float,	    f_round},
    {"rubyeval",	1, 1, FEARG_1,	    arg1_string,
			ret_any,
#ifdef FEAT_RUBY
	    f_rubyeval
#else
	    NULL
#endif
			},
    {"screenattr",	2, 2, FEARG_1,	    arg2_number,
			ret_number,	    f_screenattr},
    {"screenchar",	2, 2, FEARG_1,	    arg2_number,
			ret_number,	    f_screenchar},
    {"screenchars",	2, 2, FEARG_1,	    arg2_number,
			ret_list_number,    f_screenchars},
    {"screencol",	0, 0, 0,	    NULL,
			ret_number,	    f_screencol},
    {"screenpos",	3, 3, FEARG_1,	    arg3_number,
			ret_dict_number,    f_screenpos},
    {"screenrow",	0, 0, 0,	    NULL,
			ret_number,	    f_screenrow},
    {"screenstring",	2, 2, FEARG_1,	    arg2_number,
			ret_string,	    f_screenstring},
    {"search",		1, 5, FEARG_1,	    arg15_search,
			ret_number,	    f_search},
    {"searchcount",	0, 1, FEARG_1,	    arg1_dict_any,
			ret_dict_any,	    f_searchcount},
    {"searchdecl",	1, 3, FEARG_1,	    arg3_string_bool_bool,
			ret_number_bool,    f_searchdecl},
    {"searchpair",	3, 7, 0,	    arg37_searchpair,
			ret_number,	    f_searchpair},
    {"searchpairpos",	3, 7, 0,	    arg37_searchpair,
			ret_list_number,    f_searchpairpos},
    {"searchpos",	1, 5, FEARG_1,	    arg15_search,
			ret_list_number,    f_searchpos},
    {"server2client",	2, 2, FEARG_1,	    arg2_string,
			ret_number_bool,    f_server2client},
    {"serverlist",	0, 0, 0,	    NULL,
			ret_string,	    f_serverlist},
    {"setbufline",	3, 3, FEARG_3,	    arg3_setbufline,
			ret_number_bool,    f_setbufline},
    {"setbufvar",	3, 3, FEARG_3,	    arg3_buffer_string_any,
			ret_void,	    f_setbufvar},
    {"setcellwidths",	1, 1, FEARG_1,	    arg1_list_any,
			ret_void,	    f_setcellwidths},
    {"setcharpos",	2, 2, FEARG_2,	    arg2_string_list_number,
			ret_number_bool,    f_setcharpos},
    {"setcharsearch",	1, 1, FEARG_1,	    arg1_dict_any,
			ret_void,	    f_setcharsearch},
    {"setcmdline",	1, 2, FEARG_1,	    arg2_string_number,
			ret_number_bool,    f_setcmdline},
    {"setcmdpos",	1, 1, FEARG_1,	    arg1_number,
			ret_number_bool,    f_setcmdpos},
    {"setcursorcharpos", 1, 3, FEARG_1,	    arg13_cursor,
			ret_number_bool,    f_setcursorcharpos},
    {"setenv",		2, 2, FEARG_2,	    arg2_string_any,
			ret_void,	    f_setenv},
    {"setfperm",	2, 2, FEARG_1,	    arg2_string,
			ret_number_bool,    f_setfperm},
    {"setline",		2, 2, FEARG_2,	    arg2_setline,
			ret_number_bool,    f_setline},
    {"setloclist",	2, 4, FEARG_2,	    arg24_setloclist,
			ret_number_bool,    f_setloclist},
    {"setmatches",	1, 2, FEARG_1,	    arg2_list_any_number,
			ret_number_bool,    f_setmatches},
    {"setpos",		2, 2, FEARG_2,	    arg2_string_list_number,
			ret_number_bool,    f_setpos},
    {"setqflist",	1, 3, FEARG_1,	    arg13_setqflist,
			ret_number_bool,    f_setqflist},
    {"setreg",		2, 3, FEARG_2,	    arg3_string_any_string,
			ret_number_bool,    f_setreg},
    {"settabvar",	3, 3, FEARG_3,	    arg3_number_string_any,
			ret_void,	    f_settabvar},
    {"settabwinvar",	4, 4, FEARG_4,	    arg4_number_number_string_any,
			ret_void,	    f_settabwinvar},
    {"settagstack",	2, 3, FEARG_2,	    arg23_settagstack,
			ret_number_bool,    f_settagstack},
    {"setwinvar",	3, 3, FEARG_3,	    arg3_number_string_any,
			ret_void,	    f_setwinvar},
    {"sha256",		1, 1, FEARG_1,	    arg1_string_or_blob,
			ret_string,
#ifdef FEAT_CRYPT
	    f_sha256
#else
	    NULL
#endif
			},
    {"shellescape",	1, 2, FEARG_1,	    arg2_string_bool,
			ret_string,	    f_shellescape},
    {"shiftwidth",	0, 1, FEARG_1,	    arg1_number,
			ret_number,	    f_shiftwidth},
    {"sign_define",	1, 2, FEARG_1,	    arg2_string_or_list_dict,
			ret_any,	    SIGN_FUNC(f_sign_define)},
    {"sign_getdefined",	0, 1, FEARG_1,	    arg1_string,
			ret_list_dict_any,  SIGN_FUNC(f_sign_getdefined)},
    {"sign_getplaced",	0, 2, FEARG_1,	    arg02_sign_getplaced,
			ret_list_dict_any,  SIGN_FUNC(f_sign_getplaced)},
    {"sign_jump",	3, 3, FEARG_1,	    arg3_number_string_buffer,
			ret_number,	    SIGN_FUNC(f_sign_jump)},
    {"sign_place",	4, 5, FEARG_1,	    arg45_sign_place,
			ret_number,	    SIGN_FUNC(f_sign_place)},
    {"sign_placelist",	1, 1, FEARG_1,	    arg1_list_any,
			ret_list_number,    SIGN_FUNC(f_sign_placelist)},
    {"sign_undefine",	0, 1, FEARG_1,	    arg1_string_or_list_string,
			ret_number_bool,    SIGN_FUNC(f_sign_undefine)},
    {"sign_unplace",	1, 2, FEARG_1,	    arg2_string_dict,
			ret_number_bool,    SIGN_FUNC(f_sign_unplace)},
    {"sign_unplacelist", 1, 1, FEARG_1,	    arg1_list_any,
			ret_list_number,    SIGN_FUNC(f_sign_unplacelist)},
    {"simplify",	1, 1, FEARG_1,	    arg1_string,
			ret_string,	    f_simplify},
    {"sin",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_float,	    f_sin},
    {"sinh",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_float,	    f_sinh},
    {"slice",		2, 3, FEARG_1,	    arg23_slice,
			ret_slice,	    f_slice},
    {"sort",		1, 3, FEARG_1,	    arg13_sortuniq,
			ret_first_arg,	    f_sort},
    {"sound_clear",	0, 0, 0,	    NULL,
			ret_void,	    SOUND_FUNC(f_sound_clear)},
    {"sound_playevent",	1, 2, FEARG_1,	    arg2_string_any,
			ret_number,	    SOUND_FUNC(f_sound_playevent)},
    {"sound_playfile",	1, 2, FEARG_1,	    arg2_string_any,
			ret_number,	    SOUND_FUNC(f_sound_playfile)},
    {"sound_stop",	1, 1, FEARG_1,	    arg1_number,
			ret_void,	    SOUND_FUNC(f_sound_stop)},
    {"soundfold",	1, 1, FEARG_1,	    arg1_string,
			ret_string,	    f_soundfold},
    {"spellbadword",	0, 1, FEARG_1,	    arg1_string,
			ret_list_string,    f_spellbadword},
    {"spellsuggest",	1, 3, FEARG_1,	    arg3_string_number_bool,
			ret_list_string,    f_spellsuggest},
    {"split",		1, 3, FEARG_1,	    arg3_string_string_bool,
			ret_list_string,    f_split},
    {"sqrt",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_float,	    f_sqrt},
    {"srand",		0, 1, FEARG_1,	    arg1_number,
			ret_list_number,    f_srand},
    {"state",		0, 1, FEARG_1,	    arg1_string,
			ret_string,	    f_state},
    {"str2blob",	1, 2, FEARG_1,	    arg2_list_string_dict,
			ret_blob,	    f_str2blob},
    {"str2float",	1, 2, FEARG_1,	    arg2_string_bool,
			ret_float,	    f_str2float},
    {"str2list",	1, 2, FEARG_1,	    arg2_string_bool,
			ret_list_number,    f_str2list},
    {"str2nr",		1, 3, FEARG_1,	    arg3_string_number_bool,
			ret_number,	    f_str2nr},
    {"strcharlen",	1, 1, FEARG_1,	    arg1_string_or_nr,
			ret_number,	    f_strcharlen},
    {"strcharpart",	2, 4, FEARG_1,	    arg24_strpart,
			ret_string,	    f_strcharpart},
    {"strchars",	1, 2, FEARG_1,	    arg2_string_bool,
			ret_number,	    f_strchars},
    {"strdisplaywidth",	1, 2, FEARG_1,	    arg2_string_number,
			ret_number,	    f_strdisplaywidth},
    {"strftime",	1, 2, FEARG_1,	    arg2_string_number,
			ret_string,
#ifdef HAVE_STRFTIME
	    f_strftime
#else
	    NULL
#endif
			},
    {"strgetchar",	2, 2, FEARG_1,	    arg2_string_number,
			ret_number,	    f_strgetchar},
    {"stridx",		2, 3, FEARG_1,	    arg3_string_string_number,
			ret_number,	    f_stridx},
    {"string",		1, 1, FEARG_1|FE_X, NULL,
			ret_string,	    f_string},
    {"strlen",		1, 1, FEARG_1,	    arg1_string_or_nr,
			ret_number,	    f_strlen},
    {"strpart",		2, 4, FEARG_1,	    arg24_strpart,
			ret_string,	    f_strpart},
    {"strptime",	2, 2, FEARG_1,	    arg2_string,
			ret_number,
#ifdef HAVE_STRPTIME
	    f_strptime
#else
	    NULL
#endif
			},
    {"strridx",		2, 3, FEARG_1,	    arg3_string_string_number,
			ret_number,	    f_strridx},
    {"strtrans",	1, 1, FEARG_1,	    arg1_string,
			ret_string,	    f_strtrans},
    {"strutf16len",	1, 2, FEARG_1,	    arg2_string_bool,
			ret_number,	    f_strutf16len},
    {"strwidth",	1, 1, FEARG_1,	    arg1_string,
			ret_number,	    f_strwidth},
    {"submatch",	1, 2, FEARG_1,	    arg2_number_bool,
			ret_string,	    f_submatch},
    {"substitute",	4, 4, FEARG_1,	    arg4_string_string_any_string,
			ret_string,	    f_substitute},
    {"swapfilelist",	0, 0, 0,	    NULL,
			ret_list_string,    f_swapfilelist},
    {"swapinfo",	1, 1, FEARG_1,	    arg1_string,
			ret_dict_any,	    f_swapinfo},
    {"swapname",	1, 1, FEARG_1,	    arg1_buffer,
			ret_string,	    f_swapname},
    {"synID",		3, 3, 0,	    arg3_lnum_number_bool,
			ret_number,	    f_synID},
    {"synIDattr",	2, 3, FEARG_1,	    arg3_number_string_string,
			ret_string,	    f_synIDattr},
    {"synIDtrans",	1, 1, FEARG_1,	    arg1_number,
			ret_number,	    f_synIDtrans},
    {"synconcealed",	2, 2, 0,	    arg2_lnum_number,
			ret_list_any,	    f_synconcealed},
    {"synstack",	2, 2, 0,	    arg2_lnum_number,
			ret_list_number,    f_synstack},
    {"system",		1, 2, FEARG_1,	    arg12_system,
			ret_string,	    f_system},
    {"systemlist",	1, 2, FEARG_1,	    arg12_system,
			ret_list_string,    f_systemlist},
    {"tabpagebuflist",	0, 1, FEARG_1,	    arg1_number,
			ret_list_number,    f_tabpagebuflist},
    {"tabpagenr",	0, 1, 0,	    arg1_string,
			ret_number,	    f_tabpagenr},
    {"tabpagewinnr",	1, 2, FEARG_1,	    arg2_number_string,
			ret_number,	    f_tabpagewinnr},
    {"tagfiles",	0, 0, 0,	    NULL,
			ret_list_string,    f_tagfiles},
    {"taglist",		1, 2, FEARG_1,	    arg2_string,
			ret_list_dict_any,  f_taglist},
    {"tan",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_float,	    f_tan},
    {"tanh",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_float,	    f_tanh},
    {"tempname",	0, 0, 0,	    NULL,
			ret_string,	    f_tempname},
    {"term_dumpdiff",	2, 3, FEARG_1,	    arg3_string_string_dict,
			ret_number,	    TERM_FUNC(f_term_dumpdiff)},
    {"term_dumpload",	1, 2, FEARG_1,	    arg2_string_dict,
			ret_number,	    TERM_FUNC(f_term_dumpload)},
    {"term_dumpwrite",	2, 3, FEARG_2,	    arg3_buffer_string_dict,
			ret_void,	    TERM_FUNC(f_term_dumpwrite)},
    {"term_getaltscreen", 1, 1, FEARG_1,    arg1_buffer,
			ret_number,	    TERM_FUNC(f_term_getaltscreen)},
    {"term_getansicolors", 1, 1, FEARG_1,   arg1_buffer,
			ret_list_string,
#if defined(FEAT_TERMINAL) && (defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS))
	    f_term_getansicolors
#else
	    NULL
#endif
			},
    {"term_getattr",	2, 2, FEARG_1,	    arg2_number_string,
			ret_number,	    TERM_FUNC(f_term_getattr)},
    {"term_getcursor",	1, 1, FEARG_1,	    arg1_buffer,
			ret_list_any,	    TERM_FUNC(f_term_getcursor)},
    {"term_getjob",	1, 1, FEARG_1,	    arg1_buffer,
			ret_job,	    TERM_FUNC(f_term_getjob)},
    {"term_getline",	2, 2, FEARG_1,	    arg2_buffer_lnum,
			ret_string,	    TERM_FUNC(f_term_getline)},
    {"term_getscrolled", 1, 1, FEARG_1,	    arg1_buffer,
			ret_number,	    TERM_FUNC(f_term_getscrolled)},
    {"term_getsize",	1, 1, FEARG_1,	    arg1_buffer,
			ret_list_number,    TERM_FUNC(f_term_getsize)},
    {"term_getstatus",	1, 1, FEARG_1,	    arg1_buffer,
			ret_string,	    TERM_FUNC(f_term_getstatus)},
    {"term_gettitle",	1, 1, FEARG_1,	    arg1_buffer,
			ret_string,	    TERM_FUNC(f_term_gettitle)},
    {"term_gettty",	1, 2, FEARG_1,	    arg2_buffer_bool,
			ret_string,	    TERM_FUNC(f_term_gettty)},
    {"term_list",	0, 0, 0,	    NULL,
			ret_list_number,    TERM_FUNC(f_term_list)},
    {"term_scrape",	2, 2, FEARG_1,	    arg2_buffer_lnum,
			ret_list_dict_any,  TERM_FUNC(f_term_scrape)},
    {"term_sendkeys",	2, 2, FEARG_1,	    arg2_buffer_string,
			ret_void,	    TERM_FUNC(f_term_sendkeys)},
    {"term_setansicolors", 2, 2, FEARG_1,   arg2_buffer_list_any,
			ret_void,
#if defined(FEAT_TERMINAL) && (defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS))
	    f_term_setansicolors
#else
	    NULL
#endif
			},
    {"term_setapi",	2, 2, FEARG_1,	    arg2_buffer_string,
			ret_void,	    TERM_FUNC(f_term_setapi)},
    {"term_setkill",	2, 2, FEARG_1,	    arg2_buffer_string,
			ret_void,	    TERM_FUNC(f_term_setkill)},
    {"term_setrestore",	2, 2, FEARG_1,	    arg2_buffer_string,
			ret_void,	    TERM_FUNC(f_term_setrestore)},
    {"term_setsize",	3, 3, FEARG_1,	    arg3_buffer_number_number,
			ret_void,	    TERM_FUNC(f_term_setsize)},
    {"term_start",	1, 2, FEARG_1,	    arg2_string_or_list_dict,
			ret_number,	    TERM_FUNC(f_term_start)},
    {"term_wait",	1, 2, FEARG_1,	    arg2_buffer_number,
			ret_void,	    TERM_FUNC(f_term_wait)},
    {"terminalprops",	0, 0, 0,	    NULL,
			ret_dict_string,    f_terminalprops},
    {"test_alloc_fail",	3, 3, FEARG_1,	    arg3_number,
			ret_void,	    f_test_alloc_fail},
    {"test_autochdir",	0, 0, 0,	    NULL,
			ret_void,	    f_test_autochdir},
    {"test_feedinput",	1, 1, FEARG_1,	    arg1_string,
			ret_void,	    f_test_feedinput},
    {"test_garbagecollect_now",	0, 0, 0,    NULL,
			ret_void,	    f_test_garbagecollect_now},
    {"test_garbagecollect_soon", 0, 0, 0,   NULL,
			ret_void,	    f_test_garbagecollect_soon},
    {"test_getvalue",	1, 1, FEARG_1,	    arg1_string,
			ret_number,	    f_test_getvalue},
    {"test_gui_event",	2, 2, FEARG_1,	    arg2_string_dict,
			ret_bool,	    f_test_gui_event},
    {"test_ignore_error", 1, 1, FEARG_1,    arg1_string,
			ret_void,	    f_test_ignore_error},
    {"test_mswin_event", 2, 2, FEARG_1,     arg2_string_dict,
			ret_number,	    f_test_mswin_event},
    {"test_null_blob",	0, 0, 0,	    NULL,
			ret_blob,	    f_test_null_blob},
    {"test_null_channel", 0, 0, 0,	    NULL,
			ret_channel,	    JOB_FUNC(f_test_null_channel)},
    {"test_null_dict",	0, 0, 0,	    NULL,
			ret_dict_any,	    f_test_null_dict},
    {"test_null_function", 0, 0, 0,	    NULL,
			ret_func_any,	    f_test_null_function},
    {"test_null_job",	0, 0, 0,	    NULL,
			ret_job,	    JOB_FUNC(f_test_null_job)},
    {"test_null_list",	0, 0, 0,	    NULL,
			ret_list_any,	    f_test_null_list},
    {"test_null_partial", 0, 0, 0,	    NULL,
			ret_func_any,	    f_test_null_partial},
    {"test_null_string", 0, 0, 0,	    NULL,
			ret_string,	    f_test_null_string},
    {"test_null_tuple",	0, 0, 0,	    NULL,
			ret_tuple_any,	    f_test_null_tuple},
    {"test_option_not_set", 1, 1, FEARG_1,  arg1_string,
			ret_void,	    f_test_option_not_set},
    {"test_override",	2, 2, FEARG_2,	    arg2_string_number,
			ret_void,	    f_test_override},
    {"test_refcount",	1, 1, FEARG_1|FE_X, NULL,
			ret_number,	    f_test_refcount},
    {"test_setmouse",	2, 2, 0,	    arg2_number,
			ret_void,	    f_test_setmouse},
    {"test_settime",	1, 1, FEARG_1,	    arg1_number,
			ret_void,	    f_test_settime},
    {"test_srand_seed",	0, 1, FEARG_1,	    arg1_number,
			ret_void,	    f_test_srand_seed},
    {"test_unknown",	0, 0, 0,	    NULL,
			ret_any,	    f_test_unknown},
    {"test_void",	0, 0, 0,	    NULL,
			ret_void,	    f_test_void},
    {"timer_info",	0, 1, FEARG_1,	    arg1_number,
			ret_list_dict_any,  TIMER_FUNC(f_timer_info)},
    {"timer_pause",	2, 2, FEARG_1,	    arg2_number_bool,
			ret_void,	    TIMER_FUNC(f_timer_pause)},
    {"timer_start",	2, 3, FEARG_1,	    arg3_number_any_dict,
			ret_number,	    TIMER_FUNC(f_timer_start)},
    {"timer_stop",	1, 1, FEARG_1,	    arg1_number,
			ret_void,	    TIMER_FUNC(f_timer_stop)},
    {"timer_stopall",	0, 0, 0,	    NULL,
			ret_void,	    TIMER_FUNC(f_timer_stopall)},
    {"tolower",		1, 1, FEARG_1,	    arg1_string,
			ret_string,	    f_tolower},
    {"toupper",		1, 1, FEARG_1,	    arg1_string,
			ret_string,	    f_toupper},
    {"tr",		3, 3, FEARG_1,	    arg3_string,
			ret_string,	    f_tr},
    {"trim",		1, 3, FEARG_1,	    arg3_string_string_number,
			ret_string,	    f_trim},
    {"trunc",		1, 1, FEARG_1,	    arg1_float_or_nr,
			ret_float,	    f_trunc},
    {"tuple2list",	1, 1, FEARG_1,	    arg1_tuple_any,
			ret_list_any,	    f_tuple2list},
    {"type",		1, 1, FEARG_1|FE_X, NULL,
			ret_number,	    f_type},
    {"typename",	1, 1, FEARG_1|FE_X, NULL,
			ret_string,	    f_typename},
    {"undofile",	1, 1, FEARG_1,	    arg1_string,
			ret_string,	    f_undofile},
    {"undotree",	0, 1, FEARG_1,	    arg1_buffer,
			ret_dict_any,	    f_undotree},
    {"uniq",		1, 3, FEARG_1,	    arg13_sortuniq,
			ret_first_arg,	    f_uniq},
    {"uri_decode",	1, 1, FEARG_1,	    arg1_string,
			ret_string,	    f_uridecode},
    {"uri_encode",	1, 1, FEARG_1,	    arg1_string,
			ret_string,	    f_uriencode},
    {"utf16idx",	2, 4, FEARG_1,	    arg4_string_number_bool_bool,
			ret_number,	    f_utf16idx},
    {"values",		1, 1, FEARG_1,	    arg1_dict_any,
			ret_list_member,    f_values},
    {"virtcol",		1, 3, FEARG_1,	    arg3_string_or_list_bool_number,
			ret_virtcol,	    f_virtcol},
    {"virtcol2col",	3, 3, FEARG_1,	    arg3_number,
			ret_number,	    f_virtcol2col},
    {"visualmode",	0, 1, 0,	    arg1_bool,
			ret_string,	    f_visualmode},
    {"wildmenumode",	0, 0, 0,	    NULL,
			ret_number,	    f_wildmenumode},
    {"wildtrigger",	0, 0, 0,	    NULL,
			ret_void,	    f_wildtrigger},
    {"win_execute",	2, 3, FEARG_2,	    arg23_win_execute,
			ret_string,	    f_win_execute},
    {"win_findbuf",	1, 1, FEARG_1,	    arg1_number,
			ret_list_number,    f_win_findbuf},
    {"win_getid",	0, 2, FEARG_1,	    arg2_number,
			ret_number,	    f_win_getid},
    {"win_gettype",	0, 1, FEARG_1,	    arg1_number,
			ret_string,	    f_win_gettype},
    {"win_gotoid",	1, 1, FEARG_1,	    arg1_number,
			ret_number_bool,    f_win_gotoid},
    {"win_id2tabwin",	1, 1, FEARG_1,	    arg1_number,
			ret_list_number,    f_win_id2tabwin},
    {"win_id2win",	1, 1, FEARG_1,	    arg1_number,
			ret_number,	    f_win_id2win},
    {"win_move_separator", 2, 2, FEARG_1,   arg2_number,
			ret_number_bool,    f_win_move_separator},
    {"win_move_statusline", 2, 2, FEARG_1,  arg2_number,
			ret_number_bool,    f_win_move_statusline},
    {"win_screenpos",	1, 1, FEARG_1,	    arg1_number,
			ret_list_number,    f_win_screenpos},
    {"win_splitmove",   2, 3, FEARG_1,	    arg3_number_number_dict,
			ret_number_bool,    f_win_splitmove},
    {"winbufnr",	1, 1, FEARG_1,	    arg1_number,
			ret_number,	    f_winbufnr},
    {"wincol",		0, 0, 0,	    NULL,
			ret_number,	    f_wincol},
    {"windowsversion",	0, 0, 0,	    NULL,
			ret_string,	    f_windowsversion},
    {"winheight",	1, 1, FEARG_1,	    arg1_number,
			ret_number,	    f_winheight},
    {"winlayout",	0, 1, FEARG_1,	    arg1_number,
			ret_list_any,	    f_winlayout},
    {"winline",		0, 0, 0,	    NULL,
			ret_number,	    f_winline},
    {"winnr",		0, 1, FEARG_1,	    arg1_string,
			ret_number,	    f_winnr},
    {"winrestcmd",	0, 0, 0,	    NULL,
			ret_string,	    f_winrestcmd},
    {"winrestview",	1, 1, FEARG_1,	    arg1_dict_any,
			ret_void,	    f_winrestview},
    {"winsaveview",	0, 0, 0,	    NULL,
			ret_dict_number,    f_winsaveview},
    {"winwidth",	1, 1, FEARG_1,	    arg1_number,
			ret_number,	    f_winwidth},
    {"wordcount",	0, 0, 0,	    NULL,
			ret_dict_number,    f_wordcount},
    {"writefile",	2, 3, FEARG_1,	    arg23_writefile,
			ret_number_bool,    f_writefile},
    {"xor",		2, 2, FEARG_1,	    arg2_number,
			ret_number,	    f_xor},
};

/*
 * Return true if specified function allows a type as an argument.
 */
    static int
func_allows_type(int idx)
{
    return (global_functions[idx].f_argtype & FE_X) != 0;
}

/*
 * Function given to ExpandGeneric() to obtain the list of internal
 * or user defined function names.
 */
    char_u *
get_function_name(expand_T *xp, int idx)
{
    static int	intidx = -1;
    char_u	*name;

    if (idx == 0)
	intidx = -1;
    if (intidx < 0)
    {
	name = get_user_func_name(xp, idx);
	if (name != NULL)
	{
	    if (*name != NUL && *name != '<'
				      && STRNCMP("g:", xp->xp_pattern, 2) == 0)
		return cat_prefix_varname('g', name);
	    return name;
	}
    }
    if (++intidx < (int)ARRAY_LENGTH(global_functions))
    {
	// Skip if the function doesn't have an implementation (feature not
	// implemented).
	if (global_functions[intidx].f_func == NULL)
	    return (char_u *)"";
	STRCPY(IObuff, global_functions[intidx].f_name);
	STRCAT(IObuff, "(");
	if (global_functions[intidx].f_max_argc == 0)
	    STRCAT(IObuff, ")");
	return IObuff;
    }

    return NULL;
}

/*
 * Function given to ExpandGeneric() to obtain the list of internal or
 * user defined variable or function names.
 */
    char_u *
get_expr_name(expand_T *xp, int idx)
{
    static int	intidx = -1;
    char_u	*name;

    if (idx == 0)
	intidx = -1;
    if (intidx < 0)
    {
	name = get_function_name(xp, idx);
	if (name != NULL)
	    return name;
    }
    return get_user_var_name(xp, ++intidx);
}

/*
 * Find internal function "name" in table "global_functions".
 * Return index, or -1 if not found or "implemented" is TRUE and the function
 * is not implemented.
 */
    static int
find_internal_func_opt(char_u *name, int implemented)
{
    int		first = 0;
    int		last;
    int		cmp;
    int		x;

    last = (int)ARRAY_LENGTH(global_functions) - 1;

    // Find the function name in the table. Binary search.
    while (first <= last)
    {
	x = first + ((unsigned)(last - first) >> 1);
	cmp = STRCMP(name, global_functions[x].f_name);
	if (cmp < 0)
	    last = x - 1;
	else if (cmp > 0)
	    first = x + 1;
	else if (implemented && global_functions[x].f_func == NULL)
	    break;
	else
	    return x;
    }
    return -1;
}

/*
 * Find internal function "name" in table "global_functions".
 * Return index, or -1 if not found or the function is not implemented.
 */
    int
find_internal_func(char_u *name)
{
    return find_internal_func_opt(name, TRUE);
}

    int
has_internal_func(char_u *name)
{
    return find_internal_func_opt(name, TRUE) >= 0;
}

    static int
has_internal_func_name(char_u *name)
{
    return find_internal_func_opt(name, FALSE) >= 0;
}

    char *
internal_func_name(int idx)
{
    return global_functions[idx].f_name;
}

/*
 * Check the argument types for builtin function "idx".
 * Uses the list of types on the type stack: "types".
 * Return FAIL and gives an error message when a type is wrong.
 */
    int
internal_func_check_arg_types(
	type2_T	*types,
	int	idx,
	int	argcount,
	cctx_T	*cctx)
{
    // Some internal functions accept types like Class as arguments. For other
    // functions, check the arguments are not types.
    if (!(func_allows_type(idx)))
    {
	for (int i = 0; i < argcount; ++i)
	    if (check_type_is_value(types[i].type_curr) == FAIL)
		return FAIL;
    }

    const argcheck_T	*argchecks = global_functions[idx].f_argcheck;

    if (argchecks == NULL)
	return OK;

    argcontext_T context;

    context.arg_count = argcount;
    context.arg_types = types;
    context.arg_cctx = cctx;
    for (int i = 0; i < argcount && argchecks[i] != NULL; ++i)
    {
	context.arg_idx = i;
	if (argchecks[i](types[i].type_curr, types[i].type_decl,
							    &context) == FAIL)
	    return FAIL;
    }
    return OK;
}

/*
 * Get the argument count for function "idx".
 * "argcount" is the total argument count, "min_argcount" the non-optional
 * argument count.
 */
    void
internal_func_get_argcount(int idx, int *argcount, int *min_argcount)
{
    *argcount = global_functions[idx].f_max_argc;
    *min_argcount = global_functions[idx].f_min_argc;
}

/*
 * Call the "f_retfunc" function to obtain the return type of function "idx".
 * "decl_type" is set to the declared type.
 * "argtypes" is the list of argument types or NULL when there are no
 * arguments.
 * "argcount" may be less than the actual count when only getting the type.
 */
    type_T *
internal_func_ret_type(
	int	    idx,
	int	    argcount,
	type2_T	    *argtypes,
	type_T	    **decl_type,
	garray_T    *type_gap)
{
    type_T *ret;

    current_type_gap = type_gap;
    *decl_type = NULL;
    ret = global_functions[idx].f_retfunc(argcount, argtypes, decl_type);
    if (*decl_type == NULL)
	*decl_type = ret;
    current_type_gap = NULL;
    return ret;
}

/*
 * Return TRUE if "idx" is for the map() function.
 */
    int
internal_func_is_map(int idx)
{
    return global_functions[idx].f_func == f_map;
}

/*
 * Check the argument count to use for internal function "idx".
 * Returns -1 for failure, 0 if no method base accepted, 1 if method base is
 * first argument, 2 if method base is second argument, etc.  9 if method base
 * is last argument.
 */
    int
check_internal_func(int idx, int argcount)
{
    funcerror_T	    res;
    char	    *name;

    if (argcount < global_functions[idx].f_min_argc)
	res = FCERR_TOOFEW;
    else if (argcount > global_functions[idx].f_max_argc)
	res = FCERR_TOOMANY;
    else
	return global_functions[idx].f_argtype & FEARG_MASK;

    name = internal_func_name(idx);
    if (res == FCERR_TOOMANY)
	semsg(_(e_too_many_arguments_for_function_str), name);
    else
	semsg(_(e_not_enough_arguments_for_function_str), name);
    return -1;
}

/*
 * Some internal functions accept types like Class as arguments. For other
 * functions, check the arguments are not types.
 *
 * Return OK/FAIL.
 */
    static int
check_args_for_type(int idx, int argcount, typval_T *argvars)
{
    if (!func_allows_type(idx))
    {
	for (int i = 0; i < argcount; ++i)
	    if (check_typval_is_value(&argvars[i]) == FAIL)
		return FAIL;
    }
    return OK;
}

    funcerror_T
call_internal_func(
	char_u	    *name,
	int	    argcount,
	typval_T    *argvars,
	typval_T    *rettv)
{
    int i;

    i = find_internal_func(name);
    if (i < 0)
	return FCERR_UNKNOWN;
    if (argcount < global_functions[i].f_min_argc)
	return FCERR_TOOFEW;
    if (argcount > global_functions[i].f_max_argc)
	return FCERR_TOOMANY;
    if (check_args_for_type(i, argcount, argvars) == FAIL)
	return FCERR_OTHER;
    argvars[argcount].v_type = VAR_UNKNOWN;
    global_functions[i].f_func(argvars, rettv);
    return FCERR_NONE;
}

    void
call_internal_func_by_idx(
	int	    idx,
	typval_T    *argvars,
	typval_T    *rettv)
{
    global_functions[idx].f_func(argvars, rettv);
}

/*
 * Invoke a method for base->method().
 */
    funcerror_T
call_internal_method(
	char_u	    *name,
	int	    argcount,
	typval_T    *argvars,
	typval_T    *rettv,
	typval_T    *basetv)
{
    int		fi;
    typval_T	argv[MAX_FUNC_ARGS + 1];

    fi = find_internal_func(name);
    if (fi < 0)
	return FCERR_UNKNOWN;
    if ((global_functions[fi].f_argtype & FEARG_MASK) == 0)
	return FCERR_NOTMETHOD;
    if (argcount + 1 < global_functions[fi].f_min_argc)
	return FCERR_TOOFEW;
    if (argcount + 1 > global_functions[fi].f_max_argc)
	return FCERR_TOOMANY;
    if (check_args_for_type(fi, argcount, argvars) == FAIL)
	return FCERR_OTHER;

    if ((global_functions[fi].f_argtype & FEARG_MASK) == FEARG_2)
    {
	if (argcount < 1)
	    return FCERR_TOOFEW;

	// base value goes second
	argv[0] = argvars[0];
	argv[1] = *basetv;
	for (int i = 1; i < argcount; ++i)
	    argv[i + 1] = argvars[i];
    }
    else if ((global_functions[fi].f_argtype & FEARG_MASK) == FEARG_3)
    {
	if (argcount < 2)
	    return FCERR_TOOFEW;

	// base value goes third
	argv[0] = argvars[0];
	argv[1] = argvars[1];
	argv[2] = *basetv;
	for (int i = 2; i < argcount; ++i)
	    argv[i + 1] = argvars[i];
    }
    else if ((global_functions[fi].f_argtype & FEARG_MASK) == FEARG_4)
    {
	if (argcount < 3)
	    return FCERR_TOOFEW;

	// base value goes fourth
	argv[0] = argvars[0];
	argv[1] = argvars[1];
	argv[2] = argvars[2];
	argv[3] = *basetv;
	for (int i = 3; i < argcount; ++i)
	    argv[i + 1] = argvars[i];
    }
    else
    {
	// FEARG_1: base value goes first
	argv[0] = *basetv;
	for (int i = 0; i < argcount; ++i)
	    argv[i + 1] = argvars[i];
    }
    argv[argcount + 1].v_type = VAR_UNKNOWN;

    if (check_args_for_type(fi, argcount + 1, argv) == FAIL)
	return FCERR_OTHER;

    global_functions[fi].f_func(argv, rettv);
    return FCERR_NONE;
}

/*
 * Return TRUE for a non-zero Number and a non-empty String.
 */
    int
non_zero_arg(typval_T *argvars)
{
    return ((argvars[0].v_type == VAR_NUMBER
		&& argvars[0].vval.v_number != 0)
	    || (argvars[0].v_type == VAR_BOOL
		&& argvars[0].vval.v_number == VVAL_TRUE)
	    || (argvars[0].v_type == VAR_STRING
		&& argvars[0].vval.v_string != NULL
		&& *argvars[0].vval.v_string != NUL));
}

/*
 * "and(expr, expr)" function
 */
    static void
f_and(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL))
	return;

    rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
					& tv_get_number_chk(&argvars[1], NULL);
}

/*
 * "balloon_show()" function
 */
#ifdef FEAT_BEVAL
    static void
f_balloon_gettext(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_STRING;
    if (balloonEval == NULL)
	return;

    if (balloonEval->msg == NULL)
	rettv->vval.v_string = NULL;
    else
	rettv->vval.v_string = vim_strsave(balloonEval->msg);
}

    static void
f_balloon_show(typval_T *argvars, typval_T *rettv UNUSED)
{
    if (balloonEval == NULL)
	return;

    if (in_vim9script()
	    && check_for_string_or_list_arg(argvars, 0) == FAIL)
	return;

    if (argvars[0].v_type == VAR_LIST
# ifdef FEAT_GUI
	    && !gui.in_use
# endif
       )
    {
	list_T *l = argvars[0].vval.v_list;

	// empty list removes the balloon
	post_balloon(balloonEval, NULL,
		l == NULL || l->lv_len == 0 ? NULL : l);
    }
    else
    {
	char_u *mesg;

	if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	    return;

	mesg = tv_get_string_chk(&argvars[0]);
	if (mesg != NULL)
	    // empty string removes the balloon
	    post_balloon(balloonEval, *mesg == NUL ? NULL : mesg, NULL);
    }
}

# if defined(FEAT_BEVAL_TERM)
    static void
f_balloon_split(typval_T *argvars, typval_T *rettv UNUSED)
{
    if (rettv_list_alloc(rettv) != OK)
	return;

    char_u *msg;

    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;
    msg = tv_get_string_chk(&argvars[0]);
    if (msg != NULL)
    {
	pumitem_T	*array;
	int		size = split_message(msg, &array);

	// Skip the first and last item, they are always empty.
	for (int i = 1; i < size - 1; ++i)
	    list_append_string(rettv->vval.v_list, array[i].pum_text, -1);
	while (size > 0)
	    vim_free(array[--size].pum_text);
	vim_free(array);
    }
}
# endif
#endif

// Base64 character set
static const char_u base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

// Base64 decoding table (initialized in init_base64_dec_table() below)
static char_u base64_dec_table[256];

/*
 * Initialize the base64 decoding table
 */
    static void
init_base64_dec_table(void)
{
    static int base64_dec_tbl_initialized = FALSE;

    if (base64_dec_tbl_initialized)
	return;

    // Unsupported characters are set to 0xFF
    vim_memset(base64_dec_table, 0xFF, sizeof(base64_dec_table));

    // Initialize the index for the base64 alphabets
    for (size_t i = 0; i < sizeof(base64_table) - 1; i++)
	base64_dec_table[(char_u)base64_table[i]] = (char_u)i;

    // base64 padding character
    base64_dec_table['='] = 0;

    base64_dec_tbl_initialized = TRUE;
}

/*
 * Encode the bytes in "blob" using base-64 encoding.
 */
    static char_u *
base64_encode(blob_T *blob)
{
    size_t input_len = blob->bv_ga.ga_len;
    size_t encoded_len = ((input_len + 2) / 3) * 4;
    char_u *data = blob->bv_ga.ga_data;

    char_u *encoded = alloc(encoded_len + 1);
    if (encoded == NULL)
	return NULL;

    size_t i, j;
    for (i = 0, j = 0; i < input_len;)
    {
	int_u octet_a = i < input_len ? data[i++] : 0;
	int_u octet_b = i < input_len ? data[i++] : 0;
	int_u octet_c = i < input_len ? data[i++] : 0;

	int_u triple = (octet_a << 16) | (octet_b << 8) | octet_c;

	encoded[j++] = base64_table[(triple >> 18) & 0x3F];
	encoded[j++] = base64_table[(triple >> 12) & 0x3F];
	encoded[j++] = (!octet_b && i >= input_len) ? '='
					: base64_table[(triple >> 6) & 0x3F];
	encoded[j++] = (!octet_c && i >= input_len) ? '='
					: base64_table[triple & 0x3F];
    }
    encoded[j] = NUL;

    return encoded;
}

/*
 * Decode the string "data" using base-64 encoding.
 */
    static void
base64_decode(const char_u *data, blob_T *blob)
{
    size_t input_len = STRLEN(data);

    if (input_len == 0)
	return;

    if (input_len % 4 != 0)
    {
	// Invalid input length
	semsg(_(e_invalid_argument_str), data);
	return;
    }

    init_base64_dec_table();

    size_t decoded_len = (input_len / 4) * 3;
    if (data[input_len - 1] == '=')
	decoded_len--;
    if (data[input_len - 2] == '=')
	decoded_len--;

    size_t i, j;
    for (i = 0, j = 0; i < input_len;)
    {
	int_u sextet_a = base64_dec_table[(char_u)data[i++]];
	int_u sextet_b = base64_dec_table[(char_u)data[i++]];
	int_u sextet_c = base64_dec_table[(char_u)data[i++]];
	int_u sextet_d = base64_dec_table[(char_u)data[i++]];

	if (sextet_a == 0xFF || sextet_b == 0xFF || sextet_c == 0xFF
							|| sextet_d == 0xFF)
	{
	    // Invalid character
	    semsg(_(e_invalid_argument_str), data);
	    ga_clear(&blob->bv_ga);
	    return;
	}

	int_u triple = (sextet_a << 18) | (sextet_b << 12)
						| (sextet_c << 6) | sextet_d;

	if (j < decoded_len)
	{
	    ga_append(&blob->bv_ga, (triple >> 16) & 0xFF);
	    j++;
	}
	if (j < decoded_len)
	{
	    ga_append(&blob->bv_ga, (triple >> 8) & 0xFF);
	    j++;
	}
	if (j < decoded_len)
	{
	    ga_append(&blob->bv_ga, triple & 0xFF);
	    j++;
	}

	if (j == decoded_len)
	{
	    // Check for invalid padding bytes (based on the
	    // "Base64 Malleability in Practice" ACM paper).
	    if ((data[input_len - 2] == '=' && ((sextet_b & 0xF) != 0))
		|| ((data[input_len - 1] == '=') && ((sextet_c & 0x3) != 0)))
	    {
		semsg(_(e_invalid_argument_str), data);
		ga_clear(&blob->bv_ga);
		return;
	    }
	}
    }
}

/*
 * "base64_decode(string)" function
 */
    static void
f_base64_decode(typval_T *argvars, typval_T *rettv)
{
    if (check_for_string_arg(argvars, 0) == FAIL)
	return;

    if (rettv_blob_alloc(rettv) == FAIL)
	return;

    char_u *str = tv_get_string_chk(&argvars[0]);
    if (str != NULL)
	base64_decode(str, rettv->vval.v_blob);
}

/*
 * "base64_encode(blob)" function
 */
    static void
f_base64_encode(typval_T *argvars, typval_T *rettv)
{
    if (check_for_blob_arg(argvars, 0) == FAIL)
	return;

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;

    blob_T *blob = argvars->vval.v_blob;
    if (blob != NULL)
	rettv->vval.v_string = base64_encode(blob);
}

/*
 * Get the buffer from "arg" and give an error and return NULL if it is not
 * valid.
 */
    buf_T *
get_buf_arg(typval_T *arg)
{
    buf_T *buf;

    ++emsg_off;
    buf = tv_get_buf(arg, FALSE);
    --emsg_off;
    if (buf == NULL)
	semsg(_(e_invalid_buffer_name_str), tv_get_string(arg));
    return buf;
}

/*
 * "bindtextdomain(package, path)" function
 */
    static void
f_bindtextdomain(typval_T *argvars, typval_T *rettv)
{
    rettv->v_type = VAR_BOOL;
    rettv->vval.v_number = VVAL_TRUE;

    if (check_for_nonempty_string_arg(argvars, 0) == FAIL
	    || check_for_nonempty_string_arg(argvars, 1) == FAIL)
	return;

    if (strcmp((const char *)argvars[0].vval.v_string, VIMPACKAGE) == 0)
	semsg(_(e_invalid_argument_str), tv_get_string(&argvars[0]));
    else
    {
	if (bindtextdomain((const char *)argvars[0].vval.v_string, (const char *)argvars[1].vval.v_string) == NULL)
	{
	    do_outofmem_msg((long)0);
	    rettv->vval.v_number = VVAL_FALSE;
	}
    }

    return;
}

/*
 * "byte2line(byte)" function
 */
    static void
f_byte2line(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifndef FEAT_BYTEOFF
    rettv->vval.v_number = -1;
#else
    long	boff = 0;

    if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
	return;

    boff = tv_get_number(&argvars[0]) - 1;  // boff gets -1 on type error
    if (boff < 0)
	rettv->vval.v_number = -1;
    else
	rettv->vval.v_number = ml_find_line_or_offset(curbuf,
							  (linenr_T)0, &boff);
#endif
}

/*
 * "call(func, arglist [, dict])" function
 */
    static void
f_call(typval_T *argvars, typval_T *rettv)
{
    char_u	*func;
    partial_T   *partial = NULL;
    dict_T	*selfdict = NULL;
    char_u	*tofree = NULL;

    if (in_vim9script()
	    && (check_for_string_or_func_arg(argvars, 0) == FAIL
		|| check_for_list_arg(argvars, 1) == FAIL
		|| check_for_opt_dict_arg(argvars, 2) == FAIL))
	return;

    if (check_for_list_arg(argvars, 1) == FAIL)
	return;
    if (argvars[1].vval.v_list == NULL)
	return;

    if (argvars[0].v_type == VAR_FUNC)
	func = argvars[0].vval.v_string;
    else if (argvars[0].v_type == VAR_PARTIAL)
    {
	partial = argvars[0].vval.v_partial;
	func = partial_name(partial);
    }
    else
	func = tv_get_string(&argvars[0]);
    if (func == NULL || *func == NUL)
	return;		// type error, empty name or null function

    if (argvars[0].v_type == VAR_STRING)
    {
	char_u	*p = func;
	tofree = trans_function_name(&p, NULL, FALSE, TFN_INT|TFN_QUIET);
	if (tofree == NULL)
	{
	    emsg_funcname(e_unknown_function_str, func);
	    return;
	}
	if (*p == '<')
	{
	    // generic function
	    char_u *s = append_generic_func_type_args(tofree, STRLEN(tofree),
									&p);
	    if (s != NULL)
	    {
		vim_free(tofree);
		tofree = s;
	    }
	}
	func = tofree;
    }

    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	if (check_for_dict_arg(argvars, 2) == FAIL)
	    goto done;

	selfdict = argvars[2].vval.v_dict;
    }

    (void)func_call(func, &argvars[1], partial, selfdict, rettv);

done:
    vim_free(tofree);
}

/*
 * "changenr()" function
 */
    static void
f_changenr(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = curbuf->b_u_seq_cur;
}

/*
 * "char2nr(string)" function
 */
    static void
f_char2nr(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_opt_bool_arg(argvars, 1) == FAIL))
	return;

    if (has_mbyte)
    {
	int	utf8 = 0;

	if (argvars[1].v_type != VAR_UNKNOWN)
	    utf8 = (int)tv_get_bool_chk(&argvars[1], NULL);

	if (utf8)
	    rettv->vval.v_number = utf_ptr2char(tv_get_string(&argvars[0]));
	else
	    rettv->vval.v_number = (*mb_ptr2char)(tv_get_string(&argvars[0]));
    }
    else
	rettv->vval.v_number = tv_get_string(&argvars[0])[0];
}

/*
 * Get the current cursor column and store it in 'rettv'. If 'charcol' is TRUE,
 * returns the character index of the column. Otherwise, returns the byte index
 * of the column.
 */
    static void
get_col(typval_T *argvars, typval_T *rettv, int charcol)
{
    colnr_T	col = 0;
    pos_T	*fp;
    switchwin_T	switchwin;
    int		winchanged = FALSE;

    if (check_for_string_or_list_arg(argvars, 0) == FAIL
	    || check_for_opt_number_arg(argvars, 1) == FAIL)
	return;

    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	tabpage_T	*tp;
	win_T		*wp;

	// use the window specified in the second argument
	wp = win_id2wp_tp((int)tv_get_number(&argvars[1]), &tp);
	if (wp == NULL || tp == NULL)
	    return;

	if (switch_win_noblock(&switchwin, wp, tp, TRUE) != OK)
	    return;

	check_cursor();
	winchanged = TRUE;
    }

    int fnum = curbuf->b_fnum;
    fp = var2fpos(&argvars[0], FALSE, &fnum, charcol);
    if (fp != NULL && fnum == curbuf->b_fnum)
    {
	if (fp->col == MAXCOL)
	{
	    // '> can be MAXCOL, get the length of the line then
	    if (fp->lnum <= curbuf->b_ml.ml_line_count)
		col = ml_get_len(fp->lnum) + 1;
	    else
		col = MAXCOL;
	}
	else
	{
	    col = fp->col + 1;
	    // col(".") when the cursor is on the NUL at the end of the line
	    // because of "coladd" can be seen as an extra column.
	    if (virtual_active() && fp == &curwin->w_cursor)
	    {
		char_u	*p = ml_get_cursor();

		if (curwin->w_cursor.coladd >= (colnr_T)chartabsize(p,
				 curwin->w_virtcol - curwin->w_cursor.coladd))
		{
		    int		l;

		    if (*p != NUL && p[(l = (*mb_ptr2len)(p))] == NUL)
			col += l;
		}
	    }
	}
    }
    rettv->vval.v_number = col;

    if (winchanged)
	restore_win_noblock(&switchwin, TRUE);
}

/*
 * "charcol()" function
 */
    static void
f_charcol(typval_T *argvars, typval_T *rettv)
{
    get_col(argvars, rettv, TRUE);
}

    win_T *
get_optional_window(typval_T *argvars, int idx)
{
    win_T   *win = curwin;

    if (argvars[idx].v_type == VAR_UNKNOWN)
	return curwin;

    win = find_win_by_nr_or_id(&argvars[idx]);
    if (win == NULL)
    {
	emsg(_(e_invalid_window_number));
	return NULL;
    }
    return win;
}

/*
 * "col(string)" function
 */
    static void
f_col(typval_T *argvars, typval_T *rettv)
{
    get_col(argvars, rettv, FALSE);
}

/*
 * "confirm(message, buttons[, default [, type]])" function
 */
    static void
f_confirm(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
    char_u	*message;
    char_u	*buttons = NULL;
    char_u	buf[NUMBUFLEN];
    char_u	buf2[NUMBUFLEN];
    int		def = 1;
    int		type = VIM_GENERIC;
    char_u	*typestr;
    int		error = FALSE;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| (check_for_opt_string_arg(argvars, 1) == FAIL
		    || (argvars[1].v_type != VAR_UNKNOWN
			&& (check_for_opt_number_arg(argvars, 2) == FAIL
			    || (argvars[2].v_type != VAR_UNKNOWN
				&& check_for_opt_string_arg(argvars, 3) == FAIL))))))
	return;

    message = tv_get_string_chk(&argvars[0]);
    if (message == NULL)
	error = TRUE;
    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	buttons = tv_get_string_buf_chk(&argvars[1], buf);
	if (buttons == NULL)
	    error = TRUE;
	if (argvars[2].v_type != VAR_UNKNOWN)
	{
	    def = (int)tv_get_number_chk(&argvars[2], &error);
	    if (argvars[3].v_type != VAR_UNKNOWN)
	    {
		typestr = tv_get_string_buf_chk(&argvars[3], buf2);
		if (typestr == NULL)
		    error = TRUE;
		else
		{
		    switch (TOUPPER_ASC(*typestr))
		    {
			case 'E': type = VIM_ERROR; break;
			case 'Q': type = VIM_QUESTION; break;
			case 'I': type = VIM_INFO; break;
			case 'W': type = VIM_WARNING; break;
			case 'G': type = VIM_GENERIC; break;
		    }
		}
	    }
	}
    }

    if (buttons == NULL || *buttons == NUL)
	buttons = (char_u *)_("&Ok");

    if (!error)
	rettv->vval.v_number = do_dialog(type, NULL, message, buttons,
							    def, NULL, FALSE);
#endif
}

/*
 * "copy()" function
 */
    static void
f_copy(typval_T *argvars, typval_T *rettv)
{
    item_copy(&argvars[0], rettv, FALSE, TRUE, 0);
}

/*
 * Set the cursor position.
 * If "charcol" is TRUE, then use the column number as a character offset.
 * Otherwise use the column number as a byte offset.
 */
    static void
set_cursorpos(typval_T *argvars, typval_T *rettv, int charcol)
{
    long	lnum, col;
    long	coladd = 0;
    int		set_curswant = TRUE;

    if (in_vim9script()
	    && (check_for_string_or_number_or_list_arg(argvars, 0) == FAIL
		|| check_for_opt_number_arg(argvars, 1) == FAIL
		|| (argvars[1].v_type != VAR_UNKNOWN
		    && check_for_opt_number_arg(argvars, 2) == FAIL)))
	return;

    rettv->vval.v_number = -1;
    if (argvars[0].v_type == VAR_LIST)
    {
	pos_T	    pos;
	colnr_T	    curswant = -1;

	if (list2fpos(argvars, &pos, NULL, &curswant, charcol) == FAIL)
	{
	    emsg(_(e_invalid_argument));
	    return;
	}
	lnum = pos.lnum;
	col = pos.col;
	coladd = pos.coladd;
	if (curswant >= 0)
	{
	    curwin->w_curswant = curswant - 1;
	    set_curswant = FALSE;
	}
    }
    else if ((argvars[0].v_type == VAR_NUMBER
					    || argvars[0].v_type == VAR_STRING)
	    && (argvars[1].v_type == VAR_NUMBER
					   || argvars[1].v_type == VAR_STRING))
    {
	lnum = tv_get_lnum(argvars);
	if (lnum < 0)
	    semsg(_(e_invalid_argument_str), tv_get_string(&argvars[0]));
	else if (lnum == 0)
	    lnum = curwin->w_cursor.lnum;
	col = (long)tv_get_number_chk(&argvars[1], NULL);
	if (charcol)
	    col = buf_charidx_to_byteidx(curbuf, lnum, col) + 1;
	if (argvars[2].v_type != VAR_UNKNOWN)
	    coladd = (long)tv_get_number_chk(&argvars[2], NULL);
    }
    else
    {
	emsg(_(e_invalid_argument));
	return;
    }
    if (lnum < 0 || col < 0 || coladd < 0)
	return;		// type error; errmsg already given
    if (lnum > 0)
	curwin->w_cursor.lnum = lnum;
    if (col != MAXCOL && --col < 0)
	col = 0;
    curwin->w_cursor.col = col;
    curwin->w_cursor.coladd = coladd;

    // Make sure the cursor is in a valid position.
    check_cursor();
    // Correct cursor for multi-byte character.
    if (has_mbyte)
	mb_adjust_cursor();

    curwin->w_set_curswant = set_curswant;
    rettv->vval.v_number = 0;
}

/*
 * "cursor(lnum, col)" function, or
 * "cursor(list)"
 *
 * Moves the cursor to the specified line and column.
 * Returns 0 when the position could be set, -1 otherwise.
 */
    static void
f_cursor(typval_T *argvars, typval_T *rettv)
{
    set_cursorpos(argvars, rettv, FALSE);
}

#ifdef MSWIN
/*
 * "debugbreak()" function
 */
    static void
f_debugbreak(typval_T *argvars, typval_T *rettv)
{
    int		pid;

    rettv->vval.v_number = FAIL;
    if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
	return;

    pid = (int)tv_get_number(&argvars[0]);
    if (pid == 0)
    {
	emsg(_(e_invalid_argument));
	return;
    }

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
    if (hProcess == NULL)
	return;

    DebugBreakProcess(hProcess);
    CloseHandle(hProcess);
    rettv->vval.v_number = OK;
}
#endif

/*
 * "deepcopy()" function
 */
    static void
f_deepcopy(typval_T *argvars, typval_T *rettv)
{
    varnumber_T	noref = 0;

    if (check_for_opt_bool_arg(argvars, 1) == FAIL)
	return;

    if (argvars[1].v_type != VAR_UNKNOWN)
	noref = tv_get_bool_chk(&argvars[1], NULL);

    item_copy(&argvars[0], rettv, TRUE, TRUE, noref == 0 ? get_copyID() : 0);
}

/*
 * "did_filetype()" function
 */
    static void
f_did_filetype(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    rettv->vval.v_number = curbuf->b_did_filetype;
}

/*
 * "echoraw({expr})" function
 */
    static void
f_echoraw(typval_T *argvars, typval_T *rettv UNUSED)
{
    char_u *str;

    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    str = tv_get_string_chk(&argvars[0]);
    if (str != NULL && *str != NUL)
    {
	out_str(str);
	out_flush();
    }
}

/*
 * "empty({expr})" function
 */
    static void
f_empty(typval_T *argvars, typval_T *rettv)
{
    int		n = FALSE;

    switch (argvars[0].v_type)
    {
	case VAR_STRING:
	case VAR_FUNC:
	    n = argvars[0].vval.v_string == NULL
					  || *argvars[0].vval.v_string == NUL;
	    break;
	case VAR_PARTIAL:
	    n = argvars[0].vval.v_partial == NULL;
	    break;
	case VAR_NUMBER:
	    n = argvars[0].vval.v_number == 0;
	    break;
	case VAR_FLOAT:
	    n = argvars[0].vval.v_float == 0.0;
	    break;
	case VAR_LIST:
	    n = argvars[0].vval.v_list == NULL
					|| argvars[0].vval.v_list->lv_len == 0;
	    break;
	case VAR_TUPLE:
	    n = tuple_len(argvars[0].vval.v_tuple) == 0;
	    break;
	case VAR_DICT:
	    n = argvars[0].vval.v_dict == NULL
			|| argvars[0].vval.v_dict->dv_hashtab.ht_used == 0;
	    break;
	case VAR_BOOL:
	case VAR_SPECIAL:
	    n = argvars[0].vval.v_number != VVAL_TRUE;
	    break;
	case VAR_CLASS:
	    n = argvars[0].vval.v_class != NULL;
	    break;
	case VAR_OBJECT:
	    n = object_empty(argvars[0].vval.v_object);
	    break;

	case VAR_BLOB:
	    n = argvars[0].vval.v_blob == NULL
		|| argvars[0].vval.v_blob->bv_ga.ga_len == 0;
	    break;

	case VAR_JOB:
#ifdef FEAT_JOB_CHANNEL
	    n = argvars[0].vval.v_job == NULL
			   || argvars[0].vval.v_job->jv_status != JOB_STARTED;
	    break;
#endif
	case VAR_CHANNEL:
#ifdef FEAT_JOB_CHANNEL
	    n = argvars[0].vval.v_channel == NULL
			       || !channel_is_open(argvars[0].vval.v_channel);
	    break;
#endif
	case VAR_TYPEALIAS:
	    n = argvars[0].vval.v_typealias == NULL
		|| argvars[0].vval.v_typealias->ta_name == NULL
		|| *argvars[0].vval.v_typealias->ta_name == NUL;
	    break;

	case VAR_UNKNOWN:
	case VAR_ANY:
	case VAR_VOID:
	case VAR_INSTR:
	    internal_error_no_abort("f_empty(UNKNOWN)");
	    n = TRUE;
	    break;
    }

    rettv->vval.v_number = n;
}

/*
 * "environ()" function
 */
    static void
f_environ(typval_T *argvars UNUSED, typval_T *rettv)
{
#if !defined(AMIGA)
    int			i = 0;
    char_u		*entry, *value;
# if defined (MSWIN)
#  if !defined(_UCRT)
    extern wchar_t	**_wenviron;
#  endif
# else
    extern char		**environ;
# endif

    if (rettv_dict_alloc(rettv) == FAIL)
	return;

# ifdef MSWIN
    if (*_wenviron == NULL)
	return;
# else
    if (*environ == NULL)
	return;
# endif

    for (i = 0; ; ++i)
    {
# ifdef MSWIN
	short_u		*p;

	if ((p = (short_u *)_wenviron[i]) == NULL)
	    return;
	entry = utf16_to_enc(p, NULL);
# else
	if ((entry = (char_u *)environ[i]) == NULL)
	    return;
	entry = vim_strsave(entry);
# endif
	if (entry == NULL) // out of memory
	    return;
	if ((value = vim_strchr(entry, '=')) == NULL)
	{
	    vim_free(entry);
	    continue;
	}
	*value++ = NUL;
	dict_add_string(rettv->vval.v_dict, (char *)entry, value);
	vim_free(entry);
    }
#endif
}

/*
 * "err_teapot()" function
 */
    static void
f_err_teapot(typval_T *argvars, typval_T *rettv UNUSED)
{
    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	if (argvars[0].v_type == VAR_STRING)
	{
	    char_u *s = tv_get_string_strict(&argvars[0]);
	    if (*skipwhite(s) == NUL)
		return;
	}

	int err = FALSE;
	int do_503 = eval_expr_to_bool(&argvars[0], &err);
	if (!err && do_503)
	{
	    emsg(_(e_coffee_currently_not_available));
	    return;
	}
    }

    emsg(_(e_im_a_teapot));
}

/*
 * "escape({string}, {chars})" function
 */
    static void
f_escape(typval_T *argvars, typval_T *rettv)
{
    char_u	buf[NUMBUFLEN];

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_string_arg(argvars, 1) == FAIL))
	return;

    rettv->vval.v_string = vim_strsave_escaped(tv_get_string(&argvars[0]),
					 tv_get_string_buf(&argvars[1], buf));
    rettv->v_type = VAR_STRING;
}

/*
 * "eval()" function
 */
    static void
f_eval(typval_T *argvars, typval_T *rettv)
{
    char_u	*s, *p;

    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    s = tv_get_string_chk(&argvars[0]);
    if (s != NULL)
	s = skipwhite(s);

    p = s;
    if (s == NULL || eval1(&s, rettv, &EVALARG_EVALUATE) == FAIL)
    {
	if (p != NULL && !aborting())
	    semsg(_(e_invalid_expression_str), p);
	need_clr_eos = FALSE;
	rettv->v_type = VAR_NUMBER;
	rettv->vval.v_number = 0;
    }
    else if (*s != NUL)
	semsg(_(e_trailing_characters_str), s);
}

/*
 * "eventhandler()" function
 */
    static void
f_eventhandler(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = vgetc_busy || input_busy;
}

static garray_T	redir_execute_ga;

/*
 * Append "value[value_len]" to the execute() output.
 */
    void
execute_redir_str(char_u *value, int value_len)
{
    int		len;

    if (value_len == -1)
	len = (int)STRLEN(value);	// Append the entire string
    else
	len = value_len;		// Append only "value_len" characters
    if (ga_grow(&redir_execute_ga, len) == FAIL)
	return;

    mch_memmove((char *)redir_execute_ga.ga_data
	    + redir_execute_ga.ga_len, value, len);
    redir_execute_ga.ga_len += len;
}

#if defined(FEAT_LUA)
/*
 * Get next line from a string containing NL separated lines.
 * Called by do_cmdline() to get the next line.
 * Returns an allocated string, or NULL when at the end of the string.
 */
    static char_u *
get_str_line(
    int	    c UNUSED,
    void    *cookie,
    int	    indent UNUSED,
    getline_opt_T options UNUSED)
{
    char_u	*start = *(char_u **)cookie;
    char_u	*line;
    char_u	*p;

    p = start;
    if (p == NULL || *p == NUL)
	return NULL;
    p = vim_strchr(p, '\n');
    if (p == NULL)
	line = vim_strsave(start);
    else
    {
	line = vim_strnsave(start, p - start);
	p++;
    }

    *(char_u **)cookie = p;
    return line;
}

/*
 * Execute a series of Ex commands in 'str'
 */
    void
execute_cmds_from_string(char_u *str)
{
    do_cmdline(NULL, get_str_line, (void *)&str,
	    DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED);
}
#endif

/*
 * Get next line from a list.
 * Called by do_cmdline() to get the next line.
 * Returns allocated string, or NULL for end of function.
 */
    char_u *
get_list_line(
    int	    c UNUSED,
    void    *cookie,
    int	    indent UNUSED,
    getline_opt_T options UNUSED)
{
    listitem_T **p = (listitem_T **)cookie;
    listitem_T *item = *p;
    char_u	buf[NUMBUFLEN];
    char_u	*s;

    if (item == NULL)
	return NULL;
    s = tv_get_string_buf_chk(&item->li_tv, buf);
    *p = item->li_next;
    return s == NULL ? NULL : vim_strsave(s);
}

/*
 * "execute()" function
 */
    void
execute_common(typval_T *argvars, typval_T *rettv, int arg_off)
{
    char_u	*cmd = NULL;
    list_T	*list = NULL;
    int		save_msg_silent = msg_silent;
    int		save_emsg_silent = emsg_silent;
    int		save_emsg_noredir = emsg_noredir;
    int		save_redir_execute = redir_execute;
    int		save_redir_off = redir_off;
    garray_T	save_ga;
    int		save_msg_col = msg_col;
    int		save_sticky_cmdmod_flags = sticky_cmdmod_flags;
    int		echo_output = FALSE;

    rettv->vval.v_string = NULL;
    rettv->v_type = VAR_STRING;

    if (argvars[arg_off].v_type == VAR_LIST)
    {
	list = argvars[arg_off].vval.v_list;
	if (list == NULL || list->lv_len == 0)
	    // empty list, no commands, empty output
	    return;
	++list->lv_refcount;
    }
    else if (argvars[arg_off].v_type == VAR_JOB
	    || argvars[arg_off].v_type == VAR_CHANNEL)
    {
	semsg(_(e_using_invalid_value_as_string_str),
				       vartype_name(argvars[arg_off].v_type));
	return;
    }
    else
    {
	cmd = tv_get_string_chk(&argvars[arg_off]);
	if (cmd == NULL)
	    return;
    }

    if (argvars[arg_off + 1].v_type != VAR_UNKNOWN)
    {
	char_u	buf[NUMBUFLEN];
	char_u  *s = tv_get_string_buf_chk_strict(&argvars[arg_off + 1], buf,
							      in_vim9script());

	if (s == NULL)
	    return;
	if (*s == NUL)
	    echo_output = TRUE;
	if (STRNCMP(s, "silent", 6) == 0)
	    ++msg_silent;
	if (STRCMP(s, "silent!") == 0)
	{
	    emsg_silent = TRUE;
	    emsg_noredir = TRUE;
	}
    }
    else
	++msg_silent;

    if (redir_execute)
	save_ga = redir_execute_ga;
    ga_init2(&redir_execute_ga, sizeof(char), 500);
    redir_execute = TRUE;
    redir_off = FALSE;
    if (!echo_output)
	msg_col = 0;  // prevent leading spaces

    // For "legacy call execute('cmd')" and "vim9cmd execute('cmd')" apply the
    // command modifiers to "cmd".
    sticky_cmdmod_flags = cmdmod.cmod_flags & (CMOD_LEGACY | CMOD_VIM9CMD);
    if (cmd != NULL)
	do_cmdline_cmd(cmd);
    else
    {
	listitem_T	*item;

	CHECK_LIST_MATERIALIZE(list);
	item = list->lv_first;
	do_cmdline(NULL, get_list_line, (void *)&item,
		      DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED);
	--list->lv_refcount;
    }
    sticky_cmdmod_flags = save_sticky_cmdmod_flags;

    // Need to append a NUL to the result.
    if (ga_grow(&redir_execute_ga, 1) == OK)
    {
	((char *)redir_execute_ga.ga_data)[redir_execute_ga.ga_len] = NUL;
	rettv->vval.v_string = redir_execute_ga.ga_data;
    }
    else
    {
	ga_clear(&redir_execute_ga);
	rettv->vval.v_string = NULL;
    }
    msg_silent = save_msg_silent;
    emsg_silent = save_emsg_silent;
    emsg_noredir = save_emsg_noredir;

    redir_execute = save_redir_execute;
    if (redir_execute)
	redir_execute_ga = save_ga;
    redir_off = save_redir_off;

    // "silent reg" or "silent echo x" leaves msg_col somewhere in the line.
    if (echo_output)
	// When not working silently: put it in column zero.  A following
	// "echon" will overwrite the message, unavoidably.
	msg_col = 0;
    else
	// When working silently: Put it back where it was, since nothing
	// should have been written.
	msg_col = save_msg_col;
}

/*
 * "execute()" function
 */
    static void
f_execute(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script()
	    && (check_for_string_or_list_arg(argvars, 0) == FAIL
		|| check_for_opt_string_arg(argvars, 1) == FAIL))
	return;

    execute_common(argvars, rettv, 0);
}

/*
 * "exists()" function
 */
    void
f_exists(typval_T *argvars, typval_T *rettv)
{
    char_u	*p;
    int		n = FALSE;

    if (in_vim9script() && check_for_nonempty_string_arg(argvars, 0) == FAIL)
	return;

    p = tv_get_string(&argvars[0]);
    if (*p == '$')			// environment variable
    {
	// first try "normal" environment variables (fast)
	if (mch_getenv(p + 1) != NULL)
	    n = TRUE;
	else
	{
	    // try expanding things like $VIM and ${HOME}
	    p = expand_env_save(p);
	    if (p != NULL && *p != '$')
		n = TRUE;
	    vim_free(p);
	}
    }
    else if (*p == '&' || *p == '+')			// option
    {
	n = (eval_option(&p, NULL, TRUE) == OK);
	if (*skipwhite(p) != NUL)
	    n = FALSE;			// trailing garbage
    }
    else if (*p == '*')			// internal or user defined function
    {
	n = function_exists(p + 1, FALSE);
    }
    else if (*p == '?')			// internal function only
    {
	n = has_internal_func_name(p + 1);
    }
    else if (*p == ':')
    {
	n = cmd_exists(p + 1);
    }
    else if (*p == '#')
    {
	if (p[1] == '#')
	    n = autocmd_supported(p + 2);
	else
	    n = au_exists(p + 1);
    }
    else				// internal variable
    {
	n = var_exists(p);
    }

    rettv->vval.v_number = n;
}

    static void
f_exists_compiled(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    emsg(_(e_exists_compiled_can_only_be_used_in_def_function));
}

/*
 * "expand()" function
 */
    static void
f_expand(typval_T *argvars, typval_T *rettv)
{
    char_u	*s;
    size_t	len;
    int		options = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND;
    expand_T	xpc;
    int		error = FALSE;
    char_u	*result;
#ifdef BACKSLASH_IN_FILENAME
    char_u	*p_csl_save = p_csl;
#endif

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_opt_bool_arg(argvars, 1) == FAIL
		|| (argvars[1].v_type != VAR_UNKNOWN
		    && check_for_opt_bool_arg(argvars, 2) == FAIL)))
	return;

#ifdef BACKSLASH_IN_FILENAME
    // avoid using 'completeslash' here
    p_csl = empty_option;
#endif

    rettv->v_type = VAR_STRING;
    if (argvars[1].v_type != VAR_UNKNOWN
	    && argvars[2].v_type != VAR_UNKNOWN
	    && tv_get_bool_chk(&argvars[2], &error)
	    && !error)
	rettv_list_set(rettv, NULL);

    s = tv_get_string(&argvars[0]);
    if (*s == '%' || *s == '#' || *s == '<')
    {
	char	*errormsg = NULL;

	if (p_verbose == 0)
	    ++emsg_off;
	result = eval_vars(s, s, &len, NULL, &errormsg, NULL, FALSE);
	if (p_verbose == 0)
	    --emsg_off;
	else if (errormsg != NULL)
	    emsg(errormsg);
	if (rettv->v_type == VAR_LIST)
	{
	    if (rettv_list_alloc(rettv) == OK && result != NULL)
		list_append_string(rettv->vval.v_list, result, -1);
	    vim_free(result);
	}
	else
	    rettv->vval.v_string = result;
    }
    else
    {
	// When the optional second argument is non-zero, don't remove matches
	// for 'wildignore' and don't put matches for 'suffixes' at the end.
	if (argvars[1].v_type != VAR_UNKNOWN
				    && tv_get_bool_chk(&argvars[1], &error))
	    options |= WILD_KEEP_ALL;
	if (!error)
	{
	    ExpandInit(&xpc);
	    xpc.xp_context = EXPAND_FILES;
	    if (p_wic)
		options += WILD_ICASE;
	    if (rettv->v_type == VAR_STRING)
		rettv->vval.v_string = ExpandOne(&xpc, s, NULL,
							   options, WILD_ALL);
	    else if (rettv_list_alloc(rettv) == OK)
	    {
		ExpandOne(&xpc, s, NULL, options, WILD_ALL_KEEP);
		for (int i = 0; i < xpc.xp_numfiles; i++)
		    list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1);
		ExpandCleanup(&xpc);
	    }
	}
	else
	    rettv->vval.v_string = NULL;
    }
#ifdef BACKSLASH_IN_FILENAME
    p_csl = p_csl_save;
#endif
}

/*
 * "expandcmd()" function
 * Expand all the special characters in a command string.
 */
    static void
f_expandcmd(typval_T *argvars, typval_T *rettv)
{
    exarg_T	eap;
    char_u	*cmdstr;
    char	*errormsg = NULL;
    int		emsgoff = TRUE;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_opt_dict_arg(argvars, 1) == FAIL))
	return;

    if (argvars[1].v_type == VAR_DICT
		&& dict_get_bool(argvars[1].vval.v_dict, "errmsg", VVAL_FALSE))
	emsgoff = FALSE;

    rettv->v_type = VAR_STRING;
    cmdstr = vim_strsave(tv_get_string(&argvars[0]));

    CLEAR_FIELD(eap);
    eap.cmd = cmdstr;
    eap.arg = cmdstr;
    eap.argt |= EX_NOSPC;
    eap.usefilter = FALSE;
    eap.nextcmd = NULL;
    eap.cmdidx = CMD_USER;

    if (emsgoff)
	++emsg_off;
    if (expand_filename(&eap, &cmdstr, &errormsg) == FAIL)
	if (!emsgoff && errormsg != NULL && *errormsg != NUL)
	    emsg(errormsg);
    if (emsgoff)
	--emsg_off;

    rettv->vval.v_string = cmdstr;
}

/*
 * "feedkeys()" function
 */
    static void
f_feedkeys(typval_T *argvars, typval_T *rettv UNUSED)
{
    int		remap = TRUE;
    int		insert = FALSE;
    char_u	*keys, *flags;
    char_u	nbuf[NUMBUFLEN];
    int		typed = FALSE;
    int		execute = FALSE;
    int		context = FALSE;
    int		dangerous = FALSE;
    int		lowlevel = FALSE;

    // This is not allowed in the sandbox.  If the commands would still be
    // executed in the sandbox it would be OK, but it probably happens later,
    // when "sandbox" is no longer set.
    if (check_secure())
	return;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_opt_string_arg(argvars, 1) == FAIL))
	return;

    keys = tv_get_string(&argvars[0]);

    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	flags = tv_get_string_buf(&argvars[1], nbuf);
	for ( ; *flags != NUL; ++flags)
	{
	    switch (*flags)
	    {
		case 'n': remap = FALSE; break;
		case 'm': remap = TRUE; break;
		case 't': typed = TRUE; break;
		case 'i': insert = TRUE; break;
		case 'x': execute = TRUE; break;
		case 'c': context = TRUE; break;
		case '!': dangerous = TRUE; break;
		case 'L': lowlevel = TRUE; break;
	    }
	}
    }

    if (*keys != NUL || execute)
    {
	if (lowlevel
#ifdef FEAT_VTP
		&& (!is_term_win32()
		    || (keys[0] == 3 && ctrl_c_interrupts && typed))
#endif
	   )
	{
#ifdef USE_INPUT_BUF
	    ch_log(NULL, "feedkeys() lowlevel: %s", keys);

	    int len = (int)STRLEN(keys);
	    for (int idx = 0; idx < len; ++idx)
	    {
		// if a CTRL-C was typed, set got_int, similar to what
		// happens in fill_input_buf()
		if (keys[idx] == 3 && ctrl_c_interrupts && typed)
		    got_int = TRUE;
		add_to_input_buf(keys + idx, 1);
	    }
#else
	    emsg(_(e_lowlevel_input_not_supported));
#endif
	}
	else
	{
	    // Need to escape K_SPECIAL and CSI before putting the string in
	    // the typeahead buffer.
	    char_u *keys_esc = vim_strsave_escape_csi(keys);
	    if (keys_esc == NULL)
		return;

	    ch_log(NULL, "feedkeys(%s): %s", typed ? "typed" : "", keys);

	    ins_typebuf(keys_esc, (remap ? REMAP_YES : REMAP_NONE),
				   insert ? 0 : typebuf.tb_len, !typed, FALSE);
	    if (vgetc_busy
#ifdef FEAT_TIMERS
		    || timer_busy
#endif
		    || input_busy)
		typebuf_was_filled = TRUE;

	    vim_free(keys_esc);
	}

	if (execute)
	{
	    int		save_msg_scroll = msg_scroll;
	    sctx_T	save_sctx;

	    // Avoid a 1 second delay when the keys start Insert mode.
	    msg_scroll = FALSE;

	    ch_log(NULL, "feedkeys() executing");

	    if (context)
	    {
		save_sctx = current_sctx;
		current_sctx.sc_sid = 0;
		current_sctx.sc_version = 0;
	    }

	    if (!dangerous)
	    {
		++ex_normal_busy;
		++in_feedkeys;
	    }
	    ++allow_osc_key;
	    exec_normal(TRUE, lowlevel, TRUE);
	    --allow_osc_key;
	    if (!dangerous)
	    {
		--ex_normal_busy;
		--in_feedkeys;
	    }

	    msg_scroll |= save_msg_scroll;

	    if (context)
		current_sctx = save_sctx;
	}
    }
}

/*
 * "fnameescape({string})" function
 */
    static void
f_fnameescape(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    rettv->vval.v_string = vim_strsave_fnameescape(
					 tv_get_string(&argvars[0]), VSE_NONE);
    rettv->v_type = VAR_STRING;
}

/*
 * "foreground()" function
 */
    static void
f_foreground(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_GUI
    if (gui.in_use)
    {
	gui_mch_set_foreground();
	return;
    }
#endif
#if defined(MSWIN) && (!defined(FEAT_GUI) || defined(VIMDLL))
    win32_set_foreground();
#endif
}

/*
 * "function()" function
 * "funcref()" function
 */
    static void
common_function(typval_T *argvars, typval_T *rettv, int is_funcref)
{
    char_u	*s;
    char_u	*name;
    int		use_string = FALSE;
    partial_T   *arg_pt = NULL;
    char_u	*trans_name = NULL;
    int		is_global = FALSE;
    char_u	*start_bracket = NULL;

    if (in_vim9script()
	    && (check_for_string_or_func_arg(argvars, 0) == FAIL
		|| check_for_opt_list_arg(argvars, 1) == FAIL
		|| (argvars[1].v_type != VAR_UNKNOWN
		    && check_for_opt_dict_arg(argvars, 2) == FAIL)))
	return;

    if (argvars[0].v_type == VAR_FUNC)
    {
	// function(MyFunc, [arg], dict)
	s = argvars[0].vval.v_string;
    }
    else if (argvars[0].v_type == VAR_PARTIAL
					 && argvars[0].vval.v_partial != NULL)
    {
	// function(dict.MyFunc, [arg])
	arg_pt = argvars[0].vval.v_partial;
	s = partial_name(arg_pt);
    }
    else
    {
	// function('MyFunc', [arg], dict)
	s = tv_get_string(&argvars[0]);
	use_string = TRUE;
    }
    if (s == NULL)
    {
	semsg(_(e_invalid_argument_str), "NULL");
	return;
    }

    if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref)
    {
	name = s;
	trans_name = save_function_name(&name, &is_global, FALSE,
		   TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL);
	if (*name == '<')
	{
	    // generic function
	    start_bracket = name;
	    if (skip_generic_func_type_args(&name) == FAIL)
		goto theend;
	}
	if (*name != NUL)
	    s = NULL;
    }

    if (s == NULL || *s == NUL || (use_string && VIM_ISDIGIT(*s))
					 || (is_funcref && trans_name == NULL))
	semsg(_(e_invalid_argument_str),
				  use_string ? tv_get_string(&argvars[0]) : s);
    // Don't check an autoload name for existence here.
    else if (trans_name != NULL && (is_funcref
			 ? find_func(trans_name, is_global) == NULL
			 : !translated_function_exists(trans_name, is_global)))
	semsg(_(e_unknown_function_str_2), s);
    else
    {
	int	dict_idx = 0;
	int	arg_idx = 0;
	list_T	*list = NULL;

	if (STRNCMP(s, "s:", 2) == 0 || STRNCMP(s, "<SID>", 5) == 0)
	    // Expand s: and <SID> into <SNR>nr_, so that the function can
	    // also be called from another script. Using trans_function_name()
	    // would also work, but some plugins depend on the name being
	    // printable text.
	    name = get_scriptlocal_funcname(s);
	else if (trans_name != NULL && *trans_name == K_SPECIAL)
	    name = alloc_printable_func_name(trans_name);
	else
	    name = vim_strsave(s);

	if (argvars[1].v_type != VAR_UNKNOWN)
	{
	    if (argvars[2].v_type != VAR_UNKNOWN)
	    {
		// function(name, [args], dict)
		arg_idx = 1;
		dict_idx = 2;
	    }
	    else if (argvars[1].v_type == VAR_DICT)
		// function(name, dict)
		dict_idx = 1;
	    else
		// function(name, [args])
		arg_idx = 1;
	    if (dict_idx > 0)
	    {
		if (check_for_dict_arg(argvars, dict_idx) == FAIL)
		{
		    vim_free(name);
		    goto theend;
		}
		if (argvars[dict_idx].vval.v_dict == NULL)
		    dict_idx = 0;
	    }
	    if (arg_idx > 0)
	    {
		if (argvars[arg_idx].v_type != VAR_LIST)
		{
		    emsg(_(e_second_argument_of_function_must_be_list_or_dict));
		    vim_free(name);
		    goto theend;
		}
		list = argvars[arg_idx].vval.v_list;
		if (list == NULL || list->lv_len == 0)
		    arg_idx = 0;
		else if (list->lv_len > MAX_FUNC_ARGS)
		{
		    emsg_funcname(e_too_many_arguments_for_function_str, s);
		    vim_free(name);
		    goto theend;
		}
	    }
	}
	if (dict_idx > 0 || arg_idx > 0 || arg_pt != NULL || is_funcref)
	{
	    partial_T	*pt = ALLOC_CLEAR_ONE(partial_T);

	    // result is a VAR_PARTIAL
	    if (pt == NULL)
		vim_free(name);
	    else
	    {
		if (arg_idx > 0 || (arg_pt != NULL && arg_pt->pt_argc > 0))
		{
		    listitem_T	*li;
		    int		i = 0;
		    int		arg_len = 0;
		    int		lv_len = 0;

		    if (arg_pt != NULL)
			arg_len = arg_pt->pt_argc;
		    if (list != NULL)
			lv_len = list->lv_len;
		    pt->pt_argc = arg_len + lv_len;
		    pt->pt_argv = ALLOC_MULT(typval_T, pt->pt_argc);
		    if (pt->pt_argv == NULL)
		    {
			vim_free(pt);
			vim_free(name);
			goto theend;
		    }
		    for (i = 0; i < arg_len; i++)
			copy_tv(&arg_pt->pt_argv[i], &pt->pt_argv[i]);
		    if (lv_len > 0)
		    {
			CHECK_LIST_MATERIALIZE(list);
			FOR_ALL_LIST_ITEMS(list, li)
			    copy_tv(&li->li_tv, &pt->pt_argv[i++]);
		    }
		}

		// For "function(dict.func, [], dict)" and "func" is a partial
		// use "dict".  That is backwards compatible.
		if (dict_idx > 0)
		{
		    // The dict is bound explicitly, pt_auto is FALSE.
		    pt->pt_dict = argvars[dict_idx].vval.v_dict;
		    ++pt->pt_dict->dv_refcount;
		}
		else if (arg_pt != NULL)
		{
		    // If the dict was bound automatically the result is also
		    // bound automatically.
		    pt->pt_dict = arg_pt->pt_dict;
		    pt->pt_auto = arg_pt->pt_auto;
		    if (pt->pt_dict != NULL)
			++pt->pt_dict->dv_refcount;
		    pt->pt_obj = arg_pt->pt_obj;
		    if (pt->pt_obj != NULL)
			++pt->pt_obj->obj_refcount;
		}

		pt->pt_refcount = 1;
		if (arg_pt != NULL && arg_pt->pt_func != NULL)
		{
		    pt->pt_func = arg_pt->pt_func;
		    func_ptr_ref(pt->pt_func);
		    vim_free(name);
		}
		else if (is_funcref)
		{
		    pt->pt_func = find_func(trans_name, is_global);
		    if (IS_GENERIC_FUNC(pt->pt_func) && start_bracket != NULL)
			pt->pt_func = eval_generic_func(pt->pt_func, s,
							&start_bracket);
		    func_ptr_ref(pt->pt_func);
		    vim_free(name);
		}
		else
		{
		    pt->pt_name = name;
		    func_ref(name);
		}

		if (arg_pt != NULL)
		{
		    pt->pt_outer_partial = arg_pt;
		    ++arg_pt->pt_refcount;
		}
	    }
	    rettv->v_type = VAR_PARTIAL;
	    rettv->vval.v_partial = pt;
	}
	else
	{
	    // result is a VAR_FUNC
	    rettv->v_type = VAR_FUNC;
	    if (start_bracket == NULL)
	    {
		rettv->vval.v_string = name;
		func_ref(name);
	    }
	    else
	    {
		// generic function
		STRCPY(IObuff, name);
		STRCAT(IObuff, start_bracket);
		rettv->vval.v_string = vim_strsave(IObuff);
		vim_free(name);
	    }
	}
    }
theend:
    vim_free(trans_name);
}

/*
 * "funcref()" function
 */
    static void
f_funcref(typval_T *argvars, typval_T *rettv)
{
    common_function(argvars, rettv, TRUE);
}

/*
 * "function()" function
 */
    static void
f_function(typval_T *argvars, typval_T *rettv)
{
    common_function(argvars, rettv, FALSE);
}

/*
 * "garbagecollect()" function
 */
    static void
f_garbagecollect(typval_T *argvars, typval_T *rettv UNUSED)
{
    if (in_vim9script() && check_for_opt_bool_arg(argvars, 0) == FAIL)
	return;

    // This is postponed until we are back at the toplevel, because we may be
    // using Lists and Dicts internally.  E.g.: ":echo [garbagecollect()]".
    want_garbage_collect = TRUE;

    if (argvars[0].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[0]) == 1)
	garbage_collect_at_exit = TRUE;
}

/*
 * "get()" function
 */
    static void
f_get(typval_T *argvars, typval_T *rettv)
{
    listitem_T	*li;
    list_T	*l;
    tuple_T	*tuple;
    dictitem_T	*di;
    dict_T	*d;
    typval_T	*tv = NULL;
    int		what_is_dict = FALSE;

    if (argvars[0].v_type == VAR_BLOB)
    {
	int error = FALSE;
	int idx = tv_get_number_chk(&argvars[1], &error);

	if (!error)
	{
	    rettv->v_type = VAR_NUMBER;
	    if (idx < 0)
		idx = blob_len(argvars[0].vval.v_blob) + idx;
	    if (idx < 0 || idx >= blob_len(argvars[0].vval.v_blob))
		rettv->vval.v_number = -1;
	    else
	    {
		rettv->vval.v_number = blob_get(argvars[0].vval.v_blob, idx);
		tv = rettv;
	    }
	}
    }
    else if (argvars[0].v_type == VAR_LIST)
    {
	if ((l = argvars[0].vval.v_list) != NULL)
	{
	    int		error = FALSE;

	    li = list_find(l, (long)tv_get_number_chk(&argvars[1], &error));
	    if (!error && li != NULL)
		tv = &li->li_tv;
	}
    }
    else if (argvars[0].v_type == VAR_TUPLE)
    {
	if ((tuple = argvars[0].vval.v_tuple) != NULL)
	{
	    int		error = FALSE;
	    long	idx;

	    idx = (long)tv_get_number_chk(&argvars[1], &error);
	    if (!error)
		tv = tuple_find(tuple, idx);
	}
    }
    else if (argvars[0].v_type == VAR_DICT)
    {
	if ((d = argvars[0].vval.v_dict) != NULL)
	{
	    di = dict_find(d, tv_get_string(&argvars[1]), -1);
	    if (di != NULL)
		tv = &di->di_tv;
	}
    }
    else if (argvars[0].v_type == VAR_PARTIAL || argvars[0].v_type == VAR_FUNC)
    {
	partial_T	*pt;
	partial_T	fref_pt;

	if (argvars[0].v_type == VAR_PARTIAL)
	    pt = argvars[0].vval.v_partial;
	else
	{
	    CLEAR_FIELD(fref_pt);
	    fref_pt.pt_name = argvars[0].vval.v_string;
	    pt = &fref_pt;
	}

	if (pt != NULL)
	{
	    char_u *what = tv_get_string(&argvars[1]);

	    if (STRCMP(what, "func") == 0 || STRCMP(what, "name") == 0)
	    {
		char_u *name = partial_name(pt);

		rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING);
		if (name == NULL)
		    rettv->vval.v_string = NULL;
		else
		{
		    if (rettv->v_type == VAR_FUNC)
			func_ref(name);
		    if (*what == 'n' && pt->pt_name == NULL
							&& pt->pt_func != NULL)
			// use <SNR> instead of the byte code
			name = printable_func_name(pt->pt_func);
		    rettv->vval.v_string = vim_strsave(name);
		}
	    }
	    else if (STRCMP(what, "dict") == 0)
	    {
		what_is_dict = TRUE;
		if (pt->pt_dict != NULL)
		    rettv_dict_set(rettv, pt->pt_dict);
	    }
	    else if (STRCMP(what, "args") == 0)
	    {
		rettv->v_type = VAR_LIST;
		if (rettv_list_alloc(rettv) == OK)
		{
		    int i;

		    for (i = 0; i < pt->pt_argc; ++i)
			list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]);
		}
	    }
	    else if (STRCMP(what, "arity") == 0)
	    {
		int required = 0, optional = 0, varargs = FALSE;
		char_u *name = partial_name(pt);

		get_func_arity(name, &required, &optional, &varargs);

		rettv->v_type = VAR_DICT;
		if (rettv_dict_alloc(rettv) == OK)
		{
		    dict_T *dict = rettv->vval.v_dict;

		    // Take into account the arguments of the partial, if any.
		    // Note that it is possible to supply more arguments than the function
		    // accepts.
		    if (pt->pt_argc >= required + optional)
			required = optional = 0;
		    else if (pt->pt_argc > required)
		    {
			optional -= pt->pt_argc - required;
			required = 0;
		    }
		    else
			required -= pt->pt_argc;

		    dict_add_number(dict, "required", required);
		    dict_add_number(dict, "optional", optional);
		    dict_add_bool(dict, "varargs", varargs);
		}
	    }
	    else
		semsg(_(e_invalid_argument_str), what);

	    // When {what} == "dict" and pt->pt_dict == NULL, evaluate the
	    // third argument
	    if (!what_is_dict)
		return;
	}
    }
    else
	semsg(_(e_argument_of_str_must_be_list_tuple_dictionary_or_blob), "get()");

    if (tv == NULL)
    {
	if (argvars[2].v_type != VAR_UNKNOWN)
	    copy_tv(&argvars[2], rettv);
    }
    else
	copy_tv(tv, rettv);
}

/*
 * "getcellpixels()" function
 */
    static void
f_getcellpixels(typval_T *argvars UNUSED, typval_T *rettv)
{
    if (rettv_list_alloc(rettv) == FAIL)
	return;

#if defined(FEAT_GUI)
    if (gui.in_use)
    {
	// success pixel size and no gui.
	list_append_number(rettv->vval.v_list, (varnumber_T)gui.char_width);
	list_append_number(rettv->vval.v_list, (varnumber_T)gui.char_height);
    }
    else
#endif
    {
	struct cellsize cs;
#if defined(UNIX)
	mch_calc_cell_size(&cs);
#else
	// Non-Unix CUIs are not supported, so set this to -1x-1.
	cs.cs_xpixel = -1;
	cs.cs_ypixel = -1;
#endif

	// failed get pixel size.
	if (cs.cs_xpixel == -1)
	    return;

	// success pixel size and no gui.
	list_append_number(rettv->vval.v_list, (varnumber_T)cs.cs_xpixel);
	list_append_number(rettv->vval.v_list, (varnumber_T)cs.cs_ypixel);
    }

}

/*
 * "getchangelist()" function
 */
    static void
f_getchangelist(typval_T *argvars, typval_T *rettv)
{
    buf_T	*buf;
    int		i;
    list_T	*l;
    dict_T	*d;
    int		changelistindex;

    if (rettv_list_alloc(rettv) == FAIL)
	return;

    if (in_vim9script() && check_for_opt_buffer_arg(argvars, 0) == FAIL)
	return;

    if (argvars[0].v_type == VAR_UNKNOWN)
	buf = curbuf;
    else
	buf = tv_get_buf_from_arg(&argvars[0]);
    if (buf == NULL)
	return;

    l = list_alloc();
    if (l == NULL)
	return;
    if (list_append_list(rettv->vval.v_list, l) == FAIL)
    {
	vim_free(l);
	return;
    }

    /*
     * The current window change list index tracks only the position for the
     * current buffer. For other buffers use the stored index for the current
     * window, or, if that's not available, the change list length.
     */
    if (buf == curwin->w_buffer)
    {
	changelistindex = curwin->w_changelistidx;
    }
    else
    {
	wininfo_T	*wip;

	FOR_ALL_BUF_WININFO(buf, wip)
	    if (wip->wi_win == curwin)
		break;
	changelistindex = wip != NULL ? wip->wi_changelistidx
							: buf->b_changelistlen;
    }
    list_append_number(rettv->vval.v_list, (varnumber_T)changelistindex);

    for (i = 0; i < buf->b_changelistlen; ++i)
    {
	if (buf->b_changelist[i].lnum == 0)
	    continue;
	if ((d = dict_alloc()) == NULL)
	    return;
	if (list_append_dict(l, d) == FAIL)
	    return;
	dict_add_number(d, "lnum", (long)buf->b_changelist[i].lnum);
	dict_add_number(d, "col", (long)buf->b_changelist[i].col);
	dict_add_number(d, "coladd", (long)buf->b_changelist[i].coladd);
    }
}

    static void
getpos_both(
    typval_T	*argvars,
    typval_T	*rettv,
    int		getcurpos,
    int		charcol)
{
    pos_T	*fp = NULL;
    pos_T	pos;
    win_T	*wp = curwin;
    list_T	*l;
    int		fnum = -1;

    if (rettv_list_alloc(rettv) == OK)
    {
	l = rettv->vval.v_list;
	if (getcurpos)
	{
	    if (argvars[0].v_type != VAR_UNKNOWN)
	    {
		wp = find_win_by_nr_or_id(&argvars[0]);
		if (wp != NULL)
		    fp = &wp->w_cursor;
	    }
	    else
		fp = &curwin->w_cursor;
	    if (fp != NULL && charcol)
	    {
		pos = *fp;
		pos.col =
		    buf_byteidx_to_charidx(wp->w_buffer, pos.lnum, pos.col);
		fp = &pos;
	    }
	}
	else
	    fp = var2fpos(&argvars[0], TRUE, &fnum, charcol);
	if (fnum != -1)
	    list_append_number(l, (varnumber_T)fnum);
	else
	    list_append_number(l, (varnumber_T)0);
	list_append_number(l, (fp != NULL) ? (varnumber_T)fp->lnum
							    : (varnumber_T)0);
	list_append_number(l, (fp != NULL)
		     ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1)
							    : (varnumber_T)0);
	list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd :
							      (varnumber_T)0);
	if (getcurpos)
	{
	    int	    save_set_curswant = curwin->w_set_curswant;
	    colnr_T save_curswant = curwin->w_curswant;
	    colnr_T save_virtcol = curwin->w_virtcol;

	    if (wp == curwin)
		update_curswant();
	    list_append_number(l, wp == NULL ? 0 : wp->w_curswant == MAXCOL
		    ?  (varnumber_T)MAXCOL : (varnumber_T)wp->w_curswant + 1);

	    // Do not change "curswant", as it is unexpected that a get
	    // function has a side effect.
	    if (wp == curwin && save_set_curswant)
	    {
		curwin->w_set_curswant = save_set_curswant;
		curwin->w_curswant = save_curswant;
		curwin->w_virtcol = save_virtcol;
		curwin->w_valid &= ~VALID_VIRTCOL;
	    }
	}
    }
    else
	rettv->vval.v_number = FALSE;
}

/*
 * "getcharpos()" function
 */
    static void
f_getcharpos(typval_T *argvars UNUSED, typval_T *rettv)
{
    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    getpos_both(argvars, rettv, FALSE, TRUE);
}

/*
 * "getcharsearch()" function
 */
    static void
f_getcharsearch(typval_T *argvars UNUSED, typval_T *rettv)
{
    if (rettv_dict_alloc(rettv) != OK)
	return;

    dict_T *dict = rettv->vval.v_dict;

    dict_add_string(dict, "char", last_csearch());
    dict_add_number(dict, "forward", last_csearch_forward());
    dict_add_number(dict, "until", last_csearch_until());
}

/*
 * "getenv()" function
 */
    static void
f_getenv(typval_T *argvars, typval_T *rettv)
{
    int	    mustfree = FALSE;
    char_u  *p;

    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    p = vim_getenv(tv_get_string(&argvars[0]), &mustfree);
    if (p == NULL)
    {
	rettv->v_type = VAR_SPECIAL;
	rettv->vval.v_number = VVAL_NULL;
	return;
    }
    if (!mustfree)
	p = vim_strsave(p);
    rettv->vval.v_string = p;
    rettv->v_type = VAR_STRING;
}

/*
 * "getfontname()" function
 */
    static void
f_getfontname(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;

    if (in_vim9script() && check_for_opt_string_arg(argvars, 0) == FAIL)
	return;

#ifdef FEAT_GUI
    if (gui.in_use)
    {
	GuiFont font;
	char_u	*name = NULL;

	if (argvars[0].v_type == VAR_UNKNOWN)
	{
	    // Get the "Normal" font.  Either the name saved by
	    // hl_set_font_name() or from the font ID.
	    font = gui.norm_font;
	    name = hl_get_font_name();
	}
	else
	{
	    name = tv_get_string(&argvars[0]);
	    if (STRCMP(name, "*") == 0)	    // don't use font dialog
		return;
	    font = gui_mch_get_font(name, FALSE);
	    if (font == NOFONT)
		return;	    // Invalid font name, return empty string.
	}
	rettv->vval.v_string = gui_mch_get_fontname(font, name);
	if (argvars[0].v_type != VAR_UNKNOWN)
	    gui_mch_free_font(font);
    }
#endif
}

/*
 * "getjumplist()" function
 */
    static void
f_getjumplist(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp;
    int		i;
    list_T	*l;
    dict_T	*d;

    if (rettv_list_alloc(rettv) == FAIL)
	return;

    if (in_vim9script()
	    && (check_for_opt_number_arg(argvars, 0) == FAIL
		|| (argvars[0].v_type != VAR_UNKNOWN
		    && check_for_opt_number_arg(argvars, 1) == FAIL)))
	return;

    wp = find_tabwin(&argvars[0], &argvars[1], NULL);
    if (wp == NULL)
	return;

    cleanup_jumplist(wp, TRUE);

    l = list_alloc();
    if (l == NULL)
	return;
    if (list_append_list(rettv->vval.v_list, l) == FAIL)
    {
	vim_free(l);
	return;
    }

    list_append_number(rettv->vval.v_list, (varnumber_T)wp->w_jumplistidx);

    for (i = 0; i < wp->w_jumplistlen; ++i)
    {
	if (wp->w_jumplist[i].fmark.mark.lnum == 0)
	    continue;
	if ((d = dict_alloc()) == NULL)
	    return;
	if (list_append_dict(l, d) == FAIL)
	    return;
	dict_add_number(d, "lnum", (long)wp->w_jumplist[i].fmark.mark.lnum);
	dict_add_number(d, "col", (long)wp->w_jumplist[i].fmark.mark.col);
	dict_add_number(d, "coladd", (long)wp->w_jumplist[i].fmark.mark.coladd);
	dict_add_number(d, "bufnr", (long)wp->w_jumplist[i].fmark.fnum);
	if (wp->w_jumplist[i].fname != NULL)
	    dict_add_string(d, "filename", wp->w_jumplist[i].fname);
    }
}

/*
 * "getpid()" function
 */
    static void
f_getpid(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = mch_get_pid();
}

/*
 * "getcurpos()" function
 */
    static void
f_getcurpos(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script() && check_for_opt_number_arg(argvars, 0) == FAIL)
	return;

    getpos_both(argvars, rettv, TRUE, FALSE);
}

    static void
f_getcursorcharpos(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script() && check_for_opt_number_arg(argvars, 0) == FAIL)
	return;

    getpos_both(argvars, rettv, TRUE, TRUE);
}

/*
 * "getpos(string)" function
 */
    static void
f_getpos(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    getpos_both(argvars, rettv, FALSE, FALSE);
}

/*
 * Convert from block_def to string
 */
    static char_u *
block_def2str(struct block_def *bd)
{
    char_u *p, *ret;
    size_t size = bd->startspaces + bd->endspaces + bd->textlen;

    ret = alloc(size + 1);
    if (ret != NULL)
    {
	p = ret;
	vim_memset(p, ' ', bd->startspaces);
	p += bd->startspaces;
	mch_memmove(p, bd->textstart, bd->textlen);
	p += bd->textlen;
	vim_memset(p, ' ', bd->endspaces);
	*(p + bd->endspaces) = NUL;
    }
    return ret;
}

    static int
getregionpos(
    typval_T	*argvars,
    typval_T	*rettv,
    pos_T	*p1,
    pos_T	*p2,
    int		*inclusive,
    int		*region_type,
    oparg_T	*oap)
{
    int		fnum1 = -1, fnum2 = -1;
    char_u	*type;
    buf_T	*findbuf;
    char_u	default_type[] = "v";
    int		block_width = 0;
    int		is_select_exclusive;
    int		l;

    if (rettv_list_alloc(rettv) == FAIL)
	return FAIL;

    if (check_for_list_arg(argvars, 0) == FAIL
	    || check_for_list_arg(argvars, 1) == FAIL
	    || check_for_opt_dict_arg(argvars, 2) == FAIL)
	return FAIL;

    if (list2fpos(&argvars[0], p1, &fnum1, NULL, FALSE) != OK
	    || list2fpos(&argvars[1], p2, &fnum2, NULL, FALSE) != OK
	    || fnum1 != fnum2)
	return FAIL;

    if (argvars[2].v_type == VAR_DICT)
    {
	is_select_exclusive = dict_get_bool(
		argvars[2].vval.v_dict, "exclusive", *p_sel == 'e');
	type = dict_get_string(
		argvars[2].vval.v_dict, "type", FALSE);
	if (type == NULL)
	    type = default_type;
    }
    else
    {
	is_select_exclusive = *p_sel == 'e';
	type = default_type;
    }

    if (type[0] == 'v' && type[1] == NUL)
	*region_type = MCHAR;
    else if (type[0] == 'V' && type[1] == NUL)
	*region_type = MLINE;
    else if (type[0] == Ctrl_V)
    {
	char_u *p = type + 1;

	if (*p != NUL && ((block_width = getdigits(&p)) <= 0 || *p != NUL))
	{
	    semsg(_(e_invalid_value_for_argument_str_str), "type", type);
	    return FAIL;
	}
	*region_type = MBLOCK;
    }
    else
    {
	semsg(_(e_invalid_value_for_argument_str_str), "type", type);
	return FAIL;
    }

    findbuf = fnum1 != 0 ? buflist_findnr(fnum1) : curbuf;
    if (findbuf == NULL || findbuf->b_ml.ml_mfp == NULL)
    {
	emsg(_(e_buffer_is_not_loaded));
	return FAIL;
    }

    if (p1->lnum < 1 || p1->lnum > findbuf->b_ml.ml_line_count)
    {
	semsg(_(e_invalid_line_number_nr), p1->lnum);
	return FAIL;
    }
    if (p1->col == MAXCOL)
	p1->col = ml_get_buf_len(findbuf, p1->lnum) + 1;
    else if (p1->col < 1 || p1->col > ml_get_buf_len(findbuf, p1->lnum) + 1)
    {
	semsg(_(e_invalid_column_number_nr), p1->col);
	return FAIL;
    }

    if (p2->lnum < 1 || p2->lnum > findbuf->b_ml.ml_line_count)
    {
	semsg(_(e_invalid_line_number_nr), p2->lnum);
	return FAIL;
    }
    if (p2->col == MAXCOL)
	p2->col = ml_get_buf_len(findbuf, p2->lnum) + 1;
    else if (p2->col < 1 || p2->col > ml_get_buf_len(findbuf, p2->lnum) + 1)
    {
	semsg(_(e_invalid_column_number_nr), p2->col);
	return FAIL;
    }

    curbuf = findbuf;
    curwin->w_buffer = curbuf;
    virtual_op = virtual_active();

    // NOTE: Adjustment is needed.
    p1->col--;
    p2->col--;

    if (!LT_POS(*p1, *p2))
    {
	// swap position
	pos_T p;

	p = *p1;
	*p1 = *p2;
	*p2 = p;
    }

    if (*region_type == MCHAR)
    {
	// Handle 'selection' == "exclusive".
	if (is_select_exclusive && !EQUAL_POS(*p1, *p2))
	    // When backing up to previous line, inclusive becomes false.
	    *inclusive = !unadjust_for_sel_inner(p2);
	// If p2 is on NUL (end of line), inclusive becomes false.
	if (*inclusive && !virtual_op && *ml_get_pos(p2) == NUL)
	    *inclusive = FALSE;
    }
    else if (*region_type == MBLOCK)
    {
	colnr_T sc1, ec1, sc2, ec2;

#ifdef FEAT_LINEBREAK
	int	lbr_saved = reset_lbr();
#endif

	getvvcol(curwin, p1, &sc1, NULL, &ec1);
	getvvcol(curwin, p2, &sc2, NULL, &ec2);

#ifdef FEAT_LINEBREAK
	restore_lbr(lbr_saved);
#endif

	oap->motion_type = MBLOCK;
	oap->inclusive = TRUE;
	oap->op_type = OP_NOP;
	oap->start = *p1;
	oap->end = *p2;
	oap->start_vcol = MIN(sc1, sc2);
	if (block_width > 0)
	    oap->end_vcol = oap->start_vcol + block_width - 1;
	else if (is_select_exclusive && ec1 < sc2 && 0 < sc2 && ec2 > ec1)
	    oap->end_vcol = sc2 - 1;
	else
	    oap->end_vcol = MAX(ec1, ec2);
    }

    // Include the trailing byte of a multi-byte char.
    l = mb_ptr2len((char_u *)ml_get_pos(p2));
    if (l > 1)
	p2->col += l - 1;

    return OK;
}

/*
 * "getregion()" function
 */
    static void
f_getregion(typval_T *argvars, typval_T *rettv)
{
    pos_T		p1, p2;
    int			inclusive = TRUE;
    int			region_type = -1;
    oparg_T		oa;

    buf_T		*save_curbuf;
    int			save_virtual;
    char_u		*akt = NULL;
    linenr_T		lnum;

    save_curbuf = curbuf;
    save_virtual = virtual_op;

    if (getregionpos(argvars, rettv,
		&p1, &p2, &inclusive, &region_type, &oa) == FAIL)
	return;

    for (lnum = p1.lnum; lnum <= p2.lnum; lnum++)
    {
	int ret = 0;
	struct block_def	bd;

	if (region_type == MLINE)
	    akt = vim_strsave(ml_get(lnum));
	else if (region_type == MBLOCK)
	{
	    block_prep(&oa, &bd, lnum, FALSE);
	    akt = block_def2str(&bd);
	}
	else if (p1.lnum < lnum && lnum < p2.lnum)
	    akt = vim_strsave(ml_get(lnum));
	else
	{
	    charwise_block_prep(p1, p2, &bd, lnum, inclusive);
	    akt = block_def2str(&bd);
	}

	if (akt)
	{
	    ret = list_append_string(rettv->vval.v_list, akt, -1);
	    vim_free(akt);
	}

	if (akt == NULL || ret == FAIL)
	{
	    clear_tv(rettv);
	    (void)rettv_list_alloc(rettv);
	    break;
	}
    }

    // getregionpos() may change curbuf and virtual_op
    curbuf = save_curbuf;
    curwin->w_buffer = curbuf;
    virtual_op = save_virtual;
}

    static void
add_regionpos_range(typval_T *rettv, pos_T p1, pos_T p2)
{
    list_T	*l1, *l2, *l3;

    l1 = list_alloc();
    if (l1 == NULL)
	return;

    if (list_append_list(rettv->vval.v_list, l1) == FAIL)
    {
	vim_free(l1);
	return;
    }

    l2 = list_alloc();
    if (l2 == NULL)
    {
	vim_free(l1);
	return;
    }

    if (list_append_list(l1, l2) == FAIL)
    {
	vim_free(l1);
	vim_free(l2);
	return;
    }

    l3 = list_alloc();
    if (l3 == NULL)
    {
	vim_free(l1);
	vim_free(l2);
	return;
    }

    if (list_append_list(l1, l3) == FAIL)
    {
	vim_free(l1);
	vim_free(l2);
	vim_free(l3);
	return;
    }

    list_append_number(l2, curbuf->b_fnum);
    list_append_number(l2, p1.lnum);
    list_append_number(l2, p1.col);
    list_append_number(l2, p1.coladd);

    list_append_number(l3, curbuf->b_fnum);
    list_append_number(l3, p2.lnum);
    list_append_number(l3, p2.col);
    list_append_number(l3, p2.coladd);
}

/*
 * "getregionpos()" function
 */
    static void
f_getregionpos(typval_T *argvars, typval_T *rettv)
{
    pos_T	p1, p2;
    int		inclusive = TRUE;
    int		region_type = -1;
    int		allow_eol = FALSE;
    oparg_T	oa;
    int		lnum;

    buf_T	*save_curbuf;
    int		save_virtual;

    save_curbuf = curbuf;
    save_virtual = virtual_op;

    if (getregionpos(argvars, rettv,
		&p1, &p2, &inclusive, &region_type, &oa) == FAIL)
	return;

    if (argvars[2].v_type == VAR_DICT)
	allow_eol = dict_get_bool(argvars[2].vval.v_dict, "eol", FALSE);

    for (lnum = p1.lnum; lnum <= p2.lnum; lnum++)
    {
	pos_T		ret_p1, ret_p2;
	char_u		*line = ml_get(lnum);
	colnr_T		line_len = ml_get_len(lnum);

	if (region_type == MLINE)
	{
	    ret_p1.col = 1;
	    ret_p1.coladd = 0;
	    ret_p2.col = MAXCOL;
	    ret_p2.coladd = 0;
	}
	else
	{
	    struct block_def	bd;

	    if (region_type == MBLOCK)
		block_prep(&oa, &bd, lnum, FALSE);
	    else
		charwise_block_prep(p1, p2, &bd, lnum, inclusive);

	    if (bd.is_oneChar)  // selection entirely inside one char
	    {
		if (region_type == MBLOCK)
		{
		    ret_p1.col = mb_prevptr(line, bd.textstart) - line + 1;
		    ret_p1.coladd = bd.start_char_vcols
					     - (bd.start_vcol - oa.start_vcol);
		}
		else
		{
		    ret_p1.col = p1.col + 1;
		    ret_p1.coladd = p1.coladd;
		}
	    }
	    else if (region_type == MBLOCK && oa.start_vcol > bd.start_vcol)
	    {
		// blockwise selection entirely beyond end of line
		ret_p1.col = MAXCOL;
		ret_p1.coladd = oa.start_vcol - bd.start_vcol;
		bd.is_oneChar = TRUE;
	    }
	    else if (bd.startspaces > 0)
	    {
		ret_p1.col = mb_prevptr(line, bd.textstart) - line + 1;
		ret_p1.coladd = bd.start_char_vcols - bd.startspaces;
	    }
	    else
	    {
		ret_p1.col = bd.textcol + 1;
		ret_p1.coladd = 0;
	    }

	    if (bd.is_oneChar)  // selection entirely inside one char
	    {
		ret_p2.col = ret_p1.col;
		ret_p2.coladd = ret_p1.coladd + bd.startspaces + bd.endspaces;
	    }
	    else if (bd.endspaces > 0)
	    {
		ret_p2.col = bd.textcol + bd.textlen + 1;
		ret_p2.coladd = bd.endspaces;
	    }
	    else
	    {
		ret_p2.col = bd.textcol + bd.textlen;
		ret_p2.coladd = 0;
	    }
	}

	if (!allow_eol && ret_p1.col > line_len)
	{
	    ret_p1.col = 0;
	    ret_p1.coladd = 0;
	}
	else if (ret_p1.col > line_len + 1)
	    ret_p1.col = line_len + 1;

	if (!allow_eol && ret_p2.col > line_len)
	{
	    ret_p2.col = ret_p1.col == 0 ? 0 : line_len;
	    ret_p2.coladd = 0;
	}
	else if (ret_p2.col > line_len + 1)
	    ret_p2.col = line_len + 1;

	ret_p1.lnum = lnum;
	ret_p2.lnum = lnum;
	add_regionpos_range(rettv, ret_p1, ret_p2);
    }

    // getregionpos() may change curbuf and virtual_op
    curbuf = save_curbuf;
    curwin->w_buffer = curbuf;
    virtual_op = save_virtual;
}

/*
 * Common between getreg(), getreginfo() and getregtype(): get the register
 * name from the first argument.
 * Returns zero on error.
 */
    static int
getreg_get_regname(typval_T *argvars)
{
    char_u  *strregname;

    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	strregname = tv_get_string_chk(&argvars[0]);
	if (strregname != NULL && in_vim9script() && STRLEN(strregname) > 1)
	{
	    semsg(_(e_register_name_must_be_one_char_str), strregname);
	    strregname = NULL;
	}
	if (strregname == NULL)	    // type error; errmsg already given
	    return 0;
    }
    else
	// Default to v:register
	strregname = get_vim_var_str(VV_REG);

    return *strregname == 0 ? '"' : *strregname;
}

/*
 * "getreg()" function
 */
    static void
f_getreg(typval_T *argvars, typval_T *rettv)
{
    int		regname;
    int		arg2 = FALSE;
    int		return_list = FALSE;

    if (in_vim9script()
	    && (check_for_opt_string_arg(argvars, 0) == FAIL
		|| (argvars[0].v_type != VAR_UNKNOWN
		    && (check_for_opt_bool_arg(argvars, 1) == FAIL
			|| (argvars[1].v_type != VAR_UNKNOWN
			    && check_for_opt_bool_arg(argvars, 2) == FAIL)))))
	return;

    regname = getreg_get_regname(argvars);
    if (regname == 0)
	return;

    if (argvars[0].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_UNKNOWN)
    {
	int		error = FALSE;

	arg2 = (int)tv_get_bool_chk(&argvars[1], &error);

	if (!error && argvars[2].v_type != VAR_UNKNOWN)
	    return_list = (int)tv_get_bool_chk(&argvars[2], &error);
	if (error)
	    return;
    }

    if (return_list)
    {
	rettv->v_type = VAR_LIST;
	rettv->vval.v_list = (list_T *)get_reg_contents(regname,
				      (arg2 ? GREG_EXPR_SRC : 0) | GREG_LIST);
	if (rettv->vval.v_list == NULL)
	    (void)rettv_list_alloc(rettv);
	else
	    ++rettv->vval.v_list->lv_refcount;
    }
    else
    {
	rettv->v_type = VAR_STRING;
	rettv->vval.v_string = get_reg_contents(regname,
						    arg2 ? GREG_EXPR_SRC : 0);
    }
}

/*
 * "getregtype()" function
 */
    static void
f_getregtype(typval_T *argvars, typval_T *rettv)
{
    int		regname;
    char_u	buf[NUMBUFLEN + 2];
    long	reglen = 0;

    // on error return an empty string
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;

    if (in_vim9script() && check_for_opt_string_arg(argvars, 0) == FAIL)
	return;

    regname = getreg_get_regname(argvars);
    if (regname == 0)
	return;

    buf[0] = NUL;
    buf[1] = NUL;
    switch (get_reg_type(regname, &reglen))
    {
	case MLINE: buf[0] = 'V'; break;
	case MCHAR: buf[0] = 'v'; break;
	case MBLOCK:
		buf[0] = Ctrl_V;
		sprintf((char *)buf + 1, "%ld", reglen + 1);
		break;
    }
    rettv->vval.v_string = vim_strsave(buf);
}

/*
 * "gettagstack()" function
 */
    static void
f_gettagstack(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp = curwin;			// default is current window

    if (rettv_dict_alloc(rettv) == FAIL)
	return;

    if (in_vim9script() && check_for_opt_number_arg(argvars, 0) == FAIL)
	return;

    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	wp = find_win_by_nr_or_id(&argvars[0]);
	if (wp == NULL)
	    return;
    }

    get_tagstack(wp, rettv->vval.v_dict);
}

/*
 * "gettext()" function
 */
    static void
f_gettext(typval_T *argvars, typval_T *rettv)
{
#if defined(HAVE_BIND_TEXTDOMAIN_CODESET)
    char *prev = NULL;
#endif

    if (check_for_nonempty_string_arg(argvars, 0) == FAIL
	|| check_for_opt_string_arg(argvars, 1) == FAIL)
	return;

    rettv->v_type = VAR_STRING;

    if (argvars[1].v_type == VAR_STRING &&
	    argvars[1].vval.v_string != NULL &&
	    *(argvars[1].vval.v_string) != NUL)
    {
#if defined(HAVE_BIND_TEXTDOMAIN_CODESET)
	prev = bind_textdomain_codeset((const char *)argvars[1].vval.v_string, (char *)p_enc);
#endif

#if defined(HAVE_DGETTEXT)
	rettv->vval.v_string = vim_strsave((char_u *)dgettext((const char *)argvars[1].vval.v_string, (const char *)argvars[0].vval.v_string));
#else
	textdomain((const char *)argvars[1].vval.v_string);
	rettv->vval.v_string = vim_strsave((char_u *)_(argvars[0].vval.v_string));
	textdomain(VIMPACKAGE);
#endif

#if defined(HAVE_BIND_TEXTDOMAIN_CODESET)
	if (prev != NULL)
	    bind_textdomain_codeset((const char *)argvars[1].vval.v_string, prev);
#endif
    }
    else
	rettv->vval.v_string = vim_strsave((char_u *)_(argvars[0].vval.v_string));
}

// for VIM_VERSION_ defines
#include "version.h"

/*
 * "has()" function
 */
    void
f_has(typval_T *argvars, typval_T *rettv)
{
    int		i;
    char_u	*name;
    int		x = FALSE;
    int		n = FALSE;
    typedef struct {
	char *name;
	short present;
    } has_item_T;
    static has_item_T has_list[] =
    {
	{"amiga",
#ifdef AMIGA
		1
#else
		0
#endif
		},
	{"arp",
#if defined(AMIGA) && defined(FEAT_ARP)
		1
#else
		0
#endif
		},
	{"haiku",
#ifdef __HAIKU__
		1
#else
		0
#endif
		},
	{"bsd",
#if defined(BSD) && !defined(MACOS_X) && !defined(__GNU__)
		1
#else
		0
#endif
		},
	{"hpux",
#ifdef hpux
		1
#else
		0
#endif
		},
	{"hurd",
#ifdef __GNU__
		1
#else
		0
#endif
		},
	{"linux",
#ifdef __linux__
		1
#else
		0
#endif
		},
	{"mac",		// Mac OS X (and, once, Mac OS Classic)
#ifdef MACOS_X
		1
#else
		0
#endif
		},
	{"osx",		// Mac OS X
#ifdef MACOS_X
		1
#else
		0
#endif
		},
	{"macunix",	// Mac OS X, with the darwin feature
#if defined(MACOS_X) && defined(MACOS_X_DARWIN)
		1
#else
		0
#endif
		},
	{"osxdarwin",	// synonym for macunix
#if defined(MACOS_X) && defined(MACOS_X_DARWIN)
		1
#else
		0
#endif
		},
	{"qnx",
#ifdef __QNX__
		1
#else
		0
#endif
		},
	{"sun",
#ifdef SUN_SYSTEM
		1
#else
		0
#endif
		},
	{"unix",
#ifdef UNIX
		1
#else
		0
#endif
		},
	{"vms",
#ifdef VMS
		1
#else
		0
#endif
		},
	{"win32",
#ifdef MSWIN
		1
#else
		0
#endif
		},
	{"win32unix",
#ifdef WIN32UNIX
		1
#else
		0
#endif
		},
	{"win64",
#ifdef _WIN64
		1
#else
		0
#endif
		},
	{"ebcdic", 0 },
	{"fname_case",
#ifndef CASE_INSENSITIVE_FILENAME
		1
#else
		0
#endif
		},
	{"acl",
#ifdef HAVE_ACL
		1
#else
		0
#endif
		},
	{"arabic",
#ifdef FEAT_ARABIC
		1
#else
		0
#endif
		},
	{"autocmd", 1},
	{"autochdir",
#ifdef FEAT_AUTOCHDIR
		1
#else
		0
#endif
		},
	{"autoservername",
#ifdef FEAT_AUTOSERVERNAME
		1
#else
		0
#endif
		},
	{"socketserver",
#ifdef FEAT_SOCKETSERVER
		1
#else
		0
#endif
		},
	{"balloon_eval",
#ifdef FEAT_BEVAL_GUI
		1
#else
		0
#endif
		},
	{"balloon_multiline",
#ifdef FEAT_BEVAL_GUI
		1
#else
		0
#endif
		},
	{"balloon_eval_term",
#ifdef FEAT_BEVAL_TERM
		1
#else
		0
#endif
		},
	{"builtin_terms", 1},
	{"all_builtin_terms", 1},
	{"browsefilter",
#if defined(FEAT_BROWSE) && (defined(FEAT_GUI_GTK) \
	|| defined(FEAT_GUI_MSWIN) \
	|| defined(FEAT_GUI_MOTIF))
		1
#else
		0
#endif
		},
	{"byte_offset",
#ifdef FEAT_BYTEOFF
		1
#else
		0
#endif
		},
	{"channel",
#ifdef FEAT_JOB_CHANNEL
		1
#else
		0
#endif
		},
	{"cindent", 1},
	{"clientserver",
#ifdef FEAT_CLIENTSERVER
		1
#else
		0
#endif
		},
	{"clipboard",
#ifdef FEAT_CLIPBOARD
		1
#else
		0
#endif
		},
	{"clipboard_provider",
#ifdef FEAT_CLIPBOARD_PROVIDER
		1
#else
		0
#endif
		},
	{"cmdline_compl", 1},
	{"cmdline_hist", 1},
	{"cmdwin", 1},
	{"comments", 1},
	{"conceal",
#ifdef FEAT_CONCEAL
		1
#else
		0
#endif
		},
	{"cryptv",
#ifdef FEAT_CRYPT
		1
#else
		0
#endif
		},
	{"crypt-blowfish",
#ifdef FEAT_CRYPT
		1
#else
		0
#endif
		},
	{"crypt-blowfish2",
#ifdef FEAT_CRYPT
		1
#else
		0
#endif
		},
	{"cscope",
#ifdef FEAT_CSCOPE
		1
#else
		0
#endif
		},
	{"cursorbind", 1},
	{"cursorshape",
#ifdef CURSOR_SHAPE
		1
#else
		0
#endif
		},
	{"debug",
#ifdef DEBUG
		1
#else
		0
#endif
		},
	{"dialog_con",
#ifdef FEAT_CON_DIALOG
		1
#else
		0
#endif
		},
	{"dialog_con_gui",
#if defined(FEAT_CON_DIALOG) && defined(FEAT_GUI_DIALOG)
		1
#else
		0
#endif
		},
	{"dialog_gui",
#ifdef FEAT_GUI_DIALOG
		1
#else
		0
#endif
		},
	{"diff",
#ifdef FEAT_DIFF
		1
#else
		0
#endif
		},
	{"digraphs",
#ifdef FEAT_DIGRAPHS
		1
#else
		0
#endif
		},
	{"directx",
#ifdef FEAT_DIRECTX
		1
#else
		0
#endif
		},
	{"dnd",
#ifdef FEAT_DND
		1
#else
		0
#endif
		},
	{"drop_file",
#ifdef HAVE_DROP_FILE
		1
#else
		0
#endif
		},
	{"emacs_tags",
#ifdef FEAT_EMACS_TAGS
		1
#else
		0
#endif
		},
	{"eval", 1},		// always present, of course!
	{"ex_extra", 1},	// graduated feature
	{"extra_search",
#ifdef FEAT_SEARCH_EXTRA
		1
#else
		0
#endif
		},
	{"file_in_path", 1},
	{"filterpipe",
#if defined(FEAT_FILTERPIPE) && !defined(VIMDLL)
		1
#else
		0
#endif
		},
	{"find_in_path",
#ifdef FEAT_FIND_ID
		1
#else
		0
#endif
		},
	{"float", 1},
	{"folding",
#ifdef FEAT_FOLDING
		1
#else
		0
#endif
		},
	{"footer", 0},
	{"fork",
#if !defined(USE_SYSTEM) && defined(UNIX)
		1
#else
		0
#endif
		},
	{"gettext",
#ifdef FEAT_GETTEXT
		1
#else
		0
#endif
		},
	{"gui",
#ifdef FEAT_GUI
		1
#else
		0
#endif
		},
	{"gui_neXtaw", 0 },
	{"gui_athena", 0 },
	{"gui_gtk",
#ifdef FEAT_GUI_GTK
		1
#else
		0
#endif
		},
	{"gui_gtk2",
#if defined(FEAT_GUI_GTK) && !defined(USE_GTK3)
		1
#else
		0
#endif
		},
	{"gui_gtk3",
#if defined(FEAT_GUI_GTK) && defined(USE_GTK3)
		1
#else
		0
#endif
		},
	{"gui_gnome",
#ifdef FEAT_GUI_GNOME
		1
#else
		0
#endif
		},
	{"gui_haiku",
#ifdef FEAT_GUI_HAIKU
		1
#else
		0
#endif
		},
	{"gui_mac", 0},
	{"gui_motif",
#ifdef FEAT_GUI_MOTIF
		1
#else
		0
#endif
		},
	{"gui_photon",
#ifdef FEAT_GUI_PHOTON
		1
#else
		0
#endif
		},
	{"gui_win32",
#ifdef FEAT_GUI_MSWIN
		1
#else
		0
#endif
		},
	{"iconv",
#if defined(HAVE_ICONV_H) && defined(USE_ICONV)
		1
#else
		0
#endif
		},
	{"insert_expand", 1},
	{"ipv6",
#ifdef FEAT_IPV6
		1
#else
		0
#endif
	},
	{"job",
#ifdef FEAT_JOB_CHANNEL
		1
#else
		0
#endif
		},
	{"jumplist", 1},
	{"keymap",
#ifdef FEAT_KEYMAP
		1
#else
		0
#endif
		},
	{"lambda", 1}, // always with FEAT_EVAL, since 7.4.2120 with closure
	{"langmap",
#ifdef FEAT_LANGMAP
		1
#else
		0
#endif
		},
	{"libcall",
#ifdef FEAT_LIBCALL
		1
#else
		0
#endif
		},
	{"linebreak",
#ifdef FEAT_LINEBREAK
		1
#else
		0
#endif
		},
	{"lispindent", 1},
	{"listcmds", 1},
	{"localmap", 1},
	{"lua",
#if defined(FEAT_LUA) && !defined(DYNAMIC_LUA)
		1
#else
		0
#endif
		},
	{"menu",
#ifdef FEAT_MENU
		1
#else
		0
#endif
		},
	{"mksession",
#ifdef FEAT_SESSION
		1
#else
		0
#endif
		},
	{"modify_fname", 1},
	{"mouse", 1},
	{"mouseshape",
#ifdef FEAT_MOUSESHAPE
		1
#else
		0
#endif
		},
	{"mouse_dec",
#if (defined(UNIX) || defined(VMS)) && defined(FEAT_MOUSE_DEC)
		1
#else
		0
#endif
		},
	{"mouse_gpm",
#if (defined(UNIX) || defined(VMS)) && defined(FEAT_MOUSE_GPM) && !defined(DYNAMIC_GPM)
		1
#else
		0
#endif
		},
	{"mouse_jsbterm",
#if (defined(UNIX) || defined(VMS)) && defined(FEAT_MOUSE_JSB)
		1
#else
		0
#endif
		},
	{"mouse_netterm",
#if (defined(UNIX) || defined(VMS)) && defined(FEAT_MOUSE_NET)
		1
#else
		0
#endif
		},
	{"mouse_pterm",
#if (defined(UNIX) || defined(VMS)) && defined(FEAT_MOUSE_PTERM)
		1
#else
		0
#endif
		},
	{"mouse_sgr",
#if (defined(UNIX) || defined(VMS)) && defined(FEAT_MOUSE_XTERM)
		1
#else
		0
#endif
		},
	{"mouse_sysmouse",
#if (defined(UNIX) || defined(VMS)) && defined(FEAT_SYSMOUSE)
		1
#else
		0
#endif
		},
	{"mouse_urxvt",
#if (defined(UNIX) || defined(VMS)) && defined(FEAT_MOUSE_URXVT)
		1
#else
		0
#endif
		},
	{"mouse_xterm",
#if (defined(UNIX) || defined(VMS)) && defined(FEAT_MOUSE_XTERM)
		1
#else
		0
#endif
		},
	{"multi_byte", 1},
	{"multi_byte_ime",
#ifdef FEAT_MBYTE_IME
		1
#else
		0
#endif
		},
	{"multi_lang",
#ifdef FEAT_MULTI_LANG
		1
#else
		0
#endif
		},
	{"mzscheme",
#if defined(FEAT_MZSCHEME) && !defined(DYNAMIC_MZSCHEME)
		1
#else
		0
#endif
		},
	{"nanotime",
#ifdef ST_MTIM_NSEC
		1
#else
		0
#endif
	},
	{"num64", 1},
	{"ole",
#ifdef FEAT_OLE
		1
#else
		0
#endif
		},
	{"packages",
#ifdef FEAT_EVAL
		1
#else
		0
#endif
		},
	{"path_extra", 1},
	{"perl",
#if defined(FEAT_PERL) && !defined(DYNAMIC_PERL)
		1
#else
		0
#endif
		},
	{"persistent_undo",
#ifdef FEAT_PERSISTENT_UNDO
		1
#else
		0
#endif
		},
	{"python_compiled",
#if defined(FEAT_PYTHON)
		1
#else
		0
#endif
		},
	{"python_dynamic",
#if defined(FEAT_PYTHON) && defined(DYNAMIC_PYTHON)
		1
#else
		0
#endif
		},
	{"python",
#if defined(FEAT_PYTHON) && !defined(DYNAMIC_PYTHON)
		1
#else
		0
#endif
		},
	{"pythonx",
#if (defined(FEAT_PYTHON) && !defined(DYNAMIC_PYTHON)) \
	|| (defined(FEAT_PYTHON3) && !defined(DYNAMIC_PYTHON3))
		1
#else
		0
#endif
		},
	{"python3_compiled",
#if defined(FEAT_PYTHON3)
		1
#else
		0
#endif
		},
	{"python3_dynamic",
#if defined(FEAT_PYTHON3) && defined(DYNAMIC_PYTHON3)
		1
#else
		0
#endif
		},
	{"python3_stable",
#if defined(FEAT_PYTHON3) && defined(DYNAMIC_PYTHON3_STABLE_ABI)
		1
#else
		0
#endif
		},
	{"python3",
#if defined(FEAT_PYTHON3) && !defined(DYNAMIC_PYTHON3)
		1
#else
		0
#endif
		},
	{"popupwin",
#ifdef FEAT_PROP_POPUP
		1
#else
		0
#endif
		},
	{"postscript",
#ifdef FEAT_POSTSCRIPT
		1
#else
		0
#endif
		},
	{"printer",
#ifdef FEAT_PRINTER
		1
#else
		0
#endif
		},
	{"profile",
#ifdef FEAT_PROFILE
		1
#else
		0
#endif
		},
	{"prof_nsec",
#ifdef PROF_NSEC
		1
#else
		0
#endif
		},
	{"reltime",
#ifdef FEAT_RELTIME
		1
#else
		0
#endif
		},
	{"quickfix",
#ifdef FEAT_QUICKFIX
		1
#else
		0
#endif
		},
	{"rightleft",
#ifdef FEAT_RIGHTLEFT
		1
#else
		0
#endif
		},
	{"ruby",
#if defined(FEAT_RUBY) && !defined(DYNAMIC_RUBY)
		1
#else
		0
#endif
		},
	{"scrollbind", 1},
	{"showcmd", 1},
	{"cmdline_info", 1},
	{"signs",
#ifdef FEAT_SIGNS
		1
#else
		0
#endif
		},
	{"smartindent", 1},
	{"startuptime",
#ifdef STARTUPTIME
		1
#else
		0
#endif
		},
	{"statusline",
#ifdef FEAT_STL_OPT
		1
#else
		0
#endif
		},
	{"netbeans_intg",
#ifdef FEAT_NETBEANS_INTG
		1
#else
		0
#endif
		},
	{"sodium",
#if defined(FEAT_SODIUM) && !defined(DYNAMIC_SODIUM)
		1
#else
		0
#endif
		},
	{"sound",
#ifdef FEAT_SOUND
		1
#else
		0
#endif
		},
	{"spell",
#ifdef FEAT_SPELL
		1
#else
		0
#endif
		},
	{"syntax",
#ifdef FEAT_SYN_HL
		1
#else
		0
#endif
		},
	{"system",
#if defined(USE_SYSTEM) || !defined(UNIX)
		1
#else
		0
#endif
		},
	{"tabpanel",
#if defined(FEAT_TABPANEL)
		1,
#else
		0,
#endif
	},
	{"tag_binary", 1},	// graduated feature
	{"tcl",
#if defined(FEAT_TCL) && !defined(DYNAMIC_TCL)
		1
#else
		0
#endif
		},
	{"termguicolors",
#ifdef FEAT_TERMGUICOLORS
		1
#else
		0
#endif
		},
	{"terminal",
#if defined(FEAT_TERMINAL) && !defined(MSWIN)
		1
#else
		0
#endif
		},
	{"terminfo",
#ifdef TERMINFO
		1
#else
		0
#endif
		},
	{"termresponse",
#ifdef FEAT_TERMRESPONSE
		1
#else
		0
#endif
		},
	{"textobjects", 1},
	{"textprop",
#ifdef FEAT_PROP_POPUP
		1
#else
		0
#endif
		},
	{"tgetent",
#ifdef HAVE_TGETENT
		1
#else
		0
#endif
		},
	{"timers",
#ifdef FEAT_TIMERS
		1
#else
		0
#endif
		},
	{"title", 1},
	{"toolbar",
#ifdef FEAT_TOOLBAR
		1
#else
		0
#endif
		},
	{"user-commands", 1},    // was accidentally included in 5.4
	{"user_commands", 1},
	{"vartabs",
#ifdef FEAT_VARTABS
		1
#else
		0
#endif
		},
	{"vertsplit", 1},
	{"viminfo",
#ifdef FEAT_VIMINFO
		1
#else
		0
#endif
		},
	{"vim9script", 1},
	{"vimscript-1", 1},
	{"vimscript-2", 1},
	{"vimscript-3", 1},
	{"vimscript-4", 1},
	{"virtualedit", 1},
	{"visual", 1},
	{"visualextra", 1},
	{"vreplace", 1},
	{"vtp",
#ifdef FEAT_VTP
		1
#else
		0
#endif
		},
	{"wayland",
#ifdef FEAT_WAYLAND
		1
#else
		0
#endif
		},
	{"wayland_clipboard",
#ifdef FEAT_WAYLAND_CLIPBOARD
		1
#else
		0
#endif
		},
	{"wayland_focus_steal",
#ifdef FEAT_WAYLAND_CLIPBOARD_FS
		1
#else
		0
#endif
		},
	{"wildignore", 1},
	{"wildmenu", 1},
	{"windows", 1},
	{"winaltkeys",
#ifdef FEAT_WAK
		1
#else
		0
#endif
		},
	{"writebackup",
#ifdef FEAT_WRITEBACKUP
		1
#else
		0
#endif
		},
	{"xattr",
#ifdef FEAT_XATTR
		1
#else
		0
#endif
		},
	{"xim",
#ifdef FEAT_XIM
		1
#else
		0
#endif
		},
	{"xfontset",
#ifdef FEAT_XFONTSET
		1
#else
		0
#endif
		},
	{"xpm",
#if defined(FEAT_XPM_W32) || defined(HAVE_XPM)
		1
#else
		0
#endif
		},
	{"xpm_w32",	// for backward compatibility
#ifdef FEAT_XPM_W32
		1
#else
		0
#endif
		},
	{"xsmp",
#ifdef USE_XSMP
		1
#else
		0
#endif
		},
	{"xsmp_interact",
#ifdef USE_XSMP_INTERACT
		1
#else
		0
#endif
		},
	{"xterm_clipboard",
#ifdef FEAT_XCLIPBOARD
		1
#else
		0
#endif
		},
	{"xterm_save",
#ifdef FEAT_XTERM_SAVE
		1
#else
		0
#endif
		},
	{"X11",
#if defined(UNIX) && defined(FEAT_X11)
		1
#else
		0
#endif
		},
	{":tearoff",
// same #ifdef as used for ex_tearoff().
#if defined(FEAT_GUI_MSWIN) && defined(FEAT_MENU) && defined(FEAT_TEAROFF)
		1
#else
		0
#endif
		},
	{NULL, 0}
    };

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_opt_bool_arg(argvars, 1) == FAIL))
	return;

    name = tv_get_string(&argvars[0]);
    for (i = 0; has_list[i].name != NULL; ++i)
	if (STRICMP(name, has_list[i].name) == 0)
	{
	    x = TRUE;
	    n = has_list[i].present;
	    break;
	}

    // features also in has_list[] but sometimes enabled at runtime
    if (x == TRUE && n == FALSE)
    {
	if (0)
	{
	    // intentionally empty
	}
#ifdef VIMDLL
	else if (STRICMP(name, "filterpipe") == 0)
	    n = gui.in_use || gui.starting;
#endif
#if defined(USE_ICONV) && defined(DYNAMIC_ICONV)
	else if (STRICMP(name, "iconv") == 0)
	    n = iconv_enabled(FALSE);
#endif
#ifdef DYNAMIC_LUA
	else if (STRICMP(name, "lua") == 0)
	    n = lua_enabled(FALSE);
#endif
#ifdef DYNAMIC_MZSCHEME
	else if (STRICMP(name, "mzscheme") == 0)
	    n = mzscheme_enabled(FALSE);
#endif
#ifdef DYNAMIC_PERL
	else if (STRICMP(name, "perl") == 0)
	    n = perl_enabled(FALSE);
#endif
#ifdef DYNAMIC_PYTHON
	else if (STRICMP(name, "python") == 0)
	    n = python_enabled(FALSE);
#endif
#ifdef DYNAMIC_PYTHON3
	else if (STRICMP(name, "python3") == 0)
	    n = python3_enabled(FALSE);
#endif
#if defined(DYNAMIC_PYTHON) || defined(DYNAMIC_PYTHON3)
	else if (STRICMP(name, "pythonx") == 0)
	{
# if defined(DYNAMIC_PYTHON) && defined(DYNAMIC_PYTHON3)
	    if (p_pyx == 0)
		n = python3_enabled(FALSE) || python_enabled(FALSE);
	    else if (p_pyx == 3)
		n = python3_enabled(FALSE);
	    else if (p_pyx == 2)
		n = python_enabled(FALSE);
# elif defined(DYNAMIC_PYTHON)
	    n = python_enabled(FALSE);
# elif defined(DYNAMIC_PYTHON3)
	    n = python3_enabled(FALSE);
# endif
	}
#endif
#ifdef DYNAMIC_RUBY
	else if (STRICMP(name, "ruby") == 0)
	    n = ruby_enabled(FALSE);
#endif
#ifdef DYNAMIC_TCL
	else if (STRICMP(name, "tcl") == 0)
	    n = tcl_enabled(FALSE);
#endif
#ifdef DYNAMIC_SODIUM
	else if (STRICMP(name, "sodium") == 0)
	    n = sodium_enabled(FALSE);
#endif
#if defined(FEAT_TERMINAL) && defined(MSWIN)
	else if (STRICMP(name, "terminal") == 0)
	    n = terminal_enabled();
#endif
#ifdef DYNAMIC_GPM
	else if (STRICMP(name, "mouse_gpm") == 0)
	    n = gpm_available();
#endif
    }

    // features not in has_list[]
    if (x == FALSE)
    {
	if (STRNICMP(name, "patch", 5) == 0)
	{
	    x = TRUE;
	    if (name[5] == '-'
		    && STRLEN(name) >= 11
		    && (name[6] >= '1' && name[6] <= '9'))
	    {
		char	*end;
		int	major, minor;

		// This works for patch-8.1.2, patch-9.0.3, patch-10.0.4, etc.
		// Not for patch-9.10.5.
		major = (int)strtoul((char *)name + 6, &end, 10);
		if (*end == '.' && vim_isdigit(end[1])
			&& end[2] == '.' && vim_isdigit(end[3]))
		{
		    minor = atoi(end + 1);

		    // Expect "patch-9.9.01234".
		    n = (major < VIM_VERSION_MAJOR
			 || (major == VIM_VERSION_MAJOR
			     && (minor < VIM_VERSION_MINOR
				 || (minor == VIM_VERSION_MINOR
				     && has_patch(atoi(end + 3))))));
		}
	    }
	    else if (SAFE_isdigit(name[5]))
		n = has_patch(atoi((char *)name + 5));
	}
	else if (STRICMP(name, "vim_starting") == 0)
	{
	    x = TRUE;
	    n = (starting != 0);
	}
	else if (STRICMP(name, "ttyin") == 0)
	{
	    x = TRUE;
	    n = mch_input_isatty();
	}
	else if (STRICMP(name, "ttyout") == 0)
	{
	    x = TRUE;
	    n = stdout_isatty;
	}
	else if (STRICMP(name, "multi_byte_encoding") == 0)
	{
	    x = TRUE;
	    n = has_mbyte;
	}
	else if (STRICMP(name, "gui_running") == 0)
	{
	    x = TRUE;
#ifdef FEAT_GUI
	    n = (gui.in_use || gui.starting);
#endif
	}
	else if (STRICMP(name, "browse") == 0)
	{
	    x = TRUE;
#if defined(FEAT_GUI) && defined(FEAT_BROWSE)
	    n = gui.in_use;	// gui_mch_browse() works when GUI is running
#endif
	}
	else if (STRICMP(name, "syntax_items") == 0)
	{
	    x = TRUE;
#ifdef FEAT_SYN_HL
	    n = syntax_present(curwin);
#endif
	}
	else if (STRICMP(name, "vcon") == 0)
	{
	    x = TRUE;
#ifdef FEAT_VTP
	    n = is_term_win32() && has_vtp_working();
#endif
	}
	else if (STRICMP(name, "netbeans_enabled") == 0)
	{
	    x = TRUE;
#ifdef FEAT_NETBEANS_INTG
	    n = netbeans_active();
#endif
	}
	else if (STRICMP(name, "mouse_gpm_enabled") == 0)
	{
	    x = TRUE;
#ifdef FEAT_MOUSE_GPM
	    n = gpm_enabled();
#endif
	}
	else if (STRICMP(name, "conpty") == 0)
	{
	    x = TRUE;
#if defined(FEAT_TERMINAL) && defined(MSWIN)
	    n = use_conpty();
#endif
	}
	else if (STRICMP(name, "clipboard_working") == 0)
	{
	    x = TRUE;
#ifdef FEAT_CLIPBOARD
	    n = clipmethod == CLIPMETHOD_PROVIDER ? TRUE : clip_star.available;
#endif
	}
	else if (STRICMP(name, "unnamedplus") == 0)
	{
	    x = TRUE;
#ifdef FEAT_CLIPBOARD
	    // The + register is available when clipmethod is set to a provider,
	    // but becomes unavailable if on a platform that doesn't support it
	    // and clipmethod is "none".
	    // (Windows, MacOS).
# if defined(FEAT_X11) || defined(FEAT_WAYLAND_CLIPBOARD)
	    n = TRUE;
# elif defined(FEAT_EVAL)
	    if (clipmethod == CLIPMETHOD_PROVIDER)
		n = TRUE;
	    else
		n = FALSE;
# else
	    n = FALSE;
# endif
#endif
	}
    }

    if (argvars[1].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[1]))
	// return whether feature could ever be enabled
	rettv->vval.v_number = x;
    else
	// return whether feature is enabled
	rettv->vval.v_number = n;
}

/*
 * Return TRUE if "feature" can change later.
 * Also when checking for the feature has side effects, such as loading a DLL.
 */
    int
dynamic_feature(char_u *feature)
{
    return (feature == NULL
#if defined(FEAT_GUI) && defined(FEAT_BROWSE)
	    || (STRICMP(feature, "browse") == 0 && !gui.in_use)
#endif
#ifdef VIMDLL
	    || STRICMP(feature, "filterpipe") == 0
#endif
#if defined(FEAT_GUI) && !defined(ALWAYS_USE_GUI) && !defined(VIMDLL)
	    // this can only change on Unix where the ":gui" command could be
	    // used.
	    || (STRICMP(feature, "gui_running") == 0 && !gui.in_use)
#endif
#if defined(USE_ICONV) && defined(DYNAMIC_ICONV)
	    || STRICMP(feature, "iconv") == 0
#endif
#ifdef DYNAMIC_LUA
	    || STRICMP(feature, "lua") == 0
#endif
#ifdef FEAT_MOUSE_GPM
	    || (STRICMP(feature, "mouse_gpm_enabled") == 0 && !gpm_enabled())
#endif
#ifdef DYNAMIC_MZSCHEME
	    || STRICMP(feature, "mzscheme") == 0
#endif
#ifdef FEAT_NETBEANS_INTG
	    || STRICMP(feature, "netbeans_enabled") == 0
#endif
#ifdef DYNAMIC_PERL
	    || STRICMP(feature, "perl") == 0
#endif
#ifdef DYNAMIC_PYTHON
	    || STRICMP(feature, "python") == 0
#endif
#ifdef DYNAMIC_PYTHON3
	    || STRICMP(feature, "python3") == 0
#endif
#if defined(DYNAMIC_PYTHON) || defined(DYNAMIC_PYTHON3)
	    || STRICMP(feature, "pythonx") == 0
#endif
#ifdef DYNAMIC_RUBY
	    || STRICMP(feature, "ruby") == 0
#endif
#ifdef FEAT_SYN_HL
	    || STRICMP(feature, "syntax_items") == 0
#endif
#ifdef DYNAMIC_TCL
	    || STRICMP(feature, "tcl") == 0
#endif
	    // once "starting" is zero it will stay that way
	    || (STRICMP(feature, "vim_starting") == 0 && starting != 0)
	    || STRICMP(feature, "multi_byte_encoding") == 0
#if defined(FEAT_TERMINAL) && defined(MSWIN)
	    || STRICMP(feature, "conpty") == 0
#endif
	    );
}

/*
 * "haslocaldir()" function
 */
    static void
f_haslocaldir(typval_T *argvars, typval_T *rettv)
{
    tabpage_T	*tp = NULL;
    win_T	*wp = NULL;

    if (in_vim9script()
	    && (check_for_opt_number_arg(argvars, 0) == FAIL
		|| (argvars[0].v_type != VAR_UNKNOWN
		    && check_for_opt_number_arg(argvars, 1) == FAIL)))
	return;

    wp = find_tabwin(&argvars[0], &argvars[1], &tp);

    // Check for window-local and tab-local directories
    if (wp != NULL && wp->w_localdir != NULL)
	rettv->vval.v_number = 1;
    else if (tp != NULL && tp->tp_localdir != NULL)
	rettv->vval.v_number = 2;
    else
	rettv->vval.v_number = 0;
}

/*
 * "highlightID(name)" function
 */
    static void
f_hlID(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    rettv->vval.v_number = syn_name2id(tv_get_string(&argvars[0]));
}

/*
 * "highlight_exists()" function
 */
    static void
f_hlexists(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    rettv->vval.v_number = highlight_exists(tv_get_string(&argvars[0]));
}

/*
 * "hostname()" function
 */
    static void
f_hostname(typval_T *argvars UNUSED, typval_T *rettv)
{
    char_u hostname[256];

    mch_get_host_name(hostname, 256);
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = vim_strsave(hostname);
}

/*
 * "id()" function
 * Identity. Return address of item as a hex string, %p format.
 * Currently only valid for object/container types.
 * Return empty string if not an object.
 */
#ifdef VMS // VMS does not have defined uintptr_t
# if defined(HAVE_NO_LONG_LONG)
typedef unsigned int uintptr_t;
# else
typedef unsigned long long uintptr_t;
# endif
#endif // VMS

    static void
f_id(typval_T *argvars, typval_T *rettv)
{
    char    numbuf[NUMBUFLEN];
    char    *p = numbuf;

    switch (argvars[0].v_type)
    {
	case VAR_LIST:
	case VAR_TUPLE:
	case VAR_DICT:
	case VAR_OBJECT:
	case VAR_JOB:
	case VAR_CHANNEL:
	case VAR_BLOB:
	    // Assume pointer value in typval_T vval union at common location.
	    if (argvars[0].vval.v_object != NULL)
	    {
		// "v" gets the address as an integer
		uintptr_t v = (uintptr_t)(void *)argvars[0].vval.v_object;
		// Build a hex string from the item's address; it is in
		// reverse order. Ignore trailing zeros.
		for (; p < numbuf + sizeof(uintptr_t) * 2 && v != 0;
								++p, v >>= 4)
		    *p = "0123456789abcdef"[v & 0xf];
	    }
	default:
	    break;
    }
    *p = NUL;

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = vim_strsave((char_u *)numbuf);
}

/*
 * index() function for a blob
 */
    static void
index_func_blob(typval_T *argvars, typval_T *rettv)
{
    typval_T	tv;
    blob_T	*b;
    int		start = 0;
    int		error = FALSE;
    int		ic = FALSE;

    b = argvars[0].vval.v_blob;
    if (b == NULL)
	return;

    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	start = tv_get_number_chk(&argvars[2], &error);
	if (error)
	    return;
    }

    if (start < 0)
    {
	start = blob_len(b) + start;
	if (start < 0)
	    start = 0;
    }

    for (int idx = start; idx < blob_len(b); ++idx)
    {
	tv.v_type = VAR_NUMBER;
	tv.vval.v_number = blob_get(b, idx);
	if (tv_equal(&tv, &argvars[1], ic))
	{
	    rettv->vval.v_number = idx;
	    return;
	}
    }
}

/*
 * index() function for a tuple
 */
    static void
index_func_tuple(typval_T *argvars, typval_T *rettv)
{
    tuple_T	*tuple = argvars[0].vval.v_tuple;
    int		ic = FALSE;
    int		error = FALSE;

    if (tuple == NULL)
	return;

    int	start_idx = 0;
    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	start_idx = tv_get_number_chk(&argvars[2], &error);
	if (!error && argvars[3].v_type != VAR_UNKNOWN)
	    ic = (int)tv_get_bool_chk(&argvars[3], &error);
	if (error)
	    return;
    }

    rettv->vval.v_number = index_tuple(tuple, &argvars[1], start_idx, ic);
}

/*
 * index() function for a list
 */
    static void
index_func_list(typval_T *argvars, typval_T *rettv)
{
    list_T	*l;
    listitem_T	*item;
    long	idx = 0;
    int		ic = FALSE;
    int		error = FALSE;

    l = argvars[0].vval.v_list;
    if (l == NULL)
	return;

    CHECK_LIST_MATERIALIZE(l);
    item = l->lv_first;
    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	// Start at specified item.  Use the cached index that list_find()
	// sets, so that a negative number also works.
	item = list_find(l, (long)tv_get_number_chk(&argvars[2], &error));
	idx = l->lv_u.mat.lv_idx;
	if (argvars[3].v_type != VAR_UNKNOWN)
	    ic = (int)tv_get_bool_chk(&argvars[3], &error);
	if (error)
	    item = NULL;
    }

    for ( ; item != NULL; item = item->li_next, ++idx)
	if (tv_equal(&item->li_tv, &argvars[1], ic))
	{
	    rettv->vval.v_number = idx;
	    break;
	}
}

/*
 * "index()" function
 */
    static void
f_index(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = -1;

    if (in_vim9script()
	    && (check_for_list_or_tuple_or_blob_arg(argvars, 0) == FAIL
		|| (argvars[0].v_type == VAR_BLOB
		    && check_for_number_arg(argvars, 1) == FAIL)
		|| check_for_opt_number_arg(argvars, 2) == FAIL
		|| (argvars[2].v_type != VAR_UNKNOWN
		    && check_for_opt_bool_arg(argvars, 3) == FAIL)))
	return;

    if (argvars[0].v_type == VAR_BLOB)
	index_func_blob(argvars, rettv);
    else if (argvars[0].v_type == VAR_TUPLE)
	index_func_tuple(argvars, rettv);
    else if (argvars[0].v_type == VAR_LIST)
	index_func_list(argvars, rettv);
    else
	emsg(_(e_list_or_blob_required));
}

/*
 * Evaluate 'expr' with the v:key and v:val arguments and return the result.
 * The expression is expected to return a boolean value.  The caller should set
 * the VV_KEY and VV_VAL vim variables before calling this function.
 */
    int
indexof_eval_expr(typval_T *expr)
{
    typval_T	argv[3];
    typval_T	newtv;
    varnumber_T	found;
    int		error = FALSE;

    argv[0] = *get_vim_var_tv(VV_KEY);
    argv[1] = *get_vim_var_tv(VV_VAL);
    newtv.v_type = VAR_UNKNOWN;

    if (eval_expr_typval(expr, FALSE, argv, 2, NULL, &newtv) == FAIL)
	return FALSE;

    found = tv_get_bool_chk(&newtv, &error);
    clear_tv(&newtv);

    return error ? FALSE : found;
}

/*
 * Evaluate 'expr' for each byte in the Blob 'b' starting with the byte at
 * 'startidx' and return the index of the byte where 'expr' is TRUE.  Returns
 * -1 if 'expr' doesn't evaluate to TRUE for any of the bytes.
 */
    static int
indexof_blob(blob_T *b, long startidx, typval_T *expr)
{
    long	idx = 0;

    if (b == NULL)
	return -1;

    if (startidx < 0)
    {
	// negative index: index from the last byte
	startidx = blob_len(b) + startidx;
	if (startidx < 0)
	    startidx = 0;
    }

    set_vim_var_type(VV_KEY, VAR_NUMBER);
    set_vim_var_type(VV_VAL, VAR_NUMBER);

    int		called_emsg_start = called_emsg;
    for (idx = startidx; idx < blob_len(b); ++idx)
    {
	set_vim_var_nr(VV_KEY, idx);
	set_vim_var_nr(VV_VAL, blob_get(b, idx));

	if (indexof_eval_expr(expr))
	    return idx;

	if (called_emsg != called_emsg_start)
	    return -1;
    }

    return -1;
}

/*
 * Evaluate 'expr' for each item in the List 'l' starting with the item at
 * 'startidx' and return the index of the item where 'expr' is TRUE.  Returns
 * -1 if 'expr' doesn't evaluate to TRUE for any of the items.
 */
    static int
indexof_list(list_T *l, long startidx, typval_T *expr)
{
    listitem_T	*item;
    long	idx = 0;
    int		found;

    if (l == NULL)
	return -1;

    CHECK_LIST_MATERIALIZE(l);

    if (startidx == 0)
	item = l->lv_first;
    else
    {
	// Start at specified item.  Use the cached index that list_find()
	// sets, so that a negative number also works.
	item = list_find(l, startidx);
	if (item != NULL)
	    idx = l->lv_u.mat.lv_idx;
    }

    set_vim_var_type(VV_KEY, VAR_NUMBER);

    int		called_emsg_start = called_emsg;
    for ( ; item != NULL; item = item->li_next, ++idx)
    {
	set_vim_var_nr(VV_KEY, idx);
	copy_tv(&item->li_tv, get_vim_var_tv(VV_VAL));

	found = indexof_eval_expr(expr);
	clear_tv(get_vim_var_tv(VV_VAL));

	if (found)
	    return idx;

	if (called_emsg != called_emsg_start)
	    return -1;
    }

    return -1;
}

/*
 * "indexof()" function
 */
    static void
f_indexof(typval_T *argvars, typval_T *rettv)
{
    long	startidx = 0;
    typval_T	save_val;
    typval_T	save_key;
    int		save_did_emsg;

    rettv->vval.v_number = -1;

    if (check_for_list_or_tuple_or_blob_arg(argvars, 0) == FAIL
	    || check_for_string_or_func_arg(argvars, 1) == FAIL
	    || check_for_opt_dict_arg(argvars, 2) == FAIL)
	return;

    if ((argvars[1].v_type == VAR_STRING &&
		(argvars[1].vval.v_string == NULL
		 || *argvars[1].vval.v_string == NUL))
	    || (argvars[1].v_type == VAR_FUNC
		&& argvars[1].vval.v_partial == NULL))
	return;

    if (argvars[2].v_type == VAR_DICT)
	startidx = dict_get_number_def(argvars[2].vval.v_dict, "startidx", 0);

    prepare_vimvar(VV_VAL, &save_val);
    prepare_vimvar(VV_KEY, &save_key);

    // We reset "did_emsg" to be able to detect whether an error occurred
    // during evaluation of the expression.
    save_did_emsg = did_emsg;
    did_emsg = FALSE;

    if (argvars[0].v_type == VAR_BLOB)
	rettv->vval.v_number = indexof_blob(argvars[0].vval.v_blob, startidx,
								&argvars[1]);
    else if (argvars[0].v_type == VAR_TUPLE)
	rettv->vval.v_number = indexof_tuple(argvars[0].vval.v_tuple, startidx,
								&argvars[1]);
    else
	rettv->vval.v_number = indexof_list(argvars[0].vval.v_list, startidx,
								&argvars[1]);

    restore_vimvar(VV_KEY, &save_key);
    restore_vimvar(VV_VAL, &save_val);
    did_emsg |= save_did_emsg;
}

static int inputsecret_flag = 0;

/*
 * "input()" function
 *     Also handles inputsecret() when inputsecret is set.
 */
    static void
f_input(typval_T *argvars, typval_T *rettv)
{
    get_user_input(argvars, rettv, FALSE, inputsecret_flag);
}

/*
 * "inputdialog()" function
 */
    static void
f_inputdialog(typval_T *argvars, typval_T *rettv)
{
#if defined(FEAT_GUI_TEXTDIALOG)
    // Use a GUI dialog if the GUI is running and 'c' is not in 'guioptions'
    if (gui.in_use && vim_strchr(p_go, GO_CONDIALOG) == NULL)
    {
	char_u	*message;
	char_u	buf[NUMBUFLEN];
	char_u	*defstr = (char_u *)"";

	if (in_vim9script()
		&& (check_for_string_arg(argvars, 0) == FAIL
		    || check_for_opt_string_arg(argvars, 1) == FAIL
		    || (argvars[1].v_type != VAR_UNKNOWN
			&& check_for_opt_string_arg(argvars, 2) == FAIL)))
	    return;

	message = tv_get_string_chk(&argvars[0]);
	if (argvars[1].v_type != VAR_UNKNOWN
		&& (defstr = tv_get_string_buf_chk(&argvars[1], buf)) != NULL)
	    vim_strncpy(IObuff, defstr, IOSIZE - 1);
	else
	    IObuff[0] = NUL;
	if (message != NULL && defstr != NULL
		&& do_dialog(VIM_QUESTION, NULL, message,
			  (char_u *)_("&OK\n&Cancel"), 1, IObuff, FALSE) == 1)
	    rettv->vval.v_string = vim_strsave(IObuff);
	else
	{
	    if (message != NULL && defstr != NULL
					&& argvars[1].v_type != VAR_UNKNOWN
					&& argvars[2].v_type != VAR_UNKNOWN)
		rettv->vval.v_string = vim_strsave(
				      tv_get_string_buf(&argvars[2], buf));
	    else
		rettv->vval.v_string = NULL;
	}
	rettv->v_type = VAR_STRING;
    }
    else
#endif
	get_user_input(argvars, rettv, TRUE, inputsecret_flag);
}

/*
 * "inputlist()" function
 */
    static void
f_inputlist(typval_T *argvars, typval_T *rettv)
{
    list_T	*l;
    listitem_T	*li;
    int		selected;
    int		mouse_used;

#ifdef NO_CONSOLE_INPUT
    // While starting up, there is no place to enter text. When running tests
    // with --not-a-term we assume feedkeys() will be used.
    if (no_console_input() && !is_not_a_term())
	return;
#endif
    if (in_vim9script() && check_for_list_arg(argvars, 0) == FAIL)
	return;

    if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL)
    {
	semsg(_(e_argument_of_str_must_be_list), "inputlist()");
	return;
    }

    msg_start();
    msg_row = Rows - 1;	// for when 'cmdheight' > 1
    lines_left = Rows;	// avoid more prompt
    msg_scroll = TRUE;
    msg_clr_eos();

    l = argvars[0].vval.v_list;
    CHECK_LIST_MATERIALIZE(l);
    FOR_ALL_LIST_ITEMS(l, li)
    {
	msg_puts((char *)tv_get_string(&li->li_tv));
	msg_putchar('\n');
    }

    // Ask for choice.
    selected = prompt_for_number(&mouse_used);
    if (mouse_used)
	selected -= lines_left;

    rettv->vval.v_number = selected;
}

static garray_T	    ga_userinput = {0, 0, sizeof(tasave_T), 4, NULL};

/*
 * "inputrestore()" function
 */
    static void
f_inputrestore(typval_T *argvars UNUSED, typval_T *rettv)
{
    if (ga_userinput.ga_len > 0)
    {
	--ga_userinput.ga_len;
	restore_typeahead((tasave_T *)(ga_userinput.ga_data)
						  + ga_userinput.ga_len, TRUE);
	// default return is zero == OK
    }
    else if (p_verbose > 1)
    {
	verb_msg(_("called inputrestore() more often than inputsave()"));
	rettv->vval.v_number = 1; // Failed
    }
}

/*
 * "inputsave()" function
 */
    static void
f_inputsave(typval_T *argvars UNUSED, typval_T *rettv)
{
    // Add an entry to the stack of typeahead storage.
    if (ga_grow(&ga_userinput, 1) == OK)
    {
	save_typeahead((tasave_T *)(ga_userinput.ga_data)
						       + ga_userinput.ga_len);
	++ga_userinput.ga_len;
	// default return is zero == OK
    }
    else
	rettv->vval.v_number = 1; // Failed
}

/*
 * "inputsecret()" function
 */
    static void
f_inputsecret(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_opt_string_arg(argvars, 1) == FAIL))
	return;

    ++cmdline_star;
    ++inputsecret_flag;
    f_input(argvars, rettv);
    --cmdline_star;
    --inputsecret_flag;
}

/*
 * "interrupt()" function
 */
    static void
f_interrupt(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    got_int = TRUE;
}

/*
 * "invert(expr)" function
 */
    static void
f_invert(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
	return;

    rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL);
}

/*
 * Free resources in lval_root allocated by fill_exec_lval_root().
 */
    static void
free_lval_root(lval_root_T *root)
{
    if (root->lr_tv != NULL)
	free_tv(root->lr_tv);
    class_unref(root->lr_cl_exec);
    root->lr_tv = NULL;
    root->lr_cl_exec = NULL;
}

/*
 * This is used if executing in a method, the argument string is a
 * variable/item expr/reference. It may start with a potential class/object
 * variable.
 *
 * Adjust "root" as needed; lr_tv may be changed or freed.
 *
 * Always returns OK.
 * Free resources and return FAIL if the root should not be used. Otherwise OK.
 */

    static int
fix_variable_reference_lval_root(lval_root_T *root, char_u *name)
{

    // Check if lr_tv is the name of an object/class reference: name start with
    // "this" or name is class variable. Clear lr_tv if neither.
    int found_member = FALSE;
    if (root->lr_tv->v_type == VAR_OBJECT)
    {
	if (STRNCMP("this.", name, 5) == 0 ||STRCMP("this", name) == 0)
	    found_member = TRUE;
    }
    if (!found_member)	// not object member, try class member
    {
	// Explicitly check if the name is a class member.
	// If it's not then do nothing.
	char_u	*end;
	for (end = name; ASCII_ISALNUM(*end) || *end == '_'; ++end)
	    ;
	int idx = class_member_idx(root->lr_cl_exec, name, end - name);
	if (idx >= 0)
	{
	    // A class variable, replace lr_tv with it
	    clear_tv(root->lr_tv);
	    copy_tv(&root->lr_cl_exec->class_members_tv[idx], root->lr_tv);
	    found_member = TRUE;
	}
    }
    if (!found_member)
    {
	free_tv(root->lr_tv);
	root->lr_tv = NULL;	    // Not a member variable
    }
    // If FAIL, then must free_lval_root(root);
    return OK;
}

/*
 * "islocked()" function
 */
    static void
f_islocked(typval_T *argvars, typval_T *rettv)
{
    lval_T	lv;
    char_u	*end;
    dictitem_T	*di;

    rettv->vval.v_number = -1;

    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    char_u *name = tv_get_string(&argvars[0]);
#ifdef LOG_LOCKVAR
    ch_log(NULL, "LKVAR: f_islocked(): name: %s", name);
#endif

    lval_root_T	aroot;	// fully initialized in fill_exec_lval_root
    lval_root_T *root = NULL;

    // Set up lval_root if executing in a method.
    if (fill_exec_lval_root(&aroot) == OK)
    {
	// Almost always produces a valid lval_root since lr_cl_exec is used
	// for access verification, lr_tv may be set to NULL.
	if (fix_variable_reference_lval_root(&aroot, name) == OK)
	    root = &aroot;
    }

    lval_root_T	*lval_root_save = lval_root;
    lval_root = root;
    end = get_lval(name, NULL, &lv, FALSE, FALSE,
			     GLV_NO_AUTOLOAD | GLV_READ_ONLY | GLV_NO_DECL,
			     FNE_CHECK_START);
    lval_root = lval_root_save;

    if (end != NULL && lv.ll_name != NULL)
    {
	if (*end != NUL)
	{
	    semsg(_(lv.ll_name == lv.ll_name_end
		   ? e_invalid_argument_str : e_trailing_characters_str), end);
	}
	else
	{
	    if (lv.ll_tv == NULL)
	    {
		di = find_var(lv.ll_name, NULL, TRUE);
		if (di != NULL)
		{
		    // Consider a variable locked when:
		    // 1. the variable itself is locked
		    // 2. the value of the variable is locked.
		    // 3. the List or Dict value is locked.
		    rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK)
						   || tv_islocked(&di->di_tv));
		}
	    }
	    else if (lv.ll_is_root)
	    {
		rettv->vval.v_number = tv_islocked(lv.ll_tv);
	    }
	    else if (lv.ll_object != NULL)
	    {
		typval_T *tv = ((typval_T *)(lv.ll_object + 1)) + lv.ll_oi;
		rettv->vval.v_number = tv_islocked(tv);
#ifdef LOG_LOCKVAR
		ch_log(NULL, "LKVAR: f_islocked(): name %s (obj)", lv.ll_name);
#endif
	    }
	    else if (lv.ll_class != NULL)
	    {
		typval_T *tv = &lv.ll_class->class_members_tv[lv.ll_oi];
		rettv->vval.v_number = tv_islocked(tv);
#ifdef LOG_LOCKVAR
		ch_log(NULL, "LKVAR: f_islocked(): name %s (cl)", lv.ll_name);
#endif
	    }
	    else if (lv.ll_range)
		emsg(_(e_range_not_allowed));
	    else if (lv.ll_newkey != NULL)
		semsg(_(e_key_not_present_in_dictionary_str), lv.ll_newkey);
	    else if (lv.ll_list != NULL)
		// List item.
		rettv->vval.v_number = tv_islocked(&lv.ll_li->li_tv);
	    else if (lv.ll_tuple != NULL)
		// Tuple item.
		rettv->vval.v_number = tv_islocked(lv.ll_tv);
	    else
		// Dictionary item.
		rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv);
	}
    }

    if (root != NULL)
	free_lval_root(root);
    clear_lval(&lv);
}

/*
 * "items(dict)" function
 */
    static void
f_items(typval_T *argvars, typval_T *rettv)
{
    if (argvars[0].v_type == VAR_STRING)
	string2items(argvars, rettv);
    else if (argvars[0].v_type == VAR_LIST)
	list2items(argvars, rettv);
    else if (argvars[0].v_type == VAR_TUPLE)
	tuple2items(argvars, rettv);
    else if (argvars[0].v_type == VAR_BLOB)
	blob2items(argvars, rettv);
    else if (argvars[0].v_type == VAR_DICT)
	dict2items(argvars, rettv);
    else
	semsg(_(e_list_tuple_dict_blob_or_string_required_for_argument_nr), 1);
}

/*
 * "keytrans()" function
 */
    static void
f_keytrans(typval_T *argvars, typval_T *rettv)
{
    char_u *escaped;

    rettv->v_type = VAR_STRING;
    if (check_for_string_arg(argvars, 0) == FAIL
	    || argvars[0].vval.v_string == NULL)
	return;
    // Need to escape K_SPECIAL and CSI for mb_unescape().
    escaped = vim_strsave_escape_csi(argvars[0].vval.v_string);
    rettv->vval.v_string = str2special_save(escaped, TRUE, TRUE);
    vim_free(escaped);
}

/*
 * "last_buffer_nr()" function.
 */
    static void
f_last_buffer_nr(typval_T *argvars UNUSED, typval_T *rettv)
{
    int		n = 0;
    buf_T	*buf;

    FOR_ALL_BUFFERS(buf)
	if (n < buf->b_fnum)
	    n = buf->b_fnum;

    rettv->vval.v_number = n;
}

/*
 * "len()" function
 */
    void
f_len(typval_T *argvars, typval_T *rettv)
{
    switch (argvars[0].v_type)
    {
	case VAR_STRING:
	case VAR_NUMBER:
	    rettv->vval.v_number = (varnumber_T)STRLEN(
					       tv_get_string(&argvars[0]));
	    break;
	case VAR_BLOB:
	    rettv->vval.v_number = blob_len(argvars[0].vval.v_blob);
	    break;
	case VAR_LIST:
	    rettv->vval.v_number = list_len(argvars[0].vval.v_list);
	    break;
	case VAR_TUPLE:
	    rettv->vval.v_number = tuple_len(argvars[0].vval.v_tuple);
	    break;
	case VAR_DICT:
	    rettv->vval.v_number = dict_len(argvars[0].vval.v_dict);
	    break;
	case VAR_OBJECT:
	    rettv->vval.v_number = object_len(argvars[0].vval.v_object);
	    break;
	case VAR_UNKNOWN:
	case VAR_ANY:
	case VAR_VOID:
	case VAR_BOOL:
	case VAR_SPECIAL:
	case VAR_FLOAT:
	case VAR_FUNC:
	case VAR_PARTIAL:
	case VAR_JOB:
	case VAR_CHANNEL:
	case VAR_INSTR:
	case VAR_CLASS:
	case VAR_TYPEALIAS:
	    emsg(_(e_invalid_type_for_len));
	    break;
    }
}

    static void
libcall_common(typval_T *argvars UNUSED, typval_T *rettv, int type)
{
#ifdef FEAT_LIBCALL
    char_u		*string_in;
    char_u		**string_result;
    int			nr_result;
#endif

    rettv->v_type = type;
    if (type != VAR_NUMBER)
	rettv->vval.v_string = NULL;

    if (check_restricted() || check_secure())
	return;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_string_arg(argvars, 1) == FAIL
		|| check_for_string_or_number_arg(argvars, 2) == FAIL))
	return;

#ifdef FEAT_LIBCALL
    // The first two args must be strings, otherwise it's meaningless
    if (argvars[0].v_type == VAR_STRING && argvars[1].v_type == VAR_STRING)
    {
	string_in = NULL;
	if (argvars[2].v_type == VAR_STRING)
	    string_in = argvars[2].vval.v_string;
	if (type == VAR_NUMBER)
	{
	    string_result = NULL;
	}
	else
	{
	    rettv->vval.v_string = NULL;
	    string_result = &rettv->vval.v_string;
	}
	if (mch_libcall(argvars[0].vval.v_string,
			     argvars[1].vval.v_string,
			     string_in,
			     argvars[2].vval.v_number,
			     string_result,
			     &nr_result) == OK
		&& type == VAR_NUMBER)
	    rettv->vval.v_number = nr_result;
    }
#endif
}

/*
 * "libcall()" function
 */
    static void
f_libcall(typval_T *argvars, typval_T *rettv)
{
    libcall_common(argvars, rettv, VAR_STRING);
}

/*
 * "libcallnr()" function
 */
    static void
f_libcallnr(typval_T *argvars, typval_T *rettv)
{
    libcall_common(argvars, rettv, VAR_NUMBER);
}

/*
 * "line(string, [winid])" function
 */
    static void
f_line(typval_T *argvars, typval_T *rettv)
{
    linenr_T	lnum = 0;
    pos_T	*fp = NULL;
    int		fnum;
    int		id;
    tabpage_T	*tp;
    win_T	*wp;
    switchwin_T	switchwin;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_opt_number_arg(argvars, 1) == FAIL))
	return;

    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	// use window specified in the second argument
	id = (int)tv_get_number(&argvars[1]);
	wp = win_id2wp_tp(id, &tp);
	if (wp != NULL && tp != NULL)
	{
	    if (switch_win_noblock(&switchwin, wp, tp, TRUE) == OK)
	    {
		// With 'splitkeep' != cursor and in diff mode, prevent that the
		// window scrolls and keep the topline.
		if (*p_spk != 'c'
#ifdef FEAT_DIFF
		|| (curwin->w_p_diff && switchwin.sw_curwin->w_p_diff)
#endif
		)
		    skip_update_topline = TRUE;
		check_cursor();
		fp = var2fpos(&argvars[0], TRUE, &fnum, FALSE);
	    }
	    skip_update_topline = FALSE;
	    restore_win_noblock(&switchwin, TRUE);
	}
    }
    else
	// use current window
	fp = var2fpos(&argvars[0], TRUE, &fnum, FALSE);

    if (fp != NULL)
	lnum = fp->lnum;
    rettv->vval.v_number = lnum;
}

/*
 * "line2byte(lnum)" function
 */
    static void
f_line2byte(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifndef FEAT_BYTEOFF
    rettv->vval.v_number = -1;
#else
    linenr_T	lnum;

    if (in_vim9script() && check_for_lnum_arg(argvars, 0) == FAIL)
	return;

    lnum = tv_get_lnum(argvars);
    if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1)
	rettv->vval.v_number = -1;
    else
	rettv->vval.v_number = ml_find_line_or_offset(curbuf, lnum, NULL);
    if (rettv->vval.v_number >= 0)
	++rettv->vval.v_number;
#endif
}

#ifdef FEAT_LUA
/*
 * "luaeval()" function
 */
    static void
f_luaeval(typval_T *argvars, typval_T *rettv)
{
    char_u	*str;
    char_u	buf[NUMBUFLEN];

    if (check_restricted() || check_secure())
	return;

    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    str = tv_get_string_buf(&argvars[0], buf);
    do_luaeval(str, argvars + 1, rettv);
}
#endif

typedef enum
{
    MATCH_END,	    // matchend()
    MATCH_MATCH,    // match()
    MATCH_STR,	    // matchstr()
    MATCH_LIST,	    // matchlist()
    MATCH_POS	    // matchstrpos()
} matchtype_T;

    static void
find_some_match(typval_T *argvars, typval_T *rettv, matchtype_T type)
{
    char_u	*str = NULL;
    long	len = 0;
    char_u	*expr = NULL;
    char_u	*pat;
    regmatch_T	regmatch;
    char_u	patbuf[NUMBUFLEN];
    char_u	strbuf[NUMBUFLEN];
    char_u	*save_cpo;
    long	start = 0;
    long	nth = 1;
    colnr_T	startcol = 0;
    int		match = 0;
    list_T	*l = NULL;
    listitem_T	*li = NULL;
    long	idx = 0;
    char_u	*tofree = NULL;

    // Make 'cpoptions' empty, the 'l' flag should not be used here.
    save_cpo = p_cpo;
    p_cpo = empty_option;

    rettv->vval.v_number = -1;
    if (type == MATCH_LIST || type == MATCH_POS)
    {
	// type MATCH_LIST: return empty list when there are no matches.
	// type MATCH_POS: return ["", -1, -1, -1]
	if (rettv_list_alloc(rettv) == FAIL)
	    goto theend;
	if (type == MATCH_POS
		&& (list_append_string(rettv->vval.v_list,
					    (char_u *)"", 0) == FAIL
		    || list_append_number(rettv->vval.v_list,
					    (varnumber_T)-1) == FAIL
		    || list_append_number(rettv->vval.v_list,
					    (varnumber_T)-1) == FAIL
		    || list_append_number(rettv->vval.v_list,
					    (varnumber_T)-1) == FAIL))
	{
		list_free(rettv->vval.v_list);
		rettv->vval.v_list = NULL;
		goto theend;
	}
    }
    else if (type == MATCH_STR)
    {
	rettv->v_type = VAR_STRING;
	rettv->vval.v_string = NULL;
    }

    if (in_vim9script()
	    && (check_for_string_or_list_arg(argvars, 0) == FAIL
		|| check_for_string_arg(argvars, 1) == FAIL
		|| check_for_opt_number_arg(argvars, 2) == FAIL
		|| (argvars[2].v_type != VAR_UNKNOWN
		    && check_for_opt_number_arg(argvars, 3) == FAIL)))
	goto theend;

    if (argvars[0].v_type == VAR_LIST)
    {
	if ((l = argvars[0].vval.v_list) == NULL)
	    goto theend;
	CHECK_LIST_MATERIALIZE(l);
	li = l->lv_first;
    }
    else
    {
	expr = str = tv_get_string(&argvars[0]);
	len = (long)STRLEN(str);
    }

    pat = tv_get_string_buf_chk(&argvars[1], patbuf);
    if (pat == NULL)
	goto theend;

    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	int	    error = FALSE;

	start = (long)tv_get_number_chk(&argvars[2], &error);
	if (error)
	    goto theend;
	if (l != NULL)
	{
	    li = list_find(l, start);
	    if (li == NULL)
		goto theend;
	    idx = l->lv_u.mat.lv_idx;	// use the cached index
	}
	else
	{
	    if (start < 0)
		start = 0;
	    if (start > len)
		goto theend;
	    // When "count" argument is there ignore matches before "start",
	    // otherwise skip part of the string.  Differs when pattern is "^"
	    // or "\<".
	    if (argvars[3].v_type != VAR_UNKNOWN)
		startcol = start;
	    else
	    {
		str += start;
		len -= start;
	    }
	}

	if (argvars[3].v_type != VAR_UNKNOWN)
	    nth = (long)tv_get_number_chk(&argvars[3], &error);
	if (error)
	    goto theend;
    }

    regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
    if (regmatch.regprog != NULL)
    {
	regmatch.rm_ic = p_ic;

	for (;;)
	{
	    if (l != NULL)
	    {
		if (li == NULL)
		{
		    match = FALSE;
		    break;
		}
		vim_free(tofree);
		expr = str = echo_string(&li->li_tv, &tofree, strbuf, 0);
		if (str == NULL)
		    break;
	    }

	    match = vim_regexec_nl(&regmatch, str, startcol);

	    if (match && --nth <= 0)
		break;
	    if (l == NULL && !match)
		break;

	    // Advance to just after the match.
	    if (l != NULL)
	    {
		li = li->li_next;
		++idx;
	    }
	    else
	    {
		startcol = (colnr_T)(regmatch.startp[0]
				    + (*mb_ptr2len)(regmatch.startp[0]) - str);
		if (startcol > (colnr_T)len
				      || str + startcol <= regmatch.startp[0])
		{
		    match = FALSE;
		    break;
		}
	    }
	}

	if (match)
	{
	    if (type == MATCH_POS)
	    {
		listitem_T *li1 = rettv->vval.v_list->lv_first;
		listitem_T *li2 = li1->li_next;
		listitem_T *li3 = li2->li_next;
		listitem_T *li4 = li3->li_next;

		vim_free(li1->li_tv.vval.v_string);
		li1->li_tv.vval.v_string = vim_strnsave(regmatch.startp[0],
					regmatch.endp[0] - regmatch.startp[0]);
		li3->li_tv.vval.v_number =
				      (varnumber_T)(regmatch.startp[0] - expr);
		li4->li_tv.vval.v_number =
					(varnumber_T)(regmatch.endp[0] - expr);
		if (l != NULL)
		    li2->li_tv.vval.v_number = (varnumber_T)idx;
	    }
	    else if (type == MATCH_LIST)
	    {
		int i;

		// return list with matched string and submatches
		for (i = 0; i < NSUBEXP; ++i)
		{
		    if (regmatch.endp[i] == NULL)
		    {
			if (list_append_string(rettv->vval.v_list,
						     (char_u *)"", 0) == FAIL)
			    break;
		    }
		    else if (list_append_string(rettv->vval.v_list,
				regmatch.startp[i],
				(int)(regmatch.endp[i] - regmatch.startp[i]))
			    == FAIL)
			break;
		}
	    }
	    else if (type == MATCH_STR)
	    {
		// return matched string
		if (l != NULL)
		    copy_tv(&li->li_tv, rettv);
		else
		    rettv->vval.v_string = vim_strnsave(regmatch.startp[0],
					regmatch.endp[0] - regmatch.startp[0]);
	    }
	    else if (l != NULL)
		rettv->vval.v_number = idx;
	    else
	    {
		if (type != MATCH_END)
		    rettv->vval.v_number =
				      (varnumber_T)(regmatch.startp[0] - str);
		else
		    rettv->vval.v_number =
					(varnumber_T)(regmatch.endp[0] - str);
		rettv->vval.v_number += (varnumber_T)(str - expr);
	    }
	}
	vim_regfree(regmatch.regprog);
    }

theend:
    if (type == MATCH_POS && l == NULL && rettv->vval.v_list != NULL)
	// matchstrpos() without a list: drop the second item.
	listitem_remove(rettv->vval.v_list,
				       rettv->vval.v_list->lv_first->li_next);
    vim_free(tofree);
    p_cpo = save_cpo;
}

/*
 * Return all the matches in string "str" for pattern "rmp".
 * The matches are returned in the List "mlist".
 * If "submatches" is TRUE, then submatch information is also returned.
 * "matchbuf" is TRUE when called for matchbufline().
 */
    static int
get_matches_in_str(
    char_u	*str,
    regmatch_T	*rmp,
    list_T	*mlist,
    int		idx,
    int		submatches,
    int		matchbuf)
{
    long	len = (long)STRLEN(str);
    int		match = 0;
    colnr_T	startidx = 0;

    for (;;)
    {
	match = vim_regexec_nl(rmp, str, startidx);
	if (!match)
	    break;

	dict_T *d = dict_alloc();
	if (d == NULL)
	    return FAIL;
	if (list_append_dict(mlist, d) == FAIL)
	    return FAIL;

	if (dict_add_number(d, matchbuf ? "lnum" : "idx", idx) == FAIL)
	    return FAIL;

	if (dict_add_number(d, "byteidx",
		    (colnr_T)(rmp->startp[0] - str)) == FAIL)
	    return FAIL;

	if (dict_add_string_len(d, "text", rmp->startp[0],
		    (int)(rmp->endp[0] - rmp->startp[0])) == FAIL)
	    return FAIL;

	if (submatches)
	{
	    list_T *sml = list_alloc();
	    if (sml == NULL)
		return FAIL;

	    if (dict_add_list(d, "submatches", sml) == FAIL)
		return FAIL;

	    // return a list with the submatches
	    for (int i = 1; i < NSUBEXP; ++i)
	    {
		if (rmp->endp[i] == NULL)
		{
		    if (list_append_string(sml, (char_u *)"", 0) == FAIL)
			return FAIL;
		}
		else if (list_append_string(sml, rmp->startp[i],
			    (int)(rmp->endp[i] - rmp->startp[i])) == FAIL)
		    return FAIL;
	    }
	}
	startidx = (colnr_T)(rmp->endp[0] - str);
	if (startidx >= (colnr_T)len || str + startidx <= rmp->startp[0])
	    break;
    }

    return OK;
}

/*
 * "matchbufline()" function
 */
    static void
f_matchbufline(typval_T *argvars, typval_T *rettv)
{
    list_T	*retlist = NULL;
    char_u	*save_cpo;
    char_u	patbuf[NUMBUFLEN];
    regmatch_T	regmatch;

    rettv->vval.v_number = -1;
    if (rettv_list_alloc(rettv) != OK)
	return;
    retlist = rettv->vval.v_list;

    if (check_for_buffer_arg(argvars, 0) == FAIL
	    || check_for_string_arg(argvars, 1) == FAIL
	    || check_for_lnum_arg(argvars, 2) == FAIL
	    || check_for_lnum_arg(argvars, 3) == FAIL
	    || check_for_opt_dict_arg(argvars, 4) == FAIL)
	return;

    int prev_did_emsg = did_emsg;
    buf_T *buf = tv_get_buf(&argvars[0], FALSE);
    if (buf == NULL)
    {
	if (did_emsg == prev_did_emsg)
	    semsg(_(e_invalid_buffer_name_str), tv_get_string(&argvars[0]));
	return;
    }
    if (buf->b_ml.ml_mfp == NULL)
    {
	emsg(_(e_buffer_is_not_loaded));
	return;
    }

    char_u *pat = tv_get_string_buf(&argvars[1], patbuf);

    int		did_emsg_before = did_emsg;
    linenr_T slnum = tv_get_lnum_buf(&argvars[2], buf);
    if (did_emsg > did_emsg_before)
	return;
    if (slnum < 1)
    {
	semsg(_(e_invalid_value_for_argument_str), "lnum");
	return;
    }

    linenr_T elnum = tv_get_lnum_buf(&argvars[3], buf);
    if (did_emsg > did_emsg_before)
	return;
    if (elnum < 1 || elnum < slnum)
    {
	semsg(_(e_invalid_value_for_argument_str), "end_lnum");
	return;
    }

    if (elnum > buf->b_ml.ml_line_count)
	elnum = buf->b_ml.ml_line_count;

    int		submatches = FALSE;
    if (argvars[4].v_type != VAR_UNKNOWN)
    {
	dict_T *d = argvars[4].vval.v_dict;
	if (d != NULL)
	{
	    dictitem_T *di = dict_find(d, (char_u *)"submatches", -1);
	    if (di != NULL)
	    {
		if (di->di_tv.v_type != VAR_BOOL)
		{
		    semsg(_(e_invalid_value_for_argument_str), "submatches");
		    return;
		}
		submatches = tv_get_bool(&di->di_tv);
	    }
	}
    }

    // Make 'cpoptions' empty, the 'l' flag should not be used here.
    save_cpo = p_cpo;
    p_cpo = empty_option;

    regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
    if (regmatch.regprog == NULL)
	goto theend;
    regmatch.rm_ic = p_ic;

    while (slnum <= elnum)
    {
	char_u *str = ml_get_buf(buf, slnum, FALSE);
	if (get_matches_in_str(str, &regmatch, retlist, slnum, submatches,
								TRUE) == FAIL)
	    goto cleanup;
	slnum++;
    }

cleanup:
    vim_regfree(regmatch.regprog);

theend:
    p_cpo = save_cpo;
}

/*
 * "match()" function
 */
    static void
f_match(typval_T *argvars, typval_T *rettv)
{
    find_some_match(argvars, rettv, MATCH_MATCH);
}

/*
 * "matchend()" function
 */
    static void
f_matchend(typval_T *argvars, typval_T *rettv)
{
    find_some_match(argvars, rettv, MATCH_END);
}

/*
 * "matchlist()" function
 */
    static void
f_matchlist(typval_T *argvars, typval_T *rettv)
{
    find_some_match(argvars, rettv, MATCH_LIST);
}

/*
 * "matchstr()" function
 */
    static void
f_matchstr(typval_T *argvars, typval_T *rettv)
{
    find_some_match(argvars, rettv, MATCH_STR);
}

/*
 * "matchstrlist()" function
 */
    static void
f_matchstrlist(typval_T *argvars, typval_T *rettv)
{
    list_T	*retlist = NULL;
    char_u	*save_cpo;
    list_T	*l = NULL;
    listitem_T	*li = NULL;
    char_u	patbuf[NUMBUFLEN];
    regmatch_T	regmatch;

    rettv->vval.v_number = -1;
    if (rettv_list_alloc(rettv) != OK)
	return;
    retlist = rettv->vval.v_list;

    if (check_for_list_arg(argvars, 0) == FAIL
	    || check_for_string_arg(argvars, 1) == FAIL
	    || check_for_opt_dict_arg(argvars, 2) == FAIL)
	return;

    if ((l = argvars[0].vval.v_list) == NULL)
	return;

    char_u *pat = tv_get_string_buf_chk(&argvars[1], patbuf);
    if (pat == NULL)
	return;

    // Make 'cpoptions' empty, the 'l' flag should not be used here.
    save_cpo = p_cpo;
    p_cpo = empty_option;

    regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
    if (regmatch.regprog == NULL)
	goto theend;
    regmatch.rm_ic = p_ic;

    int		submatches = FALSE;
    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	dict_T *d = argvars[2].vval.v_dict;
	if (d != NULL)
	{
	    dictitem_T *di = dict_find(d, (char_u *)"submatches", -1);
	    if (di != NULL)
	    {
		if (di->di_tv.v_type != VAR_BOOL)
		{
		    semsg(_(e_invalid_value_for_argument_str), "submatches");
		    goto cleanup;
		}
		submatches = tv_get_bool(&di->di_tv);
	    }
	}
    }

    int idx = 0;
    CHECK_LIST_MATERIALIZE(l);
    FOR_ALL_LIST_ITEMS(l, li)
    {
	if (li->li_tv.v_type == VAR_STRING && li->li_tv.vval.v_string != NULL)
	{
	    char_u *str = li->li_tv.vval.v_string;
	    if (get_matches_in_str(str, &regmatch, retlist, idx, submatches,
								FALSE) == FAIL)
		goto cleanup;
	}
	idx++;
    }

cleanup:
    vim_regfree(regmatch.regprog);

theend:
    p_cpo = save_cpo;
}

/*
 * "matchstrpos()" function
 */
    static void
f_matchstrpos(typval_T *argvars, typval_T *rettv)
{
    find_some_match(argvars, rettv, MATCH_POS);
}

    static void
max_min(typval_T *argvars, typval_T *rettv, int domax)
{
    varnumber_T	n = 0;

    if (in_vim9script() &&
	    check_for_list_or_tuple_or_dict_arg(argvars, 0) == FAIL)
	return;

    rettv->vval.v_number = 0;

    if (argvars[0].v_type == VAR_LIST)
    {
	list_T		*l;
	listitem_T	*li;
	typval_T	*tv = NULL;

	l = argvars[0].vval.v_list;
	if (l != NULL && l->lv_len > 0)
	{
	    if (l->lv_first == &range_list_item)
	    {
		if ((l->lv_u.nonmat.lv_stride > 0) ^ domax)
		    n = l->lv_u.nonmat.lv_start;
		else
		    n = l->lv_u.nonmat.lv_start + ((varnumber_T)l->lv_len - 1)
						    * l->lv_u.nonmat.lv_stride;
		rettv->vval.v_number = n;
	    }
	    else
	    {
		FOR_ALL_LIST_ITEMS(l, li)
		{
		    if (tv == NULL)
			tv = &li->li_tv;
		    else
		    {
			int res;
			if (typval_compare2(&li->li_tv, tv,
				domax ? EXPR_GREATER : EXPR_SMALLER, FALSE, &res) == FAIL)
			    return;
			if (res == OK)
			    tv = &li->li_tv;
		    }
		}

		if (tv != NULL)
		    copy_tv(tv, rettv);
	    }
	}
    }
    else if (argvars[0].v_type == VAR_TUPLE)
    {
	typval_T *tv;

	tv = tuple_max_min(argvars[0].vval.v_tuple, domax);
	if (tv != NULL)
	    copy_tv(tv, rettv);
    }
    else if (argvars[0].v_type == VAR_DICT)
    {
	dict_T		*d;
	hashitem_T	*hi;
	int		todo;
	typval_T	*tv = NULL;

	d = argvars[0].vval.v_dict;
	if (d != NULL)
	{
	    todo = (int)d->dv_hashtab.ht_used;
	    FOR_ALL_HASHTAB_ITEMS(&d->dv_hashtab, hi, todo)
	    {
		if (!HASHITEM_EMPTY(hi))
		{
		    --todo;
		    if (tv == NULL)
			tv = &HI2DI(hi)->di_tv;
		    else
		    {
			int res;
			if (typval_compare2(&HI2DI(hi)->di_tv, tv,
				domax ? EXPR_GREATER : EXPR_SMALLER, FALSE, &res) == FAIL)
			    return;
			if (res == OK)
			    tv = &HI2DI(hi)->di_tv;
		    }
		}
	    }
	}

	if (tv != NULL)
	    copy_tv(tv, rettv);
    }
    else
	semsg(_(e_argument_of_str_must_be_list_or_dictionary), domax ? "max()" : "min()");
}

/*
 * "max()" function
 */
    static void
f_max(typval_T *argvars, typval_T *rettv)
{
    max_min(argvars, rettv, TRUE);
}

/*
 * "min()" function
 */
    static void
f_min(typval_T *argvars, typval_T *rettv)
{
    max_min(argvars, rettv, FALSE);
}

#if defined(FEAT_MZSCHEME)
/*
 * "mzeval()" function
 */
    static void
f_mzeval(typval_T *argvars, typval_T *rettv)
{
    char_u	*str;
    char_u	buf[NUMBUFLEN];

    if (check_restricted() || check_secure())
	return;

    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    str = tv_get_string_buf(&argvars[0], buf);
    do_mzeval(str, rettv);
}

    void
mzscheme_call_vim(char_u *name, typval_T *args, typval_T *rettv)
{
    typval_T argvars[3];

    argvars[0].v_type = VAR_STRING;
    argvars[0].vval.v_string = name;
    copy_tv(args, &argvars[1]);
    argvars[2].v_type = VAR_UNKNOWN;
    f_call(argvars, rettv);
    clear_tv(&argvars[1]);
}
#endif

/*
 * "nextnonblank()" function
 */
    static void
f_nextnonblank(typval_T *argvars, typval_T *rettv)
{
    linenr_T	lnum;

    if (in_vim9script() && check_for_lnum_arg(argvars, 0) == FAIL)
	return;

    for (lnum = tv_get_lnum(argvars); ; ++lnum)
    {
	if (lnum < 0 || lnum > curbuf->b_ml.ml_line_count)
	{
	    lnum = 0;
	    break;
	}
	if (*skipwhite(ml_get(lnum)) != NUL)
	    break;
    }
    rettv->vval.v_number = lnum;
}


/*
 * "ngettext()" function
 */
    static void
f_ngettext(typval_T *argvars, typval_T *rettv)
{
#if defined(HAVE_BIND_TEXTDOMAIN_CODESET)
    char *prev = NULL;
#endif

    if (check_for_nonempty_string_arg(argvars, 0) == FAIL
	|| check_for_nonempty_string_arg(argvars, 1) == FAIL
	|| check_for_number_arg(argvars, 2) == FAIL
	|| check_for_opt_string_arg(argvars, 3) == FAIL)
	return;

    rettv->v_type = VAR_STRING;

    if (argvars[3].v_type == VAR_STRING &&
	    argvars[3].vval.v_string != NULL &&
	    *(argvars[3].vval.v_string) != NUL)
    {
#if defined(HAVE_BIND_TEXTDOMAIN_CODESET)
	prev = bind_textdomain_codeset((const char *)argvars[3].vval.v_string, (char *)p_enc);
#endif

#if defined(HAVE_DNGETTEXT)
	rettv->vval.v_string = vim_strsave((char_u *)dngettext((const char *)argvars[3].vval.v_string, (const char *)argvars[0].vval.v_string, (const char *)argvars[1].vval.v_string, (int)argvars[2].vval.v_number));
#else
	textdomain((const char *)argvars[3].vval.v_string);
	rettv->vval.v_string = vim_strsave((char_u *)NGETTEXT((const char *)argvars[0].vval.v_string, (const char *)argvars[1].vval.v_string, argvars[2].vval.v_number));
	textdomain(VIMPACKAGE);
#endif

#if defined(HAVE_BIND_TEXTDOMAIN_CODESET)
	if (prev != NULL)
	    bind_textdomain_codeset((const char *)argvars[3].vval.v_string, prev);
#endif
    }
    else
	rettv->vval.v_string = vim_strsave((char_u *)NGETTEXT((const char *)argvars[0].vval.v_string, (const char *)argvars[1].vval.v_string, argvars[2].vval.v_number));
}


/*
 * "nr2char()" function
 */
    static void
f_nr2char(typval_T *argvars, typval_T *rettv)
{
    char_u	buf[NUMBUFLEN];

    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_opt_bool_arg(argvars, 1) == FAIL))
	return;

    if (has_mbyte)
    {
	int	utf8 = 0;

	if (argvars[1].v_type != VAR_UNKNOWN)
	    utf8 = (int)tv_get_bool_chk(&argvars[1], NULL);
	if (utf8)
	    buf[utf_char2bytes((int)tv_get_number(&argvars[0]), buf)] = NUL;
	else
	    buf[(*mb_char2bytes)((int)tv_get_number(&argvars[0]), buf)] = NUL;
    }
    else
    {
	buf[0] = (char_u)tv_get_number(&argvars[0]);
	buf[1] = NUL;
    }
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = vim_strsave(buf);
}

/*
 * "or(expr, expr)" function
 */
    static void
f_or(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL))
	return;

    rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
					| tv_get_number_chk(&argvars[1], NULL);
}

#ifdef FEAT_PERL
/*
 * "perleval()" function
 */
    static void
f_perleval(typval_T *argvars, typval_T *rettv)
{
    char_u	*str;
    char_u	buf[NUMBUFLEN];

    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    str = tv_get_string_buf(&argvars[0], buf);
    do_perleval(str, rettv);
}
#endif

/*
 * "prevnonblank()" function
 */
    static void
f_prevnonblank(typval_T *argvars, typval_T *rettv)
{
    linenr_T	lnum;

    if (in_vim9script() && check_for_lnum_arg(argvars, 0) == FAIL)
	return;

    lnum = tv_get_lnum(argvars);
    if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count)
	lnum = 0;
    else
	while (lnum >= 1 && *skipwhite(ml_get(lnum)) == NUL)
	    --lnum;
    rettv->vval.v_number = lnum;
}

// This dummy va_list is here because:
// - passing a NULL pointer doesn't work when va_list isn't a pointer
// - locally in the function results in a "used before set" warning
// - using va_start() to initialize it gives "function with fixed args" error
static va_list	ap;

/*
 * "printf()" function
 */
    static void
f_printf(typval_T *argvars, typval_T *rettv)
{
    char_u	buf[NUMBUFLEN];
    int		len;
    char_u	*s;
    int		saved_did_emsg = did_emsg;
    char	*fmt;

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;

    if (in_vim9script() && check_for_string_or_number_arg(argvars, 0) == FAIL)
	return;

    // Get the required length, allocate the buffer and do it for real.
    did_emsg = FALSE;
    fmt = (char *)tv_get_string_buf(&argvars[0], buf);
    len = vim_vsnprintf_typval(NULL, 0, fmt, ap, argvars + 1);
    if (!did_emsg)
    {
	s = alloc(len + 1);
	if (s != NULL)
	{
	    rettv->vval.v_string = s;
	    (void)vim_vsnprintf_typval((char *)s, len + 1, fmt,
							      ap, argvars + 1);
	}
    }
    did_emsg |= saved_did_emsg;
}

/*
 * "pum_getpos()" function
 */
    static void
f_pum_getpos(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    if (rettv_dict_alloc(rettv) == FAIL)
	return;
    pum_set_event_info(rettv->vval.v_dict);
}

/*
 * "pumvisible()" function
 */
    static void
f_pumvisible(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    if (pum_visible())
	rettv->vval.v_number = 1;
}

#ifdef FEAT_PYTHON3
/*
 * "py3eval()" function
 */
    static void
f_py3eval(typval_T *argvars, typval_T *rettv)
{
    char_u	*str;
    char_u	buf[NUMBUFLEN];
    dict_T	*locals;

    if (check_restricted() || check_secure())
	return;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_opt_dict_arg(argvars, 1) == FAIL))
	return;

    if (p_pyx == 0)
	p_pyx = 3;

    if (argvars[1].v_type == VAR_DICT)
    {
	locals = argvars[1].vval.v_dict;
    }
    else if (argvars[1].v_type != VAR_UNKNOWN)
    {
	emsg(_(e_dictionary_required));
	return;
    }
    else
    {
	locals = NULL;
    }

    str = tv_get_string_buf(&argvars[0], buf);
    do_py3eval(str, locals, rettv);
}
#endif

#ifdef FEAT_PYTHON
/*
 * "pyeval()" function
 */
    static void
f_pyeval(typval_T *argvars, typval_T *rettv)
{
    char_u	*str;
    char_u	buf[NUMBUFLEN];
    dict_T	*locals;

    if (check_restricted() || check_secure())
	return;

    if (in_vim9script() && (
	    check_for_string_arg(argvars, 0) == FAIL ||
	    check_for_opt_dict_arg(argvars, 1) == FAIL ) )
	return;

    if (p_pyx == 0)
	p_pyx = 2;

    if (argvars[1].v_type == VAR_DICT)
    {
	locals = argvars[1].vval.v_dict;
    }
    else if (argvars[1].v_type != VAR_UNKNOWN)
    {
	emsg( "Invalid argument: must be dict" );
	return;
    }
    else
    {
	locals = NULL;
    }

    str = tv_get_string_buf(&argvars[0], buf);
    do_pyeval(str, locals, rettv);
}
#endif

#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3)
/*
 * "pyxeval()" function
 */
    static void
f_pyxeval(typval_T *argvars, typval_T *rettv)
{
    if (check_restricted() || check_secure())
	return;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_opt_dict_arg(argvars, 1) == FAIL))
	return;

# if defined(FEAT_PYTHON) && defined(FEAT_PYTHON3)
    init_pyxversion();
    if (p_pyx == 2)
	f_pyeval(argvars, rettv);
    else
	f_py3eval(argvars, rettv);
# elif defined(FEAT_PYTHON)
    f_pyeval(argvars, rettv);
# elif defined(FEAT_PYTHON3)
    f_py3eval(argvars, rettv);
# endif
}
#endif

static UINT32_T srand_seed_for_testing = 0;
static int	srand_seed_for_testing_is_used = FALSE;

    static void
f_test_srand_seed(typval_T *argvars, typval_T *rettv UNUSED)
{
    if (in_vim9script() && check_for_opt_number_arg(argvars, 0) == FAIL)
	return;

    if (argvars[0].v_type == VAR_UNKNOWN)
	srand_seed_for_testing_is_used = FALSE;
    else
    {
	srand_seed_for_testing = (UINT32_T)tv_get_number(&argvars[0]);
	srand_seed_for_testing_is_used = TRUE;
    }
}

    static void
init_srand(UINT32_T *x)
{
    struct {
	union {
	    UINT32_T number;
	    char_u   bytes[sizeof(UINT32_T)];
	} contents;
    } buf;

    if (srand_seed_for_testing_is_used)
    {
	*x = srand_seed_for_testing;
	return;
    }

    if (mch_get_random(buf.contents.bytes, sizeof(buf.contents.bytes)) == OK)
    {
	*x = buf.contents.number;
	return;
    }

    // The system's random number generator doesn't work, fall back to:
    // - randombytes_random()
    // - reltime() or time()
    // - XOR with process ID
#if defined(FEAT_SODIUM)
    if (crypt_sodium_init() >= 0)
	*x = crypt_sodium_randombytes_random();
    else
#endif
    {
#if defined(FEAT_RELTIME)
	proftime_T res;
	profile_start(&res);
# if defined(MSWIN)
	*x = (UINT32_T)res.LowPart;
# else
	*x = (UINT32_T)res.tv_fsec;
# endif
#else
	*x = vim_time();
#endif
	*x ^= mch_get_pid();
    }
}

#define ROTL(x, k) (((x) << (k)) | ((x) >> (32 - (k))))
#define SPLITMIX32(x, z) ( \
    (z) = ((x) += 0x9e3779b9), \
    (z) = ((z) ^ ((z) >> 16)) * 0x85ebca6b, \
    (z) = ((z) ^ ((z) >> 13)) * 0xc2b2ae35, \
    (z) ^ ((z) >> 16) \
    )
#define SHUFFLE_XOSHIRO128STARSTAR(x, y, z, w) \
    result = ROTL((y) * 5, 7) * 9; \
    t = (y) << 9; \
    (z) ^= (x); \
    (w) ^= (y); \
    (y) ^= (z), (x) ^= (w); \
    (z) ^= t; \
    (w) = ROTL(w, 11);

/*
 * "rand()" function
 */
    static void
f_rand(typval_T *argvars, typval_T *rettv)
{
    list_T	*l = NULL;
    static UINT32_T	gx, gy, gz, gw;
    static int	initialized = FALSE;
    listitem_T	*lx, *ly, *lz, *lw;
    UINT32_T	x = 0, y, z, w, t, result;

    if (in_vim9script() && check_for_opt_list_arg(argvars, 0) == FAIL)
	return;

    if (argvars[0].v_type == VAR_UNKNOWN)
    {
	// When no argument is given use the global seed list.
	if (initialized == FALSE)
	{
	    // Initialize the global seed list.
	    init_srand(&x);

	    gx = SPLITMIX32(x, z);
	    gy = SPLITMIX32(x, z);
	    gz = SPLITMIX32(x, z);
	    gw = SPLITMIX32(x, z);
	    initialized = TRUE;
	}

	SHUFFLE_XOSHIRO128STARSTAR(gx, gy, gz, gw);
    }
    else if (argvars[0].v_type == VAR_LIST)
    {
	l = argvars[0].vval.v_list;
	if (l == NULL || list_len(l) != 4)
	    goto theend;

	lx = list_find(l, 0L);
	ly = list_find(l, 1L);
	lz = list_find(l, 2L);
	lw = list_find(l, 3L);
	if (lx->li_tv.v_type != VAR_NUMBER) goto theend;
	if (ly->li_tv.v_type != VAR_NUMBER) goto theend;
	if (lz->li_tv.v_type != VAR_NUMBER) goto theend;
	if (lw->li_tv.v_type != VAR_NUMBER) goto theend;
	x = (UINT32_T)lx->li_tv.vval.v_number;
	y = (UINT32_T)ly->li_tv.vval.v_number;
	z = (UINT32_T)lz->li_tv.vval.v_number;
	w = (UINT32_T)lw->li_tv.vval.v_number;

	SHUFFLE_XOSHIRO128STARSTAR(x, y, z, w);

	lx->li_tv.vval.v_number = (varnumber_T)x;
	ly->li_tv.vval.v_number = (varnumber_T)y;
	lz->li_tv.vval.v_number = (varnumber_T)z;
	lw->li_tv.vval.v_number = (varnumber_T)w;
    }
    else
	goto theend;

    rettv->v_type = VAR_NUMBER;
    rettv->vval.v_number = (varnumber_T)result;
    return;

theend:
    semsg(_(e_invalid_argument_str), tv_get_string(&argvars[0]));
    rettv->v_type = VAR_NUMBER;
    rettv->vval.v_number = -1;
}

/*
 * "srand()" function
 */
    static void
f_srand(typval_T *argvars, typval_T *rettv)
{
    UINT32_T x = 0, z;

    if (rettv_list_alloc(rettv) == FAIL)
	return;

    if (in_vim9script() && check_for_opt_number_arg(argvars, 0) == FAIL)
	return;

    if (argvars[0].v_type == VAR_UNKNOWN)
    {
	init_srand(&x);
    }
    else
    {
	int	    error = FALSE;

	x = (UINT32_T)tv_get_number_chk(&argvars[0], &error);
	if (error)
	    return;
    }

    list_append_number(rettv->vval.v_list, (varnumber_T)SPLITMIX32(x, z));
    list_append_number(rettv->vval.v_list, (varnumber_T)SPLITMIX32(x, z));
    list_append_number(rettv->vval.v_list, (varnumber_T)SPLITMIX32(x, z));
    list_append_number(rettv->vval.v_list, (varnumber_T)SPLITMIX32(x, z));
}

#undef ROTL
#undef SPLITMIX32
#undef SHUFFLE_XOSHIRO128STARSTAR

/*
 * "range()" function
 */
    static void
f_range(typval_T *argvars, typval_T *rettv)
{
    varnumber_T	start;
    varnumber_T	end;
    varnumber_T	stride = 1;
    int		error = FALSE;

    if (rettv_list_alloc(rettv) == FAIL)
	return;

    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_opt_number_arg(argvars, 1) == FAIL
		|| (argvars[1].v_type != VAR_UNKNOWN
		    && check_for_opt_number_arg(argvars, 2) == FAIL)))
	return;

    start = tv_get_number_chk(&argvars[0], &error);
    if (argvars[1].v_type == VAR_UNKNOWN)
    {
	end = start - 1;
	start = 0;
    }
    else
    {
	end = tv_get_number_chk(&argvars[1], &error);
	if (argvars[2].v_type != VAR_UNKNOWN)
	    stride = tv_get_number_chk(&argvars[2], &error);
    }

    if (error)
	return;		// type error; errmsg already given
    if (stride == 0)
    {
	emsg(_(e_stride_is_zero));
	return;
    }
    if (stride > 0 ? end + 1 < start : end - 1 > start)
    {
	emsg(_(e_start_past_end));
	return;
    }

    list_T *list = rettv->vval.v_list;

    // Create a non-materialized list.  This is much more efficient and
    // works with ":for".  If used otherwise CHECK_LIST_MATERIALIZE() must
    // be called.
    list->lv_first = &range_list_item;
    list->lv_u.nonmat.lv_start = start;
    list->lv_u.nonmat.lv_end = end;
    list->lv_u.nonmat.lv_stride = stride;
    if (stride > 0 ? end < start : end > start)
	list->lv_len = 0;
    else
	list->lv_len = (end - start) / stride + 1;
}

/*
 * Materialize "list".
 * Do not call directly, use CHECK_LIST_MATERIALIZE()
 */
    void
range_list_materialize(list_T *list)
{
    varnumber_T start = list->lv_u.nonmat.lv_start;
    varnumber_T end = list->lv_u.nonmat.lv_end;
    int		stride = list->lv_u.nonmat.lv_stride;
    varnumber_T i;

    list->lv_first = NULL;
    list->lv_u.mat.lv_last = NULL;
    list->lv_len = 0;
    list->lv_u.mat.lv_idx_item = NULL;
    for (i = start; stride > 0 ? i <= end : i >= end; i += stride)
    {
	if (list_append_number(list, i) == FAIL)
	    break;
	if (list->lv_lock & VAR_ITEMS_LOCKED)
	    list->lv_u.mat.lv_last->li_tv.v_lock = VAR_LOCKED;
    }
    list->lv_lock &= ~VAR_ITEMS_LOCKED;
}

/*
 * "getreginfo()" function
 */
    static void
f_getreginfo(typval_T *argvars, typval_T *rettv)
{
    int		regname;
    char_u	buf[NUMBUFLEN + 2];
    long	reglen = 0;
    dict_T	*dict;
    list_T	*list;

    if (in_vim9script() && check_for_opt_string_arg(argvars, 0) == FAIL)
	return;

    regname = getreg_get_regname(argvars);
    if (regname == 0)
	return;

    if (regname == '@')
	regname = '"';

    if (rettv_dict_alloc(rettv) == FAIL)
	return;
    dict = rettv->vval.v_dict;

    list = (list_T *)get_reg_contents(regname, GREG_EXPR_SRC | GREG_LIST);
    if (list == NULL)
	return;
    (void)dict_add_list(dict, "regcontents", list);

    buf[0] = NUL;
    buf[1] = NUL;
    switch (get_reg_type(regname, &reglen))
    {
	case MLINE: buf[0] = 'V'; break;
	case MCHAR: buf[0] = 'v'; break;
	case MBLOCK:
		    vim_snprintf((char *)buf, sizeof(buf), "%c%ld", Ctrl_V,
			    reglen + 1);
		    break;
    }
    (void)dict_add_string(dict, (char *)"regtype", buf);

    buf[0] = get_register_name(get_unname_register());
    buf[1] = NUL;
    if (regname == '"')
	(void)dict_add_string(dict, (char *)"points_to", buf);
    else
    {
	dictitem_T	*item = dictitem_alloc((char_u *)"isunnamed");

	if (item != NULL)
	{
	    item->di_tv.v_type = VAR_BOOL;
	    item->di_tv.vval.v_number = regname == buf[0]
						      ? VVAL_TRUE : VVAL_FALSE;
	    (void)dict_add(dict, item);
	}
    }
}

    static void
return_register(int regname, typval_T *rettv)
{
    char_u buf[2] = {0, 0};

    buf[0] = (char_u)regname;
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = vim_strsave(buf);
}

/*
 * "reg_executing()" function
 */
    static void
f_reg_executing(typval_T *argvars UNUSED, typval_T *rettv)
{
    return_register(reg_executing, rettv);
}

/*
 * "reg_recording()" function
 */
    static void
f_reg_recording(typval_T *argvars UNUSED, typval_T *rettv)
{
    return_register(reg_recording, rettv);
}

/*
 * "rename({from}, {to})" function
 */
    static void
f_rename(typval_T *argvars, typval_T *rettv)
{
    char_u	buf[NUMBUFLEN];

    rettv->vval.v_number = -1;
    if (check_restricted() || check_secure())
	return;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_string_arg(argvars, 1) == FAIL))
	return;

    rettv->vval.v_number = vim_rename(tv_get_string(&argvars[0]),
				      tv_get_string_buf(&argvars[1], buf));
}

/*
 * Repeat the list "l" "n" times and set "rettv" to the new list.
 */
    static void
repeat_list(list_T *l, int n, typval_T *rettv)
{
    if (rettv_list_alloc(rettv) == FAIL
	    || l == NULL
	    || n <= 0)
	return;

    while (n-- > 0)
	if (list_extend(rettv->vval.v_list, l, NULL) == FAIL)
	    break;
}

/*
 * Repeat the blob "b" "n" times and set "rettv" to the new blob.
 */
    static void
repeat_blob(typval_T *blob_tv, int n, typval_T *rettv)
{
    int		slen;
    int		len;
    int		i;
    blob_T	*blob = blob_tv->vval.v_blob;

    if (rettv_blob_alloc(rettv) == FAIL
	    || blob == NULL
	    || n <= 0)
	return;

    slen = blob->bv_ga.ga_len;
    len = (int)slen * n;
    if (len <= 0)
	return;

    if (ga_grow(&rettv->vval.v_blob->bv_ga, len) == FAIL)
	return;

    rettv->vval.v_blob->bv_ga.ga_len = len;

    for (i = 0; i < slen; ++i)
	if (blob_get(blob, i) != 0)
	    break;

    if (i == slen)
	// No need to copy since all bytes are already zero
	return;

    for (i = 0; i < n; ++i)
	blob_set_range(rettv->vval.v_blob,
		(long)i * slen, ((long)i + 1) * slen - 1, blob_tv);
}

/*
 * Repeat the string "str" "n" times and set "rettv" to the new string.
 */
    static void
repeat_string(typval_T *str_tv, int n, typval_T *rettv)
{
    char_u	*p;
    int		slen;
    int		len;
    char_u	*r;
    int		i;

    p = tv_get_string(str_tv);
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;

    slen = (int)STRLEN(p);
    len = slen * n;
    if (len <= 0)
	return;

    r = alloc(len + 1);
    if (r == NULL)
	return;

    for (i = 0; i < n; i++)
	mch_memmove(r + i * slen, p, (size_t)slen);
    r[len] = NUL;

    rettv->vval.v_string = r;
}

/*
 * "repeat()" function
 */
    static void
f_repeat(typval_T *argvars, typval_T *rettv)
{
    varnumber_T	n;

    if (in_vim9script()
	    && (check_for_repeat_func_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL))
	return;

    n = tv_get_number(&argvars[1]);
    if (argvars[0].v_type == VAR_LIST)
	repeat_list(argvars[0].vval.v_list, n, rettv);
    else if (argvars[0].v_type == VAR_TUPLE)
	tuple_repeat(argvars[0].vval.v_tuple, n, rettv);
    else if (argvars[0].v_type == VAR_BLOB)
	repeat_blob(&argvars[0], n, rettv);
    else
	repeat_string(&argvars[0], n, rettv);
}

#define SP_NOMOVE	0x01	    // don't move cursor
#define SP_REPEAT	0x02	    // repeat to find outer pair
#define SP_RETCOUNT	0x04	    // return matchcount
#define SP_SETPCMARK	0x08	    // set previous context mark
#define SP_START	0x10	    // accept match at start position
#define SP_SUBPAT	0x20	    // return nr of matching sub-pattern
#define SP_END		0x40	    // leave cursor at end of match
#define SP_COLUMN	0x80	    // start at cursor column

/*
 * Get flags for a search function.
 * Possibly sets "p_ws".
 * Returns BACKWARD, FORWARD or zero (for an error).
 */
    static int
get_search_arg(typval_T *varp, int *flagsp)
{
    int		dir = FORWARD;
    char_u	*flags;
    char_u	nbuf[NUMBUFLEN];
    int		mask;

    if (varp->v_type == VAR_UNKNOWN)
	return FORWARD;

    flags = tv_get_string_buf_chk(varp, nbuf);
    if (flags == NULL)
	return 0;		// type error; errmsg already given
    while (*flags != NUL)
    {
	switch (*flags)
	{
	    case 'b': dir = BACKWARD; break;
	    case 'w': p_ws = TRUE; break;
	    case 'W': p_ws = FALSE; break;
	    default:  mask = 0;
		      if (flagsp != NULL)
			  switch (*flags)
			  {
			      case 'c': mask = SP_START; break;
			      case 'e': mask = SP_END; break;
			      case 'm': mask = SP_RETCOUNT; break;
			      case 'n': mask = SP_NOMOVE; break;
			      case 'p': mask = SP_SUBPAT; break;
			      case 'r': mask = SP_REPEAT; break;
			      case 's': mask = SP_SETPCMARK; break;
			      case 'z': mask = SP_COLUMN; break;
			  }
		      if (mask == 0)
		      {
			  semsg(_(e_invalid_argument_str), flags);
			  dir = 0;
		      }
		      else
			  *flagsp |= mask;
	}
	if (dir == 0)
	    break;
	++flags;
    }
    return dir;
}

/*
 * Shared by search() and searchpos() functions.
 */
    static int
search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
{
    int		flags;
    char_u	*pat;
    size_t	patlen;
    pos_T	pos;
    pos_T	save_cursor;
    int		save_p_ws = p_ws;
    int		dir;
    int		retval = 0;	// default: FAIL
    long	lnum_stop = 0;
#ifdef FEAT_RELTIME
    long	time_limit = 0;
#endif
    int		options = SEARCH_KEEP;
    int		subpatnum;
    searchit_arg_T sia;
    int		use_skip = FALSE;
    pos_T	firstpos;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_opt_string_arg(argvars, 1) == FAIL
		|| (argvars[1].v_type != VAR_UNKNOWN
		    && (check_for_opt_number_arg(argvars, 2) == FAIL
			|| (argvars[2].v_type != VAR_UNKNOWN
			    && check_for_opt_number_arg(argvars, 3) == FAIL)))))
	goto theend;

    pat = tv_get_string(&argvars[0]);
    dir = get_search_arg(&argvars[1], flagsp);	// may set p_ws
    if (dir == 0)
	goto theend;
    flags = *flagsp;
    if (flags & SP_START)
	options |= SEARCH_START;
    if (flags & SP_END)
	options |= SEARCH_END;
    if (flags & SP_COLUMN)
	options |= SEARCH_COL;

    // Optional arguments: line number to stop searching, timeout and skip.
    if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN)
    {
	lnum_stop = (long)tv_get_number_chk(&argvars[2], NULL);
	if (lnum_stop < 0)
	    goto theend;
	if (argvars[3].v_type != VAR_UNKNOWN)
	{
#ifdef FEAT_RELTIME
	    time_limit = (long)tv_get_number_chk(&argvars[3], NULL);
	    if (time_limit < 0)
		goto theend;
#endif
	    use_skip = eval_expr_valid_arg(&argvars[4]);
	}
    }

    /*
     * This function does not accept SP_REPEAT and SP_RETCOUNT flags.
     * Check to make sure only those flags are set.
     * Also, Only the SP_NOMOVE or the SP_SETPCMARK flag can be set. Both
     * flags cannot be set. Check for that condition also.
     */
    if (((flags & (SP_REPEAT | SP_RETCOUNT)) != 0)
	    || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK)))
    {
	semsg(_(e_invalid_argument_str), tv_get_string(&argvars[1]));
	goto theend;
    }

    pos = save_cursor = curwin->w_cursor;
    CLEAR_FIELD(firstpos);
    CLEAR_FIELD(sia);
    sia.sa_stop_lnum = (linenr_T)lnum_stop;
#ifdef FEAT_RELTIME
    sia.sa_tm = time_limit;
#endif

    patlen = STRLEN(pat);

    // Repeat until {skip} returns FALSE.
    for (;;)
    {
	subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, pat, patlen, 1L,
						     options, RE_SEARCH, &sia);
	// finding the first match again means there is no match where {skip}
	// evaluates to zero.
	if (firstpos.lnum != 0 && EQUAL_POS(pos, firstpos))
	    subpatnum = FAIL;

	if (subpatnum == FAIL || !use_skip)
	    // didn't find it or no skip argument
	    break;
	if (firstpos.lnum == 0)
	    firstpos = pos;

	// If the skip expression matches, ignore this match.
	{
	    int	    do_skip;
	    int	    err;
	    pos_T   save_pos = curwin->w_cursor;

	    curwin->w_cursor = pos;
	    err = FALSE;
	    do_skip = eval_expr_to_bool(&argvars[4], &err);
	    curwin->w_cursor = save_pos;
	    if (err)
	    {
		// Evaluating {skip} caused an error, break here.
		subpatnum = FAIL;
		break;
	    }
	    if (!do_skip)
		break;
	}

	// clear the start flag to avoid getting stuck here
	options &= ~SEARCH_START;
    }

    if (subpatnum != FAIL)
    {
	if (flags & SP_SUBPAT)
	    retval = subpatnum;
	else
	    retval = pos.lnum;
	if (flags & SP_SETPCMARK)
	    setpcmark();
	curwin->w_cursor = pos;
	if (match_pos != NULL)
	{
	    // Store the match cursor position
	    match_pos->lnum = pos.lnum;
	    match_pos->col = pos.col + 1;
	}
	// "/$" will put the cursor after the end of the line, may need to
	// correct that here
	check_cursor();
    }

    // If 'n' flag is used: restore cursor position.
    if (flags & SP_NOMOVE)
	curwin->w_cursor = save_cursor;
    else
	curwin->w_set_curswant = TRUE;
theend:
    p_ws = save_p_ws;

    return retval;
}

#ifdef FEAT_RUBY
/*
 * "rubyeval()" function
 */
    static void
f_rubyeval(typval_T *argvars, typval_T *rettv)
{
    char_u	*str;
    char_u	buf[NUMBUFLEN];

    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    str = tv_get_string_buf(&argvars[0], buf);
    do_rubyeval(str, rettv);
}
#endif

/*
 * "screenattr()" function
 */
    static void
f_screenattr(typval_T *argvars, typval_T *rettv)
{
    int		row;
    int		col;
    int		c;

    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL))
	return;

    row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
    col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
    if (row < 0 || row >= screen_Rows
	    || col < 0 || col >= screen_Columns)
	c = -1;
    else
	c = ScreenAttrs[LineOffset[row] + col];
    rettv->vval.v_number = c;
}

/*
 * "screenchar()" function
 */
    static void
f_screenchar(typval_T *argvars, typval_T *rettv)
{
    int		row;
    int		col;
    int		c;

    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL))
	return;

    row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
    col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
    if (row < 0 || row >= screen_Rows || col < 0 || col >= screen_Columns)
	c = -1;
    else
    {
	char_u buf[MB_MAXBYTES + 1];
	screen_getbytes(row, col, buf, NULL);
	c = (*mb_ptr2char)(buf);
    }
    rettv->vval.v_number = c;
}

/*
 * "screenchars()" function
 */
    static void
f_screenchars(typval_T *argvars, typval_T *rettv)
{
    int		row;
    int		col;
    int		c;
    int		i;

    if (rettv_list_alloc(rettv) == FAIL)
	return;

    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL))
	return;

    row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
    col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
    if (row < 0 || row >= screen_Rows || col < 0 || col >= screen_Columns)
	return;

    char_u buf[MB_MAXBYTES + 1];
    screen_getbytes(row, col, buf, NULL);
    int pcc[MAX_MCO];
    if (enc_utf8)
	c = utfc_ptr2char(buf, pcc);
    else
	c = (*mb_ptr2char)(buf);
    list_append_number(rettv->vval.v_list, (varnumber_T)c);

    if (enc_utf8)
	for (i = 0; i < Screen_mco && pcc[i] != 0; ++i)
	    list_append_number(rettv->vval.v_list, (varnumber_T)pcc[i]);
}

/*
 * "screencol()" function
 *
 * First column is 1 to be consistent with virtcol().
 */
    static void
f_screencol(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = screen_screencol() + 1;
}

/*
 * "screenrow()" function
 */
    static void
f_screenrow(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = screen_screenrow() + 1;
}

/*
 * "screenstring()" function
 */
    static void
f_screenstring(typval_T *argvars, typval_T *rettv)
{
    int		row;
    int		col;
    char_u	buf[MB_MAXBYTES + 1];

    rettv->vval.v_string = NULL;
    rettv->v_type = VAR_STRING;

    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL))
	return;

    row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
    col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
    if (row < 0 || row >= screen_Rows || col < 0 || col >= screen_Columns)
	return;

    screen_getbytes(row, col, buf, NULL);
    rettv->vval.v_string = vim_strsave(buf);
}

/*
 * "search()" function
 */
    static void
f_search(typval_T *argvars, typval_T *rettv)
{
    int		flags = 0;

    rettv->vval.v_number = search_cmn(argvars, NULL, &flags);
}

/*
 * "searchdecl()" function
 */
    static void
f_searchdecl(typval_T *argvars, typval_T *rettv)
{
    int		locally = TRUE;
    int		thisblock = FALSE;
    int		error = FALSE;
    char_u	*name;

    rettv->vval.v_number = 1;	// default: FAIL

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_opt_bool_arg(argvars, 1) == FAIL
		|| (argvars[1].v_type != VAR_UNKNOWN
		    && check_for_opt_bool_arg(argvars, 2) == FAIL)))
	return;

    name = tv_get_string_chk(&argvars[0]);
    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	locally = !(int)tv_get_bool_chk(&argvars[1], &error);
	if (!error && argvars[2].v_type != VAR_UNKNOWN)
	    thisblock = (int)tv_get_bool_chk(&argvars[2], &error);
    }
    if (!error && name != NULL)
	rettv->vval.v_number = find_decl(name, (int)STRLEN(name),
				     locally, thisblock, SEARCH_KEEP) == FAIL;
}

/*
 * Used by searchpair() and searchpairpos()
 */
    static int
searchpair_cmn(typval_T *argvars, pos_T *match_pos)
{
    char_u	*spat, *mpat, *epat;
    typval_T	*skip;
    int		save_p_ws = p_ws;
    int		dir;
    int		flags = 0;
    char_u	nbuf1[NUMBUFLEN];
    char_u	nbuf2[NUMBUFLEN];
    int		retval = 0;		// default: FAIL
    long	lnum_stop = 0;
    long	time_limit = 0;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_string_arg(argvars, 1) == FAIL
		|| check_for_string_arg(argvars, 2) == FAIL
		|| check_for_opt_string_arg(argvars, 3) == FAIL
		|| (argvars[3].v_type != VAR_UNKNOWN
		    && argvars[4].v_type != VAR_UNKNOWN
		    && (check_for_opt_number_arg(argvars, 5) == FAIL
			|| (argvars[5].v_type != VAR_UNKNOWN
			    && check_for_opt_number_arg(argvars, 6) == FAIL)))))
	goto theend;

    // Get the three pattern arguments: start, middle, end. Will result in an
    // error if not a valid argument.
    spat = tv_get_string_chk(&argvars[0]);
    mpat = tv_get_string_buf_chk(&argvars[1], nbuf1);
    epat = tv_get_string_buf_chk(&argvars[2], nbuf2);
    if (spat == NULL || mpat == NULL || epat == NULL)
	goto theend;	    // type error

    // Handle the optional fourth argument: flags
    dir = get_search_arg(&argvars[3], &flags); // may set p_ws
    if (dir == 0)
	goto theend;

    // Don't accept SP_END or SP_SUBPAT.
    // Only one of the SP_NOMOVE or SP_SETPCMARK flags can be set.
    if ((flags & (SP_END | SP_SUBPAT)) != 0
	    || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK)))
    {
	semsg(_(e_invalid_argument_str), tv_get_string(&argvars[3]));
	goto theend;
    }

    // Using 'r' implies 'W', otherwise it doesn't work.
    if (flags & SP_REPEAT)
	p_ws = FALSE;

    // Optional fifth argument: skip expression
    if (argvars[3].v_type == VAR_UNKNOWN
	    || argvars[4].v_type == VAR_UNKNOWN)
	skip = NULL;
    else
    {
	// Type is checked later.
	skip = &argvars[4];

	if (argvars[5].v_type != VAR_UNKNOWN)
	{
	    lnum_stop = (long)tv_get_number_chk(&argvars[5], NULL);
	    if (lnum_stop < 0)
	    {
		semsg(_(e_invalid_argument_str), tv_get_string(&argvars[5]));
		goto theend;
	    }
#ifdef FEAT_RELTIME
	    if (argvars[6].v_type != VAR_UNKNOWN)
	    {
		time_limit = (long)tv_get_number_chk(&argvars[6], NULL);
		if (time_limit < 0)
		{
		    semsg(_(e_invalid_argument_str), tv_get_string(&argvars[6]));
		    goto theend;
		}
	    }
#endif
	}
    }

    retval = do_searchpair(spat, mpat, epat, dir, skip, flags,
					    match_pos, lnum_stop, time_limit);

theend:
    p_ws = save_p_ws;

    return retval;
}

/*
 * "searchpair()" function
 */
    static void
f_searchpair(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = searchpair_cmn(argvars, NULL);
}

/*
 * "searchpairpos()" function
 */
    static void
f_searchpairpos(typval_T *argvars, typval_T *rettv)
{
    pos_T	match_pos;
    int		lnum = 0;
    int		col = 0;

    if (rettv_list_alloc(rettv) == FAIL)
	return;

    if (searchpair_cmn(argvars, &match_pos) > 0)
    {
	lnum = match_pos.lnum;
	col = match_pos.col;
    }

    list_append_number(rettv->vval.v_list, (varnumber_T)lnum);
    list_append_number(rettv->vval.v_list, (varnumber_T)col);
}

/*
 * Search for a start/middle/end thing.
 * Used by searchpair(), see its documentation for the details.
 * Returns 0 or -1 for no match,
 */
    long
do_searchpair(
    char_u	*spat,	    // start pattern
    char_u	*mpat,	    // middle pattern
    char_u	*epat,	    // end pattern
    int		dir,	    // BACKWARD or FORWARD
    typval_T	*skip,	    // skip expression
    int		flags,	    // SP_SETPCMARK and other SP_ values
    pos_T	*match_pos,
    linenr_T	lnum_stop,  // stop at this line if not zero
    long	time_limit UNUSED) // stop after this many msec
{
    char_u	*save_cpo;
    char_u	*pat, *pat2 = NULL, *pat3 = NULL;
    size_t	patlen;
    size_t	spatlen;
    size_t	epatlen;
    size_t	pat2size;
    size_t	pat2len;
    size_t	pat3size;
    size_t	pat3len;
    long	retval = 0;
    pos_T	pos;
    pos_T	firstpos;
    pos_T	foundpos;
    pos_T	save_cursor;
    pos_T	save_pos;
    int		n;
    int		r;
    int		nest = 1;
    int		use_skip = FALSE;
    int		err;
    int		options = SEARCH_KEEP;

    // Make 'cpoptions' empty, the 'l' flag should not be used here.
    save_cpo = p_cpo;
    p_cpo = empty_option;

    // Make two search patterns: start/end (pat2, for in nested pairs) and
    // start/middle/end (pat3, for the top pair).
    spatlen = STRLEN(spat);
    epatlen = STRLEN(epat);
    pat2size = spatlen + epatlen + 17;
    pat2 = alloc(pat2size);
    if (pat2 == NULL)
	goto theend;
    pat3size = spatlen + STRLEN(mpat) + epatlen + 25;
    pat3 = alloc(pat3size);
    if (pat3 == NULL)
	goto theend;
    pat2len = vim_snprintf((char *)pat2, pat2size, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat);
    if (*mpat == NUL)
    {
	STRCPY(pat3, pat2);
	pat3len = pat2len;
    }
    else
	pat3len = vim_snprintf((char *)pat3, pat3size, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)",
							    spat, epat, mpat);
    if (flags & SP_START)
	options |= SEARCH_START;

    if (skip != NULL)
	use_skip = eval_expr_valid_arg(skip);

#ifdef FEAT_RELTIME
    if (time_limit > 0)
	init_regexp_timeout(time_limit);
#endif
    save_cursor = curwin->w_cursor;
    pos = curwin->w_cursor;
    CLEAR_POS(&firstpos);
    CLEAR_POS(&foundpos);
    pat = pat3;
    patlen = pat3len;
    for (;;)
    {
	searchit_arg_T sia;

	CLEAR_FIELD(sia);
	sia.sa_stop_lnum = lnum_stop;
	n = searchit(curwin, curbuf, &pos, NULL, dir, pat, patlen, 1L,
						     options, RE_SEARCH, &sia);
	if (n == FAIL || (firstpos.lnum != 0 && EQUAL_POS(pos, firstpos)))
	    // didn't find it or found the first match again: FAIL
	    break;

	if (firstpos.lnum == 0)
	    firstpos = pos;
	if (EQUAL_POS(pos, foundpos))
	{
	    // Found the same position again.  Can happen with a pattern that
	    // has "\zs" at the end and searching backwards.  Advance one
	    // character and try again.
	    if (dir == BACKWARD)
		decl(&pos);
	    else
		incl(&pos);
	}
	foundpos = pos;

	// clear the start flag to avoid getting stuck here
	options &= ~SEARCH_START;

	// If the skip pattern matches, ignore this match.
	if (use_skip)
	{
	    save_pos = curwin->w_cursor;
	    curwin->w_cursor = pos;
	    err = FALSE;
	    r = eval_expr_to_bool(skip, &err);
	    curwin->w_cursor = save_pos;
	    if (err)
	    {
		// Evaluating {skip} caused an error, break here.
		curwin->w_cursor = save_cursor;
		retval = -1;
		break;
	    }
	    if (r)
		continue;
	}

	if ((dir == BACKWARD && n == 3) || (dir == FORWARD && n == 2))
	{
	    // Found end when searching backwards or start when searching
	    // forward: nested pair.
	    ++nest;
	    pat = pat2;		// nested, don't search for middle
	}
	else
	{
	    // Found end when searching forward or start when searching
	    // backward: end of (nested) pair; or found middle in outer pair.
	    if (--nest == 1)
		pat = pat3;	// outer level, search for middle
	}

	if (nest == 0)
	{
	    // Found the match: return matchcount or line number.
	    if (flags & SP_RETCOUNT)
		++retval;
	    else
		retval = pos.lnum;
	    if (flags & SP_SETPCMARK)
		setpcmark();
	    curwin->w_cursor = pos;
	    if (!(flags & SP_REPEAT))
		break;
	    nest = 1;	    // search for next unmatched
	}
    }

    if (match_pos != NULL)
    {
	// Store the match cursor position
	match_pos->lnum = curwin->w_cursor.lnum;
	match_pos->col = curwin->w_cursor.col + 1;
    }

    // If 'n' flag is used or search failed: restore cursor position.
    if ((flags & SP_NOMOVE) || retval == 0)
	curwin->w_cursor = save_cursor;

theend:
#ifdef FEAT_RELTIME
    if (time_limit > 0)
	disable_regexp_timeout();
#endif
    vim_free(pat2);
    vim_free(pat3);
    if (p_cpo == empty_option)
	p_cpo = save_cpo;
    else
    {
	// Darn, evaluating the {skip} expression changed the value.
	// If it's still empty it was changed and restored, need to restore in
	// the complicated way.
	if (*p_cpo == NUL)
	    set_option_value_give_err((char_u *)"cpo", 0L, save_cpo, 0);
	free_string_option(save_cpo);
    }

    return retval;
}

/*
 * "searchpos()" function
 */
    static void
f_searchpos(typval_T *argvars, typval_T *rettv)
{
    pos_T	match_pos;
    int		lnum = 0;
    int		col = 0;
    int		n;
    int		flags = 0;

    if (rettv_list_alloc(rettv) == FAIL)
	return;

    n = search_cmn(argvars, &match_pos, &flags);
    if (n > 0)
    {
	lnum = match_pos.lnum;
	col = match_pos.col;
    }

    list_append_number(rettv->vval.v_list, (varnumber_T)lnum);
    list_append_number(rettv->vval.v_list, (varnumber_T)col);
    if (flags & SP_SUBPAT)
	list_append_number(rettv->vval.v_list, (varnumber_T)n);
}

/*
 * Set the cursor or mark position.
 * If "charpos" is TRUE, then use the column number as a character offset.
 * Otherwise use the column number as a byte offset.
 */
    static void
set_position(typval_T *argvars, typval_T *rettv, int charpos)
{
    pos_T	pos;
    int		fnum;
    char_u	*name;
    colnr_T	curswant = -1;

    rettv->vval.v_number = -1;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_list_arg(argvars, 1) == FAIL))
	return;

    name = tv_get_string_chk(argvars);
    if (name == NULL)
	return;

    if (list2fpos(&argvars[1], &pos, &fnum, &curswant, charpos) != OK)
	return;

    if (pos.col != MAXCOL && --pos.col < 0)
	pos.col = 0;
    if ((name[0] == '.' && name[1] == NUL))
    {
	// set cursor; "fnum" is ignored
	curwin->w_cursor = pos;
	if (curswant >= 0)
	{
	    curwin->w_curswant = curswant - 1;
	    curwin->w_set_curswant = FALSE;
	}
	check_cursor();
	rettv->vval.v_number = 0;
    }
    else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL)
    {
	// set mark
	if (setmark_pos(name[1], &pos, fnum) == OK)
	    rettv->vval.v_number = 0;
    }
    else
	emsg(_(e_invalid_argument));
}
/*
 * "setcharpos()" function
 */
    static void
f_setcharpos(typval_T *argvars, typval_T *rettv)
{
    set_position(argvars, rettv, TRUE);
}

    static void
f_setcharsearch(typval_T *argvars, typval_T *rettv UNUSED)
{
    dict_T	*d;
    dictitem_T	*di;
    char_u	*csearch;

    if (check_for_dict_arg(argvars, 0) == FAIL)
	return;

    if ((d = argvars[0].vval.v_dict) == NULL)
	return;

    csearch = dict_get_string(d, "char", FALSE);
    if (csearch != NULL)
    {
	if (enc_utf8)
	{
	    int pcc[MAX_MCO];
	    int c = utfc_ptr2char(csearch, pcc);

	    set_last_csearch(c, csearch, utfc_ptr2len(csearch));
	}
	else
	    set_last_csearch(PTR2CHAR(csearch),
		    csearch, mb_ptr2len(csearch));
    }

    di = dict_find(d, (char_u *)"forward", -1);
    if (di != NULL)
	set_csearch_direction((int)tv_get_number(&di->di_tv)
		? FORWARD : BACKWARD);

    di = dict_find(d, (char_u *)"until", -1);
    if (di != NULL)
	set_csearch_until(!!tv_get_number(&di->di_tv));
}

/*
 * "setcursorcharpos" function
 */
    static void
f_setcursorcharpos(typval_T *argvars, typval_T *rettv)
{
    set_cursorpos(argvars, rettv, TRUE);
}

/*
 * "setenv()" function
 */
    static void
f_setenv(typval_T *argvars, typval_T *rettv UNUSED)
{
    char_u   namebuf[NUMBUFLEN];
    char_u   valbuf[NUMBUFLEN];
    char_u  *name;

    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    // setting an environment variable may be dangerous, e.g. you could
    // setenv GCONV_PATH=/tmp and then have iconv() unexpectedly call
    // a shell command using some shared library:
    if (check_restricted() || check_secure())
	return;

    name = tv_get_string_buf(&argvars[0], namebuf);
    if (argvars[1].v_type == VAR_SPECIAL
				      && argvars[1].vval.v_number == VVAL_NULL)
	vim_unsetenv_ext(name);
    else
	vim_setenv_ext(name, tv_get_string_buf(&argvars[1], valbuf));
}

/*
 * "setfperm({fname}, {mode})" function
 */
    static void
f_setfperm(typval_T *argvars, typval_T *rettv)
{
    char_u	*fname;
    char_u	modebuf[NUMBUFLEN];
    char_u	*mode_str;
    int		i;
    int		mask;
    int		mode = 0;

    rettv->vval.v_number = 0;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_string_arg(argvars, 1) == FAIL))
	return;

    fname = tv_get_string_chk(&argvars[0]);
    if (fname == NULL)
	return;
    mode_str = tv_get_string_buf_chk(&argvars[1], modebuf);
    if (mode_str == NULL)
	return;
    if (STRLEN(mode_str) != 9)
    {
	semsg(_(e_invalid_argument_str), mode_str);
	return;
    }

    mask = 1;
    for (i = 8; i >= 0; --i)
    {
	if (mode_str[i] != '-')
	    mode |= mask;
	mask = mask << 1;
    }
    rettv->vval.v_number = mch_setperm(fname, mode) == OK;
}

/*
 * "setpos()" function
 */
    static void
f_setpos(typval_T *argvars, typval_T *rettv)
{
    set_position(argvars, rettv, FALSE);
}

/*
 * Translate a register type string to the yank type and block length
 */
    int
get_yank_type(char_u **pp, char_u *yank_type, long *block_len)
{
    char_u *stropt = *pp;
    switch (*stropt)
    {
	case 'v': case 'c':	// character-wise selection
	    *yank_type = MCHAR;
	    break;
	case 'V': case 'l':	// line-wise selection
	    *yank_type = MLINE;
	    break;
	case 'b': case Ctrl_V:	// block-wise selection
	    *yank_type = MBLOCK;
	    if (VIM_ISDIGIT(stropt[1]))
	    {
		++stropt;
		*block_len = getdigits(&stropt) - 1;
		--stropt;
	    }
	    break;
	default:
	    return FAIL;
    }
    *pp = stropt;
    return OK;
}

/*
 * "setreg()" function
 */
    static void
f_setreg(typval_T *argvars, typval_T *rettv)
{
    int		regname;
    char_u	*strregname;
    char_u	*stropt;
    char_u	*strval;
    int		append;
    char_u	yank_type;
    long	block_len;
    typval_T	*regcontents;
    int		pointreg;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_opt_string_arg(argvars, 2) == FAIL))
	return;

    pointreg = 0;
    regcontents = NULL;
    block_len = -1;
    yank_type = MAUTO;
    append = FALSE;

    strregname = tv_get_string_chk(argvars);
    rettv->vval.v_number = 1;		// FAIL is default

    if (strregname == NULL)
	return;		// type error; errmsg already given
    if (in_vim9script() && STRLEN(strregname) > 1)
    {
	semsg(_(e_register_name_must_be_one_char_str), strregname);
	return;
    }
    regname = *strregname;
    if (regname == 0 || regname == '@')
	regname = '"';

    if (argvars[1].v_type == VAR_DICT)
    {
	dict_T	    *d = argvars[1].vval.v_dict;
	dictitem_T  *di;

	if (d == NULL || d->dv_hashtab.ht_used == 0)
	{
	    // Empty dict, clear the register (like setreg(0, []))
	    char_u *lstval[2] = {NULL, NULL};
	    write_reg_contents_lst(regname, lstval, 0, FALSE, MAUTO, -1);
	    return;
	}

	di = dict_find(d, (char_u *)"regcontents", -1);
	if (di != NULL)
	    regcontents = &di->di_tv;

	stropt = dict_get_string(d, "regtype", FALSE);
	if (stropt != NULL)
	{
	    int ret = get_yank_type(&stropt, &yank_type, &block_len);

	    if (ret == FAIL || *++stropt != NUL)
	    {
		semsg(_(e_invalid_value_for_argument_str), "value");
		return;
	    }
	}

	if (regname == '"')
	{
	    stropt = dict_get_string(d, "points_to", FALSE);
	    if (stropt != NULL)
	    {
		pointreg = *stropt;
		regname = pointreg;
	    }
	}
	else if (dict_get_bool(d, "isunnamed", -1) > 0)
	    pointreg = regname;
    }
    else
	regcontents = &argvars[1];

    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	if (yank_type != MAUTO)
	{
	    semsg(_(e_too_many_arguments_for_function_str), "setreg");
	    return;
	}

	stropt = tv_get_string_chk(&argvars[2]);
	if (stropt == NULL)
	    return;		// type error
	for (; *stropt != NUL; ++stropt)
	    switch (*stropt)
	    {
		case 'a': case 'A':	// append
		    append = TRUE;
		    break;
		default:
		    get_yank_type(&stropt, &yank_type, &block_len);
	    }
    }

    if (regcontents && regcontents->v_type == VAR_LIST)
    {
	char_u		**lstval;
	char_u		**allocval;
	char_u		buf[NUMBUFLEN];
	char_u		**curval;
	char_u		**curallocval;
	list_T		*ll = regcontents->vval.v_list;
	listitem_T	*li;
	int		len;

	// If the list is NULL handle like an empty list.
	len = ll == NULL ? 0 : ll->lv_len;

	// First half: use for pointers to result lines; second half: use for
	// pointers to allocated copies.
	lstval = ALLOC_MULT(char_u *, (len + 1) * 2);
	if (lstval == NULL)
	    return;
	curval = lstval;
	allocval = lstval + len + 2;
	curallocval = allocval;

	if (ll != NULL)
	{
	    CHECK_LIST_MATERIALIZE(ll);
	    FOR_ALL_LIST_ITEMS(ll, li)
	    {
		strval = tv_get_string_buf_chk(&li->li_tv, buf);
		if (strval == NULL)
		    goto free_lstval;
		if (strval == buf)
		{
		    // Need to make a copy, next tv_get_string_buf_chk() will
		    // overwrite the string.
		    strval = vim_strsave(buf);
		    if (strval == NULL)
			goto free_lstval;
		    *curallocval++ = strval;
		}
		*curval++ = strval;
	    }
	}
	*curval++ = NULL;

	write_reg_contents_lst(regname, lstval, -1,
						append, yank_type, block_len);
free_lstval:
	while (curallocval > allocval)
	    vim_free(*--curallocval);
	vim_free(lstval);
    }
    else if (regcontents)
    {
	strval = tv_get_string_chk(regcontents);
	if (strval == NULL)
	    return;
	write_reg_contents_ex(regname, strval, -1,
						append, yank_type, block_len);
    }
    if (pointreg != 0)
	get_yank_register(pointreg, TRUE);

    rettv->vval.v_number = 0;
}

/*
 * "settagstack()" function
 */
    static void
f_settagstack(typval_T *argvars, typval_T *rettv)
{
    win_T	*wp;
    dict_T	*d;
    int		action = 'r';

    rettv->vval.v_number = -1;

    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_dict_arg(argvars, 1) == FAIL
		|| check_for_opt_string_arg(argvars, 2) == FAIL))
	return;

    // first argument: window number or id
    wp = find_win_by_nr_or_id(&argvars[0]);
    if (wp == NULL)
	return;

    // second argument: dict with items to set in the tag stack
    if (check_for_dict_arg(argvars, 1) == FAIL)
	return;
    d = argvars[1].vval.v_dict;
    if (d == NULL)
	return;

    // third argument: action - 'a' for append and 'r' for replace.
    // default is to replace the stack.
    if (argvars[2].v_type == VAR_UNKNOWN)
	action = 'r';
    else if (check_for_string_arg(argvars, 2) == FAIL)
	return;
    else
    {
	char_u	*actstr;
	actstr = tv_get_string_chk(&argvars[2]);
	if (actstr == NULL)
	    return;
	if ((*actstr == 'r' || *actstr == 'a' || *actstr == 't')
		&& actstr[1] == NUL)
	    action = *actstr;
	else
	{
	    semsg(_(e_invalid_action_str_2), actstr);
	    return;
	}
    }

    if (set_tagstack(wp, d, action) == OK)
	rettv->vval.v_number = 0;
}

#ifdef FEAT_CRYPT
/*
 * "sha256({expr})" function
 */
    static void
f_sha256(typval_T *argvars, typval_T *rettv)
{
    char_u	*p;
    int		len;

    if (in_vim9script() && check_for_string_or_blob_arg(argvars, 0) == FAIL)
	return;

    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;

    if (argvars[0].v_type == VAR_BLOB)
    {
	blob_T *blob = argvars[0].vval.v_blob;
	p = blob != NULL ? (char_u *)blob->bv_ga.ga_data : (char_u *)"";
	len = blob != NULL ? blob->bv_ga.ga_len : 0;
	rettv->vval.v_string = vim_strsave(sha256_bytes(p, len, NULL, 0));
    }
    else
    {
	p = tv_get_string(&argvars[0]);
	rettv->vval.v_string = vim_strsave(
				    sha256_bytes(p, (int)STRLEN(p), NULL, 0));
    }
}
#endif // FEAT_CRYPT

/*
 * "shellescape({string})" function
 */
    static void
f_shellescape(typval_T *argvars, typval_T *rettv)
{
    int do_special;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_opt_bool_arg(argvars, 1) == FAIL))
	return;

    do_special = non_zero_arg(&argvars[1]);
    rettv->vval.v_string = vim_strsave_shellescape(
			   tv_get_string(&argvars[0]), do_special, do_special);
    rettv->v_type = VAR_STRING;
}

/*
 * shiftwidth() function
 */
    static void
f_shiftwidth(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->vval.v_number = 0;

    if (in_vim9script() && check_for_opt_number_arg(argvars, 0) == FAIL)
	return;

    if (argvars[0].v_type != VAR_UNKNOWN)
    {
	long	col;

	col = (long)tv_get_number_chk(argvars, NULL);
	if (col < 0)
	    return;	// type error; errmsg already given
#ifdef FEAT_VARTABS
	rettv->vval.v_number = get_sw_value_col(curbuf, col, FALSE);
	return;
#endif
    }

    rettv->vval.v_number = get_sw_value(curbuf);
}

/*
 * "soundfold({word})" function
 */
    static void
f_soundfold(typval_T *argvars, typval_T *rettv)
{
    char_u	*s;

    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    rettv->v_type = VAR_STRING;
    s = tv_get_string(&argvars[0]);
#ifdef FEAT_SPELL
    rettv->vval.v_string = eval_soundfold(s);
#else
    rettv->vval.v_string = vim_strsave(s);
#endif
}

/*
 * "spellbadword()" function
 */
    static void
f_spellbadword(typval_T *argvars UNUSED, typval_T *rettv)
{
    char_u	*word = (char_u *)"";
    hlf_T	attr = HLF_COUNT;
    int		len = 0;
#ifdef FEAT_SPELL
    int		wo_spell_save = curwin->w_p_spell;

    if (in_vim9script() && check_for_opt_string_arg(argvars, 0) == FAIL)
	return;

    if (!curwin->w_p_spell)
    {
	parse_spelllang(curwin);
	curwin->w_p_spell = TRUE;
    }

    if (*curwin->w_s->b_p_spl == NUL)
    {
	emsg(_(e_spell_checking_is_not_possible));
	curwin->w_p_spell = wo_spell_save;
	return;
    }
#endif

    if (rettv_list_alloc(rettv) == FAIL)
    {
#ifdef FEAT_SPELL
	curwin->w_p_spell = wo_spell_save;
#endif
	return;
    }

#ifdef FEAT_SPELL
    if (argvars[0].v_type == VAR_UNKNOWN)
    {
	// Find the start and length of the badly spelled word.
	len = spell_move_to(curwin, FORWARD, SMT_ALL, TRUE, &attr);
	if (len != 0)
	{
	    word = ml_get_cursor();
	    curwin->w_set_curswant = TRUE;
	}
    }
    else if (*curbuf->b_s.b_p_spl != NUL)
    {
	char_u	*str = tv_get_string_chk(&argvars[0]);
	int	capcol = -1;

	if (str != NULL)
	{
	    // Check the argument for spelling.
	    while (*str != NUL)
	    {
		len = spell_check(curwin, str, &attr, &capcol, FALSE);
		if (attr != HLF_COUNT)
		{
		    word = str;
		    break;
		}
		str += len;
		capcol -= len;
		len = 0;
	    }
	}
    }
    curwin->w_p_spell = wo_spell_save;
#endif

    list_append_string(rettv->vval.v_list, word, len);
    list_append_string(rettv->vval.v_list, (char_u *)(
			attr == HLF_SPB ? "bad" :
			attr == HLF_SPR ? "rare" :
			attr == HLF_SPL ? "local" :
			attr == HLF_SPC ? "caps" :
			""), -1);
}

/*
 * "spellsuggest()" function
 */
    static void
f_spellsuggest(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifdef FEAT_SPELL
    char_u	*str;
    int		typeerr = FALSE;
    int		maxcount;
    garray_T	ga;
    int		i;
    listitem_T	*li;
    int		need_capital = FALSE;
    int		wo_spell_save = curwin->w_p_spell;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_opt_number_arg(argvars, 1) == FAIL
		|| (argvars[1].v_type != VAR_UNKNOWN
		    && check_for_opt_bool_arg(argvars, 2) == FAIL)))
	return;

    if (!curwin->w_p_spell)
    {
	parse_spelllang(curwin);
	curwin->w_p_spell = TRUE;
    }

    if (*curwin->w_s->b_p_spl == NUL)
    {
	emsg(_(e_spell_checking_is_not_possible));
	curwin->w_p_spell = wo_spell_save;
	return;
    }
#endif

    if (rettv_list_alloc(rettv) == FAIL)
    {
#ifdef FEAT_SPELL
	curwin->w_p_spell = wo_spell_save;
#endif
	return;
    }

#ifdef FEAT_SPELL
    str = tv_get_string(&argvars[0]);
    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	maxcount = (int)tv_get_number_chk(&argvars[1], &typeerr);
	if (maxcount <= 0)
	    return;
	if (argvars[2].v_type != VAR_UNKNOWN)
	{
	    need_capital = (int)tv_get_bool_chk(&argvars[2], &typeerr);
	    if (typeerr)
		return;
	}
    }
    else
	maxcount = 25;

    spell_suggest_list(&ga, str, maxcount, need_capital, FALSE);

    for (i = 0; i < ga.ga_len; ++i)
    {
	str = ((char_u **)ga.ga_data)[i];

	li = listitem_alloc();
	if (li == NULL)
	    vim_free(str);
	else
	{
	    li->li_tv.v_type = VAR_STRING;
	    li->li_tv.v_lock = 0;
	    li->li_tv.vval.v_string = str;
	    list_append(rettv->vval.v_list, li);
	}
    }
    ga_clear(&ga);
    curwin->w_p_spell = wo_spell_save;
#endif
}

    static void
f_split(typval_T *argvars, typval_T *rettv)
{
    char_u	*str;
    char_u	*end;
    char_u	*pat = NULL;
    regmatch_T	regmatch;
    char_u	patbuf[NUMBUFLEN];
    char_u	*save_cpo;
    int		match;
    colnr_T	col = 0;
    int		keepempty = FALSE;
    int		typeerr = FALSE;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_opt_string_arg(argvars, 1) == FAIL
		|| (argvars[1].v_type != VAR_UNKNOWN
		    && check_for_opt_bool_arg(argvars, 2) == FAIL)))
	return;

    // Make 'cpoptions' empty, the 'l' flag should not be used here.
    save_cpo = p_cpo;
    p_cpo = empty_option;

    str = tv_get_string(&argvars[0]);
    if (argvars[1].v_type != VAR_UNKNOWN)
    {
	pat = tv_get_string_buf_chk(&argvars[1], patbuf);
	if (pat == NULL)
	    typeerr = TRUE;
	if (argvars[2].v_type != VAR_UNKNOWN)
	    keepempty = (int)tv_get_bool_chk(&argvars[2], &typeerr);
    }
    if (pat == NULL || *pat == NUL)
	pat = (char_u *)"[\\x01- ]\\+";

    if (rettv_list_alloc(rettv) == FAIL)
	goto theend;
    if (typeerr)
	goto theend;

    regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
    if (regmatch.regprog != NULL)
    {
	regmatch.rm_ic = FALSE;
	while (*str != NUL || keepempty)
	{
	    if (*str == NUL)
		match = FALSE;	// empty item at the end
	    else
		match = vim_regexec_nl(&regmatch, str, col);
	    if (match)
		end = regmatch.startp[0];
	    else
		end = str + STRLEN(str);
	    if (keepempty || end > str || (rettv->vval.v_list->lv_len > 0
			   && *str != NUL && match && end < regmatch.endp[0]))
	    {
		if (list_append_string(rettv->vval.v_list, str,
						    (int)(end - str)) == FAIL)
		    break;
	    }
	    if (!match)
		break;
	    // Advance to just after the match.
	    if (regmatch.endp[0] > str)
		col = 0;
	    else
		// Don't get stuck at the same match.
		col = (*mb_ptr2len)(regmatch.endp[0]);
	    str = regmatch.endp[0];
	}

	vim_regfree(regmatch.regprog);
    }

theend:
    p_cpo = save_cpo;
}

/*
 * "submatch()" function
 */
    static void
f_submatch(typval_T *argvars, typval_T *rettv)
{
    int		error = FALSE;
    int		no;
    int		retList = 0;

    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_opt_bool_arg(argvars, 1) == FAIL))
	return;

    no = (int)tv_get_number_chk(&argvars[0], &error);
    if (error)
	return;
    if (no < 0 || no >= NSUBEXP)
    {
	semsg(_(e_invalid_submatch_number_nr), no);
	return;
    }
    if (argvars[1].v_type != VAR_UNKNOWN)
	retList = (int)tv_get_bool_chk(&argvars[1], &error);
    if (error)
	return;

    if (retList == 0)
    {
	rettv->v_type = VAR_STRING;
	rettv->vval.v_string = reg_submatch(no);
    }
    else
    {
	rettv->v_type = VAR_LIST;
	rettv->vval.v_list = reg_submatch_list(no);
    }
}

/*
 * "substitute()" function
 */
    static void
f_substitute(typval_T *argvars, typval_T *rettv)
{
    char_u	patbuf[NUMBUFLEN];
    char_u	subbuf[NUMBUFLEN];
    char_u	flagsbuf[NUMBUFLEN];
    char_u	*str;
    char_u	*pat;
    char_u	*sub = NULL;
    typval_T	*expr = NULL;
    char_u	*flg;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_string_arg(argvars, 1) == FAIL
		|| check_for_string_arg(argvars, 3) == FAIL))
	return;

    str = tv_get_string_chk(&argvars[0]);
    pat = tv_get_string_buf_chk(&argvars[1], patbuf);
    flg = tv_get_string_buf_chk(&argvars[3], flagsbuf);

    if (argvars[2].v_type == VAR_FUNC
	    || argvars[2].v_type == VAR_PARTIAL
	    || argvars[2].v_type == VAR_INSTR
	    || argvars[2].v_type == VAR_CLASS
	    || argvars[2].v_type == VAR_OBJECT)
	expr = &argvars[2];
    else
	sub = tv_get_string_buf_chk(&argvars[2], subbuf);

    rettv->v_type = VAR_STRING;
    if (str == NULL || pat == NULL || (sub == NULL && expr == NULL)
								|| flg == NULL)
	rettv->vval.v_string = NULL;
    else
	rettv->vval.v_string = do_string_sub(str, STRLEN(str), pat, sub, expr, flg, NULL);
}

/*
 * "swapfilelist()" function
 */
    static void
f_swapfilelist(typval_T *argvars UNUSED, typval_T *rettv)
{
    if (rettv_list_alloc(rettv) == FAIL)
	return;
    recover_names(NULL, FALSE, rettv->vval.v_list, 0, NULL);
}

/*
 * "swapinfo(swap_filename)" function
 */
    static void
f_swapinfo(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    if (rettv_dict_alloc(rettv) == OK)
	get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict);
}

/*
 * "swapname(expr)" function
 */
    static void
f_swapname(typval_T *argvars, typval_T *rettv)
{
    buf_T	*buf;

    rettv->v_type = VAR_STRING;

    if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL)
	return;

    buf = tv_get_buf(&argvars[0], FALSE);
    if (buf == NULL || buf->b_ml.ml_mfp == NULL
					|| buf->b_ml.ml_mfp->mf_fname == NULL)
	rettv->vval.v_string = NULL;
    else
	rettv->vval.v_string = vim_strsave(buf->b_ml.ml_mfp->mf_fname);
}

/*
 * "synID(lnum, col, trans)" function
 */
    static void
f_synID(typval_T *argvars UNUSED, typval_T *rettv)
{
    int		id = 0;
#ifdef FEAT_SYN_HL
    linenr_T	lnum;
    colnr_T	col;
    int		trans;
    int		transerr = FALSE;

    if (in_vim9script()
	    && (check_for_lnum_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL
		|| check_for_bool_arg(argvars, 2) == FAIL))
	return;

    lnum = tv_get_lnum(argvars);		// -1 on type error
    col = (linenr_T)tv_get_number(&argvars[1]) - 1;	// -1 on type error
    trans = (int)tv_get_bool_chk(&argvars[2], &transerr);

    if (!transerr && lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count
	    && col >= 0 && col < (long)ml_get_len(lnum))
	id = syn_get_id(curwin, lnum, col, trans, NULL, FALSE);
#endif

    rettv->vval.v_number = id;
}

/*
 * "synIDattr(id, what [, mode])" function
 */
    static void
f_synIDattr(typval_T *argvars UNUSED, typval_T *rettv)
{
    char_u	*p = NULL;
#ifdef FEAT_SYN_HL
    int		id;
    char_u	*what;
    char_u	*mode;
    char_u	modebuf[NUMBUFLEN];
    int		modec;

    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_string_arg(argvars, 1) == FAIL
		|| check_for_opt_string_arg(argvars, 2) == FAIL))
	return;

    id = (int)tv_get_number(&argvars[0]);
    what = tv_get_string(&argvars[1]);
    if (argvars[2].v_type != VAR_UNKNOWN)
    {
	mode = tv_get_string_buf(&argvars[2], modebuf);
	modec = TOLOWER_ASC(mode[0]);
	if (modec != 't' && modec != 'c' && modec != 'g')
	    modec = 0;	// replace invalid with current
    }
    else
    {
# if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
	if (USE_24BIT)
	    modec = 'g';
	else
# endif
	    if (t_colors > 1)
		modec = 'c';
	    else
		modec = 't';
    }

    switch (TOLOWER_ASC(what[0]))
    {
	case 'b':
		if (TOLOWER_ASC(what[1]) == 'g')	// bg[#]
		    p = highlight_color(id, what, modec);
		else					// bold
		    p = highlight_has_attr(id, HL_BOLD, modec);
		break;

	case 'f':					// fg[#] or font
		p = highlight_color(id, what, modec);
		break;

	case 'i':
		if (TOLOWER_ASC(what[1]) == 'n')	// inverse
		    p = highlight_has_attr(id, HL_INVERSE, modec);
		else					// italic
		    p = highlight_has_attr(id, HL_ITALIC, modec);
		break;

	case 'n':
		if (TOLOWER_ASC(what[1]) == 'o')	// nocombine
		    p = highlight_has_attr(id, HL_NOCOMBINE, modec);
		else					// name
		    p = get_highlight_name_ext(NULL, id - 1, FALSE);
		break;

	case 'r':					// reverse
		p = highlight_has_attr(id, HL_INVERSE, modec);
		break;

	case 's':
		if (TOLOWER_ASC(what[1]) == 'p')	// sp[#]
		    p = highlight_color(id, what, modec);
							// strikeout
		else if (TOLOWER_ASC(what[1]) == 't' &&
			TOLOWER_ASC(what[2]) == 'r')
		    p = highlight_has_attr(id, HL_STRIKETHROUGH, modec);
		else					// standout
		    p = highlight_has_attr(id, HL_STANDOUT, modec);
		break;

	case 'u':
		if (STRLEN(what) >= 9)
		{
		    if (TOLOWER_ASC(what[5]) == 'l')
							// underline
			p = highlight_has_attr(id, HL_UNDERLINE, modec);
		    else if (TOLOWER_ASC(what[5]) != 'd')
							// undercurl
			p = highlight_has_attr(id, HL_UNDERCURL, modec);
		    else if (TOLOWER_ASC(what[6]) != 'o')
							// underdashed
			p = highlight_has_attr(id, HL_UNDERDASHED, modec);
		    else if (TOLOWER_ASC(what[7]) == 'u')
							// underdouble
			p = highlight_has_attr(id, HL_UNDERDOUBLE, modec);
		    else
							// underdotted
			p = highlight_has_attr(id, HL_UNDERDOTTED, modec);
		}
		else
							// ul
		    p = highlight_color(id, what, modec);
		break;
    }

    if (p != NULL)
	p = vim_strsave(p);
#endif
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = p;
}

/*
 * "synIDtrans(id)" function
 */
    static void
f_synIDtrans(typval_T *argvars UNUSED, typval_T *rettv)
{
    int		id;

#ifdef FEAT_SYN_HL
    if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
	return;

    id = (int)tv_get_number(&argvars[0]);

    if (id > 0)
	id = syn_get_final_id(id);
    else
#endif
	id = 0;

    rettv->vval.v_number = id;
}

/*
 * "synconcealed(lnum, col)" function
 */
    static void
f_synconcealed(typval_T *argvars UNUSED, typval_T *rettv)
{
#if defined(FEAT_SYN_HL) && defined(FEAT_CONCEAL)
    linenr_T	lnum;
    colnr_T	col;
    int		syntax_flags = 0;
    int		cchar;
    int		matchid = 0;
    char_u	str[NUMBUFLEN];
#endif

    rettv_list_set(rettv, NULL);

    if (in_vim9script()
	    && (check_for_lnum_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL))
	return;

#if defined(FEAT_SYN_HL) && defined(FEAT_CONCEAL)
    lnum = tv_get_lnum(argvars);		// -1 on type error
    col = (colnr_T)tv_get_number(&argvars[1]) - 1;	// -1 on type error

    CLEAR_FIELD(str);

    if (rettv_list_alloc(rettv) == OK)
    {
	if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count
	    && col >= 0 && col <= (long)ml_get_len(lnum)
	    && curwin->w_p_cole > 0)
	{
	    (void)syn_get_id(curwin, lnum, col, FALSE, NULL, FALSE);
	    syntax_flags = get_syntax_info(&matchid);

	    // get the conceal character
	    if ((syntax_flags & HL_CONCEAL) && curwin->w_p_cole < 3)
	    {
		cchar = syn_get_sub_char();
		if (cchar == NUL && curwin->w_p_cole == 1)
		    cchar = (curwin->w_lcs_chars.conceal == NUL) ? ' '
					: curwin->w_lcs_chars.conceal;
		if (cchar != NUL)
		{
		    if (has_mbyte)
			(*mb_char2bytes)(cchar, str);
		    else
			str[0] = cchar;
		}
	    }
	}

	list_append_number(rettv->vval.v_list,
					    (syntax_flags & HL_CONCEAL) != 0);
	// -1 to auto-determine strlen
	list_append_string(rettv->vval.v_list, str, -1);
	list_append_number(rettv->vval.v_list, matchid);
    }
#endif
}

/*
 * "synstack(lnum, col)" function
 */
    static void
f_synstack(typval_T *argvars UNUSED, typval_T *rettv)
{
#ifdef FEAT_SYN_HL
    linenr_T	lnum;
    colnr_T	col;
    int		i;
    int		id;
#endif

    rettv_list_set(rettv, NULL);

    if (in_vim9script()
	    && (check_for_lnum_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL))
	return;

#ifdef FEAT_SYN_HL
    lnum = tv_get_lnum(argvars);		// -1 on type error
    col = (colnr_T)tv_get_number(&argvars[1]) - 1;	// -1 on type error

    if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count
	    && col >= 0 && col <= (long)ml_get_len(lnum)
	    && rettv_list_alloc(rettv) == OK)
    {
	(void)syn_get_id(curwin, lnum, col, FALSE, NULL, TRUE);
	for (i = 0; ; ++i)
	{
	    id = syn_get_stack_item(i);
	    if (id < 0)
		break;
	    if (list_append_number(rettv->vval.v_list, id) == FAIL)
		break;
	}
    }
#endif
}

/*
 * "tabpagebuflist()" function
 */
    static void
f_tabpagebuflist(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    tabpage_T	*tp;
    win_T	*wp = NULL;

    if (in_vim9script() && check_for_opt_number_arg(argvars, 0) == FAIL)
	return;

    if (argvars[0].v_type == VAR_UNKNOWN)
	wp = firstwin;
    else
    {
	tp = find_tabpage((int)tv_get_number(&argvars[0]));
	if (tp != NULL)
	    wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
    }
    if (wp != NULL && rettv_list_alloc(rettv) == OK)
    {
	for (; wp != NULL; wp = wp->w_next)
	    if (list_append_number(rettv->vval.v_list,
						wp->w_buffer->b_fnum) == FAIL)
		break;
    }
}

/*
 * "tagfiles()" function
 */
    static void
f_tagfiles(typval_T *argvars UNUSED, typval_T *rettv)
{
    char_u	*fname;
    tagname_T	tn;
    int		first;

    if (rettv_list_alloc(rettv) == FAIL)
	return;
    fname = alloc(MAXPATHL);
    if (fname == NULL)
	return;

    for (first = TRUE; ; first = FALSE)
	if (get_tagfname(&tn, first, fname) == FAIL
		|| list_append_string(rettv->vval.v_list, fname, -1) == FAIL)
	    break;
    tagname_free(&tn);
    vim_free(fname);
}

/*
 * "taglist()" function
 */
    static void
f_taglist(typval_T *argvars, typval_T *rettv)
{
    char_u  *fname = NULL;
    char_u  *tag_pattern;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_opt_string_arg(argvars, 1) == FAIL))
	return;

    tag_pattern = tv_get_string(&argvars[0]);

    rettv->vval.v_number = FALSE;
    if (*tag_pattern == NUL)
	return;

    if (argvars[1].v_type != VAR_UNKNOWN)
	fname = tv_get_string(&argvars[1]);
    if (rettv_list_alloc(rettv) == OK)
	(void)get_tags(rettv->vval.v_list, tag_pattern, fname);
}

/*
 * "type(expr)" function
 */
    static void
f_type(typval_T *argvars, typval_T *rettv)
{
    int n = -1;

    switch (argvars[0].v_type)
    {
	case VAR_NUMBER:  n = VAR_TYPE_NUMBER; break;
	case VAR_STRING:  n = VAR_TYPE_STRING; break;
	case VAR_PARTIAL:
	case VAR_FUNC:    n = VAR_TYPE_FUNC; break;
	case VAR_LIST:    n = VAR_TYPE_LIST; break;
	case VAR_TUPLE:   n = VAR_TYPE_TUPLE; break;
	case VAR_DICT:    n = VAR_TYPE_DICT; break;
	case VAR_FLOAT:   n = VAR_TYPE_FLOAT; break;
	case VAR_BOOL:	  n = VAR_TYPE_BOOL; break;
	case VAR_SPECIAL: n = VAR_TYPE_NONE; break;
	case VAR_JOB:     n = VAR_TYPE_JOB; break;
	case VAR_CHANNEL: n = VAR_TYPE_CHANNEL; break;
	case VAR_BLOB:    n = VAR_TYPE_BLOB; break;
	case VAR_INSTR:   n = VAR_TYPE_INSTR; break;
	case VAR_TYPEALIAS: n = VAR_TYPE_TYPEALIAS; break;
	case VAR_CLASS:
	    {
		class_T *cl = argvars[0].vval.v_class;
		if (cl != NULL && IS_ENUM(cl))
		    n = VAR_TYPE_ENUM;
		else
		    n = VAR_TYPE_CLASS;
		break;
	    }
	case VAR_OBJECT:
	    {
		object_T *obj = argvars[0].vval.v_object;

		if (obj == NULL)
		    n = VAR_TYPE_OBJECT;
		else
		{
		    class_T *cl = argvars[0].vval.v_object->obj_class;
		    if (IS_ENUM(cl))
			n = VAR_TYPE_ENUMVALUE;
		    else
			n = VAR_TYPE_OBJECT;
		}
		break;
	    }
	case VAR_VOID:
	    emsg(_(e_cannot_use_void_value));
	    break;
	case VAR_UNKNOWN:
	case VAR_ANY:
	    internal_error_no_abort("f_type(UNKNOWN)");
	    n = -1;
	    break;
    }
    rettv->vval.v_number = n;
}

/*
 * "virtcol({expr}, [, {list} [, {winid}]])" function
 */
    static void
f_virtcol(typval_T *argvars, typval_T *rettv)
{
    colnr_T	vcol_start = 0;
    colnr_T	vcol_end = 0;
    pos_T	*fp;
    switchwin_T	switchwin;
    int		winchanged = FALSE;
    int		len;

    if (in_vim9script()
	    && (check_for_string_or_list_arg(argvars, 0) == FAIL
		|| (argvars[1].v_type != VAR_UNKNOWN
		    && (check_for_bool_arg(argvars, 1) == FAIL
			|| check_for_opt_number_arg(argvars, 2) == FAIL))))
	return;

    if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN)
    {
	tabpage_T	*tp;
	win_T		*wp;

	// use the window specified in the third argument
	wp = win_id2wp_tp((int)tv_get_number(&argvars[2]), &tp);
	if (wp == NULL || tp == NULL)
	    goto theend;

	if (switch_win_noblock(&switchwin, wp, tp, TRUE) != OK)
	    goto theend;

	check_cursor();
	winchanged = TRUE;
    }

    int fnum = curbuf->b_fnum;
    fp = var2fpos(&argvars[0], FALSE, &fnum, FALSE);
    if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count
	    && fnum == curbuf->b_fnum)
    {
	// Limit the column to a valid value, getvvcol() doesn't check.
	if (fp->col < 0)
	    fp->col = 0;
	else
	{
	    len = (int)ml_get_len(fp->lnum);
	    if (fp->col > len)
		fp->col = len;
	}
	getvvcol(curwin, fp, &vcol_start, NULL, &vcol_end);
	++vcol_start;
	++vcol_end;
    }

theend:
    if (argvars[1].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[1]))
    {
	if (rettv_list_alloc(rettv) == OK)
	{
	    list_append_number(rettv->vval.v_list, vcol_start);
	    list_append_number(rettv->vval.v_list, vcol_end);
	}
	else
	    rettv->vval.v_number = 0;
    }
    else
	rettv->vval.v_number = vcol_end;

    if (winchanged)
	restore_win_noblock(&switchwin, TRUE);
}

/*
 * "visualmode()" function
 */
    static void
f_visualmode(typval_T *argvars, typval_T *rettv)
{
    char_u	str[2];

    if (in_vim9script() && check_for_opt_bool_arg(argvars, 0) == FAIL)
	return;

    rettv->v_type = VAR_STRING;
    str[0] = curbuf->b_visual_mode_eval;
    str[1] = NUL;
    rettv->vval.v_string = vim_strsave(str);

    // A non-zero number or non-empty string argument: reset mode.
    if (non_zero_arg(&argvars[0]))
	curbuf->b_visual_mode_eval = NUL;
}

/*
 * "wildmenumode()" function
 */
    static void
f_wildmenumode(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    if (wild_menu_showing || ((State & MODE_CMDLINE) && cmdline_pum_active()))
	rettv->vval.v_number = 1;
}

/*
 * "windowsversion()" function
 */
    static void
f_windowsversion(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = vim_strsave((char_u *)windowsVersion);
}

/*
 * "wordcount()" function
 */
    static void
f_wordcount(typval_T *argvars UNUSED, typval_T *rettv)
{
    if (rettv_dict_alloc(rettv) == FAIL)
	return;
    cursor_pos_info(rettv->vval.v_dict);
}

/*
 * "xor(expr, expr)" function
 */
    static void
f_xor(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL))
	return;

    rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
					^ tv_get_number_chk(&argvars[1], NULL);
}

#endif // FEAT_EVAL
