diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-03 22:21:25 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-04 00:23:03 +0300 |
| commit | 2eae5db069dc171f74cd863487655f6a88e5384d (patch) | |
| tree | 2d9d05e09978a2a44acbfbb8d651f240df3ca052 /uvim/src/vim9expr.c | |
| parent | 473d922faed49241a5d29d9e37dc4819cd512006 (diff) | |
| download | Project-Tick-2eae5db069dc171f74cd863487655f6a88e5384d.tar.gz Project-Tick-2eae5db069dc171f74cd863487655f6a88e5384d.zip | |
NOISSUE rebrand vim to MNV's not Vim
Signed-off-by: Mehmet Samet Duman <yongdohyun@projecttick.org>
Diffstat (limited to 'uvim/src/vim9expr.c')
| -rw-r--r-- | uvim/src/vim9expr.c | 4071 |
1 files changed, 0 insertions, 4071 deletions
diff --git a/uvim/src/vim9expr.c b/uvim/src/vim9expr.c deleted file mode 100644 index 89219996ef..0000000000 --- a/uvim/src/vim9expr.c +++ /dev/null @@ -1,4071 +0,0 @@ -/* 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. - */ - -/* - * vim9expr.c: Dealing with compiled function expressions - */ - -#define USING_FLOAT_STUFF -#include "vim.h" - -#if defined(FEAT_EVAL) - -// flag passed from compile_subscript() to compile_load_scriptvar() -static int paren_follows_after_expr = 0; - -/* - * 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 tuple. Stack has the indexable value and - * the index or the two indexes of a slice. - */ - static int -compile_tuple_member( - type2_T *typep, - int is_slice, - cctx_T *cctx) -{ - if (is_slice) - { - if (generate_instr_drop(cctx, ISN_TUPLESLICE, 2) == FAIL) - return FAIL; - // a copy is made so the member type is no longer declared - if (typep->type_decl->tt_type == VAR_TUPLE) - typep->type_decl = &t_tuple_any; - - // a copy is made, the composite is no longer "const" - if (typep->type_curr->tt_flags & TTFLAG_CONST) - { - type_T *type = copy_type(typep->type_curr, cctx->ctx_type_list); - - if (type != typep->type_curr) // did get a copy - { - type->tt_flags &= ~(TTFLAG_CONST | TTFLAG_STATIC); - typep->type_curr = type; - } - } - } - else - { - if (typep->type_curr->tt_type == VAR_TUPLE) - { - if (typep->type_curr->tt_argcount == 1) - { - if (typep->type_curr->tt_flags & TTFLAG_VARARGS) - typep->type_curr - = typep->type_curr->tt_args[0]->tt_member; - else - typep->type_curr = typep->type_curr->tt_args[0]; - } - else - typep->type_curr = &t_any; - if (typep->type_decl->tt_type == VAR_TUPLE) - { - if (typep->type_decl->tt_argcount == 1) - { - if (typep->type_decl->tt_flags & TTFLAG_VARARGS) - typep->type_decl - = typep->type_decl->tt_args[0]->tt_member; - else - typep->type_decl = typep->type_decl->tt_args[0]; - } - else - typep->type_curr = &t_any; - } - else - typep->type_decl = typep->type_curr; - } - if (generate_instr_drop(cctx, ISN_TUPLEINDEX, 1) == FAIL) - return FAIL; - } - - return OK; -} - -/* - * Compile getting a member from a list/tuple/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) -{ - type2_T *typep; - garray_T *stack = &cctx->ctx_type_stack; - vartype_T vartype; - type_T *idxtype; - - // We can index a list, tuple, 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. - // TODO: what about the decl type? - typep = (((type2_T *)stack->ga_data) + stack->ga_len - (is_slice ? 3 : 2)); - vartype = typep->type_curr->tt_type; - idxtype = (((type2_T *)stack->ga_data) + stack->ga_len - 1)->type_curr; - // If the index is a string, the variable must be a Dict. - if ((typep->type_curr->tt_type == VAR_ANY - || typep->type_curr->tt_type == VAR_UNKNOWN) - && idxtype == &t_string) - vartype = VAR_DICT; - if (vartype == VAR_STRING || vartype == VAR_LIST || vartype == VAR_BLOB - || vartype == VAR_TUPLE) - { - if (need_type(idxtype, &t_number, 0, - -1, 0, cctx, FALSE, FALSE) == FAIL) - return FAIL; - if (is_slice) - { - idxtype = get_type_on_stack(cctx, 1); - if (need_type(idxtype, &t_number, 0, - -2, 0, cctx, FALSE, FALSE) == FAIL) - return FAIL; - } - } - - if (vartype == VAR_DICT) - { - if (is_slice) - { - emsg(_(e_cannot_slice_dictionary)); - return FAIL; - } - if (typep->type_curr->tt_type == VAR_DICT) - { - typep->type_curr = typep->type_curr->tt_member; - if (typep->type_curr == &t_unknown) - // empty dict was used - typep->type_curr = &t_any; - if (typep->type_decl->tt_type == VAR_DICT) - { - typep->type_decl = typep->type_decl->tt_member; - if (typep->type_decl == &t_unknown) - // empty dict was used - typep->type_decl = &t_any; - } - else - typep->type_decl = typep->type_curr; - } - else - { - if (need_type(typep->type_curr, &t_dict_any, 0, - -2, 0, cctx, FALSE, FALSE) == FAIL) - return FAIL; - typep->type_curr = &t_any; - typep->type_decl = &t_any; - } - if (may_generate_2STRING(-1, TOSTRING_NONE, cctx) == FAIL - || generate_instr_drop(cctx, ISN_MEMBER, 1) == FAIL) - return FAIL; - if (keeping_dict != NULL) - *keeping_dict = TRUE; - } - else if (vartype == VAR_STRING) - { - typep->type_curr = &t_string; - typep->type_decl = &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->type_curr = &t_blob; - typep->type_decl = &t_blob; - if (generate_instr_drop(cctx, ISN_BLOBSLICE, 2) == FAIL) - return FAIL; - } - else - { - typep->type_curr = &t_number; - typep->type_decl = &t_number; - if (generate_instr_drop(cctx, ISN_BLOBINDEX, 1) == FAIL) - return FAIL; - } - } - else if (vartype == VAR_TUPLE) - { - if (compile_tuple_member(typep, is_slice, cctx) == FAIL) - return FAIL; - } - else if (vartype == VAR_LIST || typep->type_curr->tt_type == VAR_ANY - || typep->type_curr->tt_type == VAR_UNKNOWN) - { - if (is_slice) - { - if (generate_instr_drop(cctx, - vartype == VAR_LIST ? ISN_LISTSLICE : ISN_ANYSLICE, - 2) == FAIL) - return FAIL; - // a copy is made so the member type is no longer declared - if (typep->type_decl->tt_type == VAR_LIST) - typep->type_decl = &t_list_any; - - // a copy is made, the composite is no longer "const" - if (typep->type_curr->tt_flags & TTFLAG_CONST) - { - type_T *type = copy_type(typep->type_curr, cctx->ctx_type_list); - - if (type != typep->type_curr) // did get a copy - { - type->tt_flags &= ~(TTFLAG_CONST | TTFLAG_STATIC); - typep->type_curr = type; - } - } - } - else - { - if (typep->type_curr->tt_type == VAR_LIST) - { - typep->type_curr = typep->type_curr->tt_member; - if (typep->type_curr == &t_unknown) - // empty list was used - typep->type_curr = &t_any; - if (typep->type_decl->tt_type == VAR_LIST) - { - typep->type_decl = typep->type_decl->tt_member; - if (typep->type_decl == &t_unknown) - // empty list was used - typep->type_decl = &t_any; - } - else - typep->type_decl = typep->type_curr; - } - 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_CLASS: - case VAR_OBJECT: - case VAR_TYPEALIAS: - 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; -} - -/* - * Returns TRUE if the current function is inside the class "cl" or one of the - * parent classes. - */ - static int -inside_class_hierarchy(cctx_T *cctx_arg, class_T *cl) -{ - for (cctx_T *cctx = cctx_arg; cctx != NULL; cctx = cctx->ctx_outer) - { - if (cctx->ctx_ufunc != NULL && cctx->ctx_ufunc->uf_class != NULL) - { - class_T *clp = cctx->ctx_ufunc->uf_class; - while (clp != NULL) - { - if (clp == cl) - return TRUE; - clp = clp->class_extends; - } - } - } - - return FALSE; -} - -/* - * Compile ".member" coming after an object or class. - */ - static int -compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type) -{ - int m_idx; - int ret = FAIL; - - if (VIM_ISWHITE((*arg)[1])) - { - semsg(_(e_no_white_space_allowed_after_str_str), ".", *arg); - return FAIL; - } - - class_T *cl = type->tt_class; - int is_super = ((type->tt_flags & TTFLAG_SUPER) == TTFLAG_SUPER); - if (type == &t_super) - { - if (cctx->ctx_ufunc == NULL || cctx->ctx_ufunc->uf_class == NULL) - { - emsg(_(e_using_super_not_in_class_method)); - return FAIL; - } - is_super = TRUE; - cl = cctx->ctx_ufunc->uf_class; - // Remove &t_super from the stack. - --cctx->ctx_type_stack.ga_len; - } - else if (type->tt_type == VAR_CLASS) - { - garray_T *instr = &cctx->ctx_instr; - if (instr->ga_len > 0) - { - isn_T *isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1; - if (isn->isn_type == ISN_LOADSCRIPT) - { - // The class was recognized as a script item. We only need - // to know what class it is, drop the instruction. - --instr->ga_len; - vim_free(isn->isn_arg.script.scriptref); - } - } - } - - if (cl == NULL) - { - // TODO: this should not give an error but be handled at runtime - emsg(_(e_incomplete_type)); - return FAIL; - } - - ++*arg; - char_u *name = *arg; - char_u *name_end = find_name_end(name, NULL, NULL, FNE_CHECK_START); - if (name_end == name) - { - emsg(_(e_missing_name_after_dot)); - return FAIL; - } - size_t len = name_end - name; - - gfargs_tab_T gfatab; - - generic_func_args_table_init(&gfatab); - - if (*name_end == '<') - { - // generic method call - name_end = parse_generic_func_type_args(name, len, name_end, - &gfatab, cctx); - if (name_end == NULL) - return FAIL; - } - - if (*name_end == '(') - { - int function_count; - int child_count; - ufunc_T **functions; - - if (type->tt_type == VAR_CLASS) - { - function_count = cl->class_class_function_count; - child_count = cl->class_class_function_count_child; - functions = cl->class_class_functions; - } - else - { - // type->tt_type == VAR_OBJECT: method call - // When compiling Func and doing "super.SomeFunc()", must be in the - // class context that defines Func. - if (is_super) - cl = cctx->ctx_ufunc->uf_defclass; - - function_count = cl->class_obj_method_count; - child_count = cl->class_obj_method_count_child; - functions = cl->class_obj_methods; - } - - ufunc_T *ufunc = NULL; - int fi; - for (fi = is_super ? child_count : 0; fi < function_count; ++fi) - { - ufunc_T *fp = functions[fi]; - // Use a separate pointer to avoid that ASAN complains about - // uf_name[] only being 4 characters. - char_u *ufname = (char_u *)fp->uf_name; - if (STRNCMP(name, ufname, len) == 0 && ufname[len] == NUL) - { - ufunc = fp; - break; - } - } - - ocmember_T *ocm = NULL; - if (ufunc == NULL) - { - // could be a funcref in a member variable - ocm = member_lookup(cl, type->tt_type, name, len, &m_idx); - if (ocm == NULL || ocm->ocm_type->tt_type != VAR_FUNC) - { - method_not_found_msg(cl, type->tt_type, name, len); - goto done; - } - if (type->tt_type == VAR_CLASS) - { - // Remove the class type from the stack - --cctx->ctx_type_stack.ga_len; - if (generate_CLASSMEMBER(cctx, TRUE, cl, m_idx) == FAIL) - goto done; - } - else - { - int status; - - if (IS_INTERFACE(cl)) - status = generate_GET_ITF_MEMBER(cctx, cl, m_idx, - ocm->ocm_type); - else - status = generate_GET_OBJ_MEMBER(cctx, m_idx, - ocm->ocm_type); - if (status == FAIL) - goto done; - } - } - - if (is_super && ufunc != NULL && IS_ABSTRACT_METHOD(ufunc)) - { - // Trying to invoke an abstract method in a super class is not - // allowed. - semsg(_(e_abstract_method_str_direct), ufunc->uf_name, - ufunc->uf_defclass->class_name.string); - goto done; - } - - // A private object method can be used only inside the class where it - // is defined or in one of the child classes. - // A private class method can be used only in the class where it is - // defined. - if (ocm == NULL && *ufunc->uf_name == '_' && - ((type->tt_type == VAR_OBJECT - && !inside_class_hierarchy(cctx, cl)) - || (type->tt_type == VAR_CLASS - && cctx->ctx_ufunc->uf_defclass != cl))) - { - semsg(_(e_cannot_access_protected_method_str), name); - goto done; - } - - // process generic function call - if (ufunc != NULL) - { - ufunc = generic_func_get(ufunc, &gfatab); - if (ufunc == NULL) - goto done; - } - - // Compile the arguments and call the class function or object method. - // The object method will know that the object is on the stack, just - // before the arguments. - *arg = skipwhite(name_end + 1); - int argcount = 0; - if (compile_arguments(arg, cctx, &argcount, CA_NOT_SPECIAL) == FAIL) - goto done; - - if (ocm != NULL) - { - ret = generate_PCALL(cctx, argcount, name, ocm->ocm_type, TRUE); - goto done; - } - if (type->tt_type == VAR_OBJECT - && (cl->class_flags & (CLASS_INTERFACE | CLASS_EXTENDED))) - ret = generate_CALL(cctx, ufunc, cl, fi, argcount, is_super); - else - ret = generate_CALL(cctx, ufunc, NULL, 0, argcount, FALSE); - goto done; - } - - if (type->tt_type == VAR_OBJECT) - { - ocmember_T *m = object_member_lookup(cl, name, len, &m_idx); - if (m_idx >= 0) - { - if (*name == '_' && !inside_class(cctx, cl)) - { - emsg_var_cl_define(e_cannot_access_protected_variable_str, - m->ocm_name.string, 0, cl); - goto done; - } - - *arg = name_end; - if (cl->class_flags & (CLASS_INTERFACE | CLASS_EXTENDED)) - ret = generate_GET_ITF_MEMBER(cctx, cl, m_idx, m->ocm_type); - else - ret = generate_GET_OBJ_MEMBER(cctx, m_idx, m->ocm_type); - goto done; - } - - // Could be an object method reference: "obj.Func". - m_idx = object_method_idx(cl, name, len); - if (m_idx >= 0) - { - ufunc_T *fp = cl->class_obj_methods[m_idx]; - // Private object methods are not accessible outside the class - if (*name == '_' && !inside_class(cctx, cl)) - { - semsg(_(e_cannot_access_protected_method_str), fp->uf_name); - goto done; - } - *arg = name_end; - // Remove the object type from the stack - --cctx->ctx_type_stack.ga_len; - ret = generate_FUNCREF(cctx, fp, cl, TRUE, m_idx, NULL); - goto done; - } - - member_not_found_msg(cl, VAR_OBJECT, name, len); - } - else - { - // load class member - int idx; - ocmember_T *m = class_member_lookup(cl, name, len, &idx); - if (m != NULL) - { - // Note: type->tt_type = VAR_CLASS - // A private class variable can be accessed only in the class where - // it is defined. - if (*name == '_' && cctx->ctx_ufunc->uf_class != cl) - { - emsg_var_cl_define(e_cannot_access_protected_variable_str, - m->ocm_name.string, 0, cl); - goto done; - } - - *arg = name_end; - // Remove the class type from the stack - --cctx->ctx_type_stack.ga_len; - ret = generate_CLASSMEMBER(cctx, TRUE, cl, idx); - goto done; - } - - // Could be a class method reference: "class.Func". - m_idx = class_method_idx(cl, name, len); - if (m_idx >= 0) - { - ufunc_T *fp = cl->class_class_functions[m_idx]; - // Private class methods are not accessible outside the class - if (*name == '_' && !inside_class(cctx, cl)) - { - semsg(_(e_cannot_access_protected_method_str), fp->uf_name); - goto done; - } - *arg = name_end; - // Remove the class type from the stack - --cctx->ctx_type_stack.ga_len; - ret = generate_FUNCREF(cctx, fp, cl, FALSE, m_idx, NULL); - goto done; - } - - member_not_found_msg(cl, VAR_CLASS, name, len); - } - -done: - generic_func_args_table_clear(&gfatab); - return ret; -} - -/* - * 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, may be NULL - imported_T *import) // found imported item, can be NULL -{ - scriptitem_T *si; - int idx; - imported_T *imp; - - 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, NULL); - 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; - } - - if (end == NULL) - imp = NULL; - else if (import == NULL) - imp = find_imported(name, 0, FALSE); - else - imp = import; - - if (imp != NULL) - { - char_u *p = skipwhite(*end); - char_u *exp_name; - int cc; - ufunc_T *ufunc = NULL; - type_T *type; - int done = FALSE; - int res = OK; - - check_script_symlink(imp->imp_sid); - import_check_sourced_sid(&imp->imp_sid); - - // 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; - - si = SCRIPT_ITEM(imp->imp_sid); - if (si->sn_import_autoload && si->sn_state == SN_STATE_NOT_LOADED) - // "import autoload './dir/script.vim'" or - // "import autoload './autoload/script.vim'" - load script first - res = generate_SOURCE(cctx, imp->imp_sid); - - if (res == OK) - { - if (si->sn_autoload_prefix != NULL - && si->sn_state == SN_STATE_NOT_LOADED) - { - char_u *auto_name = - concat_str(si->sn_autoload_prefix, exp_name); - - // autoload script must be loaded later, access by the autoload - // name. If a '(' follows it must be a function. Otherwise we - // don't know, it can be "script.Func". - if (cc == '(' || paren_follows_after_expr) - res = generate_PUSHFUNC(cctx, auto_name, &t_func_any, TRUE); - else - res = generate_AUTOLOAD(cctx, auto_name, &t_any); - vim_free(auto_name); - done = TRUE; - } - else if (si->sn_import_autoload - && si->sn_state == SN_STATE_NOT_LOADED) - { - // If a '(' follows it must be a function. Otherwise we don't - // know, it can be "script.Func". - if (cc == '(' || paren_follows_after_expr) - { - char_u sid_name[MAX_FUNC_NAME_LEN]; - - func_name_with_sid(exp_name, imp->imp_sid, sid_name); - res = generate_PUSHFUNC(cctx, sid_name, &t_func_any, TRUE); - } - else - res = generate_OLDSCRIPT(cctx, ISN_LOADEXPORT, exp_name, - imp->imp_sid, &t_any); - done = TRUE; - } - else - { - idx = find_exported(imp->imp_sid, exp_name, &ufunc, &type, - cctx, NULL, TRUE); - } - } - - *p = cc; - *end = p; - if (done) - return res; - - if (idx < 0) - { - if (ufunc != NULL) - { - gfargs_tab_T gfatab; - - generic_func_args_table_init(&gfatab); - - if (IS_GENERIC_FUNC(ufunc)) - { - if (*p == '<') - { - // generic function call - p = parse_generic_func_type_args(name, STRLEN(name), p, - &gfatab, cctx); - if (p != NULL) - { - *end = p; - - // generic function call - ufunc = generic_func_get(ufunc, &gfatab); - } - - generic_func_args_table_clear(&gfatab); - - if (p == NULL || ufunc == NULL) - return FAIL; - } - else - { - emsg_funcname(e_generic_func_missing_type_args_str, - name); - return FAIL; - } - } - else if (*p == '<') - { - emsg_funcname(e_not_a_generic_function_str, name); - return FAIL; - } - - // function call or function reference - generate_PUSHFUNC(cctx, ufunc->uf_name, NULL, TRUE); - return OK; - } - return FAIL; - } - - generate_VIM9SCRIPT(cctx, ISN_LOADSCRIPT, - imp->imp_sid, - idx, - type); - return OK; - } - - // Can only get here if we know "name" is a script variable and not in a - // Vim9 script (variable is not in sn_var_vals): old style script. - return generate_OLDSCRIPT(cctx, ISN_LOADS, name, current_sctx.sc_sid, - &t_any); -} - - static int -generate_funcref( - cctx_T *cctx, - char_u *name, - gfargs_tab_T *gfatab, - int has_g_prefix) -{ - ufunc_T *ufunc = find_func(name, FALSE); - compiletype_T compile_type; - - // Reject a global non-autoload function found without the "g:" prefix. - if (ufunc == NULL || (!has_g_prefix && func_requires_g_prefix(ufunc))) - return FAIL; - - // process generic function call - ufunc = generic_func_get(ufunc, gfatab); - if (ufunc == NULL) - return FAIL; - - // Need to compile any default values to get the argument types. - compile_type = get_compile_type(ufunc); - if (func_needs_compiling(ufunc, compile_type) - && compile_def_function(ufunc, TRUE, compile_type, NULL) == FAIL) - return FAIL; - return generate_PUSHFUNC(cctx, ufunc->uf_name, ufunc->uf_func_type, TRUE); -} - -/* - * Returns TRUE if compiling a class method. - */ - static int -compiling_a_class_method(cctx_T *cctx) -{ - // For an object method, the FC_OBJECT flag will be set. - // For a constructor method, the FC_NEW flag will be set. - // Excluding these methods, the others are class methods. - // When compiling a closure function inside an object method, - // cctx->ctx_outer->ctx_func will point to the object method. - return cctx->ctx_ufunc != NULL - && (cctx->ctx_ufunc->uf_flags & (FC_OBJECT|FC_NEW)) == 0 - && (cctx->ctx_outer == NULL - || cctx->ctx_outer->ctx_ufunc == NULL - || cctx->ctx_outer->ctx_ufunc->uf_class == NULL - || (cctx->ctx_outer->ctx_ufunc->uf_flags - & (FC_OBJECT|FC_NEW)) == 0); -} - -/* - * 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, - size_t namelen, - 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; - gfargs_tab_T gfatab; - - generic_func_args_table_init(&gfatab); - - if (*(*arg + namelen) == '<') - { - // generic function call - if (parse_generic_func_type_args(*arg, namelen, - *arg + namelen, &gfatab, cctx) == NULL) - return FAIL; - *(*arg + namelen) = NUL; - } - - 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) - goto theend; - - switch (**arg) - { - case 'v': res = generate_LOADV(cctx, name); - break; - case 's': if (current_script_is_vim9()) - { - semsg(_(e_cannot_use_s_colon_in_vim9_script_str), - *arg); - goto theend; - } - if (is_expr && find_func(name, FALSE) != NULL) - res = generate_funcref(cctx, name, &gfatab, FALSE); - else - res = compile_load_scriptvar(cctx, name, - NULL, &end, NULL); - break; - case 'g': if (vim_strchr(name, AUTOLOAD_CHAR) == NULL) - { - if (is_expr && ASCII_ISUPPER(*name) - && (find_func(name, FALSE) != NULL - || gfatab.gfat_args.ga_len > 0)) - res = generate_funcref(cctx, name, &gfatab, - TRUE); - else - isn_type = ISN_LOADG; - } - else - { - isn_type = ISN_LOADAUTO; - vim_free(name); - name = vim_strnsave(*arg, end - *arg); - if (name == NULL) - goto theend; - } - 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 - { - int idx; - int method_idx; - int gen_load = FALSE; - int gen_load_outer = 0; - int outer_loop_depth = -1; - int outer_loop_idx = -1; - - name = vim_strnsave(*arg, namelen); - if (name == NULL) - goto theend; - - if (STRCMP(name, "super") == 0 && compiling_a_class_method(cctx)) - { - // super.SomeFunc() in a class function: push &t_super type, this - // is recognized in compile_subscript(). - res = push_type_stack(cctx, &t_super); - if (*end != '.') - emsg(_(e_super_must_be_followed_by_dot)); - } - else 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, namelen, &idx, &type, &gen_load_outer, cctx) - == OK) - { - if (gen_load_outer == 0) - gen_load = TRUE; - } - else - { - lvar_T lvar; - class_T *cl = NULL; - - if (lookup_local(*arg, namelen, &lvar, cctx) == OK) - { - type = lvar.lv_type; - idx = lvar.lv_idx; - if (lvar.lv_from_outer != 0) - { - gen_load_outer = lvar.lv_from_outer; - outer_loop_depth = lvar.lv_loop_depth; - outer_loop_idx = lvar.lv_loop_idx; - } - else - gen_load = TRUE; - } - else if (cctx->ctx_ufunc->uf_defclass != NULL && - (((idx = - cctx_class_member_idx(cctx, *arg, namelen, &cl)) >= 0) - || ((method_idx = - cctx_class_method_idx(cctx, *arg, namelen, &cl)) >= 0))) - { - // Referencing a class variable or method without the class - // name. A class variable or method can be referenced without - // the class name only in the class where the function is - // defined. - if (cctx->ctx_ufunc->uf_defclass == cl) - { - if (idx >= 0) - res = generate_CLASSMEMBER(cctx, TRUE, cl, idx); - else - { - ufunc_T *fp = cl->class_class_functions[method_idx]; - res = generate_FUNCREF(cctx, fp, cl, FALSE, method_idx, - NULL); - } - } - else - { - semsg(_(e_class_variable_str_accessible_only_inside_class_str), - name, cl->class_name.string); - res = FAIL; - } - } - else - { - imported_T *imp = NULL; - - // "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, namelen, cctx, NULL) == OK - || (imp = find_imported(name, 0, FALSE)) != NULL) - res = compile_load_scriptvar(cctx, name, *arg, &end, imp); - - // 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, &gfatab, FALSE); - } - } - 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, - outer_loop_depth, outer_loop_idx, 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); - generic_func_args_table_clear(&gfatab); - return res; -} - -/* - * Compile a string in a ISN_PUSHS instruction into an ISN_INSTR. - * "str_offset" is the number of leading bytes to skip from the string. - * Returns FAIL if compilation fails. - */ - static int -compile_string(isn_T *isn, cctx_T *cctx, int str_offset) -{ - char_u *s = isn->isn_arg.string + str_offset; - 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; - - // avoid peeking a next line - int galen_save = cctx->ctx_ufunc->uf_lines.ga_len; - cctx->ctx_ufunc->uf_lines.ga_len = 0; - - expr_res = compile_expr0(&s, cctx); - - cctx->ctx_ufunc->uf_lines.ga_len = galen_save; - - 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_characters_str), 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 ")" - */ - int -compile_arguments( - char_u **arg, - cctx_T *cctx, - int *argcount, - ca_special_T special_fn) -{ - 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 (special_fn == CA_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 != NULL - && *isn->isn_arg.string != NUL) - compile_string(isn, cctx, 0); - } - else if (special_fn == CA_SUBSTITUTE && *argcount == 3 - && cctx->ctx_instr.ga_len == instr_count + 1) - { - isn_T *isn = ((isn_T *)cctx->ctx_instr.ga_data) + instr_count; - - // {sub} argument of substitute() can be compiled if it starts - // with \= - if (isn->isn_type == ISN_PUSHS - && isn->isn_arg.string != NULL - && isn->isn_arg.string[0] == '\\' - && isn->isn_arg.string[1] == '=') - compile_string(isn, cctx, 2); - } - - if (*p != ',' && *skipwhite(p) == ',') - { - semsg(_(e_no_white_space_allowed_before_str_str), ",", p); - return FAIL; - } - if (*p == ',') - { - ++p; - if (*p != NUL && !VIM_ISWHITE(*p)) - { - semsg(_(e_white_space_required_after_str_str), ",", p - 1); - return FAIL; - } - } - else - must_end = TRUE; - whitep = p; - p = skipwhite(p); - } -failret: - emsg(_(e_missing_closing_paren)); - return FAIL; -} - -/* - * Compile a builtin method call of an object (e.g. string(), len(), empty(), - * etc.) if the class implements it. - */ - static int -compile_builtin_method_call(cctx_T *cctx, class_builtin_T builtin_method) -{ - type_T *type = get_decl_type_on_stack(cctx, 0); - int res = FAIL; - - // If the built in function is invoked on an object and the class - // implements the corresponding built in method, then invoke the object - // method. - if (type->tt_type == VAR_OBJECT) - { - int method_idx; - ufunc_T *uf = class_get_builtin_method(type->tt_class, builtin_method, - &method_idx); - if (uf != NULL) - res = generate_CALL(cctx, uf, type->tt_class, method_idx, 0, FALSE); - } - - return res; -} - - -/* - * 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[MAX_FUNC_NAME_LEN]; - char_u fname_buf[FLEN_FIXED + 1]; - char_u *tofree = NULL; - ufunc_T *ufunc = NULL; - int res = FAIL; - int is_autoload; - int has_g_namespace; - ca_special_T special_fn; - imported_T *import; - - if (varlen >= sizeof(namebuf)) - { - semsg(_(e_name_too_long_str), name); - return FAIL; - } - vim_strncpy(namebuf, *arg, varlen); - - import = find_imported(name, varlen, FALSE); - if (import != NULL) - { - semsg(_(e_cannot_use_str_itself_it_is_imported), namebuf); - return FAIL; - } - - // We can evaluate "has('name')" at compile time. - // We can evaluate "len('string')" at compile time. - // We always evaluate "exists_compiled()" at compile time. - if ((varlen == 3 - && (STRNCMP(*arg, "has", 3) == 0 || STRNCMP(*arg, "len", 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'; - int is_len = **arg == 'l'; - - argvars[0].v_type = VAR_UNKNOWN; - if (*s == '"') - (void)eval_string(&s, &argvars[0], TRUE, FALSE); - else if (*s == '\'') - (void)eval_lit_string(&s, &argvars[0], TRUE, FALSE); - s = skipwhite(s); - if (*s == ')' && argvars[0].v_type == VAR_STRING - && ((is_has && !dynamic_feature(argvars[0].vval.v_string)) - || !is_has)) - { - - *arg = s + 1; - - if (cctx->ctx_skip != SKIP_YES) - { - typval_T *tv = &ppconst->pp_tv[ppconst->pp_used]; - - argvars[1].v_type = VAR_UNKNOWN; - tv->v_type = VAR_NUMBER; - tv->vval.v_number = 0; - if (is_has) - f_has(argvars, tv); - else if (is_len) - f_len(argvars, tv); - else - f_exists(argvars, tv); - ++ppconst->pp_used; - } - clear_tv(&argvars[0]); - return OK; - } - clear_tv(&argvars[0]); - if (!is_has && !is_len) - { - emsg(_(e_argument_of_exists_compiled_must_be_literal_string)); - return FAIL; - } - } - - if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - - funcerror_T error; - name = fname_trans_sid(namebuf, fname_buf, &tofree, &error); - - // We handle the "skip" argument of searchpair() and searchpairpos() - // differently. - if ((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)) - special_fn = CA_SEARCHPAIR; - else if (varlen == 10 && STRNCMP(*arg, "substitute", 10) == 0) - special_fn = CA_SUBSTITUTE; - else - special_fn = CA_NOT_SPECIAL; - - gfargs_tab_T gfatab; - - generic_func_args_table_init(&gfatab); - - if (*(*arg + varlen) == '<') - { - // generic function - *arg = parse_generic_func_type_args(*arg, varlen, *arg + varlen, - &gfatab, cctx); - if (*arg == NULL) - goto theend; - ++*arg; // skip '(' - } - else - *arg += varlen + 1; - - if (compile_arguments(arg, cctx, &argcount, special_fn) == 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) - { - type_T *type = get_decl_type_on_stack(cctx, 1); - if (check_type_is_value(get_type_on_stack(cctx, 0)) == FAIL) - goto theend; - - // 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 ((STRCMP(name, "writefile") == 0 && argcount > 2) - || (STRCMP(name, "mkdir") == 0 && argcount > 1)) - { - // May have the "D" or "R" flag, reserve a variable for a - // deferred function call. - if (get_defer_var_idx(cctx) == 0) - idx = -1; - } - - class_builtin_T builtin_method = CLASS_BUILTIN_INVALID; - if (STRCMP(name, "string") == 0) - builtin_method = CLASS_BUILTIN_STRING; - else if (STRCMP(name, "empty") == 0) - builtin_method = CLASS_BUILTIN_EMPTY; - else if (STRCMP(name, "len") == 0) - builtin_method = CLASS_BUILTIN_LEN; - if (builtin_method != CLASS_BUILTIN_INVALID) - { - res = compile_builtin_method_call(cctx, builtin_method); - if (res == OK) - idx = -1; - } - - if (idx >= 0) - res = generate_BCALL(cctx, idx, argcount, argcount_init == 1); - } - else - emsg_funcname(e_unknown_function_str, namebuf); - goto theend; - } - - has_g_namespace = STRNCMP(namebuf, "g:", 2) == 0; - - // 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) - { - class_T *cl = NULL; - int mi = 0; - - // 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); - if (ufunc != NULL) - { - // process generic function call - ufunc = generic_func_get(ufunc, &gfatab); - if (ufunc == NULL) - goto theend; - - if (!func_is_global(ufunc)) - { - res = generate_CALL(cctx, ufunc, NULL, 0, argcount, FALSE); - goto theend; - } - if (!has_g_namespace - && vim_strchr(ufunc->uf_name, AUTOLOAD_CHAR) == NULL - && !IS_GENERIC_FUNC(ufunc)) - { - // A function name without g: prefix must be found locally - // A generic function has the name emptied out. - emsg_funcname(e_unknown_function_str, namebuf); - goto theend; - } - } - else if ((mi = cctx_class_method_idx(cctx, name, varlen, &cl)) >= 0) - { - // Class method invocation without the class name. - // A class method can be referenced without the class name only in - // the class where the function is defined. - if (cctx->ctx_ufunc->uf_defclass == cl) - { - res = generate_CALL(cctx, cl->class_class_functions[mi], NULL, - 0, argcount, FALSE); - } - else - { - semsg(_(e_class_method_str_accessible_only_inside_class_str), - name, cl->class_name.string); - res = FAIL; - } - 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 some#Func(), it will be loaded later. - p = namebuf; - if (!has_g_namespace && !is_autoload - && compile_load(&p, varlen, namebuf + varlen, cctx, FALSE, FALSE) == OK) - { - type_T *s_type = get_type_on_stack(cctx, 0); - - res = generate_PCALL(cctx, argcount, namebuf, s_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, NULL, 0, argcount, FALSE); - goto theend; - } - - // A global function may be defined only later. Need to figure out at - // runtime. Also handles a FuncRef at runtime. - if (has_g_namespace || is_autoload) - res = generate_UCALL(cctx, name, argcount); - else - emsg_funcname(e_unknown_function_str, namebuf); - -theend: - generic_func_args_table_clear(&gfatab); - 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 - int must_end = FALSE; - - for (;;) - { - if (may_get_next_line(whitep, &p, cctx) == FAIL) - { - semsg(_(e_missing_end_of_list_rsb_str), *arg); - return FAIL; - } - if (*p == ',') - { - semsg(_(e_no_white_space_allowed_before_str_str), ",", p); - return FAIL; - } - if (*p == ']') - { - ++p; - break; - } - if (must_end) - { - semsg(_(e_missing_comma_in_list_str), p); - return FAIL; - } - 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; - } - } - else - must_end = TRUE; - whitep = p; - p = skipwhite(p); - } - *arg = p; - - ppconst->pp_is_const = is_all_const; - return generate_NEWLIST(cctx, count, FALSE); -} - -/* - * parse a tuple: (expr, expr) - * "*arg" points to the ','. - * ppconst->pp_is_const is set if all the items are constants. - */ - static int -compile_tuple( - char_u **arg, - cctx_T *cctx, - ppconst_T *ppconst, - int first_item_const) -{ - char_u *p = *arg + 1; - char_u *whitep = *arg + 1; - int count = 0; - int is_const; - int is_all_const = TRUE; // reset when non-const encountered - int must_end = FALSE; - - if (**arg != ')') - { - if (*p != ')' && !IS_WHITE_OR_NUL(*p)) - { - semsg(_(e_white_space_required_after_str_str), ",", p - 1); - return FAIL; - } - count = 1; // the first tuple item is already processed - is_all_const = first_item_const; - for (;;) - { - if (may_get_next_line(whitep, &p, cctx) == FAIL) - { - semsg(_(e_missing_end_of_tuple_rsp_str), *arg); - return FAIL; - } - if (*p == ',') - { - semsg(_(e_no_white_space_allowed_before_str_str), ",", p); - return FAIL; - } - if (*p == ')') - { - ++p; - break; - } - if (must_end) - { - semsg(_(e_missing_comma_in_tuple_str), p); - return FAIL; - } - 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; - } - } - else - must_end = TRUE; - whitep = p; - p = skipwhite(p); - } - } - *arg = p; - - ppconst->pp_is_const = is_all_const; - return generate_NEWTUPLE(cctx, count, FALSE); -} - -/* - * 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. - */ - 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, cctx); - 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); - - if (cctx->ctx_ufunc != NULL) - // This lambda might be defined in a class method. Inherit the class - // from the current function. - ufunc->uf_defclass = cctx->ctx_ufunc->uf_defclass; - - // 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); - - // if the outer function is not compiled for debugging or profiling, this - // one might be - if (cctx->ctx_compile_type == CT_NONE) - { - compiletype_T compile_type = get_compile_type(ufunc); - - if (compile_type != CT_NONE) - compile_def_function(ufunc, FALSE, compile_type, 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; - evalarg.eval_using_cmdline = FALSE; - } - - 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, NULL, FALSE, 0, NULL); - } - - 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, NULL); - current_sctx.sc_version = save_sc_version; - if (r != OK) - return r; // currently unreachable - - // "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) - { - dict_unref(d); - 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, TOSTRING_NONE, 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_in_dictionary_str), 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_colon_in_dictionary_str), *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_comma_in_dictionary_str), *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, FALSE); - -failret: - if (*arg == NULL) - { - semsg(_(e_missing_dict_end_str), _("[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); - 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 $"string" or $'string'. - */ - static int -compile_interp_string(char_u **arg, cctx_T *cctx) -{ - typval_T tv; - int ret; - int quote; - int evaluate = cctx->ctx_skip != SKIP_YES; - int count = 0; - char_u *p; - - // *arg is on the '$' character, move it to the first string character. - ++*arg; - quote = **arg; - ++*arg; - - for (;;) - { - // Get the string up to the matching quote or to a single '{'. - // "arg" is advanced to either the quote or the '{'. - if (quote == '"') - ret = eval_string(arg, &tv, evaluate, TRUE); - else - ret = eval_lit_string(arg, &tv, evaluate, TRUE); - if (ret == FAIL) - break; - if (evaluate) - { - if ((tv.vval.v_string != NULL && *tv.vval.v_string != NUL) - || (**arg != '{' && count == 0)) - { - // generate non-empty string or empty string if it's the only - // one - if (generate_PUSHS(cctx, &tv.vval.v_string) == FAIL) - return FAIL; - tv.vval.v_string = NULL; // don't free it now - ++count; - } - clear_tv(&tv); - } - - if (**arg != '{') - { - // found terminating quote - ++*arg; - break; - } - - p = compile_one_expr_in_str(*arg, cctx); - if (p == NULL) - { - ret = FAIL; - break; - } - ++count; - *arg = p; - } - - if (ret == FAIL || !evaluate) - return ret; - - // Small optimization, if there's only a single piece skip the ISN_CONCAT. - if (count > 1) - return generate_CONCAT(cctx, count); - - return OK; -} - -/* - * 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)) - { - emsg_invreg(**arg); - return FAIL; - } - ret = generate_LOAD(cctx, ISN_LOADREG, **arg, NULL, &t_string); - ++*arg; - return ret; -} - -/* - * Apply leading '!', '-' and '+' to constant "rettv". - * When "numeric_only" is TRUE do not apply '!'. - */ - static int -apply_leader(typval_T *rettv, int numeric_only, char_u *start, char_u **end) -{ - char_u *p = *end; - - // this works from end to start - while (p > start) - { - --p; - if (*p == '-' || *p == '+') - { - // only '-' has an effect, for '+' we only check the type - if (rettv->v_type == VAR_FLOAT) - { - if (*p == '-') - rettv->vval.v_float = -rettv->vval.v_float; - } - else - { - varnumber_T val; - int error = FALSE; - - // tv_get_number_chk() accepts a string, but we don't want that - // here - if (check_not_string(rettv) == FAIL) - return FAIL; - val = tv_get_number_chk(rettv, &error); - clear_tv(rettv); - if (error) - return FAIL; - if (*p == '-') - val = -val; - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = val; - } - } - else if (numeric_only) - { - ++p; - break; - } - else if (*p == '!') - { - int v = tv2bool(rettv); - - // '!' is permissive in the type. - clear_tv(rettv); - rettv->v_type = VAR_BOOL; - rettv->vval.v_number = v ? VVAL_FALSE : VVAL_TRUE; - } - } - *end = p; - return OK; -} - -/* - * Recognize v: variables that are constants and set "rettv". - */ - static void -get_vim_constant(char_u **arg, typval_T *rettv) -{ - if (STRNCMP(*arg, "v:true", 6) == 0) - { - rettv->v_type = VAR_BOOL; - rettv->vval.v_number = VVAL_TRUE; - *arg += 6; - } - else if (STRNCMP(*arg, "v:false", 7) == 0) - { - rettv->v_type = VAR_BOOL; - rettv->vval.v_number = VVAL_FALSE; - *arg += 7; - } - else if (STRNCMP(*arg, "v:null", 6) == 0) - { - rettv->v_type = VAR_SPECIAL; - rettv->vval.v_number = VVAL_NULL; - *arg += 6; - } - else if (STRNCMP(*arg, "v:none", 6) == 0) - { - rettv->v_type = VAR_SPECIAL; - rettv->vval.v_number = VVAL_NONE; - *arg += 6; - } -} - - exprtype_T -get_compare_type(char_u *p, int *len, int *type_is) -{ - exprtype_T type = EXPR_UNKNOWN; - int i; - - switch (p[0]) - { - case '=': if (p[1] == '=') - type = EXPR_EQUAL; - else if (p[1] == '~') - type = EXPR_MATCH; - break; - case '!': if (p[1] == '=') - type = EXPR_NEQUAL; - else if (p[1] == '~') - type = EXPR_NOMATCH; - break; - case '>': if (p[1] != '=') - { - type = EXPR_GREATER; - *len = 1; - } - else - type = EXPR_GEQUAL; - break; - case '<': if (p[1] != '=') - { - type = EXPR_SMALLER; - *len = 1; - } - else - type = EXPR_SEQUAL; - break; - case 'i': if (p[1] == 's') - { - // "is" and "isnot"; but not a prefix of a name - if (p[2] == 'n' && p[3] == 'o' && p[4] == 't') - *len = 5; - i = p[*len]; - if (!SAFE_isalnum(i) && i != '_') - { - type = *len == 2 ? EXPR_IS : EXPR_ISNOT; - *type_is = TRUE; - } - } - break; - } - return type; -} - -/* - * Skip over an expression, ignoring most errors. - */ - void -skip_expr_cctx(char_u **arg, cctx_T *cctx) -{ - evalarg_T evalarg; - - init_evalarg(&evalarg); - evalarg.eval_cctx = cctx; - skip_expr(arg, &evalarg); - clear_evalarg(&evalarg, NULL); -} - -/* - * Check that the top of the type stack has a type that can be used as a - * condition. Give an error and return FAIL if not. - */ - int -bool_on_stack(cctx_T *cctx) -{ - type_T *type; - - type = get_type_on_stack(cctx, 0); - if (type == &t_bool) - return OK; - - if (type->tt_type == VAR_ANY - || type->tt_type == VAR_UNKNOWN - || type->tt_type == VAR_NUMBER - || type == &t_number_bool - || type == &t_const_number_bool) - // Number 0 and 1 are OK to use as a bool. "any" could also be a bool. - // This requires a runtime type check. - return generate_COND2BOOL(cctx); - - return need_type(type, &t_bool, 0, -1, 0, cctx, FALSE, FALSE); -} - -/* - * Give the "white on both sides" error, taking the operator from "p[len]". - */ - void -error_white_both(char_u *op, int len) -{ - char_u buf[10]; - - vim_strncpy(buf, op, len); - semsg(_(e_white_space_required_before_and_after_str_at_str), buf, op); -} - -/* - * Compile code to apply '-', '+' and '!'. - * When "numeric_only" is TRUE do not apply '!'. - */ - static int -compile_leader(cctx_T *cctx, int numeric_only, char_u *start, char_u **end) -{ - char_u *p = *end; - - // this works from end to start - while (p > start) - { - --p; - while (VIM_ISWHITE(*p)) - --p; - if (*p == '-' || *p == '+') - { - type_T *type = get_type_on_stack(cctx, 0); - if (type->tt_type != VAR_FLOAT && need_type(type, &t_number, - 0, -1, 0, cctx, FALSE, FALSE) == FAIL) - return FAIL; - - // only '-' has an effect, for '+' we only check the type - if (*p == '-' && generate_instr(cctx, ISN_NEGATENR) == NULL) - return FAIL; - } - else if (numeric_only) - { - ++p; - break; - } - else - { - int invert = *p == '!'; - - while (p > start && (p[-1] == '!' || VIM_ISWHITE(p[-1]))) - { - if (p[-1] == '!') - invert = !invert; - --p; - } - if (check_type_is_value(get_type_on_stack(cctx, 0)) == FAIL) - return FAIL; - if (generate_2BOOL(cctx, invert, -1) == FAIL) - return FAIL; - } - } - *end = p; - return OK; -} - -/* - * Compile "(expression)": recursive! - * Return FAIL/OK. - */ - static int -compile_parenthesis(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) -{ - int ret; - char_u *p = *arg + 1; - - if (may_get_next_line_error(p, arg, cctx) == FAIL) - return FAIL; - - if (**arg == ')') - // empty tuple - return compile_tuple(arg, cctx, ppconst, FALSE); - - if (ppconst->pp_used <= PPSIZE - 10) - { - ret = compile_expr1(arg, cctx, ppconst); - } - else - { - // Not enough space in ppconst, flush constants. - if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - ret = compile_expr0(arg, cctx); - } - if (may_get_next_line_error(*arg, arg, cctx) == FAIL) - return FAIL; - if (ret == OK && **arg == ',') - { - // tuple - int is_const = ppconst->pp_used > 0 || ppconst->pp_is_const; - if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - return compile_tuple(arg, cctx, ppconst, is_const); - } - - if (**arg == ')') - ++*arg; - else if (ret == OK) - { - emsg(_(e_missing_closing_paren)); - ret = FAIL; - } - return ret; -} - -static int compile_expr9(char_u **arg, cctx_T *cctx, ppconst_T *ppconst); - -/* - * Compile whatever comes after "name" or "name()". - * Advances "*arg" only when something was recognized. - */ - static int -compile_subscript( - char_u **arg, - cctx_T *cctx, - char_u *start_leader, - char_u **end_leader, - ppconst_T *ppconst) -{ - char_u *name_start = *end_leader; - int keeping_dict = FALSE; - - for (;;) - { - char_u *p = skipwhite(*arg); - type_T *type; - - if (*p == NUL || (VIM_ISWHITE(**arg) && vim9_comment_start(p))) - { - char_u *next = peek_next_line_from_context(cctx); - - // If a following line starts with "->{", "->(" or "->X" advance to - // that line, so that a line break before "->" is allowed. - // Also if a following line starts with ".x". - if (next != NULL && - ((next[0] == '-' && next[1] == '>' - && (next[2] == '{' - || next[2] == '(' - || ASCII_ISALPHA(*skipwhite(next + 2)))) - || (next[0] == '.' && eval_isdictc(next[1])))) - { - next = next_line_from_context(cctx, TRUE); - if (next == NULL) - return FAIL; - *arg = next; - p = skipwhite(*arg); - } - } - - // Do not skip over white space to find the "(", "execute 'x' (expr)" - // is not a function call. - if (**arg == '(') - { - int argcount = 0; - - if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - ppconst->pp_is_const = FALSE; - - // funcref(arg) - type = get_type_on_stack(cctx, 0); - - *arg = skipwhite(p + 1); - if (compile_arguments(arg, cctx, &argcount, CA_NOT_SPECIAL) == FAIL) - return FAIL; - if (generate_PCALL(cctx, argcount, name_start, type, TRUE) == FAIL) - return FAIL; - if (keeping_dict) - { - keeping_dict = FALSE; - if (generate_instr(cctx, ISN_CLEARDICT) == NULL) - return FAIL; - } - } - else if (*p == '-' && p[1] == '>') - { - char_u *pstart = p; - int alt; - char_u *paren; - - // something->method() - if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - ppconst->pp_is_const = FALSE; - - // Apply the '!', '-' and '+' first: - // -1.0->func() works like (-1.0)->func() - if (compile_leader(cctx, TRUE, start_leader, end_leader) == FAIL) - return FAIL; - - p += 2; - *arg = skipwhite(p); - // No line break supported right after "->". - - // Three alternatives handled here: - // 1. "base->name(" only a name, use compile_call() - // 2. "base->(expr)(" evaluate "expr", then use PCALL - // 3. "base->expr(" Same, find the end of "expr" by "(" - if (**arg == '(') - alt = 2; - else - { - // alternative 1 or 3 - p = *arg; - if (!eval_isnamec1(*p)) - { - semsg(_(e_trailing_characters_str), pstart); - return FAIL; - } - if (ASCII_ISALPHA(*p) && p[1] == ':') - p += 2; - for ( ; eval_isnamec(*p); ++p) - ; - if (*p == '(') - { - // alternative 1 - alt = 1; - if (compile_call(arg, p - *arg, cctx, ppconst, 1) == FAIL) - return FAIL; - } - else - { - // Must be alternative 3, find the "(". Only works within - // one line. - alt = 3; - paren = vim_strchr(p, '('); - if (paren == NULL) - { - semsg(_(e_missing_parenthesis_str), *arg); - return FAIL; - } - } - } - - if (alt != 1) - { - int argcount = 1; - garray_T *stack = &cctx->ctx_type_stack; - int type_idx_start = stack->ga_len; - int expr_isn_start = cctx->ctx_instr.ga_len; - int expr_isn_end; - int arg_isn_count; - - if (alt == 2) - { - // Funcref call: list->(Refs[2])(arg) - // or lambda: list->((arg) => expr)(arg) - // - // Fist compile the function expression. - if (compile_parenthesis(arg, cctx, ppconst) == FAIL) - return FAIL; - } - else - { - int fail; - int save_len = cctx->ctx_ufunc->uf_lines.ga_len; - int prev_did_emsg = did_emsg; - - *paren = NUL; - - // instead of using LOADG for "import.Func" use PUSHFUNC - ++paren_follows_after_expr; - - // do not look in the next line - cctx->ctx_ufunc->uf_lines.ga_len = 1; - - fail = compile_expr9(arg, cctx, ppconst) == FAIL - || *skipwhite(*arg) != NUL; - *paren = '('; - --paren_follows_after_expr; - cctx->ctx_ufunc->uf_lines.ga_len = save_len; - - if (fail) - { - if (did_emsg == prev_did_emsg) - semsg(_(e_invalid_expression_str), pstart); - return FAIL; - } - } - - // Compile the arguments. - if (**arg != '(') - { - if (*skipwhite(*arg) == '(') - emsg(_(e_no_white_space_allowed_before_parenthesis)); - else - semsg(_(e_missing_parenthesis_str), *arg); - return FAIL; - } - - // Remember the next instruction index, where the instructions - // for arguments are being written. - expr_isn_end = cctx->ctx_instr.ga_len; - - *arg = skipwhite(*arg + 1); - if (compile_arguments(arg, cctx, &argcount, CA_NOT_SPECIAL) - == FAIL) - return FAIL; - - // Move the instructions for the arguments to before the - // instructions of the expression and move the type of the - // expression after the argument types. This is what ISN_PCALL - // expects. - arg_isn_count = cctx->ctx_instr.ga_len - expr_isn_end; - if (arg_isn_count > 0) - { - int expr_isn_count = expr_isn_end - expr_isn_start; - isn_T *isn = ALLOC_MULT(isn_T, expr_isn_count); - type_T *decl_type; - type2_T *typep; - - if (isn == NULL) - return FAIL; - mch_memmove(isn, ((isn_T *)cctx->ctx_instr.ga_data) - + expr_isn_start, - sizeof(isn_T) * expr_isn_count); - mch_memmove(((isn_T *)cctx->ctx_instr.ga_data) - + expr_isn_start, - ((isn_T *)cctx->ctx_instr.ga_data) + expr_isn_end, - sizeof(isn_T) * arg_isn_count); - mch_memmove(((isn_T *)cctx->ctx_instr.ga_data) - + expr_isn_start + arg_isn_count, - isn, sizeof(isn_T) * expr_isn_count); - vim_free(isn); - - typep = ((type2_T *)stack->ga_data) + type_idx_start; - type = typep->type_curr; - decl_type = typep->type_decl; - mch_memmove(((type2_T *)stack->ga_data) + type_idx_start, - ((type2_T *)stack->ga_data) + type_idx_start + 1, - sizeof(type2_T) - * (stack->ga_len - type_idx_start - 1)); - typep = ((type2_T *)stack->ga_data) + stack->ga_len - 1; - typep->type_curr = type; - typep->type_decl = decl_type; - } - - type = get_type_on_stack(cctx, 0); - if (generate_PCALL(cctx, argcount, p - 2, type, FALSE) == FAIL) - return FAIL; - } - - if (keeping_dict) - { - keeping_dict = FALSE; - if (generate_instr(cctx, ISN_CLEARDICT) == NULL) - return FAIL; - } - } - else if (**arg == '[') - { - int is_slice = FALSE; - - // list index: list[123] - // tuple index: tuple[123] - // dict member: dict[key] - // string index: text[123] - // blob index: blob[123] - if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - ppconst->pp_is_const = FALSE; - - ++p; - if (may_get_next_line_error(p, arg, cctx) == FAIL) - return FAIL; - if (**arg == ':') - { - // missing first index is equal to zero - generate_PUSHNR(cctx, 0); - } - else - { - if (compile_expr0(arg, cctx) == FAIL) - return FAIL; - if (**arg == ':') - { - semsg(_(e_white_space_required_before_and_after_str_at_str), - ":", *arg); - return FAIL; - } - if (may_get_next_line_error(*arg, arg, cctx) == FAIL) - return FAIL; - *arg = skipwhite(*arg); - } - if (**arg == ':') - { - is_slice = TRUE; - ++*arg; - if (!IS_WHITE_OR_NUL(**arg) && **arg != ']') - { - semsg(_(e_white_space_required_before_and_after_str_at_str), - ":", *arg); - return FAIL; - } - if (may_get_next_line_error(*arg, arg, cctx) == FAIL) - return FAIL; - if (**arg == ']') - // missing second index is equal to end of string - generate_PUSHNR(cctx, -1); - else - { - if (compile_expr0(arg, cctx) == FAIL) - return FAIL; - if (may_get_next_line_error(*arg, arg, cctx) == FAIL) - return FAIL; - *arg = skipwhite(*arg); - } - } - - if (**arg != ']') - { - emsg(_(e_missing_closing_square_brace)); - return FAIL; - } - *arg = *arg + 1; - - if (keeping_dict) - { - keeping_dict = FALSE; - if (generate_instr(cctx, ISN_CLEARDICT) == NULL) - return FAIL; - } - if (cctx->ctx_skip != SKIP_YES - && compile_member(is_slice, &keeping_dict, cctx) == FAIL) - return FAIL; - } - else if (*p == '.' && p[1] != '.') - { - // dictionary member: dict.name - if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - ppconst->pp_is_const = FALSE; - - type = get_type_on_stack(cctx, 0); - if (type != &t_unknown - && (type->tt_type == VAR_CLASS - || (type->tt_type == VAR_OBJECT - && type != &t_object_any))) - { - // class member: SomeClass.varname - // class method: SomeClass.SomeMethod() - // class constructor: SomeClass.new() - // object member: someObject.varname, this.varname - // object method: someObject.SomeMethod(), this.SomeMethod() - *arg = p; - if (compile_class_object_index(cctx, arg, type) == FAIL) - return FAIL; - } - else - { - *arg = p + 1; - if (IS_WHITE_OR_NUL(**arg)) - { - emsg(_(e_missing_name_after_dot)); - return FAIL; - } - p = *arg; - if (eval_isdictc(*p)) - while (eval_isnamec(*p)) - MB_PTR_ADV(p); - if (p == *arg) - { - semsg(_(e_syntax_error_at_str), *arg); - return FAIL; - } - if (keeping_dict && generate_instr(cctx, ISN_CLEARDICT) == NULL) - return FAIL; - if (generate_STRINGMEMBER(cctx, *arg, p - *arg) == FAIL) - return FAIL; - keeping_dict = TRUE; - *arg = p; - } - } - else - break; - } - - // Turn "dict.Func" into a partial for "Func" bound to "dict". - // This needs to be done at runtime to be able to check the type. - if (keeping_dict && cctx->ctx_skip != SKIP_YES - && generate_instr(cctx, ISN_USEDICT) == NULL) - return FAIL; - - return OK; -} - -/* - * Compile an expression at "*arg" and add instructions to "cctx->ctx_instr". - * "arg" is advanced until after the expression, skipping white space. - * - * If the value is a constant "ppconst->pp_used" will be non-zero. - * Before instructions are generated, any values in "ppconst" will generated. - * - * This is the compiling equivalent of eval1(), eval2(), etc. - */ - -/* - * number number constant - * 0zFFFFFFFF Blob constant - * "string" string constant - * 'string' literal string constant - * &option-name option value - * @r register contents - * identifier variable value - * function() function call - * $VAR environment variable - * (expression) nested expression - * [expr, expr] List - * {key: val, [key]: val} Dictionary - * - * Also handle: - * ! in front logical NOT - * - in front unary minus - * + in front unary plus (ignored) - * trailing (arg) funcref/partial call - * trailing [] subscript in String or List - * trailing .name entry in Dictionary - * trailing ->name() method call - */ - static int -compile_expr9( - char_u **arg, - cctx_T *cctx, - ppconst_T *ppconst) -{ - char_u *start_leader, *end_leader; - int ret = OK; - typval_T *rettv = &ppconst->pp_tv[ppconst->pp_used]; - int used_before = ppconst->pp_used; - - ppconst->pp_is_const = FALSE; - - /* - * Skip '!', '-' and '+' characters. They are handled later. - */ - start_leader = *arg; - if (eval_leader(arg, TRUE) == FAIL) - return FAIL; - end_leader = *arg; - - rettv->v_type = VAR_UNKNOWN; - switch (**arg) - { - /* - * Number constant. - */ - case '0': // also for blob starting with 0z - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '.': if (eval_number(arg, rettv, TRUE, FALSE) == FAIL) - return FAIL; - // Apply "-" and "+" just before the number now, right to - // left. Matters especially when "->" follows. Stops at - // '!'. - if (apply_leader(rettv, TRUE, - start_leader, &end_leader) == FAIL) - { - clear_tv(rettv); - return FAIL; - } - break; - - /* - * String constant: "string". - */ - case '"': if (eval_string(arg, rettv, TRUE, FALSE) == FAIL) - return FAIL; - break; - - /* - * Literal string constant: 'str''ing'. - */ - case '\'': if (eval_lit_string(arg, rettv, TRUE, FALSE) == FAIL) - return FAIL; - break; - - /* - * Constant Vim variable. - */ - case 'v': get_vim_constant(arg, rettv); - ret = NOTDONE; - break; - - /* - * "true" constant - */ - case 't': if (STRNCMP(*arg, "true", 4) == 0 - && !eval_isnamec((*arg)[4])) - { - *arg += 4; - rettv->v_type = VAR_BOOL; - rettv->vval.v_number = VVAL_TRUE; - } - else - ret = NOTDONE; - break; - - /* - * "false" constant - */ - case 'f': if (STRNCMP(*arg, "false", 5) == 0 - && !eval_isnamec((*arg)[5])) - { - *arg += 5; - rettv->v_type = VAR_BOOL; - rettv->vval.v_number = VVAL_FALSE; - } - else - ret = NOTDONE; - break; - - /* - * "null" or "null_*" constant - */ - case 'n': if (STRNCMP(*arg, "null", 4) == 0) - { - char_u *p = *arg + 4; - int len; - - for (len = 0; eval_isnamec(p[len]); ++len) - ; - ret = handle_predefined(*arg, len + 4, rettv); - if (ret == FAIL) - ret = NOTDONE; - else - *arg += len + 4; - } - else - ret = NOTDONE; - break; - - /* - * List: [expr, expr] - */ - case '[': if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - ret = compile_list(arg, cctx, ppconst); - break; - - /* - * Dictionary: {'key': val, 'key': val} - */ - case '{': if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - ret = compile_dict(arg, cctx, ppconst); - break; - - /* - * Option value: &name - */ - case '&': if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - ret = compile_get_option(arg, cctx); - break; - - /* - * Environment variable: $VAR. - * Interpolated string: $"string" or $'string'. - */ - case '$': if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - if ((*arg)[1] == '"' || (*arg)[1] == '\'') - ret = compile_interp_string(arg, cctx); - else - ret = compile_get_env(arg, cctx); - break; - - /* - * Register contents: @r. - */ - case '@': if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - ret = compile_get_register(arg, cctx); - break; - /* - * nested expression: (expression). - * lambda: (arg, arg) => expr - * funcref: (arg, arg) => { statement } - */ - case '(': // if compile_lambda returns NOTDONE then it must be (expr) - ret = compile_lambda(arg, cctx); - if (ret == NOTDONE) - ret = compile_parenthesis(arg, cctx, ppconst); - break; - - default: ret = NOTDONE; - break; - } - if (ret == FAIL) - return FAIL; - - if (rettv->v_type != VAR_UNKNOWN && used_before == ppconst->pp_used) - { - if (cctx->ctx_skip == SKIP_YES) - clear_tv(rettv); - else - // A constant expression can possibly be handled compile time, - // return the value instead of generating code. - ++ppconst->pp_used; - } - else if (ret == NOTDONE) - { - char_u *p; - int r; - - if (!eval_isnamec1(**arg)) - { - if (!vim9_bad_comment(*arg)) - { - if (ends_excmd(*skipwhite(*arg))) - semsg(_(e_empty_expression_str), *arg); - else - semsg(_(e_name_expected_str), *arg); - } - return FAIL; - } - - // "name" or "name()" - p = to_name_end(*arg, TRUE); - if (p - *arg == (size_t)1 && **arg == '_') - { - emsg(_(e_cannot_use_underscore_here)); - return FAIL; - } - - size_t namelen = p - *arg; - if (*p == '<') - { - if (skip_generic_func_type_args(&p) == FAIL) - return FAIL; - } - - if (*p == '(') - { - r = compile_call(arg, namelen, cctx, ppconst, 0); - } - else - { - if (cctx->ctx_skip != SKIP_YES - && generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - r = compile_load(arg, namelen, p, cctx, TRUE, TRUE); - } - if (r == FAIL) - return FAIL; - } - - // Handle following "[]", ".member", etc. - // Then deal with prefixed '-', '+' and '!', if not done already. - if (compile_subscript(arg, cctx, start_leader, &end_leader, - ppconst) == FAIL) - return FAIL; - if ((ppconst->pp_used > 0) && (cctx->ctx_skip != SKIP_YES)) - { - // apply the '!', '-' and '+' before the constant - rettv = &ppconst->pp_tv[ppconst->pp_used - 1]; - if (apply_leader(rettv, FALSE, start_leader, &end_leader) == FAIL) - return FAIL; - return OK; - } - if (compile_leader(cctx, FALSE, start_leader, &end_leader) == FAIL) - return FAIL; - return OK; -} - -/* - * <type>expr9: runtime type check / conversion - */ - static int -compile_expr8(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) -{ - type_T *want_type = NULL; - - // Recognize <type> - if (**arg == '<' && eval_isnamec1((*arg)[1])) - { - ++*arg; - want_type = parse_type(arg, cctx->ctx_type_list, cctx->ctx_ufunc, cctx, TRUE); - if (want_type == NULL) - return FAIL; - - if (**arg != '>') - { - if (*skipwhite(*arg) == '>') - semsg(_(e_no_white_space_allowed_before_str_str), ">", *arg); - else - emsg(_(e_missing_gt)); - return FAIL; - } - ++*arg; - if (may_get_next_line_error(*arg, arg, cctx) == FAIL) - return FAIL; - } - - if (compile_expr9(arg, cctx, ppconst) == FAIL) - return FAIL; - - if (want_type != NULL) - { - type_T *actual; - where_T where = WHERE_INIT; - - where.wt_kind = WT_CAST; - generate_ppconst(cctx, ppconst); - actual = get_type_on_stack(cctx, 0); - if (check_type_maybe(want_type, actual, FALSE, where) != OK) - { - if (need_type_where(actual, want_type, 0, -1, where, cctx, FALSE, - FALSE) == FAIL) - return FAIL; - } - } - - return OK; -} - -/* - * * number multiplication - * / number division - * % number modulo - */ - static int -compile_expr7(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) -{ - char_u *op; - char_u *next; - int ppconst_used = ppconst->pp_used; - - // get the first expression - if (compile_expr8(arg, cctx, ppconst) == FAIL) - return FAIL; - - /* - * Repeat computing, until no "*", "/" or "%" is following. - */ - for (;;) - { - op = may_peek_next_line(cctx, *arg, &next); - if (*op != '*' && *op != '/' && *op != '%') - break; - if (next != NULL) - { - *arg = next_line_from_context(cctx, TRUE); - op = skipwhite(*arg); - } - - if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(op[1])) - { - error_white_both(op, 1); - return FAIL; - } - if (may_get_next_line_error(op + 1, arg, cctx) == FAIL) - return FAIL; - - // get the second expression - if (compile_expr8(arg, cctx, ppconst) == FAIL) - return FAIL; - - if (ppconst->pp_used == ppconst_used + 2 - && ppconst->pp_tv[ppconst_used].v_type == VAR_NUMBER - && ppconst->pp_tv[ppconst_used + 1].v_type == VAR_NUMBER) - { - typval_T *tv1 = &ppconst->pp_tv[ppconst_used]; - typval_T *tv2 = &ppconst->pp_tv[ppconst_used + 1]; - varnumber_T res = 0; - int failed = FALSE; - - // both are numbers: compute the result - switch (*op) - { - case '*': res = tv1->vval.v_number * tv2->vval.v_number; - break; - case '/': res = num_divide(tv1->vval.v_number, - tv2->vval.v_number, &failed); - break; - case '%': res = num_modulus(tv1->vval.v_number, - tv2->vval.v_number, &failed); - break; - } - if (failed) - return FAIL; - tv1->vval.v_number = res; - --ppconst->pp_used; - } - else - { - generate_ppconst(cctx, ppconst); - generate_two_op(cctx, op); - } - } - - return OK; -} - -/* - * + number addition or list/blobl concatenation - * - number subtraction - * .. string concatenation - */ - static int -compile_expr6(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) -{ - char_u *op; - char_u *next; - int oplen; - int ppconst_used = ppconst->pp_used; - - // get the first variable - if (compile_expr7(arg, cctx, ppconst) == FAIL) - return FAIL; - - /* - * Repeat computing, until no "+", "-" or ".." is following. - */ - for (;;) - { - op = may_peek_next_line(cctx, *arg, &next); - if (*op != '+' && *op != '-' && !(*op == '.' && *(op + 1) == '.')) - break; - if (op[0] == op[1] && *op != '.' && next) - // Finding "++" or "--" on the next line is a separate command. - // But ".." is concatenation. - break; - oplen = (*op == '.' ? 2 : 1); - if (next != NULL) - { - *arg = next_line_from_context(cctx, TRUE); - op = skipwhite(*arg); - } - - if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(op[oplen])) - { - error_white_both(op, oplen); - return FAIL; - } - - if (may_get_next_line_error(op + oplen, arg, cctx) == FAIL) - return FAIL; - - // get the second expression - if (compile_expr7(arg, cctx, ppconst) == FAIL) - return FAIL; - - if (ppconst->pp_used == ppconst_used + 2 - && (*op == '.' - ? (ppconst->pp_tv[ppconst_used].v_type == VAR_STRING - && ppconst->pp_tv[ppconst_used + 1].v_type == VAR_STRING) - : (ppconst->pp_tv[ppconst_used].v_type == VAR_NUMBER - && ppconst->pp_tv[ppconst_used + 1].v_type == VAR_NUMBER))) - { - typval_T *tv1 = &ppconst->pp_tv[ppconst_used]; - typval_T *tv2 = &ppconst->pp_tv[ppconst_used + 1]; - - // concat/subtract/add constant numbers - if (*op == '+') - tv1->vval.v_number = tv1->vval.v_number + tv2->vval.v_number; - else if (*op == '-') - tv1->vval.v_number = tv1->vval.v_number - tv2->vval.v_number; - else - { - // concatenate constant strings - char_u *s1 = tv1->vval.v_string; - char_u *s2 = tv2->vval.v_string; - size_t len1 = STRLEN(s1); - - tv1->vval.v_string = alloc((int)(len1 + STRLEN(s2) + 1)); - if (tv1->vval.v_string == NULL) - { - clear_ppconst(ppconst); - return FAIL; - } - mch_memmove(tv1->vval.v_string, s1, len1); - STRCPY(tv1->vval.v_string + len1, s2); - vim_free(s1); - vim_free(s2); - } - --ppconst->pp_used; - } - else - { - generate_ppconst(cctx, ppconst); - ppconst->pp_is_const = FALSE; - if (*op == '.') - { - if (may_generate_2STRING(-2, TOSTRING_NONE, cctx) == FAIL - || may_generate_2STRING(-1, TOSTRING_NONE, cctx) == FAIL) - return FAIL; - if (generate_CONCAT(cctx, 2) == FAIL) - return FAIL; - } - else - generate_two_op(cctx, op); - } - } - - return OK; -} - -/* - * expr6a >> expr6b - * expr6a << expr6b - * - * Produces instructions: - * OPNR bitwise left or right shift - */ - static int -compile_expr5(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) -{ - exprtype_T type = EXPR_UNKNOWN; - char_u *p; - char_u *next; - int len = 2; - int ppconst_used = ppconst->pp_used; - isn_T *isn; - - // get the first variable - if (compile_expr6(arg, cctx, ppconst) == FAIL) - return FAIL; - - /* - * Repeat computing, until no "+", "-" or ".." is following. - */ - for (;;) - { - type = EXPR_UNKNOWN; - - p = may_peek_next_line(cctx, *arg, &next); - if (p[0] == '<' && p[1] == '<') - type = EXPR_LSHIFT; - else if (p[0] == '>' && p[1] == '>') - type = EXPR_RSHIFT; - - if (type == EXPR_UNKNOWN) - return OK; - - // Handle a bitwise left or right shift operator - if (ppconst->pp_used == ppconst_used + 1) - { - if (ppconst->pp_tv[ppconst->pp_used - 1].v_type != VAR_NUMBER) - { - // left operand should be a number - emsg(_(e_bitshift_ops_must_be_number)); - return FAIL; - } - } - else - { - type_T *t = get_type_on_stack(cctx, 0); - - if (need_type(t, &t_number, 0, 0, 0, cctx, FALSE, FALSE) == FAIL) - { - emsg(_(e_bitshift_ops_must_be_number)); - return FAIL; - } - } - - if (next != NULL) - { - *arg = next_line_from_context(cctx, TRUE); - p = skipwhite(*arg); - } - - if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[len])) - { - error_white_both(p, len); - return FAIL; - } - - // get the second variable - if (may_get_next_line_error(p + len, arg, cctx) == FAIL) - return FAIL; - - if (compile_expr6(arg, cctx, ppconst) == FAIL) - return FAIL; - - if (ppconst->pp_used == ppconst_used + 2) - { - typval_T *tv1 = &ppconst->pp_tv[ppconst->pp_used - 2]; - typval_T *tv2 = &ppconst->pp_tv[ppconst->pp_used - 1]; - - // Both sides are a constant, compute the result now. - if (tv2->v_type != VAR_NUMBER || tv2->vval.v_number < 0) - { - // right operand should be a positive number - if (tv2->v_type != VAR_NUMBER) - emsg(_(e_bitshift_ops_must_be_number)); - else - emsg(_(e_bitshift_ops_must_be_positive)); - return FAIL; - } - - if (tv2->vval.v_number > MAX_LSHIFT_BITS) - tv1->vval.v_number = 0; - else if (type == EXPR_LSHIFT) - tv1->vval.v_number = - (uvarnumber_T)tv1->vval.v_number << tv2->vval.v_number; - else - tv1->vval.v_number = - (uvarnumber_T)tv1->vval.v_number >> tv2->vval.v_number; - clear_tv(tv2); - --ppconst->pp_used; - } - else - { - if (need_type(get_type_on_stack(cctx, 0), &t_number, 0, - 0, 0, cctx, FALSE, FALSE) == FAIL) - { - emsg(_(e_bitshift_ops_must_be_number)); - return FAIL; - } - - generate_ppconst(cctx, ppconst); - - isn = generate_instr_drop(cctx, ISN_OPNR, 1); - if (isn == NULL) - return FAIL; - - if (isn != NULL) - isn->isn_arg.op.op_type = type; - } - } - - return OK; -} - -/* - * expr5a == expr5b - * expr5a =~ expr5b - * expr5a != expr5b - * expr5a !~ expr5b - * expr5a > expr5b - * expr5a >= expr5b - * expr5a < expr5b - * expr5a <= expr5b - * expr5a is expr5b - * expr5a isnot expr5b - * - * Produces instructions: - * EVAL expr5a Push result of "expr5a" - * EVAL expr5b Push result of "expr5b" - * COMPARE one of the compare instructions - */ - static int -compile_expr4(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) -{ - exprtype_T type = EXPR_UNKNOWN; - char_u *p; - char_u *next; - int len = 2; - int type_is = FALSE; - int ppconst_used = ppconst->pp_used; - - // get the first variable - if (compile_expr5(arg, cctx, ppconst) == FAIL) - return FAIL; - - p = may_peek_next_line(cctx, *arg, &next); - - type = get_compare_type(p, &len, &type_is); - - /* - * If there is a comparative operator, use it. - */ - if (type != EXPR_UNKNOWN) - { - int ic = FALSE; // Default: do not ignore case - - if (next != NULL) - { - *arg = next_line_from_context(cctx, TRUE); - p = skipwhite(*arg); - } - if (type_is && (p[len] == '?' || p[len] == '#')) - { - semsg(_(e_invalid_expression_str), *arg); - return FAIL; - } - // extra question mark appended: ignore case - if (p[len] == '?') - { - ic = TRUE; - ++len; - } - // extra '#' appended: match case (ignored) - else if (p[len] == '#') - ++len; - // nothing appended: match case - - if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[len])) - { - error_white_both(p, len); - return FAIL; - } - - // get the second variable - if (may_get_next_line_error(p + len, arg, cctx) == FAIL) - return FAIL; - - if (compile_expr5(arg, cctx, ppconst) == FAIL) - return FAIL; - - if (ppconst->pp_used == ppconst_used + 2) - { - typval_T *tv1 = &ppconst->pp_tv[ppconst->pp_used - 2]; - typval_T *tv2 = &ppconst->pp_tv[ppconst->pp_used - 1]; - int ret; - - // Both sides are a constant, compute the result now. - // First check for a valid combination of types, this is more - // strict than typval_compare(). - if (check_compare_types(type, tv1, tv2) == FAIL) - ret = FAIL; - else - { - ret = typval_compare(tv1, tv2, type, ic); - tv1->v_type = VAR_BOOL; - tv1->vval.v_number = tv1->vval.v_number - ? VVAL_TRUE : VVAL_FALSE; - clear_tv(tv2); - --ppconst->pp_used; - } - return ret; - } - - generate_ppconst(cctx, ppconst); - return generate_COMPARE(cctx, type, ic); - } - - return OK; -} - -static int compile_expr3(char_u **arg, cctx_T *cctx, ppconst_T *ppconst); - -/* - * Compile || or &&. - */ - static int -compile_and_or( - char_u **arg, - cctx_T *cctx, - char *op, - ppconst_T *ppconst, - int ppconst_used UNUSED) -{ - char_u *next; - char_u *p = may_peek_next_line(cctx, *arg, &next); - int opchar = *op; - - if (p[0] == opchar && p[1] == opchar) - { - garray_T *instr = &cctx->ctx_instr; - garray_T end_ga; - int save_skip = cctx->ctx_skip; - - /* - * Repeat until there is no following "||" or "&&" - */ - ga_init2(&end_ga, sizeof(int), 10); - while (p[0] == opchar && p[1] == opchar) - { - long start_lnum = SOURCING_LNUM; - long save_sourcing_lnum; - int start_ctx_lnum = cctx->ctx_lnum; - int save_lnum; - int const_used; - int status; - jumpwhen_T jump_when = opchar == '|' - ? JUMP_IF_COND_TRUE : JUMP_IF_COND_FALSE; - - if (next != NULL) - { - *arg = next_line_from_context(cctx, TRUE); - p = skipwhite(*arg); - } - - if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[2])) - { - semsg(_(e_white_space_required_before_and_after_str_at_str), - op, p); - ga_clear(&end_ga); - return FAIL; - } - - save_sourcing_lnum = SOURCING_LNUM; - SOURCING_LNUM = start_lnum; - save_lnum = cctx->ctx_lnum; - cctx->ctx_lnum = start_ctx_lnum; - - status = check_ppconst_bool(ppconst); - if (status != FAIL) - { - // Use the last ppconst if possible. - if (ppconst->pp_used > 0) - { - typval_T *tv = &ppconst->pp_tv[ppconst->pp_used - 1]; - int is_true = tv2bool(tv); - - if ((is_true && opchar == '|') - || (!is_true && opchar == '&')) - { - // For "false && expr" and "true || expr" the "expr" - // does not need to be evaluated. - cctx->ctx_skip = SKIP_YES; - clear_tv(tv); - tv->v_type = VAR_BOOL; - tv->vval.v_number = is_true ? VVAL_TRUE : VVAL_FALSE; - } - else - { - // For "true && expr" and "false || expr" only "expr" - // needs to be evaluated. - --ppconst->pp_used; - jump_when = JUMP_NEVER; - } - } - else - { - // Every part must evaluate to a bool. - status = bool_on_stack(cctx); - } - } - if (status != FAIL) - status = ga_grow(&end_ga, 1); - cctx->ctx_lnum = save_lnum; - if (status == FAIL) - { - ga_clear(&end_ga); - return FAIL; - } - - if (jump_when != JUMP_NEVER) - { - if (cctx->ctx_skip != SKIP_YES) - { - *(((int *)end_ga.ga_data) + end_ga.ga_len) = instr->ga_len; - ++end_ga.ga_len; - } - generate_JUMP(cctx, jump_when, 0); - } - - // eval the next expression - SOURCING_LNUM = save_sourcing_lnum; - if (may_get_next_line_error(p + 2, arg, cctx) == FAIL) - { - ga_clear(&end_ga); - return FAIL; - } - - const_used = ppconst->pp_used; - if ((opchar == '|' ? compile_expr3(arg, cctx, ppconst) - : compile_expr4(arg, cctx, ppconst)) == FAIL) - { - ga_clear(&end_ga); - return FAIL; - } - - // "0 || 1" results in true, "1 && 0" results in false. - if (ppconst->pp_used == const_used + 1) - { - typval_T *tv = &ppconst->pp_tv[ppconst->pp_used - 1]; - - if (tv->v_type == VAR_NUMBER - && (tv->vval.v_number == 1 || tv->vval.v_number == 0)) - { - tv->vval.v_number = tv->vval.v_number == 1 - ? VVAL_TRUE : VVAL_FALSE; - tv->v_type = VAR_BOOL; - } - } - - p = may_peek_next_line(cctx, *arg, &next); - } - - if (check_ppconst_bool(ppconst) == FAIL) - { - ga_clear(&end_ga); - return FAIL; - } - - if (cctx->ctx_skip != SKIP_YES && ppconst->pp_used == 0) - // Every part must evaluate to a bool. - if (bool_on_stack(cctx) == FAIL) - { - ga_clear(&end_ga); - return FAIL; - } - - if (end_ga.ga_len > 0) - { - // Fill in the end label in all jumps. - generate_ppconst(cctx, ppconst); - while (end_ga.ga_len > 0) - { - isn_T *isn; - - --end_ga.ga_len; - isn = ((isn_T *)instr->ga_data) - + *(((int *)end_ga.ga_data) + end_ga.ga_len); - isn->isn_arg.jump.jump_where = instr->ga_len; - } - } - ga_clear(&end_ga); - - cctx->ctx_skip = save_skip; - } - - return OK; -} - -/* - * expr4a && expr4a && expr4a logical AND - * - * Produces instructions: - * EVAL expr4a Push result of "expr4a" - * COND2BOOL convert to bool if needed - * JUMP_IF_COND_FALSE end - * EVAL expr4b Push result of "expr4b" - * JUMP_IF_COND_FALSE end - * EVAL expr4c Push result of "expr4c" - * end: - */ - static int -compile_expr3(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) -{ - int ppconst_used = ppconst->pp_used; - - // get the first variable - if (compile_expr4(arg, cctx, ppconst) == FAIL) - return FAIL; - - // || and && work almost the same - return compile_and_or(arg, cctx, "&&", ppconst, ppconst_used); -} - -/* - * expr3a || expr3b || expr3c logical OR - * - * Produces instructions: - * EVAL expr3a Push result of "expr3a" - * COND2BOOL convert to bool if needed - * JUMP_IF_COND_TRUE end - * EVAL expr3b Push result of "expr3b" - * JUMP_IF_COND_TRUE end - * EVAL expr3c Push result of "expr3c" - * end: - */ - static int -compile_expr2(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) -{ - int ppconst_used = ppconst->pp_used; - - // eval the first expression - if (compile_expr3(arg, cctx, ppconst) == FAIL) - return FAIL; - - // || and && work almost the same - return compile_and_or(arg, cctx, "||", ppconst, ppconst_used); -} - -/* - * Toplevel expression: expr2 ? expr1a : expr1b - * Produces instructions: - * EVAL expr2 Push result of "expr2" - * JUMP_IF_FALSE alt jump if false - * EVAL expr1a - * JUMP_ALWAYS end - * alt: EVAL expr1b - * end: - * - * Toplevel expression: expr2 ?? expr1 - * Produces instructions: - * EVAL expr2 Push result of "expr2" - * JUMP_AND_KEEP_IF_TRUE end jump if true - * EVAL expr1 - * end: - */ - int -compile_expr1(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) -{ - char_u *p; - int ppconst_used = ppconst->pp_used; - char_u *next; - - // Ignore all kinds of errors when not producing code. - if (cctx->ctx_skip == SKIP_YES) - { - int prev_did_emsg = did_emsg; - - skip_expr_cctx(arg, cctx); - return did_emsg == prev_did_emsg ? OK : FAIL; - } - - // Evaluate the first expression. - if (compile_expr2(arg, cctx, ppconst) == FAIL) - return FAIL; - - p = may_peek_next_line(cctx, *arg, &next); - if (*p == '?') - { - int op_falsy = p[1] == '?'; - garray_T *instr = &cctx->ctx_instr; - garray_T *stack = &cctx->ctx_type_stack; - int alt_idx = instr->ga_len; - int end_idx = 0; - isn_T *isn; - type_T *type1 = NULL; - int has_const_expr = FALSE; - int const_value = FALSE; - int save_skip = cctx->ctx_skip; - - if (next != NULL) - { - *arg = next_line_from_context(cctx, TRUE); - p = skipwhite(*arg); - } - - if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[1 + op_falsy])) - { - semsg(_(e_white_space_required_before_and_after_str_at_str), - op_falsy ? "??" : "?", p); - return FAIL; - } - - if (ppconst->pp_used == ppconst_used + 1) - { - // the condition is a constant, we know whether the ? or the : - // expression is to be evaluated. - has_const_expr = TRUE; - if (op_falsy) - const_value = tv2bool(&ppconst->pp_tv[ppconst_used]); - else - { - int error = FALSE; - - const_value = tv_get_bool_chk(&ppconst->pp_tv[ppconst_used], - &error); - if (error) - return FAIL; - } - cctx->ctx_skip = save_skip == SKIP_YES || - (op_falsy ? const_value : !const_value) ? SKIP_YES : SKIP_NOT; - - if (op_falsy && cctx->ctx_skip == SKIP_YES) - // "left ?? right" and "left" is truthy: produce "left" - generate_ppconst(cctx, ppconst); - else - { - clear_tv(&ppconst->pp_tv[ppconst_used]); - --ppconst->pp_used; - } - } - else - { - generate_ppconst(cctx, ppconst); - if (op_falsy) - end_idx = instr->ga_len; - generate_JUMP(cctx, op_falsy - ? JUMP_AND_KEEP_IF_TRUE : JUMP_IF_FALSE, 0); - if (op_falsy) - { - type1 = get_type_on_stack(cctx, -1); - if (check_type_is_value(type1) == FAIL) - return FAIL; - } - } - - // evaluate the second expression; any type is accepted - if (may_get_next_line_error(p + 1 + op_falsy, arg, cctx) == FAIL) - return FAIL; - if (compile_expr1(arg, cctx, ppconst) == FAIL) - return FAIL; - - if (!has_const_expr) - { - generate_ppconst(cctx, ppconst); - - if (!op_falsy) - { - // remember the type and drop it - type1 = get_type_on_stack(cctx, 0); - --stack->ga_len; - - end_idx = instr->ga_len; - generate_JUMP(cctx, JUMP_ALWAYS, 0); - - // jump here from JUMP_IF_FALSE - isn = ((isn_T *)instr->ga_data) + alt_idx; - isn->isn_arg.jump.jump_where = instr->ga_len; - } - } - - if (!op_falsy) - { - // Check for the ":". - p = may_peek_next_line(cctx, *arg, &next); - if (*p != ':') - { - emsg(_(e_missing_colon_after_questionmark)); - return FAIL; - } - if (next != NULL) - { - *arg = next_line_from_context(cctx, TRUE); - p = skipwhite(*arg); - } - - if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[1])) - { - semsg(_(e_white_space_required_before_and_after_str_at_str), - ":", p); - return FAIL; - } - - // evaluate the third expression - if (has_const_expr) - cctx->ctx_skip = save_skip == SKIP_YES || const_value - ? SKIP_YES : SKIP_NOT; - if (may_get_next_line_error(p + 1, arg, cctx) == FAIL) - return FAIL; - if (compile_expr1(arg, cctx, ppconst) == FAIL) - return FAIL; - } - - if (!has_const_expr) - { - type_T **typep; - - generate_ppconst(cctx, ppconst); - ppconst->pp_is_const = FALSE; - - // If the types differ, the result has a more generic type. - typep = &((((type2_T *)stack->ga_data) - + stack->ga_len - 1)->type_curr); - common_type(type1, *typep, typep, cctx->ctx_type_list); - - // jump here from JUMP_ALWAYS or JUMP_AND_KEEP_IF_TRUE - isn = ((isn_T *)instr->ga_data) + end_idx; - isn->isn_arg.jump.jump_where = instr->ga_len; - } - - cctx->ctx_skip = save_skip; - } - return OK; -} - -/* - * Toplevel expression. - * Sets "is_const" (if not NULL) to indicate the value is a constant. - * Returns OK or FAIL. - */ - int -compile_expr0_ext(char_u **arg, cctx_T *cctx, int *is_const) -{ - ppconst_T ppconst; - - CLEAR_FIELD(ppconst); - if (compile_expr1(arg, cctx, &ppconst) == FAIL) - { - clear_ppconst(&ppconst); - return FAIL; - } - if (is_const != NULL) - *is_const = ppconst.pp_used > 0 || ppconst.pp_is_const; - if (generate_ppconst(cctx, &ppconst) == FAIL) - return FAIL; - return OK; -} - -/* - * Toplevel expression. - */ - int -compile_expr0(char_u **arg, cctx_T *cctx) -{ - return compile_expr0_ext(arg, cctx, NULL); -} - - -#endif // defined(FEAT_EVAL) |
