diff options
Diffstat (limited to 'src/vim9expr.c')
-rw-r--r-- | src/vim9expr.c | 2893 |
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 |