summaryrefslogtreecommitdiffstats
path: root/src/vim9expr.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/vim9expr.c')
-rw-r--r--src/vim9expr.c2893
1 files changed, 2893 insertions, 0 deletions
diff --git a/src/vim9expr.c b/src/vim9expr.c
new file mode 100644
index 0000000000..66d0fc441f
--- /dev/null
+++ b/src/vim9expr.c
@@ -0,0 +1,2893 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved by Bram Moolenaar
+ *
+ * Do ":help uganda" in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * vim9cmds.c: Dealing with compiled function expressions
+ */
+
+#define USING_FLOAT_STUFF
+#include "vim.h"
+
+#if defined(FEAT_EVAL) || defined(PROTO)
+
+// When not generating protos this is included in proto.h
+#ifdef PROTO
+# include "vim9.h"
+#endif
+
+/*
+ * Generate code for any ppconst entries.
+ */
+ int
+generate_ppconst(cctx_T *cctx, ppconst_T *ppconst)
+{
+ int i;
+ int ret = OK;
+ int save_skip = cctx->ctx_skip;
+
+ cctx->ctx_skip = SKIP_NOT;
+ for (i = 0; i < ppconst->pp_used; ++i)
+ if (generate_tv_PUSH(cctx, &ppconst->pp_tv[i]) == FAIL)
+ ret = FAIL;
+ ppconst->pp_used = 0;
+ cctx->ctx_skip = save_skip;
+ return ret;
+}
+
+/*
+ * Check that the last item of "ppconst" is a bool, if there is an item.
+ */
+ static int
+check_ppconst_bool(ppconst_T *ppconst)
+{
+ if (ppconst->pp_used > 0)
+ {
+ typval_T *tv = &ppconst->pp_tv[ppconst->pp_used - 1];
+ where_T where = WHERE_INIT;
+
+ return check_typval_type(&t_bool, tv, where);
+ }
+ return OK;
+}
+
+/*
+ * Clear ppconst constants. Used when failing.
+ */
+ void
+clear_ppconst(ppconst_T *ppconst)
+{
+ int i;
+
+ for (i = 0; i < ppconst->pp_used; ++i)
+ clear_tv(&ppconst->pp_tv[i]);
+ ppconst->pp_used = 0;
+}
+
+/*
+ * Compile getting a member from a list/dict/string/blob. Stack has the
+ * indexable value and the index or the two indexes of a slice.
+ * "keeping_dict" is used for dict[func](arg) to pass dict to func.
+ */
+ int
+compile_member(int is_slice, int *keeping_dict, cctx_T *cctx)
+{
+ type_T **typep;
+ garray_T *stack = &cctx->ctx_type_stack;
+ vartype_T vartype;
+ type_T *idxtype;
+
+ // We can index a list, dict and blob. If we don't know the type
+ // we can use the index value type. If we still don't know use an "ANY"
+ // instruction.
+ typep = ((type_T **)stack->ga_data) + stack->ga_len
+ - (is_slice ? 3 : 2);
+ vartype = (*typep)->tt_type;
+ idxtype = ((type_T **)stack->ga_data)[stack->ga_len - 1];
+ // If the index is a string, the variable must be a Dict.
+ if (*typep == &t_any && idxtype == &t_string)
+ vartype = VAR_DICT;
+ if (vartype == VAR_STRING || vartype == VAR_LIST || vartype == VAR_BLOB)
+ {
+ if (need_type(idxtype, &t_number, -1, 0, cctx, FALSE, FALSE) == FAIL)
+ return FAIL;
+ if (is_slice)
+ {
+ idxtype = ((type_T **)stack->ga_data)[stack->ga_len - 2];
+ if (need_type(idxtype, &t_number, -2, 0, cctx,
+ FALSE, FALSE) == FAIL)
+ return FAIL;
+ }
+ }
+
+ if (vartype == VAR_DICT)
+ {
+ if (is_slice)
+ {
+ emsg(_(e_cannot_slice_dictionary));
+ return FAIL;
+ }
+ if ((*typep)->tt_type == VAR_DICT)
+ {
+ *typep = (*typep)->tt_member;
+ if (*typep == &t_unknown)
+ // empty dict was used
+ *typep = &t_any;
+ }
+ else
+ {
+ if (need_type(*typep, &t_dict_any, -2, 0, cctx,
+ FALSE, FALSE) == FAIL)
+ return FAIL;
+ *typep = &t_any;
+ }
+ if (may_generate_2STRING(-1, FALSE, cctx) == FAIL)
+ return FAIL;
+ if (generate_instr_drop(cctx, ISN_MEMBER, 1) == FAIL)
+ return FAIL;
+ if (keeping_dict != NULL)
+ *keeping_dict = TRUE;
+ }
+ else if (vartype == VAR_STRING)
+ {
+ *typep = &t_string;
+ if ((is_slice
+ ? generate_instr_drop(cctx, ISN_STRSLICE, 2)
+ : generate_instr_drop(cctx, ISN_STRINDEX, 1)) == FAIL)
+ return FAIL;
+ }
+ else if (vartype == VAR_BLOB)
+ {
+ if (is_slice)
+ {
+ *typep = &t_blob;
+ if (generate_instr_drop(cctx, ISN_BLOBSLICE, 2) == FAIL)
+ return FAIL;
+ }
+ else
+ {
+ *typep = &t_number;
+ if (generate_instr_drop(cctx, ISN_BLOBINDEX, 1) == FAIL)
+ return FAIL;
+ }
+ }
+ else if (vartype == VAR_LIST || *typep == &t_any)
+ {
+ if (is_slice)
+ {
+ if (generate_instr_drop(cctx,
+ vartype == VAR_LIST ? ISN_LISTSLICE : ISN_ANYSLICE,
+ 2) == FAIL)
+ return FAIL;
+ }
+ else
+ {
+ if ((*typep)->tt_type == VAR_LIST)
+ {
+ *typep = (*typep)->tt_member;
+ if (*typep == &t_unknown)
+ // empty list was used
+ *typep = &t_any;
+ }
+ if (generate_instr_drop(cctx,
+ vartype == VAR_LIST ? ISN_LISTINDEX : ISN_ANYINDEX, 1)
+ == FAIL)
+ return FAIL;
+ }
+ }
+ else
+ {
+ switch (vartype)
+ {
+ case VAR_FUNC:
+ case VAR_PARTIAL:
+ emsg(_(e_cannot_index_a_funcref));
+ break;
+ case VAR_BOOL:
+ case VAR_SPECIAL:
+ case VAR_JOB:
+ case VAR_CHANNEL:
+ case VAR_INSTR:
+ case VAR_UNKNOWN:
+ case VAR_ANY:
+ case VAR_VOID:
+ emsg(_(e_cannot_index_special_variable));
+ break;
+ default:
+ emsg(_(e_string_list_dict_or_blob_required));
+ }
+ return FAIL;
+ }
+ return OK;
+}
+
+/*
+ * Generate an instruction to load script-local variable "name", without the
+ * leading "s:".
+ * Also finds imported variables.
+ */
+ int
+compile_load_scriptvar(
+ cctx_T *cctx,
+ char_u *name, // variable NUL terminated
+ char_u *start, // start of variable
+ char_u **end, // end of variable
+ int error) // when TRUE may give error
+{
+ scriptitem_T *si;
+ int idx;
+ imported_T *import;
+
+ if (!SCRIPT_ID_VALID(current_sctx.sc_sid))
+ return FAIL;
+ si = SCRIPT_ITEM(current_sctx.sc_sid);
+ idx = get_script_item_idx(current_sctx.sc_sid, name, 0, cctx);
+ if (idx == -1 || si->sn_version != SCRIPT_VERSION_VIM9)
+ {
+ // variable is not in sn_var_vals: old style script.
+ return generate_OLDSCRIPT(cctx, ISN_LOADS, name, current_sctx.sc_sid,
+ &t_any);
+ }
+ if (idx >= 0)
+ {
+ svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + idx;
+
+ generate_VIM9SCRIPT(cctx, ISN_LOADSCRIPT,
+ current_sctx.sc_sid, idx, sv->sv_type);
+ return OK;
+ }
+
+ import = find_imported(name, 0, cctx);
+ if (import != NULL)
+ {
+ if (import->imp_flags & IMP_FLAGS_STAR)
+ {
+ char_u *p = skipwhite(*end);
+ char_u *exp_name;
+ int cc;
+ ufunc_T *ufunc;
+ type_T *type;
+
+ // Used "import * as Name", need to lookup the member.
+ if (*p != '.')
+ {
+ semsg(_(e_expected_dot_after_name_str), start);
+ return FAIL;
+ }
+ ++p;
+ if (VIM_ISWHITE(*p))
+ {
+ emsg(_(e_no_white_space_allowed_after_dot));
+ return FAIL;
+ }
+
+ // isolate one name
+ exp_name = p;
+ while (eval_isnamec(*p))
+ ++p;
+ cc = *p;
+ *p = NUL;
+
+ idx = find_exported(import->imp_sid, exp_name, &ufunc, &type,
+ cctx, TRUE);
+ *p = cc;
+ p = skipwhite(p);
+ *end = p;
+
+ if (idx < 0)
+ {
+ if (*p == '(' && ufunc != NULL)
+ {
+ generate_PUSHFUNC(cctx, ufunc->uf_name, import->imp_type);
+ return OK;
+ }
+ return FAIL;
+ }
+
+ generate_VIM9SCRIPT(cctx, ISN_LOADSCRIPT,
+ import->imp_sid,
+ idx,
+ type);
+ }
+ else if (import->imp_funcname != NULL)
+ generate_PUSHFUNC(cctx, import->imp_funcname, import->imp_type);
+ else
+ generate_VIM9SCRIPT(cctx, ISN_LOADSCRIPT,
+ import->imp_sid,
+ import->imp_var_vals_idx,
+ import->imp_type);
+ return OK;
+ }
+
+ if (error)
+ semsg(_(e_item_not_found_str), name);
+ return FAIL;
+}
+
+ static int
+generate_funcref(cctx_T *cctx, char_u *name)
+{
+ ufunc_T *ufunc = find_func(name, FALSE, cctx);
+
+ if (ufunc == NULL)
+ return FAIL;
+
+ // Need to compile any default values to get the argument types.
+ if (func_needs_compiling(ufunc, COMPILE_TYPE(ufunc))
+ && compile_def_function(ufunc, TRUE, COMPILE_TYPE(ufunc), NULL)
+ == FAIL)
+ return FAIL;
+ return generate_PUSHFUNC(cctx, ufunc->uf_name, ufunc->uf_func_type);
+}
+
+/*
+ * Compile a variable name into a load instruction.
+ * "end" points to just after the name.
+ * "is_expr" is TRUE when evaluating an expression, might be a funcref.
+ * When "error" is FALSE do not give an error when not found.
+ */
+ int
+compile_load(
+ char_u **arg,
+ char_u *end_arg,
+ cctx_T *cctx,
+ int is_expr,
+ int error)
+{
+ type_T *type;
+ char_u *name = NULL;
+ char_u *end = end_arg;
+ int res = FAIL;
+ int prev_called_emsg = called_emsg;
+
+ if (*(*arg + 1) == ':')
+ {
+ if (end <= *arg + 2)
+ {
+ isntype_T isn_type;
+
+ // load dictionary of namespace
+ switch (**arg)
+ {
+ case 'g': isn_type = ISN_LOADGDICT; break;
+ case 'w': isn_type = ISN_LOADWDICT; break;
+ case 't': isn_type = ISN_LOADTDICT; break;
+ case 'b': isn_type = ISN_LOADBDICT; break;
+ default:
+ semsg(_(e_namespace_not_supported_str), *arg);
+ goto theend;
+ }
+ if (generate_instr_type(cctx, isn_type, &t_dict_any) == NULL)
+ goto theend;
+ res = OK;
+ }
+ else
+ {
+ isntype_T isn_type = ISN_DROP;
+
+ // load namespaced variable
+ name = vim_strnsave(*arg + 2, end - (*arg + 2));
+ if (name == NULL)
+ return FAIL;
+
+ switch (**arg)
+ {
+ case 'v': res = generate_LOADV(cctx, name, error);
+ break;
+ case 's': if (is_expr && ASCII_ISUPPER(*name)
+ && find_func(name, FALSE, cctx) != NULL)
+ res = generate_funcref(cctx, name);
+ else
+ res = compile_load_scriptvar(cctx, name,
+ NULL, &end, error);
+ break;
+ case 'g': if (vim_strchr(name, AUTOLOAD_CHAR) == NULL)
+ {
+ if (is_expr && ASCII_ISUPPER(*name)
+ && find_func(name, FALSE, cctx) != NULL)
+ res = generate_funcref(cctx, name);
+ else
+ isn_type = ISN_LOADG;
+ }
+ else
+ {
+ isn_type = ISN_LOADAUTO;
+ vim_free(name);
+ name = vim_strnsave(*arg, end - *arg);
+ if (name == NULL)
+ return FAIL;
+ }
+ break;
+ case 'w': isn_type = ISN_LOADW; break;
+ case 't': isn_type = ISN_LOADT; break;
+ case 'b': isn_type = ISN_LOADB; break;
+ default: // cannot happen, just in case
+ semsg(_(e_namespace_not_supported_str), *arg);
+ goto theend;
+ }
+ if (isn_type != ISN_DROP)
+ {
+ // Global, Buffer-local, Window-local and Tabpage-local
+ // variables can be defined later, thus we don't check if it
+ // exists, give an error at runtime.
+ res = generate_LOAD(cctx, isn_type, 0, name, &t_any);
+ }
+ }
+ }
+ else
+ {
+ size_t len = end - *arg;
+ int idx;
+ int gen_load = FALSE;
+ int gen_load_outer = 0;
+
+ name = vim_strnsave(*arg, end - *arg);
+ if (name == NULL)
+ return FAIL;
+
+ if (vim_strchr(name, AUTOLOAD_CHAR) != NULL)
+ {
+ script_autoload(name, FALSE);
+ res = generate_LOAD(cctx, ISN_LOADAUTO, 0, name, &t_any);
+ }
+ else if (arg_exists(*arg, len, &idx, &type, &gen_load_outer, cctx)
+ == OK)
+ {
+ if (gen_load_outer == 0)
+ gen_load = TRUE;
+ }
+ else
+ {
+ lvar_T lvar;
+
+ if (lookup_local(*arg, len, &lvar, cctx) == OK)
+ {
+ type = lvar.lv_type;
+ idx = lvar.lv_idx;
+ if (lvar.lv_from_outer != 0)
+ gen_load_outer = lvar.lv_from_outer;
+ else
+ gen_load = TRUE;
+ }
+ else
+ {
+ // "var" can be script-local even without using "s:" if it
+ // already exists in a Vim9 script or when it's imported.
+ if (script_var_exists(*arg, len, cctx) == OK
+ || find_imported(name, 0, cctx) != NULL)
+ res = compile_load_scriptvar(cctx, name, *arg, &end, FALSE);
+
+ // When evaluating an expression and the name starts with an
+ // uppercase letter it can be a user defined function.
+ // generate_funcref() will fail if the function can't be found.
+ if (res == FAIL && is_expr && ASCII_ISUPPER(*name))
+ res = generate_funcref(cctx, name);
+ }
+ }
+ if (gen_load)
+ res = generate_LOAD(cctx, ISN_LOAD, idx, NULL, type);
+ if (gen_load_outer > 0)
+ {
+ res = generate_LOADOUTER(cctx, idx, gen_load_outer, type);
+ cctx->ctx_outer_used = TRUE;
+ }
+ }
+
+ *arg = end;
+
+theend:
+ if (res == FAIL && error && called_emsg == prev_called_emsg)
+ semsg(_(e_variable_not_found_str), name);
+ vim_free(name);
+ return res;
+}
+
+/*
+ * Compile a string in a ISN_PUSHS instruction into an ISN_INSTR.
+ * Returns FAIL if compilation fails.
+ */
+ static int
+compile_string(isn_T *isn, cctx_T *cctx)
+{
+ char_u *s = isn->isn_arg.string;
+ garray_T save_ga = cctx->ctx_instr;
+ int expr_res;
+ int trailing_error;
+ int instr_count;
+ isn_T *instr = NULL;
+
+ // Remove the string type from the stack.
+ --cctx->ctx_type_stack.ga_len;
+
+ // Temporarily reset the list of instructions so that the jump labels are
+ // correct.
+ cctx->ctx_instr.ga_len = 0;
+ cctx->ctx_instr.ga_maxlen = 0;
+ cctx->ctx_instr.ga_data = NULL;
+ expr_res = compile_expr0(&s, cctx);
+ s = skipwhite(s);
+ trailing_error = *s != NUL;
+
+ if (expr_res == FAIL || trailing_error
+ || GA_GROW_FAILS(&cctx->ctx_instr, 1))
+ {
+ if (trailing_error)
+ semsg(_(e_trailing_arg), s);
+ clear_instr_ga(&cctx->ctx_instr);
+ cctx->ctx_instr = save_ga;
+ ++cctx->ctx_type_stack.ga_len;
+ return FAIL;
+ }
+
+ // Move the generated instructions into the ISN_INSTR instruction, then
+ // restore the list of instructions.
+ instr_count = cctx->ctx_instr.ga_len;
+ instr = cctx->ctx_instr.ga_data;
+ instr[instr_count].isn_type = ISN_FINISH;
+
+ cctx->ctx_instr = save_ga;
+ vim_free(isn->isn_arg.string);
+ isn->isn_type = ISN_INSTR;
+ isn->isn_arg.instr = instr;
+ return OK;
+}
+
+/*
+ * Compile the argument expressions.
+ * "arg" points to just after the "(" and is advanced to after the ")"
+ */
+ static int
+compile_arguments(char_u **arg, cctx_T *cctx, int *argcount, int is_searchpair)
+{
+ char_u *p = *arg;
+ char_u *whitep = *arg;
+ int must_end = FALSE;
+ int instr_count;
+
+ for (;;)
+ {
+ if (may_get_next_line(whitep, &p, cctx) == FAIL)
+ goto failret;
+ if (*p == ')')
+ {
+ *arg = p + 1;
+ return OK;
+ }
+ if (must_end)
+ {
+ semsg(_(e_missing_comma_before_argument_str), p);
+ return FAIL;
+ }
+
+ instr_count = cctx->ctx_instr.ga_len;
+ if (compile_expr0(&p, cctx) == FAIL)
+ return FAIL;
+ ++*argcount;
+
+ if (is_searchpair && *argcount == 5
+ && cctx->ctx_instr.ga_len == instr_count + 1)
+ {
+ isn_T *isn = ((isn_T *)cctx->ctx_instr.ga_data) + instr_count;
+
+ // {skip} argument of searchpair() can be compiled if not empty
+ if (isn->isn_type == ISN_PUSHS && *isn->isn_arg.string != NUL)
+ compile_string(isn, cctx);
+ }
+
+ if (*p != ',' && *skipwhite(p) == ',')
+ {
+ semsg(_(e_no_white_space_allowed_before_str_str), ",", p);
+ p = skipwhite(p);
+ }
+ if (*p == ',')
+ {
+ ++p;
+ if (*p != NUL && !VIM_ISWHITE(*p))
+ semsg(_(e_white_space_required_after_str_str), ",", p - 1);
+ }
+ else
+ must_end = TRUE;
+ whitep = p;
+ p = skipwhite(p);
+ }
+failret:
+ emsg(_(e_missing_closing_paren));
+ return FAIL;
+}
+
+/*
+ * Compile a function call: name(arg1, arg2)
+ * "arg" points to "name", "arg + varlen" to the "(".
+ * "argcount_init" is 1 for "value->method()"
+ * Instructions:
+ * EVAL arg1
+ * EVAL arg2
+ * BCALL / DCALL / UCALL
+ */
+ static int
+compile_call(
+ char_u **arg,
+ size_t varlen,
+ cctx_T *cctx,
+ ppconst_T *ppconst,
+ int argcount_init)
+{
+ char_u *name = *arg;
+ char_u *p;
+ int argcount = argcount_init;
+ char_u namebuf[100];
+ char_u fname_buf[FLEN_FIXED + 1];
+ char_u *tofree = NULL;
+ int error = FCERR_NONE;
+ ufunc_T *ufunc = NULL;
+ int res = FAIL;
+ int is_autoload;
+ int is_searchpair;
+
+ // We can evaluate "has('name')" at compile time.
+ // We always evaluate "exists_compiled()" at compile time.
+ if ((varlen == 3 && STRNCMP(*arg, "has", 3) == 0)
+ || (varlen == 15 && STRNCMP(*arg, "exists_compiled", 6) == 0))
+ {
+ char_u *s = skipwhite(*arg + varlen + 1);
+ typval_T argvars[2];
+ int is_has = **arg == 'h';
+
+ argvars[0].v_type = VAR_UNKNOWN;
+ if (*s == '"')
+ (void)eval_string(&s, &argvars[0], TRUE);
+ else if (*s == '\'')
+ (void)eval_lit_string(&s, &argvars[0], TRUE);
+ s = skipwhite(s);
+ if (*s == ')' && argvars[0].v_type == VAR_STRING
+ && ((is_has && !dynamic_feature(argvars[0].vval.v_string))
+ || !is_has))
+ {
+ typval_T *tv = &ppconst->pp_tv[ppconst->pp_used];
+
+ *arg = s + 1;
+ argvars[1].v_type = VAR_UNKNOWN;
+ tv->v_type = VAR_NUMBER;
+ tv->vval.v_number = 0;
+ if (is_has)
+ f_has(argvars, tv);
+ else
+ f_exists(argvars, tv);
+ clear_tv(&argvars[0]);
+ ++ppconst->pp_used;
+ return OK;
+ }
+ clear_tv(&argvars[0]);
+ if (!is_has)
+ {
+ emsg(_(e_argument_of_exists_compiled_must_be_literal_string));
+ return FAIL;
+ }
+ }
+
+ if (generate_ppconst(cctx, ppconst) == FAIL)
+ return FAIL;
+
+ if (varlen >= sizeof(namebuf))
+ {
+ semsg(_(e_name_too_long_str), name);
+ return FAIL;
+ }
+ vim_strncpy(namebuf, *arg, varlen);
+ name = fname_trans_sid(namebuf, fname_buf, &tofree, &error);
+
+ // We handle the "skip" argument of searchpair() and searchpairpos()
+ // differently.
+ is_searchpair = (varlen == 6 && STRNCMP(*arg, "search", 6) == 0)
+ || (varlen == 9 && STRNCMP(*arg, "searchpos", 9) == 0)
+ || (varlen == 10 && STRNCMP(*arg, "searchpair", 10) == 0)
+ || (varlen == 13 && STRNCMP(*arg, "searchpairpos", 13) == 0);
+
+ *arg = skipwhite(*arg + varlen + 1);
+ if (compile_arguments(arg, cctx, &argcount, is_searchpair) == FAIL)
+ goto theend;
+
+ is_autoload = vim_strchr(name, AUTOLOAD_CHAR) != NULL;
+ if (ASCII_ISLOWER(*name) && name[1] != ':' && !is_autoload)
+ {
+ int idx;
+
+ // builtin function
+ idx = find_internal_func(name);
+ if (idx >= 0)
+ {
+ if (STRCMP(name, "flatten") == 0)
+ {
+ emsg(_(e_cannot_use_flatten_in_vim9_script));
+ goto theend;
+ }
+
+ if (STRCMP(name, "add") == 0 && argcount == 2)
+ {
+ garray_T *stack = &cctx->ctx_type_stack;
+ type_T *type = ((type_T **)stack->ga_data)[
+ stack->ga_len - 2];
+
+ // add() can be compiled to instructions if we know the type
+ if (type->tt_type == VAR_LIST)
+ {
+ // inline "add(list, item)" so that the type can be checked
+ res = generate_LISTAPPEND(cctx);
+ idx = -1;
+ }
+ else if (type->tt_type == VAR_BLOB)
+ {
+ // inline "add(blob, nr)" so that the type can be checked
+ res = generate_BLOBAPPEND(cctx);
+ idx = -1;
+ }
+ }
+
+ if (idx >= 0)
+ res = generate_BCALL(cctx, idx, argcount, argcount_init == 1);
+ }
+ else
+ semsg(_(e_unknown_function_str), namebuf);
+ goto theend;
+ }
+
+ // An argument or local variable can be a function reference, this
+ // overrules a function name.
+ if (lookup_local(namebuf, varlen, NULL, cctx) == FAIL
+ && arg_exists(namebuf, varlen, NULL, NULL, NULL, cctx) != OK)
+ {
+ // If we can find the function by name generate the right call.
+ // Skip global functions here, a local funcref takes precedence.
+ ufunc = find_func(name, FALSE, cctx);
+ if (ufunc != NULL && !func_is_global(ufunc))
+ {
+ res = generate_CALL(cctx, ufunc, argcount);
+ goto theend;
+ }
+ }
+
+ // If the name is a variable, load it and use PCALL.
+ // Not for g:Func(), we don't know if it is a variable or not.
+ // Not for eome#Func(), it will be loaded later.
+ p = namebuf;
+ if (STRNCMP(namebuf, "g:", 2) != 0 && !is_autoload
+ && compile_load(&p, namebuf + varlen, cctx, FALSE, FALSE) == OK)
+ {
+ garray_T *stack = &cctx->ctx_type_stack;
+ type_T *type = ((type_T **)stack->ga_data)[stack->ga_len - 1];
+
+ res = generate_PCALL(cctx, argcount, namebuf, type, FALSE);
+ goto theend;
+ }
+
+ // If we can find a global function by name generate the right call.
+ if (ufunc != NULL)
+ {
+ res = generate_CALL(cctx, ufunc, argcount);
+ goto theend;
+ }
+
+ // A global function may be defined only later. Need to figure out at
+ // runtime. Also handles a FuncRef at runtime.
+ if (STRNCMP(namebuf, "g:", 2) == 0 || is_autoload)
+ res = generate_UCALL(cctx, name, argcount);
+ else
+ semsg(_(e_unknown_function_str), namebuf);
+
+theend:
+ vim_free(tofree);
+ return res;
+}
+
+// like NAMESPACE_CHAR but with 'a' and 'l'.
+#define VIM9_NAMESPACE_CHAR (char_u *)"bgstvw"
+
+/*
+ * Find the end of a variable or function name. Unlike find_name_end() this
+ * does not recognize magic braces.
+ * When "use_namespace" is TRUE recognize "b:", "s:", etc.
+ * Return a pointer to just after the name. Equal to "arg" if there is no
+ * valid name.
+ */
+ char_u *
+to_name_end(char_u *arg, int use_namespace)
+{
+ char_u *p;
+
+ // Quick check for valid starting character.
+ if (!eval_isnamec1(*arg))
+ return arg;
+
+ for (p = arg + 1; *p != NUL && eval_isnamec(*p); MB_PTR_ADV(p))
+ // Include a namespace such as "s:var" and "v:var". But "n:" is not
+ // and can be used in slice "[n:]".
+ if (*p == ':' && (p != arg + 1
+ || !use_namespace
+ || vim_strchr(VIM9_NAMESPACE_CHAR, *arg) == NULL))
+ break;
+ return p;
+}
+
+/*
+ * Like to_name_end() but also skip over a list or dict constant.
+ * Also accept "<SNR>123_Func".
+ * This intentionally does not handle line continuation.
+ */
+ char_u *
+to_name_const_end(char_u *arg)
+{
+ char_u *p = arg;
+ typval_T rettv;
+
+ if (STRNCMP(p, "<SNR>", 5) == 0)
+ p = skipdigits(p + 5);
+ p = to_name_end(p, TRUE);
+ if (p == arg && *arg == '[')
+ {
+
+ // Can be "[1, 2, 3]->Func()".
+ if (eval_list(&p, &rettv, NULL, FALSE) == FAIL)
+ p = arg;
+ }
+ return p;
+}
+
+/*
+ * parse a list: [expr, expr]
+ * "*arg" points to the '['.
+ * ppconst->pp_is_const is set if all items are a constant.
+ */
+ static int
+compile_list(char_u **arg, cctx_T *cctx, ppconst_T *ppconst)
+{
+ char_u *p = skipwhite(*arg + 1);
+ char_u *whitep = *arg + 1;
+ int count = 0;
+ int is_const;
+ int is_all_const = TRUE; // reset when non-const encountered
+
+ for (;;)
+ {
+ if (may_get_next_line(whitep, &p, cctx) == FAIL)
+ {
+ semsg(_(e_list_end), *arg);
+ return FAIL;
+ }
+ if (*p == ',')
+ {
+ semsg(_(e_no_white_space_allowed_before_str_str), ",", p);
+ return FAIL;
+ }
+ if (*p == ']')
+ {
+ ++p;
+ break;
+ }
+ if (compile_expr0_ext(&p, cctx, &is_const) == FAIL)
+ return FAIL;
+ if (!is_const)
+ is_all_const = FALSE;
+ ++count;
+ if (*p == ',')
+ {
+ ++p;
+ if (*p != ']' && !IS_WHITE_OR_NUL(*p))
+ {
+ semsg(_(e_white_space_required_after_str_str), ",", p - 1);
+ return FAIL;
+ }
+ }
+ whitep = p;
+ p = skipwhite(p);
+ }
+ *arg = p;
+
+ ppconst->pp_is_const = is_all_const;
+ return generate_NEWLIST(cctx, count);
+}
+
+/*
+ * Parse a lambda: "(arg, arg) => expr"
+ * "*arg" points to the '('.
+ * Returns OK/FAIL when a lambda is recognized, NOTDONE if it's not a lambda.
+ */
+ static int
+compile_lambda(char_u **arg, cctx_T *cctx)
+{
+ int r;
+ typval_T rettv;
+ ufunc_T *ufunc;
+ evalarg_T evalarg;
+
+ init_evalarg(&evalarg);
+ evalarg.eval_flags = EVAL_EVALUATE;
+ evalarg.eval_cctx = cctx;
+
+ // Get the funcref in "rettv".
+ r = get_lambda_tv(arg, &rettv, TRUE, &evalarg);
+ if (r != OK)
+ {
+ clear_evalarg(&evalarg, NULL);
+ return r;
+ }
+
+ // "rettv" will now be a partial referencing the function.
+ ufunc = rettv.vval.v_partial->pt_func;
+ ++ufunc->uf_refcount;
+ clear_tv(&rettv);
+
+ // Compile it here to get the return type. The return type is optional,
+ // when it's missing use t_unknown. This is recognized in
+ // compile_return().
+ if (ufunc->uf_ret_type->tt_type == VAR_VOID)
+ ufunc->uf_ret_type = &t_unknown;
+ compile_def_function(ufunc, FALSE, cctx->ctx_compile_type, cctx);
+
+ // When the outer function is compiled for profiling or debugging, the
+ // lambda may be called without profiling or debugging. Compile it here in
+ // the right context.
+ if (cctx->ctx_compile_type == CT_DEBUG
+#ifdef FEAT_PROFILE
+ || cctx->ctx_compile_type == CT_PROFILE
+#endif
+ )
+ compile_def_function(ufunc, FALSE, CT_NONE, cctx);
+
+ // The last entry in evalarg.eval_tofree_ga is a copy of the last line and
+ // "*arg" may point into it. Point into the original line to avoid a
+ // dangling pointer.
+ if (evalarg.eval_using_cmdline)
+ {
+ garray_T *gap = &evalarg.eval_tofree_ga;
+ size_t off = *arg - ((char_u **)gap->ga_data)[gap->ga_len - 1];
+
+ *arg = ((char_u **)cctx->ctx_ufunc->uf_lines.ga_data)[cctx->ctx_lnum]
+ + off;
+ }
+
+ clear_evalarg(&evalarg, NULL);
+
+ if (ufunc->uf_def_status == UF_COMPILED)
+ {
+ // The return type will now be known.
+ set_function_type(ufunc);
+
+ // The function reference count will be 1. When the ISN_FUNCREF
+ // instruction is deleted the reference count is decremented and the
+ // function is freed.
+ return generate_FUNCREF(cctx, ufunc);
+ }
+
+ func_ptr_unref(ufunc);
+ return FAIL;
+}
+
+/*
+ * Get a lambda and compile it. Uses Vim9 syntax.
+ */
+ int
+get_lambda_tv_and_compile(
+ char_u **arg,
+ typval_T *rettv,
+ int types_optional,
+ evalarg_T *evalarg)
+{
+ int r;
+ ufunc_T *ufunc;
+ int save_sc_version = current_sctx.sc_version;
+
+ // Get the funcref in "rettv".
+ current_sctx.sc_version = SCRIPT_VERSION_VIM9;
+ r = get_lambda_tv(arg, rettv, types_optional, evalarg);
+ current_sctx.sc_version = save_sc_version;
+ if (r != OK)
+ return r;
+
+ // "rettv" will now be a partial referencing the function.
+ ufunc = rettv->vval.v_partial->pt_func;
+
+ // Compile it here to get the return type. The return type is optional,
+ // when it's missing use t_unknown. This is recognized in
+ // compile_return().
+ if (ufunc->uf_ret_type == NULL || ufunc->uf_ret_type->tt_type == VAR_VOID)
+ ufunc->uf_ret_type = &t_unknown;
+ compile_def_function(ufunc, FALSE, CT_NONE, NULL);
+
+ if (ufunc->uf_def_status == UF_COMPILED)
+ {
+ // The return type will now be known.
+ set_function_type(ufunc);
+ return OK;
+ }
+ clear_tv(rettv);
+ return FAIL;
+}
+
+/*
+ * parse a dict: {key: val, [key]: val}
+ * "*arg" points to the '{'.
+ * ppconst->pp_is_const is set if all item values are a constant.
+ */
+ static int
+compile_dict(char_u **arg, cctx_T *cctx, ppconst_T *ppconst)
+{
+ garray_T *instr = &cctx->ctx_instr;
+ int count = 0;
+ dict_T *d = dict_alloc();
+ dictitem_T *item;
+ char_u *whitep = *arg + 1;
+ char_u *p;
+ int is_const;
+ int is_all_const = TRUE; // reset when non-const encountered
+
+ if (d == NULL)
+ return FAIL;
+ if (generate_ppconst(cctx, ppconst) == FAIL)
+ return FAIL;
+ for (;;)
+ {
+ char_u *key = NULL;
+
+ if (may_get_next_line(whitep, arg, cctx) == FAIL)
+ {
+ *arg = NULL;
+ goto failret;
+ }
+
+ if (**arg == '}')
+ break;
+
+ if (**arg == '[')
+ {
+ isn_T *isn;
+
+ // {[expr]: value} uses an evaluated key.
+ *arg = skipwhite(*arg + 1);
+ if (compile_expr0(arg, cctx) == FAIL)
+ return FAIL;
+ isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1;
+ if (isn->isn_type == ISN_PUSHNR)
+ {
+ char buf[NUMBUFLEN];
+
+ // Convert to string at compile time.
+ vim_snprintf(buf, NUMBUFLEN, "%lld", isn->isn_arg.number);
+ isn->isn_type = ISN_PUSHS;
+ isn->isn_arg.string = vim_strsave((char_u *)buf);
+ }
+ if (isn->isn_type == ISN_PUSHS)
+ key = isn->isn_arg.string;
+ else if (may_generate_2STRING(-1, FALSE, cctx) == FAIL)
+ return FAIL;
+ *arg = skipwhite(*arg);
+ if (**arg != ']')
+ {
+ emsg(_(e_missing_matching_bracket_after_dict_key));
+ return FAIL;
+ }
+ ++*arg;
+ }
+ else
+ {
+ // {"name": value},
+ // {'name': value},
+ // {name: value} use "name" as a literal key
+ key = get_literal_key(arg);
+ if (key == NULL)
+ return FAIL;
+ if (generate_PUSHS(cctx, &key) == FAIL)
+ return FAIL;
+ }
+
+ // Check for duplicate keys, if using string keys.
+ if (key != NULL)
+ {
+ item = dict_find(d, key, -1);
+ if (item != NULL)
+ {
+ semsg(_(e_duplicate_key), key);
+ goto failret;
+ }
+ item = dictitem_alloc(key);
+ if (item != NULL)
+ {
+ item->di_tv.v_type = VAR_UNKNOWN;
+ item->di_tv.v_lock = 0;
+ if (dict_add(d, item) == FAIL)
+ dictitem_free(item);
+ }
+ }
+
+ if (**arg != ':')
+ {
+ if (*skipwhite(*arg) == ':')
+ semsg(_(e_no_white_space_allowed_before_str_str), ":", *arg);
+ else
+ semsg(_(e_missing_dict_colon), *arg);
+ return FAIL;
+ }
+ whitep = *arg + 1;
+ if (!IS_WHITE_OR_NUL(*whitep))
+ {
+ semsg(_(e_white_space_required_after_str_str), ":", *arg);
+ return FAIL;
+ }
+
+ if (may_get_next_line(whitep, arg, cctx) == FAIL)
+ {
+ *arg = NULL;
+ goto failret;
+ }
+
+ if (compile_expr0_ext(arg, cctx, &is_const) == FAIL)
+ return FAIL;
+ if (!is_const)
+ is_all_const = FALSE;
+ ++count;
+
+ whitep = *arg;
+ if (may_get_next_line(whitep, arg, cctx) == FAIL)
+ {
+ *arg = NULL;
+ goto failret;
+ }
+ if (**arg == '}')
+ break;
+ if (**arg != ',')
+ {
+ semsg(_(e_missing_dict_comma), *arg);
+ goto failret;
+ }
+ if (IS_WHITE_OR_NUL(*whitep))
+ {
+ semsg(_(e_no_white_space_allowed_before_str_str), ",", whitep);
+ return FAIL;
+ }
+ whitep = *arg + 1;
+ if (!IS_WHITE_OR_NUL(*whitep))
+ {
+ semsg(_(e_white_space_required_after_str_str), ",", *arg);
+ return FAIL;
+ }
+ *arg = skipwhite(whitep);
+ }
+
+ *arg = *arg + 1;
+
+ // Allow for following comment, after at least one space.
+ p = skipwhite(*arg);
+ if (VIM_ISWHITE(**arg) && vim9_comment_start(p))
+ *arg += STRLEN(*arg);
+
+ dict_unref(d);
+ ppconst->pp_is_const = is_all_const;
+ return generate_NEWDICT(cctx, count);
+
+failret:
+ if (*arg == NULL)
+ {
+ semsg(_(e_missing_dict_end), _("[end of lines]"));
+ *arg = (char_u *)"";
+ }
+ dict_unref(d);
+ return FAIL;
+}
+
+/*
+ * Compile "&option".
+ */
+ static int
+compile_get_option(char_u **arg, cctx_T *cctx)
+{
+ typval_T rettv;
+ char_u *start = *arg;
+ int ret;
+
+ // parse the option and get the current value to get the type.
+ rettv.v_type = VAR_UNKNOWN;
+ ret = eval_option(arg, &rettv, TRUE);
+ if (ret == OK)
+ {
+ // include the '&' in the name, eval_option() expects it.
+ char_u *name = vim_strnsave(start, *arg - start);
+ type_T *type = rettv.v_type == VAR_BOOL ? &t_bool
+ : rettv.v_type == VAR_NUMBER ? &t_number : &t_string;
+
+ ret = generate_LOAD(cctx, ISN_LOADOPT, 0, name, type);
+ vim_free(name);
+ }
+ clear_tv(&rettv);
+
+ return ret;
+}
+
+/*
+ * Compile "$VAR".
+ */
+ static int
+compile_get_env(char_u **arg, cctx_T *cctx)
+{
+ char_u *start = *arg;
+ int len;
+ int ret;
+ char_u *name;
+
+ ++*arg;
+ len = get_env_len(arg);
+ if (len == 0)
+ {
+ semsg(_(e_syntax_error_at_str), start - 1);
+ return FAIL;
+ }
+
+ // include the '$' in the name, eval_env_var() expects it.
+ name = vim_strnsave(start, len + 1);
+ ret = generate_LOAD(cctx, ISN_LOADENV, 0, name, &t_string);
+ vim_free(name);
+ return ret;
+}
+
+/*
+ * Compile "@r".
+ */
+ static int
+compile_get_register(char_u **arg, cctx_T *cctx)
+{
+ int ret;
+
+ ++*arg;
+ if (**arg == NUL)
+ {
+ semsg(_(e_syntax_error_at_str), *arg - 1);
+ return FAIL;
+ }
+ if (!valid_yank_reg(**arg, FALSE))
+ {
+ ems