diff options
author | Bram Moolenaar <Bram@vim.org> | 2021-12-20 15:04:29 +0000 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2021-12-20 15:04:29 +0000 |
commit | dc7c366f3aae65ee691010b08f37acfb26e0742b (patch) | |
tree | 90d12bd3030048100a9c5084474aef484f6fc58f /src/vim9compile.c | |
parent | a99fb23842f055c511bfe1b62de7bbd14d5a99c0 (diff) | |
download | vim-git-dc7c366f3aae65ee691010b08f37acfb26e0742b.tar.gz |
patch 8.2.3860: Vim9: codecov struggles with the file sizev8.2.3860
Problem: Vim9: codecov struggles with the file size.
Solution: Split vim9compile.c into four files.
Diffstat (limited to 'src/vim9compile.c')
-rw-r--r-- | src/vim9compile.c | 7655 |
1 files changed, 79 insertions, 7576 deletions
diff --git a/src/vim9compile.c b/src/vim9compile.c index 4bce9972d..50fe7e257 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -8,7 +8,7 @@ */ /* - * vim9compile.c: :def and dealing with instructions + * vim9compile.c: compiling a :def function */ #define USING_FLOAT_STUFF @@ -16,194 +16,15 @@ #if defined(FEAT_EVAL) || defined(PROTO) -#ifdef VMS -# include <float.h> +// When not generating protos this is included in proto.h +#ifdef PROTO +# include "vim9.h" #endif -#define DEFINE_VIM9_GLOBALS -#include "vim9.h" - -// values for ctx_skip -typedef enum { - SKIP_NOT, // condition is a constant, produce code - SKIP_YES, // condition is a constant, do NOT produce code - SKIP_UNKNOWN // condition is not a constant, produce code -} skip_T; - -/* - * Chain of jump instructions where the end label needs to be set. - */ -typedef struct endlabel_S endlabel_T; -struct endlabel_S { - endlabel_T *el_next; // chain end_label locations - int el_end_label; // instruction idx where to set end -}; - -/* - * info specific for the scope of :if / elseif / else - */ -typedef struct { - int is_seen_else; - int is_seen_skip_not; // a block was unconditionally executed - int is_had_return; // every block ends in :return - int is_if_label; // instruction idx at IF or ELSEIF - endlabel_T *is_end_label; // instructions to set end label -} ifscope_T; - -/* - * info specific for the scope of :while - */ -typedef struct { - int ws_top_label; // instruction idx at WHILE - endlabel_T *ws_end_label; // instructions to set end -} whilescope_T; - -/* - * info specific for the scope of :for - */ -typedef struct { - int fs_top_label; // instruction idx at FOR - endlabel_T *fs_end_label; // break instructions -} forscope_T; - -/* - * info specific for the scope of :try - */ -typedef struct { - int ts_try_label; // instruction idx at TRY - endlabel_T *ts_end_label; // jump to :finally or :endtry - int ts_catch_label; // instruction idx of last CATCH - int ts_caught_all; // "catch" without argument encountered -} tryscope_T; - -typedef enum { - NO_SCOPE, - IF_SCOPE, - WHILE_SCOPE, - FOR_SCOPE, - TRY_SCOPE, - BLOCK_SCOPE -} scopetype_T; - -/* - * Info for one scope, pointed to by "ctx_scope". - */ -typedef struct scope_S scope_T; -struct scope_S { - scope_T *se_outer; // scope containing this one - scopetype_T se_type; - int se_local_count; // ctx_locals.ga_len before scope - skip_T se_skip_save; // ctx_skip before the block - union { - ifscope_T se_if; - whilescope_T se_while; - forscope_T se_for; - tryscope_T se_try; - } se_u; -}; - -/* - * Entry for "ctx_locals". Used for arguments and local variables. - */ -typedef struct { - char_u *lv_name; - type_T *lv_type; - int lv_idx; // index of the variable on the stack - int lv_from_outer; // nesting level, using ctx_outer scope - int lv_const; // when TRUE cannot be assigned to - int lv_arg; // when TRUE this is an argument -} lvar_T; - -// Destination for an assignment or ":unlet" with an index. -typedef enum { - dest_local, - dest_option, - dest_func_option, - dest_env, - dest_global, - dest_buffer, - dest_window, - dest_tab, - dest_vimvar, - dest_script, - dest_reg, - dest_expr, -} assign_dest_T; - -// Used by compile_lhs() to store information about the LHS of an assignment -// and one argument of ":unlet" with an index. -typedef struct { - assign_dest_T lhs_dest; // type of destination - - char_u *lhs_name; // allocated name excluding the last - // "[expr]" or ".name". - size_t lhs_varlen; // length of the variable without - // "[expr]" or ".name" - char_u *lhs_whole; // allocated name including the last - // "[expr]" or ".name" for :redir - size_t lhs_varlen_total; // length of the variable including - // any "[expr]" or ".name" - char_u *lhs_dest_end; // end of the destination, including - // "[expr]" or ".name". - char_u *lhs_end; // end including any type - - int lhs_has_index; // has "[expr]" or ".name" - - int lhs_new_local; // create new local variable - int lhs_opt_flags; // for when destination is an option - int lhs_vimvaridx; // for when destination is a v:var - - lvar_T lhs_local_lvar; // used for existing local destination - lvar_T lhs_arg_lvar; // used for argument destination - lvar_T *lhs_lvar; // points to destination lvar - int lhs_scriptvar_sid; - int lhs_scriptvar_idx; - - int lhs_has_type; // type was specified - type_T *lhs_type; - type_T *lhs_member_type; - - int lhs_append; // used by ISN_REDIREND -} lhs_T; - -/* - * Context for compiling lines of Vim script. - * Stores info about the local variables and condition stack. - */ -struct cctx_S { - ufunc_T *ctx_ufunc; // current function - int ctx_lnum; // line number in current function - char_u *ctx_line_start; // start of current line or NULL - garray_T ctx_instr; // generated instructions - - int ctx_prev_lnum; // line number below previous command, for - // debugging - - compiletype_T ctx_compile_type; - - garray_T ctx_locals; // currently visible local variables - - int ctx_has_closure; // set to one if a closures was created in - // the function - - garray_T ctx_imports; // imported items - - skip_T ctx_skip; - scope_T *ctx_scope; // current scope, NULL at toplevel - int ctx_had_return; // last seen statement was "return" - - cctx_T *ctx_outer; // outer scope for lambda or nested - // function - int ctx_outer_used; // var in ctx_outer was used - - garray_T ctx_type_stack; // type of each item on the stack - garray_T *ctx_type_list; // list of pointers to allocated types - - int ctx_has_cmdmod; // ISN_CMDMOD was generated - - lhs_T ctx_redir_lhs; // LHS for ":redir => var", valid when - // lhs_name is not NULL -}; +// Functions defined with :def are stored in this growarray. +// They are never removed, so that they can be found by index. +// Deleted functions have the df_deleted flag set. +garray_T def_functions = {0, 0, sizeof(dfunc_T), 50, NULL}; static void delete_def_function_contents(dfunc_T *dfunc, int mark_deleted); @@ -213,7 +34,7 @@ static void delete_def_function_contents(dfunc_T *dfunc, int mark_deleted); * If "lvar" is NULL only check if the variable can be found. * Return FAIL if not found. */ - static int + int lookup_local(char_u *name, size_t len, lvar_T *lvar, cctx_T *cctx) { int idx; @@ -262,7 +83,7 @@ lookup_local(char_u *name, size_t len, lvar_T *lvar, cctx_T *cctx) * Sets "gen_load_outer" to TRUE if found in outer scope. * Returns OK when found, FAIL otherwise. */ - static int + int arg_exists( char_u *name, size_t len, @@ -397,7 +218,7 @@ find_script_var(char_u *name, size_t len, cctx_T *cctx) /* * Return TRUE if the script context is Vim9 script. */ - static int + int script_is_vim9() { return SCRIPT_ITEM(current_sctx.sc_sid)->sn_version == SCRIPT_VERSION_VIM9; @@ -408,7 +229,7 @@ script_is_vim9() * "cctx" is NULL at the script level. * Returns OK or FAIL. */ - static int + int script_var_exists(char_u *name, size_t len, cctx_T *cctx) { if (current_sctx.sc_sid <= 0) @@ -533,493 +354,6 @@ check_defined(char_u *p, size_t len, cctx_T *cctx, int is_arg) } -///////////////////////////////////////////////////////////////////// -// Following generate_ functions expect the caller to call ga_grow(). - -#define RETURN_NULL_IF_SKIP(cctx) if (cctx->ctx_skip == SKIP_YES) return NULL -#define RETURN_OK_IF_SKIP(cctx) if (cctx->ctx_skip == SKIP_YES) return OK - -/* - * Generate an instruction without arguments. - * Returns a pointer to the new instruction, NULL if failed. - */ - static isn_T * -generate_instr(cctx_T *cctx, isntype_T isn_type) -{ - garray_T *instr = &cctx->ctx_instr; - isn_T *isn; - - RETURN_NULL_IF_SKIP(cctx); - if (GA_GROW_FAILS(instr, 1)) - return NULL; - isn = ((isn_T *)instr->ga_data) + instr->ga_len; - isn->isn_type = isn_type; - isn->isn_lnum = cctx->ctx_lnum + 1; - ++instr->ga_len; - - return isn; -} - -/* - * Generate an instruction without arguments. - * "drop" will be removed from the stack. - * Returns a pointer to the new instruction, NULL if failed. - */ - static isn_T * -generate_instr_drop(cctx_T *cctx, isntype_T isn_type, int drop) -{ - garray_T *stack = &cctx->ctx_type_stack; - - RETURN_NULL_IF_SKIP(cctx); - stack->ga_len -= drop; - return generate_instr(cctx, isn_type); -} - -/* - * Generate instruction "isn_type" and put "type" on the type stack. - */ - static isn_T * -generate_instr_type(cctx_T *cctx, isntype_T isn_type, type_T *type) -{ - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - - if ((isn = generate_instr(cctx, isn_type)) == NULL) - return NULL; - - if (GA_GROW_FAILS(stack, 1)) - return NULL; - ((type_T **)stack->ga_data)[stack->ga_len] = type == NULL ? &t_any : type; - ++stack->ga_len; - - return isn; -} - -/* - * Generate an ISN_DEBUG instruction. - */ - static isn_T * -generate_instr_debug(cctx_T *cctx) -{ - isn_T *isn; - dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) - + cctx->ctx_ufunc->uf_dfunc_idx; - - if ((isn = generate_instr(cctx, ISN_DEBUG)) == NULL) - return NULL; - isn->isn_arg.debug.dbg_var_names_len = dfunc->df_var_names.ga_len; - isn->isn_arg.debug.dbg_break_lnum = cctx->ctx_prev_lnum; - return isn; -} - -/* - * If type at "offset" isn't already VAR_STRING then generate ISN_2STRING. - * But only for simple types. - * When "tolerant" is TRUE convert most types to string, e.g. a List. - */ - static int -may_generate_2STRING(int offset, int tolerant, cctx_T *cctx) -{ - isn_T *isn; - isntype_T isntype = ISN_2STRING; - garray_T *stack = &cctx->ctx_type_stack; - type_T **type; - - RETURN_OK_IF_SKIP(cctx); - type = ((type_T **)stack->ga_data) + stack->ga_len + offset; - switch ((*type)->tt_type) - { - // nothing to be done - case VAR_STRING: return OK; - - // conversion possible - case VAR_SPECIAL: - case VAR_BOOL: - case VAR_NUMBER: - case VAR_FLOAT: - break; - - // conversion possible (with runtime check) - case VAR_ANY: - case VAR_UNKNOWN: - isntype = ISN_2STRING_ANY; - break; - - // conversion possible when tolerant - case VAR_LIST: - if (tolerant) - { - isntype = ISN_2STRING_ANY; - break; - } - // FALLTHROUGH - - // conversion not possible - case VAR_VOID: - case VAR_BLOB: - case VAR_FUNC: - case VAR_PARTIAL: - case VAR_DICT: - case VAR_JOB: - case VAR_CHANNEL: - case VAR_INSTR: - to_string_error((*type)->tt_type); - return FAIL; - } - - *type = &t_string; - if ((isn = generate_instr(cctx, isntype)) == NULL) - return FAIL; - isn->isn_arg.tostring.offset = offset; - isn->isn_arg.tostring.tolerant = tolerant; - - return OK; -} - - static int -check_number_or_float(vartype_T type1, vartype_T type2, char_u *op) -{ - if (!((type1 == VAR_NUMBER || type1 == VAR_FLOAT || type1 == VAR_ANY) - && (type2 == VAR_NUMBER || type2 == VAR_FLOAT - || type2 == VAR_ANY))) - { - if (*op == '+') - emsg(_(e_wrong_argument_type_for_plus)); - else - semsg(_(e_char_requires_number_or_float_arguments), *op); - return FAIL; - } - return OK; -} - -/* - * Generate instruction for "+". For a list this creates a new list. - */ - static int -generate_add_instr( - cctx_T *cctx, - vartype_T vartype, - type_T *type1, - type_T *type2, - exprtype_T expr_type) -{ - garray_T *stack = &cctx->ctx_type_stack; - isn_T *isn = generate_instr_drop(cctx, - vartype == VAR_NUMBER ? ISN_OPNR - : vartype == VAR_LIST ? ISN_ADDLIST - : vartype == VAR_BLOB ? ISN_ADDBLOB -#ifdef FEAT_FLOAT - : vartype == VAR_FLOAT ? ISN_OPFLOAT -#endif - : ISN_OPANY, 1); - - if (vartype != VAR_LIST && vartype != VAR_BLOB - && type1->tt_type != VAR_ANY - && type2->tt_type != VAR_ANY - && check_number_or_float( - type1->tt_type, type2->tt_type, (char_u *)"+") == FAIL) - return FAIL; - - if (isn != NULL) - { - if (isn->isn_type == ISN_ADDLIST) - isn->isn_arg.op.op_type = expr_type; - else - isn->isn_arg.op.op_type = EXPR_ADD; - } - - // When concatenating two lists with different member types the member type - // becomes "any". - if (vartype == VAR_LIST - && type1->tt_type == VAR_LIST && type2->tt_type == VAR_LIST - && type1->tt_member != type2->tt_member) - (((type_T **)stack->ga_data)[stack->ga_len - 1]) = &t_list_any; - - return isn == NULL ? FAIL : OK; -} - -/* - * Get the type to use for an instruction for an operation on "type1" and - * "type2". If they are matching use a type-specific instruction. Otherwise - * fall back to runtime type checking. - */ - static vartype_T -operator_type(type_T *type1, type_T *type2) -{ - if (type1->tt_type == type2->tt_type - && (type1->tt_type == VAR_NUMBER - || type1->tt_type == VAR_LIST -#ifdef FEAT_FLOAT - || type1->tt_type == VAR_FLOAT -#endif - || type1->tt_type == VAR_BLOB)) - return type1->tt_type; - return VAR_ANY; -} - -/* - * Generate an instruction with two arguments. The instruction depends on the - * type of the arguments. - */ - static int -generate_two_op(cctx_T *cctx, char_u *op) -{ - garray_T *stack = &cctx->ctx_type_stack; - type_T *type1; - type_T *type2; - vartype_T vartype; - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - - // Get the known type of the two items on the stack. - type1 = ((type_T **)stack->ga_data)[stack->ga_len - 2]; - type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - vartype = operator_type(type1, type2); - - switch (*op) - { - case '+': - if (generate_add_instr(cctx, vartype, type1, type2, - EXPR_COPY) == FAIL) - return FAIL; - break; - - case '-': - case '*': - case '/': if (check_number_or_float(type1->tt_type, type2->tt_type, - op) == FAIL) - return FAIL; - if (vartype == VAR_NUMBER) - isn = generate_instr_drop(cctx, ISN_OPNR, 1); -#ifdef FEAT_FLOAT - else if (vartype == VAR_FLOAT) - isn = generate_instr_drop(cctx, ISN_OPFLOAT, 1); -#endif - else - isn = generate_instr_drop(cctx, ISN_OPANY, 1); - if (isn != NULL) - isn->isn_arg.op.op_type = *op == '*' - ? EXPR_MULT : *op == '/'? EXPR_DIV : EXPR_SUB; - break; - - case '%': if ((type1->tt_type != VAR_ANY - && type1->tt_type != VAR_NUMBER) - || (type2->tt_type != VAR_ANY - && type2->tt_type != VAR_NUMBER)) - { - emsg(_(e_percent_requires_number_arguments)); - return FAIL; - } - isn = generate_instr_drop(cctx, - vartype == VAR_NUMBER ? ISN_OPNR : ISN_OPANY, 1); - if (isn != NULL) - isn->isn_arg.op.op_type = EXPR_REM; - break; - } - - // correct type of result - if (vartype == VAR_ANY) - { - type_T *type = &t_any; - -#ifdef FEAT_FLOAT - // float+number and number+float results in float - if ((type1->tt_type == VAR_NUMBER || type1->tt_type == VAR_FLOAT) - && (type2->tt_type == VAR_NUMBER || type2->tt_type == VAR_FLOAT)) - type = &t_float; -#endif - ((type_T **)stack->ga_data)[stack->ga_len - 1] = type; - } - - return OK; -} - -/* - * Get the instruction to use for comparing "type1" with "type2" - * Return ISN_DROP when failed. - */ - static isntype_T -get_compare_isn(exprtype_T exprtype, vartype_T type1, vartype_T type2) -{ - isntype_T isntype = ISN_DROP; - - if (type1 == VAR_UNKNOWN) - type1 = VAR_ANY; - if (type2 == VAR_UNKNOWN) - type2 = VAR_ANY; - - if (type1 == type2) - { - switch (type1) - { - case VAR_BOOL: isntype = ISN_COMPAREBOOL; break; - case VAR_SPECIAL: isntype = ISN_COMPARESPECIAL; break; - case VAR_NUMBER: isntype = ISN_COMPARENR; break; - case VAR_FLOAT: isntype = ISN_COMPAREFLOAT; break; - case VAR_STRING: isntype = ISN_COMPARESTRING; break; - case VAR_BLOB: isntype = ISN_COMPAREBLOB; break; - case VAR_LIST: isntype = ISN_COMPARELIST; break; - case VAR_DICT: isntype = ISN_COMPAREDICT; break; - case VAR_FUNC: isntype = ISN_COMPAREFUNC; break; - default: isntype = ISN_COMPAREANY; break; - } - } - else if (type1 == VAR_ANY || type2 == VAR_ANY - || ((type1 == VAR_NUMBER || type1 == VAR_FLOAT) - && (type2 == VAR_NUMBER || type2 == VAR_FLOAT))) - isntype = ISN_COMPAREANY; - - if ((exprtype == EXPR_IS || exprtype == EXPR_ISNOT) - && (isntype == ISN_COMPAREBOOL - || isntype == ISN_COMPARESPECIAL - || isntype == ISN_COMPARENR - || isntype == ISN_COMPAREFLOAT)) - { - semsg(_(e_cannot_use_str_with_str), - exprtype == EXPR_IS ? "is" : "isnot" , vartype_name(type1)); - return ISN_DROP; - } - if (isntype == ISN_DROP - || ((exprtype != EXPR_EQUAL && exprtype != EXPR_NEQUAL - && (type1 == VAR_BOOL || type1 == VAR_SPECIAL - || type2 == VAR_BOOL || type2 == VAR_SPECIAL))) - || ((exprtype != EXPR_EQUAL && exprtype != EXPR_NEQUAL - && exprtype != EXPR_IS && exprtype != EXPR_ISNOT - && (type1 == VAR_BLOB || type2 == VAR_BLOB - || type1 == VAR_LIST || type2 == VAR_LIST)))) - { - semsg(_(e_cannot_compare_str_with_str), - vartype_name(type1), vartype_name(type2)); - return ISN_DROP; - } - return isntype; -} - - int -check_compare_types(exprtype_T type, typval_T *tv1, typval_T *tv2) -{ - if (get_compare_isn(type, tv1->v_type, tv2->v_type) == ISN_DROP) - return FAIL; - return OK; -} - -/* - * Generate an ISN_COMPARE* instruction with a boolean result. - */ - static int -generate_COMPARE(cctx_T *cctx, exprtype_T exprtype, int ic) -{ - isntype_T isntype; - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - vartype_T type1; - vartype_T type2; - - RETURN_OK_IF_SKIP(cctx); - - // Get the known type of the two items on the stack. If they are matching - // use a type-specific instruction. Otherwise fall back to runtime type - // checking. - type1 = ((type_T **)stack->ga_data)[stack->ga_len - 2]->tt_type; - type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]->tt_type; - isntype = get_compare_isn(exprtype, type1, type2); - if (isntype == ISN_DROP) - return FAIL; - - if ((isn = generate_instr(cctx, isntype)) == NULL) - return FAIL; - isn->isn_arg.op.op_type = exprtype; - isn->isn_arg.op.op_ic = ic; - - // takes two arguments, puts one bool back - if (stack->ga_len >= 2) - { - --stack->ga_len; - ((type_T **)stack->ga_data)[stack->ga_len - 1] = &t_bool; - } - - return OK; -} - -/* - * Generate an ISN_2BOOL instruction. - * "offset" is the offset in the type stack. - */ - static int -generate_2BOOL(cctx_T *cctx, int invert, int offset) -{ - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_2BOOL)) == NULL) - return FAIL; - isn->isn_arg.tobool.invert = invert; - isn->isn_arg.tobool.offset = offset; - - // type becomes bool - ((type_T **)stack->ga_data)[stack->ga_len + offset] = &t_bool; - - return OK; -} - -/* - * Generate an ISN_COND2BOOL instruction. - */ - static int -generate_COND2BOOL(cctx_T *cctx) -{ - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_COND2BOOL)) == NULL) - return FAIL; - - // type becomes bool - ((type_T **)stack->ga_data)[stack->ga_len - 1] = &t_bool; - - return OK; -} - - static int -generate_TYPECHECK( - cctx_T *cctx, - type_T *expected, - int offset, - int argidx) -{ - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_CHECKTYPE)) == NULL) - return FAIL; - isn->isn_arg.type.ct_type = alloc_type(expected); - isn->isn_arg.type.ct_off = (int8_T)offset; - isn->isn_arg.type.ct_arg_idx = (int8_T)argidx; - - // type becomes expected - ((type_T **)stack->ga_data)[stack->ga_len + offset] = expected; - - return OK; -} - - static int -generate_SETTYPE( - cctx_T *cctx, - type_T *expected) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_SETTYPE)) == NULL) - return FAIL; - isn->isn_arg.type.ct_type = alloc_type(expected); - return OK; -} - /* * Return TRUE if "actual" could be "expected" and a runtime typecheck is to be * used. Return FALSE if the types will never match. @@ -1108,1397 +442,10 @@ need_type( } /* - * 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. - */ - static int -bool_on_stack(cctx_T *cctx) -{ - garray_T *stack = &cctx->ctx_type_stack; - type_T *type; - - type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - if (type == &t_bool) - return OK; - - if (type == &t_any || type == &t_number || type == &t_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, -1, 0, cctx, FALSE, FALSE); -} - -/* - * Generate an ISN_PUSHNR instruction. - */ - static int -generate_PUSHNR(cctx_T *cctx, varnumber_T number) -{ - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr_type(cctx, ISN_PUSHNR, &t_number)) == NULL) - return FAIL; - isn->isn_arg.number = number; - - if (number == 0 || number == 1) - // A 0 or 1 number can also be used as a bool. - ((type_T **)stack->ga_data)[stack->ga_len - 1] = &t_number_bool; - return OK; -} - -/* - * Generate an ISN_PUSHBOOL instruction. - */ - static int -generate_PUSHBOOL(cctx_T *cctx, varnumber_T number) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr_type(cctx, ISN_PUSHBOOL, &t_bool)) == NULL) - return FAIL; - isn->isn_arg.number = number; - - return OK; -} - -/* - * Generate an ISN_PUSHSPEC instruction. - */ - static int -generate_PUSHSPEC(cctx_T *cctx, varnumber_T number) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr_type(cctx, ISN_PUSHSPEC, &t_special)) == NULL) - return FAIL; - isn->isn_arg.number = number; - - return OK; -} - -#ifdef FEAT_FLOAT -/* - * Generate an ISN_PUSHF instruction. - */ - static int -generate_PUSHF(cctx_T *cctx, float_T fnumber) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr_type(cctx, ISN_PUSHF, &t_float)) == NULL) - return FAIL; - isn->isn_arg.fnumber = fnumber; - - return OK; -} -#endif - -/* - * Generate an ISN_PUSHS instruction. - * Consumes "*str". When freed *str is set to NULL, unless "str" is NULL. - */ - static int -generate_PUSHS(cctx_T *cctx, char_u **str) -{ - isn_T *isn; - - if (cctx->ctx_skip == SKIP_YES) - { - if (str != NULL) - VIM_CLEAR(*str); - return OK; - } - if ((isn = generate_instr_type(cctx, ISN_PUSHS, &t_string)) == NULL) - { - if (str != NULL) - VIM_CLEAR(*str); - return FAIL; - } - isn->isn_arg.string = str == NULL ? NULL : *str; - - return OK; -} - -/* - * Generate an ISN_PUSHCHANNEL instruction. - * Consumes "channel". - */ - static int -generate_PUSHCHANNEL(cctx_T *cctx, channel_T *channel) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr_type(cctx, ISN_PUSHCHANNEL, &t_channel)) == NULL) - return FAIL; - isn->isn_arg.channel = channel; - - return OK; -} - -/* - * Generate an ISN_PUSHJOB instruction. - * Consumes "job". - */ - static int -generate_PUSHJOB(cctx_T *cctx, job_T *job) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr_type(cctx, ISN_PUSHJOB, &t_channel)) == NULL) - return FAIL; - isn->isn_arg.job = job; - - return OK; -} - -/* - * Generate an ISN_PUSHBLOB instruction. - * Consumes "blob". - */ - static int -generate_PUSHBLOB(cctx_T *cctx, blob_T *blob) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr_type(cctx, ISN_PUSHBLOB, &t_blob)) == NULL) - return FAIL; - isn->isn_arg.blob = blob; - - return OK; -} - -/* - * Generate an ISN_PUSHFUNC instruction with name "name". - * Consumes "name". - */ - static int -generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type) -{ - isn_T *isn; - char_u *funcname; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr_type(cctx, ISN_PUSHFUNC, type)) == NULL) - return FAIL; - if (name == NULL) - funcname = NULL; - else if (*name == K_SPECIAL) // script-local - funcname = vim_strsave(name); - else - { - funcname = alloc(STRLEN(name) + 3); - if (funcname != NULL) - { - STRCPY(funcname, "g:"); - STRCPY(funcname + 2, name); - } - } - - isn->isn_arg.string = funcname; - return OK; -} - -/* - * Generate an ISN_GETITEM instruction with "index". - * "with_op" is TRUE for "+=" and other operators, the stack has the current - * value below the list with values. - */ - static int -generate_GETITEM(cctx_T *cctx, int index, int with_op) -{ - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - type_T *type = ((type_T **)stack->ga_data)[stack->ga_len - - (with_op ? 2 : 1)]; - type_T *item_type = &t_any; - - RETURN_OK_IF_SKIP(cctx); - - if (type->tt_type != VAR_LIST) - { - // cannot happen, caller has checked the type - emsg(_(e_listreq)); - return FAIL; - } - item_type = type->tt_member; - if ((isn = generate_instr(cctx, ISN_GETITEM)) == NULL) - return FAIL; - isn->isn_arg.getitem.gi_index = index; - isn->isn_arg.getitem.gi_with_op = with_op; - - // add the item type to the type stack - if (GA_GROW_FAILS(stack, 1)) - return FAIL; - ((type_T **)stack->ga_data)[stack->ga_len] = item_type; - ++stack->ga_len; - return OK; -} - -/* - * Generate an ISN_SLICE instruction with "count". - */ - static int -generate_SLICE(cctx_T *cctx, int count) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_SLICE)) == NULL) - return FAIL; - isn->isn_arg.number = count; - return OK; -} - -/* - * Generate an ISN_CHECKLEN instruction with "min_len". - */ - static int -generate_CHECKLEN(cctx_T *cctx, int min_len, int more_OK) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - - if ((isn = generate_instr(cctx, ISN_CHECKLEN)) == NULL) - return FAIL; - isn->isn_arg.checklen.cl_min_len = min_len; - isn->isn_arg.checklen.cl_more_OK = more_OK; - - return OK; -} - -/* - * Generate an ISN_STORE instruction. - */ - static int -generate_STORE(cctx_T *cctx, isntype_T isn_type, int idx, char_u *name) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr_drop(cctx, isn_type, 1)) == NULL) - return FAIL; - if (name != NULL) - isn->isn_arg.string = vim_strsave(name); - else - isn->isn_arg.number = idx; - - return OK; -} - -/* - * Generate an ISN_STOREOUTER instruction. - */ - static int -generate_STOREOUTER(cctx_T *cctx, int idx, int level) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr_drop(cctx, ISN_STOREOUTER, 1)) == NULL) - return FAIL; - isn->isn_arg.outer.outer_idx = idx; - isn->isn_arg.outer.outer_depth = level; - - return OK; -} - -/* - * Generate an ISN_STORENR instruction (short for ISN_PUSHNR + ISN_STORE) - */ - static int -generate_STORENR(cctx_T *cctx, int idx, varnumber_T value) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_STORENR)) == NULL) - return FAIL; - isn->isn_arg.storenr.stnr_idx = idx; - isn->isn_arg.storenr.stnr_val = value; - - return OK; -} - -/* - * Generate an ISN_STOREOPT or ISN_STOREFUNCOPT instruction - */ - static int -generate_STOREOPT( - cctx_T *cctx, - isntype_T isn_type, - char_u *name, - int opt_flags) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr_drop(cctx, isn_type, 1)) == NULL) - return FAIL; - isn->isn_arg.storeopt.so_name = vim_strsave(name); - isn->isn_arg.storeopt.so_flags = opt_flags; - - return OK; -} - -/* - * Generate an ISN_LOAD or similar instruction. - */ - static int -generate_LOAD( - cctx_T *cctx, - isntype_T isn_type, - int idx, - char_u *name, - type_T *type) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr_type(cctx, isn_type, type)) == NULL) - return FAIL; - if (name != NULL) - isn->isn_arg.string = vim_strsave(name); - else - isn->isn_arg.number = idx; - - return OK; -} - -/* - * Generate an ISN_LOADOUTER instruction - */ - static int -generate_LOADOUTER( - cctx_T *cctx, - int idx, - int nesting, - type_T *type) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr_type(cctx, ISN_LOADOUTER, type)) == NULL) - return FAIL; - isn->isn_arg.outer.outer_idx = idx; - isn->isn_arg.outer.outer_depth = nesting; - - return OK; -} - -/* - * Generate an ISN_LOADV instruction for v:var. - */ - static int -generate_LOADV( - cctx_T *cctx, - char_u *name, - int error) -{ - int di_flags; - int vidx = find_vim_var(name, &di_flags); - type_T *type; - - RETURN_OK_IF_SKIP(cctx); - if (vidx < 0) - { - if (error) - semsg(_(e_variable_not_found_str), name); - return FAIL; - } - type = typval2type_vimvar(get_vim_var_tv(vidx), cctx->ctx_type_list); - - return generate_LOAD(cctx, ISN_LOADV, vidx, NULL, type); -} - -/* - * Generate an ISN_UNLET instruction. - */ - static int -generate_UNLET(cctx_T *cctx, isntype_T isn_type, char_u *name, int forceit) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, isn_type)) == NULL) - return FAIL; - isn->isn_arg.unlet.ul_name = vim_strsave(name); - isn->isn_arg.unlet.ul_forceit = forceit; - - return OK; -} - -/* - * Generate an ISN_LOCKCONST instruction. - */ - static int -generate_LOCKCONST(cctx_T *cctx) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_LOCKCONST)) == NULL) - return FAIL; - return OK; -} - -/* - * Generate an ISN_LOADS instruction. - */ - static int -generate_OLDSCRIPT( - cctx_T *cctx, - isntype_T isn_type, - char_u *name, - int sid, - type_T *type) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if (isn_type == ISN_LOADS) - isn = generate_instr_type(cctx, isn_type, type); - else - isn = generate_instr_drop(cctx, isn_type, 1); - if (isn == NULL) - return FAIL; - isn->isn_arg.loadstore.ls_name = vim_strsave(name); - isn->isn_arg.loadstore.ls_sid = sid; - - return OK; -} - -/* - * Generate an ISN_LOADSCRIPT or ISN_STORESCRIPT instruction. - */ - static int -generate_VIM9SCRIPT( - cctx_T *cctx, - isntype_T isn_type, - int sid, - int idx, - type_T *type) -{ - isn_T *isn; - scriptref_T *sref; - scriptitem_T *si = SCRIPT_ITEM(sid); - - RETURN_OK_IF_SKIP(cctx); - if (isn_type == ISN_LOADSCRIPT) - isn = generate_instr_type(cctx, isn_type, type); - else - isn = generate_instr_drop(cctx, isn_type, 1); - if (isn == NULL) - return FAIL; - - // This requires three arguments, which doesn't fit in an instruction, thus - // we need to allocate a struct for this. - sref = ALLOC_ONE(scriptref_T); - if (sref == NULL) - return FAIL; - isn->isn_arg.script.scriptref = sref; - sref->sref_sid = sid; - sref->sref_idx = idx; - sref->sref_seq = si->sn_script_seq; - sref->sref_type = type; - return OK; -} - -/* - * Generate an ISN_NEWLIST instruction. - */ - static int -generate_NEWLIST(cctx_T *cctx, int count) -{ - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - type_T *type; - type_T *member; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_NEWLIST)) == NULL) - return FAIL; - isn->isn_arg.number = count; - - // get the member type from all the items on the stack. - if (count == 0) - member = &t_unknown; - else - member = get_member_type_from_stack( - ((type_T **)stack->ga_data) + stack->ga_len, count, 1, - cctx->ctx_type_list); - type = get_list_type(member, cctx->ctx_type_list); - - // drop the value types - stack->ga_len -= count; - - // add the list type to the type stack - if (GA_GROW_FAILS(stack, 1)) - return FAIL; - ((type_T **)stack->ga_data)[stack->ga_len] = type; - ++stack->ga_len; - - return OK; -} - -/* - * Generate an ISN_NEWDICT instruction. - */ - static int -generate_NEWDICT(cctx_T *cctx, int count) -{ - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - type_T *type; - type_T *member; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_NEWDICT)) == NULL) - return FAIL; - isn->isn_arg.number = count; - - if (count == 0) - member = &t_void; - else - member = get_member_type_from_stack( - ((type_T **)stack->ga_data) + stack->ga_len, count, 2, - cctx->ctx_type_list); - type = get_dict_type(member, cctx->ctx_type_list); - - // drop the key and value types - stack->ga_len -= 2 * count; - - // add the dict type to the type stack - if (GA_GROW_FAILS(stack, 1)) - return FAIL; - ((type_T **)stack->ga_data)[stack->ga_len] = type; - ++stack->ga_len; - - return OK; -} - -/* - * Generate an ISN_FUNCREF instruction. - */ - static int -generate_FUNCREF(cctx_T *cctx, ufunc_T *ufunc) -{ - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_FUNCREF)) == NULL) - return FAIL; - if (ufunc->uf_def_status == UF_NOT_COMPILED) - isn->isn_arg.funcref.fr_func_name = vim_strsave(ufunc->uf_name); - else - isn->isn_arg.funcref.fr_dfunc_idx = ufunc->uf_dfunc_idx; - cctx->ctx_has_closure = 1; - - // If the referenced function is a closure, it may use items further up in - // the nested context, including this one. - if (ufunc->uf_flags & FC_CLOSURE) - cctx->ctx_ufunc->uf_flags |= FC_CLOSURE; - - if (GA_GROW_FAILS(stack, 1)) - return FAIL; - ((type_T **)stack->ga_data)[stack->ga_len] = - ufunc->uf_func_type == NULL ? &t_func_any : ufunc->uf_func_type; - ++stack->ga_len; - - return OK; -} - -/* - * Generate an ISN_NEWFUNC instruction. - * "lambda_name" and "func_name" must be in allocated memory and will be - * consumed. - */ - static int -generate_NEWFUNC(cctx_T *cctx, char_u *lambda_name, char_u *func_name) -{ - isn_T *isn; - - if (cctx->ctx_skip == SKIP_YES) - { - vim_free(lambda_name); - vim_free(func_name); - return OK; - } - if ((isn = generate_instr(cctx, ISN_NEWFUNC)) == NULL) - { - vim_free(lambda_name); - vim_free(func_name); - return FAIL; - } - isn->isn_arg.newfunc.nf_lambda = lambda_name; - isn->isn_arg.newfunc.nf_global = func_name; - - return OK; -} - -/* - * Generate an ISN_DEF instruction: list functions - */ - static int -generate_DEF(cctx_T *cctx, char_u *name, size_t len) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_DEF)) == NULL) - return FAIL; - if (len > 0) - { - isn->isn_arg.string = vim_strnsave(name, len); - if (isn->isn_arg.string == NULL) - return FAIL; - } - return OK; -} - -/* - * Generate an ISN_JUMP instruction. - */ - static int -generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where) -{ - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_JUMP)) == NULL) - return FAIL; - isn->isn_arg.jump.jump_when = when; - isn->isn_arg.jump.jump_where = where; - - if (when != JUMP_ALWAYS && stack->ga_len > 0) - --stack->ga_len; - - return OK; -} - -/* - * Generate an ISN_JUMP_IF_ARG_SET instruction. - */ - static int -generate_JUMP_IF_ARG_SET(cctx_T *cctx, int arg_off) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_JUMP_IF_ARG_SET)) == NULL) - return FAIL; - isn->isn_arg.jumparg.jump_arg_off = arg_off; - // jump_where is set later - return OK; -} - - static int -generate_FOR(cctx_T *cctx, int loop_idx) -{ - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_FOR)) == NULL) - return FAIL; - isn->isn_arg.forloop.for_idx = loop_idx; - - if (GA_GROW_FAILS(stack, 1)) - return FAIL; - // type doesn't matter, will be stored next - ((type_T **)stack->ga_data)[stack->ga_len] = &t_any; - ++stack->ga_len; - - return OK; -} -/* - * Generate an ISN_TRYCONT instruction. - */ - static int -generate_TRYCONT(cctx_T *cctx, int levels, int where) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_TRYCONT)) == NULL) - return FAIL; - isn->isn_arg.trycont.tct_levels = levels; - isn->isn_arg.trycont.tct_where = where; - - return OK; -} - - -/* - * Generate an ISN_BCALL instruction. - * "method_call" is TRUE for "value->method()" - * Return FAIL if the number of arguments is wrong. - */ - static int -generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call) -{ - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - int argoff; - type_T **argtypes = NULL; - type_T *shuffled_argtypes[MAX_FUNC_ARGS]; - type_T *maptype = NULL; - - RETURN_OK_IF_SKIP(cctx); - argoff = check_internal_func(func_idx, argcount); - if (argoff < 0) - return FAIL; - - if (method_call && argoff > 1) - { - if ((isn = generate_instr(cctx, ISN_SHUFFLE)) == NULL) - return FAIL; - isn->isn_arg.shuffle.shfl_item = argcount; - isn->isn_arg.shuffle.shfl_up = argoff - 1; - } - - if (argcount > 0) - { - // Check the types of the arguments. - argtypes = ((type_T **)stack->ga_data) + stack->ga_len - argcount; - if (method_call && argoff > 1) - { - int i; - - for (i = 0; i < argcount; ++i) - shuffled_argtypes[i] = (i < argoff - 1) - ? argtypes[i + 1] - : (i == argoff - 1) ? argtypes[0] : argtypes[i]; - argtypes = shuffled_argtypes; - } - if (internal_func_check_arg_types(argtypes, func_idx, argcount, - cctx) == FAIL) - return FAIL; - if (internal_func_is_map(func_idx)) - maptype = *argtypes; - } - - if ((isn = generate_instr(cctx, ISN_BCALL)) == NULL) - return FAIL; - isn->isn_arg.bfunc.cbf_idx = func_idx; - isn->isn_arg.bfunc.cbf_argcount = argcount; - - // Drop the argument types and push the return type. - stack->ga_len -= argcount; - if (GA_GROW_FAILS(stack, 1)) - return FAIL; - ((type_T **)stack->ga_data)[stack->ga_len] = - internal_func_ret_type(func_idx, argcount, argtypes); - ++stack->ga_len; - - if (maptype != NULL && maptype->tt_member != NULL - && maptype->tt_member != &t_any) - // Check that map() didn't change the item types. - generate_TYPECHECK(cctx, maptype, -1, 1); - - return OK; -} - -/* - * Generate an ISN_LISTAPPEND instruction. Works like add(). - * Argument count is already checked. - */ - static int -generate_LISTAPPEND(cctx_T *cctx) -{ - garray_T *stack = &cctx->ctx_type_stack; - type_T *list_type; - type_T *item_type; - type_T *expected; - - // Caller already checked that list_type is a list. - list_type = ((type_T **)stack->ga_data)[stack->ga_len - 2]; - item_type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - expected = list_type->tt_member; - if (need_type(item_type, expected, -1, 0, cctx, FALSE, FALSE) == FAIL) - return FAIL; - - if (generate_instr(cctx, ISN_LISTAPPEND) == NULL) - return FAIL; - - --stack->ga_len; // drop the argument - return OK; -} - -/* - * Generate an ISN_BLOBAPPEND instruction. Works like add(). - * Argument count is already checked. - */ - static int -generate_BLOBAPPEND(cctx_T *cctx) -{ - garray_T *stack = &cctx->ctx_type_stack; - type_T *item_type; - - // Caller already checked that blob_type is a blob. - item_type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - if (need_type(item_type, &t_number, -1, 0, cctx, FALSE, FALSE) == FAIL) - return FAIL; - - if (generate_instr(cctx, ISN_BLOBAPPEND) == NULL) - return FAIL; - - --stack->ga_len; // drop the argument - return OK; -} - -/* - * Return TRUE if "ufunc" should be compiled, taking into account whether - * "profile" indicates profiling is to be done. - */ - int -func_needs_compiling(ufunc_T *ufunc, compiletype_T compile_type) -{ - switch (ufunc->uf_def_status) - { - case UF_TO_BE_COMPILED: - return TRUE; - - case UF_COMPILED: - { - dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) - + ufunc->uf_dfunc_idx; - - switch (compile_type) - { - case CT_PROFILE: -#ifdef FEAT_PROFILE - return dfunc->df_instr_prof == NULL; -#endif - case CT_NONE: - return dfunc->df_instr == NULL; - case CT_DEBUG: - return dfunc->df_instr_debug == NULL; - } - } - - case UF_NOT_COMPILED: - case UF_COMPILE_ERROR: - case UF_COMPILING: - break; - } - return FALSE; -} - -/* - * Generate an ISN_DCALL or ISN_UCALL instruction. - * Return FAIL if the number of arguments is wrong. - */ - static int -generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount) -{ - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - int regular_args = ufunc->uf_args.ga_len; - int argcount = pushed_argcount; - - RETURN_OK_IF_SKIP(cctx); - if (argcount > regular_args && !has_varargs(ufunc)) - { - semsg(_(e_too_many_arguments_for_function_str), - printable_func_name(ufunc)); - return FAIL; - } - if (argcount < regular_args - ufunc->uf_def_args.ga_len) - { - semsg(_(e_not_enough_arguments_for_function_str), - printable_func_name(ufunc)); - return FAIL; - } - - if (ufunc->uf_def_status != UF_NOT_COMPILED - && ufunc->uf_def_status != UF_COMPILE_ERROR) - { - int i; - - for (i = 0; i < argcount; ++i) - { - type_T *expected; - type_T *actual; - - actual = ((type_T **)stack->ga_data)[stack->ga_len - argcount + i]; - if (actual == &t_special - && i >= regular_args - ufunc->uf_def_args.ga_len) - { - // assume v:none used for default argument value - continue; - } - if (i < regular_args) - { - if (ufunc->uf_arg_types == NULL) - continue; - expected = ufunc->uf_arg_types[i]; - } - else if (ufunc->uf_va_type == NULL - || ufunc->uf_va_type == &t_list_any) - // possibly a lambda or "...: any" - expected = &t_any; - else - expected = ufunc->uf_va_type->tt_member; - if (need_type(actual, expected, -argcount + i, i + 1, cctx, - TRUE, FALSE) == FAIL) - { - arg_type_mismatch(expected, actual, i + 1); - return FAIL; - } - } - if (func_needs_compiling(ufunc, COMPILE_TYPE(ufunc)) - && compile_def_function(ufunc, ufunc->uf_ret_type == NULL, - COMPILE_TYPE(ufunc), NULL) == FAIL) - return FAIL; - } - if (ufunc->uf_def_status == UF_COMPILE_ERROR) - { - emsg_funcname(_(e_call_to_function_that_failed_to_compile_str), - ufunc->uf_name); - return FAIL; - } - - if ((isn = generate_instr(cctx, - ufunc->uf_def_status != UF_NOT_COMPILED ? ISN_DCALL - : ISN_UCALL)) == NULL) - return FAIL; - if (isn->isn_type == ISN_DCALL) - { - isn->isn_arg.dfunc.cdf_idx = ufunc->uf_dfunc_idx; - isn->isn_arg.dfunc.cdf_argcount = argcount; - } - else - { - // A user function may be deleted and redefined later, can't use the - // ufunc pointer, need to look it up again at runtime. - isn->isn_arg.ufunc.cuf_name = vim_strsave(ufunc->uf_name); - isn->isn_arg.ufunc.cuf_argcount = argcount; - } - - stack->ga_len -= argcount; // drop the arguments - if (GA_GROW_FAILS(stack, 1)) - return FAIL; - // add return value - ((type_T **)stack->ga_data)[stack->ga_len] = ufunc->uf_ret_type; - ++stack->ga_len; - - return OK; -} - -/* - * Generate an ISN_UCALL instruction when the function isn't defined yet. - */ - static int -generate_UCALL(cctx_T *cctx, char_u *name, int argcount) -{ - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_UCALL)) == NULL) - return FAIL; - isn->isn_arg.ufunc.cuf_name = vim_strsave(name); - isn->isn_arg.ufunc.cuf_argcount = argcount; - - stack->ga_len -= argcount; // drop the arguments - if (GA_GROW_FAILS(stack, 1)) - return FAIL; - // add return value - ((type_T **)stack->ga_data)[stack->ga_len] = &t_any; - ++stack->ga_len; - - return OK; -} - -/* - * Generate an ISN_PCALL instruction. - * "type" is the type of the FuncRef. - */ - static int -generate_PCALL( - cctx_T *cctx, - int argcount, - char_u *name, - type_T *type, - int at_top) -{ - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - type_T *ret_type; - - RETURN_OK_IF_SKIP(cctx); - - if (type->tt_type == VAR_ANY) - ret_type = &t_any; - else if (type->tt_type == VAR_FUNC || type->tt_type == VAR_PARTIAL) - { - if (type->tt_argcount != -1) - { - int varargs = (type->tt_flags & TTFLAG_VARARGS) ? 1 : 0; - - if (argcount < type->tt_min_argcount - varargs) - { - semsg(_(e_not_enough_arguments_for_function_str), name); - return FAIL; - } - if (!varargs && argcount > type->tt_argcount) - { - semsg(_(e_too_many_arguments_for_function_str), name); - return FAIL; - } - if (type->tt_args != NULL) - { - int i; - - for (i = 0; i < argcount; ++i) - { - int offset = -argcount + i - (at_top ? 0 : 1); - type_T *actual = ((type_T **)stack->ga_data)[ - stack->ga_len + offset]; - type_T *expected; - - if (varargs && i >= type->tt_argcount - 1) - expected = type->tt_args[ - type->tt_argcount - 1]->tt_member; - else if (i >= type->tt_min_argcount - && actual == &t_special) - expected = &t_any; - else - expected = type->tt_args[i]; - if (need_type(actual, expected, offset, i + 1, - cctx, TRUE, FALSE) == FAIL) - { - arg_type_mismatch(expected, actual, i + 1); - return FAIL; - } - } - } - } - ret_type = type->tt_member; - if (ret_type == &t_unknown) - // return type not known yet, use a runtime check - ret_type = &t_any; - } - else - { - semsg(_(e_not_callable_type_str), name); - return FAIL; - } - - if ((isn = generate_instr(cctx, ISN_PCALL)) == NULL) - return FAIL; - isn->isn_arg.pfunc.cpf_top = at_top; - isn->isn_arg.pfunc.cpf_argcount = argcount; - - stack->ga_len -= argcount; // drop the arguments - - // drop the funcref/partial, get back the return value - ((type_T **)stack->ga_data)[stack->ga_len - 1] = ret_type; - - // If partial is above the arguments it must be cleared and replaced with - // the return value. - if (at_top && generate_instr(cctx, ISN_PCALL_END) == NULL) - return FAIL; - - return OK; -} - -/* - * Generate an ISN_STRINGMEMBER instruction. - */ - static int -generate_STRINGMEMBER(cctx_T *cctx, char_u *name, size_t len) -{ - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - type_T *type; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_STRINGMEMBER)) == NULL) - return FAIL; - isn->isn_arg.string = vim_strnsave(name, len); - - // check for dict type - type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - if (type->tt_type != VAR_DICT && type != &t_any) - { - char *tofree; - - semsg(_(e_expected_dictionary_for_using_key_str_but_got_str), - name, type_name(type, &tofree)); - vim_free(tofree); - return FAIL; - } - // change dict type to dict member type - if (type->tt_type == VAR_DICT) - { - ((type_T **)stack->ga_data)[stack->ga_len - 1] = - type->tt_member == &t_unknown ? &t_any : type->tt_member; - } - - return OK; -} - -/* - * Generate an ISN_ECHO instruction. - */ - static int -generate_ECHO(cctx_T *cctx, int with_white, int count) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr_drop(cctx, ISN_ECHO, count)) == NULL) - return FAIL; - isn->isn_arg.echo.echo_with_white = with_white; - isn->isn_arg.echo.echo_count = count; - - return OK; -} - -/* - * Generate an ISN_EXECUTE/ISN_ECHOMSG/ISN_ECHOERR instruction. - */ - static int -generate_MULT_EXPR(cctx_T *cctx, isntype_T isn_type, int count) -{ - isn_T *isn; - - if ((isn = generate_instr_drop(cctx, isn_type, count)) == NULL) - return FAIL; - isn->isn_arg.number = count; - - return OK; -} - -/* - * Generate an ISN_PUT instruction. - */ - static int -generate_PUT(cctx_T *cctx, int regname, linenr_T lnum) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_PUT)) == NULL) - return FAIL; - isn->isn_arg.put.put_regname = regname; - isn->isn_arg.put.put_lnum = lnum; - return OK; -} - -/* - * Generate an EXEC instruction that takes a string argument. - * A copy is made of "line". - */ - static int -generate_EXEC_copy(cctx_T *cctx, isntype_T isntype, char_u *line) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, isntype)) == NULL) - return FAIL; - isn->isn_arg.string = vim_strsave(line); - return OK; -} - -/* - * Generate an EXEC instruction that takes a string argument. - * "str" must be allocated, it is consumed. - */ - static int -generate_EXEC(cctx_T *cctx, isntype_T isntype, char_u *str) -{ - isn_T *isn; - - if (cctx->ctx_skip == SKIP_YES) - { - vim_free(str); - return OK; - } - if ((isn = generate_instr(cctx, isntype)) == NULL) - { - vim_free(str); - return FAIL; - } - isn->isn_arg.string = str; - return OK; -} - - static int -generate_LEGACY_EVAL(cctx_T *cctx, char_u *line) -{ - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_LEGACY_EVAL)) == NULL) - return FAIL; - isn->isn_arg.string = vim_strsave(line); - - if (GA_GROW_FAILS(stack, 1)) - return FAIL; - ((type_T **)stack->ga_data)[stack->ga_len] = &t_any; - ++stack->ga_len; - - return OK; -} - - static int -generate_EXECCONCAT(cctx_T *cctx, int count) -{ - isn_T *isn; - - if ((isn = generate_instr_drop(cctx, ISN_EXECCONCAT, count)) == NULL) - return FAIL; - isn->isn_arg.number = count; - return OK; -} - -/* - * Generate ISN_RANGE. Consumes "range". Return OK/FAIL. - */ - static int -generate_RANGE(cctx_T *cctx, char_u *range) -{ - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - - if ((isn = generate_instr(cctx, ISN_RANGE)) == NULL) - return FAIL; - isn->isn_arg.string = range; - - if (GA_GROW_FAILS(stack, 1)) - return FAIL; - ((type_T **)stack->ga_data)[stack->ga_len] = &t_number; - ++stack->ga_len; - return OK; -} - - static int -generate_UNPACK(cctx_T *cctx, int var_count, int semicolon) -{ - isn_T *isn; - - RETURN_OK_IF_SKIP(cctx); - if ((isn = generate_instr(cctx, ISN_UNPACK)) == NULL) - return FAIL; - isn->isn_arg.unpack.unp_count = var_count; - isn->isn_arg.unpack.unp_semicolon = semicolon; - return OK; -} - -/* - * Generate an instruction for any command modifiers. - */ - static int -generate_cmdmods(cctx_T *cctx, cmdmod_T *cmod) -{ - isn_T *isn; - - if (has_cmdmod(cmod, FALSE)) - { - cctx->ctx_has_cmdmod = TRUE; - - if ((isn = generate_instr(cctx, ISN_CMDMOD)) == NULL) - return FAIL; - isn->isn_arg.cmdmod.cf_cmdmod = ALLOC_ONE(cmdmod_T); - if (isn->isn_arg.cmdmod.cf_cmdmod == NULL) - return FAIL; - mch_memmove(isn->isn_arg.cmdmod.cf_cmdmod, cmod, sizeof(cmdmod_T)); - // filter program now belongs to the instruction - cmod->cmod_filter_regmatch.regprog = NULL; - } - - return OK; -} - - static int -generate_undo_cmdmods(cctx_T *cctx) -{ - if (cctx->ctx_has_cmdmod && generate_instr(cctx, ISN_CMDMOD_REV) == NULL) - return FAIL; - cctx->ctx_has_cmdmod = FALSE; - return OK; -} - - static int -misplaced_cmdmod(cctx_T *cctx) -{ - garray_T *instr = &cctx->ctx_instr; - - if (cctx->ctx_has_cmdmod - && ((isn_T *)instr->ga_data)[instr->ga_len - 1].isn_type - == ISN_CMDMOD) - { - emsg(_(e_misplaced_command_modifier)); - return TRUE; - } - return FALSE; -} - -/* - * Get the index of the current instruction. - * This compensates for a preceding ISN_CMDMOD and ISN_PROF_START. - */ - static int -current_instr_idx(cctx_T *cctx) -{ - garray_T *instr = &cctx->ctx_instr; - int idx = instr->ga_len; - - while (idx > 0) - { - if (cctx->ctx_has_cmdmod && ((isn_T *)instr->ga_data)[idx - 1] - .isn_type == ISN_CMDMOD) - { - --idx; - continue; - } -#ifdef FEAT_PROFILE - if (((isn_T *)instr->ga_data)[idx - 1].isn_type == ISN_PROF_START) - { - --idx; - continue; - } -#endif - if (((isn_T *)instr->ga_data)[idx - 1].isn_type == ISN_DEBUG) - { - --idx; - continue; - } - break; - } - return idx; -} - -#ifdef FEAT_PROFILE - static void -may_generate_prof_end(cctx_T *cctx, int prof_lnum) -{ - if (cctx->ctx_compile_type == CT_PROFILE && prof_lnum >= 0) - generate_instr(cctx, ISN_PROF_END); -} -#endif - -/* * Reserve space for a local variable. * Return the variable or NULL if it failed. */ - static lvar_T * + lvar_T * reserve_local( cctx_T *cctx, char_u *name, @@ -2542,36 +489,6 @@ reserve_local( } /* - * Remove local variables above "new_top". - */ - static void -unwind_locals(cctx_T *cctx, int new_top) -{ - if (cctx->ctx_locals.ga_len > new_top) - { - int idx; - lvar_T *lvar; - - for (idx = new_top; idx < cctx->ctx_locals.ga_len; ++idx) - { - lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx; - vim_free(lvar->lv_name); - } - } - cctx->ctx_locals.ga_len = new_top; -} - -/* - * Free all local variables. - */ - static void -free_locals(cctx_T *cctx) -{ - unwind_locals(cctx, 0); - ga_clear(&cctx->ctx_locals); -} - -/* * If "check_writable" is ASSIGN_CONST give an error if the variable was * defined with :final or :const, if "check_writable" is ASSIGN_FINAL give an * error if the variable was defined with :const. @@ -2705,6 +622,27 @@ free_imported(cctx_T *cctx) } /* + * Called when checking for a following operator at "arg". When the rest of + * the line is empty or only a comment, peek the next line. If there is a next + * line return a pointer to it and set "nextp". + * Otherwise skip over white space. + */ + char_u * +may_peek_next_line(cctx_T *cctx, char_u *arg, char_u **nextp) +{ + char_u *p = skipwhite(arg); + + *nextp = NULL; + if (*p == NUL || (VIM_ISWHITE(*arg) && vim9_comment_start(p))) + { + *nextp = peek_next_line_from_context(cctx); + if (*nextp != NULL) + return *nextp; + } + return p; +} + +/* * Return a pointer to the next line that isn't empty or only contains a * comment. Skips over white space. * Returns NULL if there is none. @@ -2733,27 +671,6 @@ peek_next_line_from_context(cctx_T *cctx) } /* - * Called when checking for a following operator at "arg". When the rest of - * the line is empty or only a comment, peek the next line. If there is a next - * line return a pointer to it and set "nextp". - * Otherwise skip over white space. - */ - static char_u * -may_peek_next_line(cctx_T *cctx, char_u *arg, char_u **nextp) -{ - char_u *p = skipwhite(arg); - - *nextp = NULL; - if (*p == NUL || (VIM_ISWHITE(*arg) && vim9_comment_start(p))) - { - *nextp = peek_next_line_from_context(cctx); - if (*nextp != NULL) - return *nextp; - } - return p; -} - -/* * Get the next line of the function from "cctx". * Skips over empty lines. Skips over comment lines if "skip_comment" is TRUE. * Returns NULL when at the end. @@ -2785,7 +702,7 @@ next_line_from_context(cctx_T *cctx, int skip_comment) * Also when "whitep" points to white space and "*arg" is on a "#". * Return FAIL if beyond the last line, "*arg" is unmodified then. */ - static int + int may_get_next_line(char_u *whitep, char_u **arg, cctx_T *cctx) { *arg = skipwhite(whitep); @@ -2805,7 +722,7 @@ may_get_next_line(char_u *whitep, char_u **arg, cctx_T *cctx) /* * Idem, and give an error when failed. */ - static int + int may_get_next_line_error(char_u *whitep, char_u **arg, cctx_T *cctx) { if (may_get_next_line(whitep, arg, cctx) == FAIL) @@ -2817,3013 +734,6 @@ may_get_next_line_error(char_u *whitep, char_u **arg, cctx_T *cctx) return OK; } - -// Structure passed between the compile_expr* functions to keep track of -// constants that have been parsed but for which no code was produced yet. If -// possible expressions on these constants are applied at compile time. If -// that is not possible, the code to push the constants needs to be generated -// before other instructions. -// Using 50 should be more than enough of 5 levels of (). -#define PPSIZE 50 -typedef struct { - typval_T pp_tv[PPSIZE]; // stack of ppconst constants - int pp_used; // active entries in pp_tv[] - int pp_is_const; // all generated code was constants, used for a - // list or dict with constant members -} ppconst_T; - -static int compile_expr0_ext(char_u **arg, cctx_T *cctx, int *is_const); -static int compile_expr0(char_u **arg, cctx_T *cctx); -static int compile_expr1(char_u **arg, cctx_T *cctx, ppconst_T *ppconst); - -/* - * Generate a PUSH instruction for "tv". - * "tv" will be consumed or cleared. - * Nothing happens if "tv" is NULL or of type VAR_UNKNOWN; - */ - static int -generate_tv_PUSH(cctx_T *cctx, typval_T *tv) -{ - if (tv != NULL) - { - switch (tv->v_type) - { - case VAR_UNKNOWN: - break; - case VAR_BOOL: - generate_PUSHBOOL(cctx, tv->vval.v_number); - break; - case VAR_SPECIAL: - generate_PUSHSPEC(cctx, tv->vval.v_number); - break; - case VAR_NUMBER: - generate_PUSHNR(cctx, tv->vval.v_number); - break; -#ifdef FEAT_FLOAT - case VAR_FLOAT: - generate_PUSHF(cctx, tv->vval.v_float); - break; -#endif - case VAR_BLOB: - generate_PUSHBLOB(cctx, tv->vval.v_blob); - tv->vval.v_blob = NULL; - break; - case VAR_STRING: - generate_PUSHS(cctx, &tv->vval.v_string); - tv->vval.v_string = NULL; - break; - default: - iemsg("constant type not supported"); - clear_tv(tv); - return FAIL; - } - tv->v_type = VAR_UNKNOWN; - } - return OK; -} - -/* - * Generate code for any ppconst entries. - */ - static 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. - */ - static void -clear_ppconst(ppconst_T *ppconst) -{ - int i; - - for (i = 0; i < ppconst->pp_used; ++i) - clear_tv(&ppconst->pp_tv[i]); - ppconst->pp_used = 0; -} - -/* - * Compile getting a member from a list/dict/string/blob. Stack has the - * indexable value and the index or the two indexes of a slice. - * "keeping_dict" is used for dict[func](arg) to pass dict to func. - */ - static int -compile_member(int is_slice, int *keeping_dict, cctx_T *cctx) -{ - type_T **typep; - garray_T *stack = &cctx->ctx_type_stack; - vartype_T vartype; - type_T *idxtype; - - // We can index a list, dict and blob. If we don't know the type - // we can use the index value type. If we still don't know use an "ANY" - // instruction. - typep = ((type_T **)stack->ga_data) + stack->ga_len - - (is_slice ? 3 : 2); - vartype = (*typep)->tt_type; - idxtype = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - // If the index is a string, the variable must be a Dict. - if (*typep == &t_any && idxtype == &t_string) - vartype = VAR_DICT; - if (vartype == VAR_STRING || vartype == VAR_LIST || vartype == VAR_BLOB) - { - if (need_type(idxtype, &t_number, -1, 0, cctx, FALSE, FALSE) == FAIL) - return FAIL; - if (is_slice) - { - idxtype = ((type_T **)stack->ga_data)[stack->ga_len - 2]; - if (need_type(idxtype, &t_number, -2, 0, cctx, - FALSE, FALSE) == FAIL) - return FAIL; - } - } - - if (vartype == VAR_DICT) - { - if (is_slice) - { - emsg(_(e_cannot_slice_dictionary)); - return FAIL; - } - if ((*typep)->tt_type == VAR_DICT) - { - *typep = (*typep)->tt_member; - if (*typep == &t_unknown) - // empty dict was used - *typep = &t_any; - } - else - { - if (need_type(*typep, &t_dict_any, -2, 0, cctx, - FALSE, FALSE) == FAIL) - return FAIL; - *typep = &t_any; - } - if (may_generate_2STRING(-1, FALSE, cctx) == FAIL) - return FAIL; - if (generate_instr_drop(cctx, ISN_MEMBER, 1) == FAIL) - return FAIL; - if (keeping_dict != NULL) - *keeping_dict = TRUE; - } - else if (vartype == VAR_STRING) - { - *typep = &t_string; - if ((is_slice - ? generate_instr_drop(cctx, ISN_STRSLICE, 2) - : generate_instr_drop(cctx, ISN_STRINDEX, 1)) == FAIL) - return FAIL; - } - else if (vartype == VAR_BLOB) - { - if (is_slice) - { - *typep = &t_blob; - if (generate_instr_drop(cctx, ISN_BLOBSLICE, 2) == FAIL) - return FAIL; - } - else - { - *typep = &t_number; - if (generate_instr_drop(cctx, ISN_BLOBINDEX, 1) == FAIL) - return FAIL; - } - } - else if (vartype == VAR_LIST || *typep == &t_any) - { - if (is_slice) - { - if (generate_instr_drop(cctx, - vartype == VAR_LIST ? ISN_LISTSLICE : ISN_ANYSLICE, - 2) == FAIL) - return FAIL; - } - else - { - if ((*typep)->tt_type == VAR_LIST) - { - *typep = (*typep)->tt_member; - if (*typep == &t_unknown) - // empty list was used - *typep = &t_any; - } - if (generate_instr_drop(cctx, - vartype == VAR_LIST ? ISN_LISTINDEX : ISN_ANYINDEX, 1) - == FAIL) - return FAIL; - } - } - else - { - switch (vartype) - { - case VAR_FUNC: - case VAR_PARTIAL: - emsg(_(e_cannot_index_a_funcref)); - break; - case VAR_BOOL: - case VAR_SPECIAL: - case VAR_JOB: - case VAR_CHANNEL: - case VAR_INSTR: - case VAR_UNKNOWN: - case VAR_ANY: - case VAR_VOID: - emsg(_(e_cannot_index_special_variable)); - break; - default: - emsg(_(e_string_list_dict_or_blob_required)); - } - return FAIL; - } - return OK; -} - -/* - * Generate an instruction to load script-local variable "name", without the - * leading "s:". - * Also finds imported variables. - */ - static int -compile_load_scriptvar( - cctx_T *cctx, - char_u *name, // variable NUL terminated - char_u *start, // start of variable - char_u **end, // end of variable - int error) // when TRUE may give error -{ - scriptitem_T *si; - int idx; - imported_T *import; - - if (!SCRIPT_ID_VALID(current_sctx.sc_sid)) - return FAIL; - si = SCRIPT_ITEM(current_sctx.sc_sid); - idx = get_script_item_idx(current_sctx.sc_sid, name, 0, cctx); - if (idx == -1 || si->sn_version != SCRIPT_VERSION_VIM9) - { - // variable is not in sn_var_vals: old style script. - return generate_OLDSCRIPT(cctx, ISN_LOADS, name, current_sctx.sc_sid, - &t_any); - } - if (idx >= 0) - { - svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + idx; - - generate_VIM9SCRIPT(cctx, ISN_LOADSCRIPT, - current_sctx.sc_sid, idx, sv->sv_type); - return OK; - } - - import = find_imported(name, 0, cctx); - if (import != NULL) - { - if (import->imp_flags & IMP_FLAGS_STAR) - { - char_u *p = skipwhite(*end); - char_u *exp_name; - int cc; - ufunc_T *ufunc; - type_T *type; - - // Used "import * as Name", need to lookup the member. - if (*p != '.') - { - semsg(_(e_expected_dot_after_name_str), start); - return FAIL; - } - ++p; - if (VIM_ISWHITE(*p)) - { - emsg(_(e_no_white_space_allowed_after_dot)); - return FAIL; - } - - // isolate one name - exp_name = p; - while (eval_isnamec(*p)) - ++p; - cc = *p; - *p = NUL; - - idx = find_exported(import->imp_sid, exp_name, &ufunc, &type, - cctx, TRUE); - *p = cc; - p = skipwhite(p); - *end = p; - - if (idx < 0) - { - if (*p == '(' && ufunc != NULL) - { - generate_PUSHFUNC(cctx, ufunc->uf_name, import->imp_type); - return OK; - } - return FAIL; - } - - generate_VIM9SCRIPT(cctx, ISN_LOADSCRIPT, - import->imp_sid, - idx, - type); - } - else if (import->imp_funcname != NULL) - generate_PUSHFUNC(cctx, import->imp_funcname, import->imp_type); - else - generate_VIM9SCRIPT(cctx, ISN_LOADSCRIPT, - import->imp_sid, - import->imp_var_vals_idx, - import->imp_type); - return OK; - } - - if (error) - semsg(_(e_item_not_found_str), name); - return FAIL; -} - - static int -generate_funcref(cctx_T *cctx, char_u *name) -{ - ufunc_T *ufunc = find_func(name, FALSE, cctx); - - if (ufunc == NULL) - return FAIL; - - // Need to compile any default values to get the argument types. - if (func_needs_compiling(ufunc, COMPILE_TYPE(ufunc)) - && compile_def_function(ufunc, TRUE, COMPILE_TYPE(ufunc), NULL) - == FAIL) - return FAIL; - return generate_PUSHFUNC(cctx, ufunc->uf_name, ufunc->uf_func_type); -} - -/* - * Compile a variable name into a load instruction. - * "end" points to just after the name. - * "is_expr" is TRUE when evaluating an expression, might be a funcref. - * When "error" is FALSE do not give an error when not found. - */ - static int -compile_load( - char_u **arg, - char_u *end_arg, - cctx_T *cctx, - int is_expr, - int error) -{ - type_T *type; - char_u *name = NULL; - char_u *end = end_arg; - int res = FAIL; - int prev_called_emsg = called_emsg; - - if (*(*arg + 1) == ':') - { - if (end <= *arg + 2) - { - isntype_T isn_type; - - // load dictionary of namespace - switch (**arg) - { - case 'g': isn_type = ISN_LOADGDICT; break; - case 'w': isn_type = ISN_LOADWDICT; break; - case 't': isn_type = ISN_LOADTDICT; break; - case 'b': isn_type = ISN_LOADBDICT; break; - default: - semsg(_(e_namespace_not_supported_str), *arg); - goto theend; - } - if (generate_instr_type(cctx, isn_type, &t_dict_any) == NULL) - goto theend; - res = OK; - } - else - { - isntype_T isn_type = ISN_DROP; - - // load namespaced variable - name = vim_strnsave(*arg + 2, end - (*arg + 2)); - if (name == NULL) - return FAIL; - - switch (**arg) - { - case 'v': res = generate_LOADV(cctx, name, error); - break; - case 's': if (is_expr && ASCII_ISUPPER(*name) - && find_func(name, FALSE, cctx) != NULL) - res = generate_funcref(cctx, name); - else - res = compile_load_scriptvar(cctx, name, - NULL, &end, error); - break; - case 'g': if (vim_strchr(name, AUTOLOAD_CHAR) == NULL) - { - if (is_expr && ASCII_ISUPPER(*name) - && find_func(name, FALSE, cctx) != NULL) - res = generate_funcref(cctx, name); - else - isn_type = ISN_LOADG; - } - else - { - isn_type = ISN_LOADAUTO; - vim_free(name); - name = vim_strnsave(*arg, end - *arg); - if (name == NULL) - return FAIL; - } - break; - case 'w': isn_type = ISN_LOADW; break; - case 't': isn_type = ISN_LOADT; break; - case 'b': isn_type = ISN_LOADB; break; - default: // cannot happen, just in case - semsg(_(e_namespace_not_supported_str), *arg); - goto theend; - } - if (isn_type != ISN_DROP) - { - // Global, Buffer-local, Window-local and Tabpage-local - // variables can be defined later, thus we don't check if it - // exists, give an error at runtime. - res = generate_LOAD(cctx, isn_type, 0, name, &t_any); - } - } - } - else - { - size_t len = end - *arg; - int idx; - int gen_load = FALSE; - int gen_load_outer = 0; - - name = vim_strnsave(*arg, end - *arg); - if (name == NULL) - return FAIL; - - if (vim_strchr(name, AUTOLOAD_CHAR) != NULL) - { - script_autoload(name, FALSE); - res = generate_LOAD(cctx, ISN_LOADAUTO, 0, name, &t_any); - } - else if (arg_exists(*arg, len, &idx, &type, &gen_load_outer, cctx) - == OK) - { - if (gen_load_outer == 0) - gen_load = TRUE; - } - else - { - lvar_T lvar; - - if (lookup_local(*arg, len, &lvar, cctx) == OK) - { - type = lvar.lv_type; - idx = lvar.lv_idx; - if (lvar.lv_from_outer != 0) - gen_load_outer = lvar.lv_from_outer; - else - gen_load = TRUE; - } - else - { - // "var" can be script-local even without using "s:" if it - // already exists in a Vim9 script or when it's imported. - if (script_var_exists(*arg, len, cctx) == OK - || find_imported(name, 0, cctx) != NULL) - res = compile_load_scriptvar(cctx, name, *arg, &end, FALSE); - - // When evaluating an expression and the name starts with an - // uppercase letter it can be a user defined function. - // generate_funcref() will fail if the function can't be found. - if (res == FAIL && is_expr && ASCII_ISUPPER(*name)) - res = generate_funcref(cctx, name); - } - } - if (gen_load) - res = generate_LOAD(cctx, ISN_LOAD, idx, NULL, type); - if (gen_load_outer > 0) - { - res = generate_LOADOUTER(cctx, idx, gen_load_outer, type); - cctx->ctx_outer_used = TRUE; - } - } - - *arg = end; - -theend: - if (res == FAIL && error && called_emsg == prev_called_emsg) - semsg(_(e_variable_not_found_str), name); - vim_free(name); - return res; -} - - static void -clear_instr_ga(garray_T *gap) -{ - int idx; - - for (idx = 0; idx < gap->ga_len; ++idx) - delete_instr(((isn_T *)gap->ga_data) + idx); - ga_clear(gap); -} - -/* - * Compile a string in a ISN_PUSHS instruction into an ISN_INSTR. - * Returns FAIL if compilation fails. - */ - static int -compile_string(isn_T *isn, cctx_T *cctx) -{ - char_u *s = isn->isn_arg.string; - garray_T save_ga = cctx->ctx_instr; - int expr_res; - int trailing_error; - int instr_count; - isn_T *instr = NULL; - - // Remove the string type from the stack. - --cctx->ctx_type_stack.ga_len; - - // Temporarily reset the list of instructions so that the jump labels are - // correct. - cctx->ctx_instr.ga_len = 0; - cctx->ctx_instr.ga_maxlen = 0; - cctx->ctx_instr.ga_data = NULL; - expr_res = compile_expr0(&s, cctx); - s = skipwhite(s); - trailing_error = *s != NUL; - - if (expr_res == FAIL || trailing_error - || GA_GROW_FAILS(&cctx->ctx_instr, 1)) - { - if (trailing_error) - semsg(_(e_trailing_arg), s); - clear_instr_ga(&cctx->ctx_instr); - cctx->ctx_instr = save_ga; - ++cctx->ctx_type_stack.ga_len; - return FAIL; - } - - // Move the generated instructions into the ISN_INSTR instruction, then - // restore the list of instructions. - instr_count = cctx->ctx_instr.ga_len; - instr = cctx->ctx_instr.ga_data; - instr[instr_count].isn_type = ISN_FINISH; - - cctx->ctx_instr = save_ga; - vim_free(isn->isn_arg.string); - isn->isn_type = ISN_INSTR; - isn->isn_arg.instr = instr; - return OK; -} - -/* - * Compile the argument expressions. - * "arg" points to just after the "(" and is advanced to after the ")" - */ - static int -compile_arguments(char_u **arg, cctx_T *cctx, int *argcount, int is_searchpair) -{ - char_u *p = *arg; - char_u *whitep = *arg; - int must_end = FALSE; - int instr_count; - - for (;;) - { - if (may_get_next_line(whitep, &p, cctx) == FAIL) - goto failret; - if (*p == ')') - { - *arg = p + 1; - return OK; - } - if (must_end) - { - semsg(_(e_missing_comma_before_argument_str), p); - return FAIL; - } - - instr_count = cctx->ctx_instr.ga_len; - if (compile_expr0(&p, cctx) == FAIL) - return FAIL; - ++*argcount; - - if (is_searchpair && *argcount == 5 - && cctx->ctx_instr.ga_len == instr_count + 1) - { - isn_T *isn = ((isn_T *)cctx->ctx_instr.ga_data) + instr_count; - - // {skip} argument of searchpair() can be compiled if not empty - if (isn->isn_type == ISN_PUSHS && *isn->isn_arg.string != NUL) - compile_string(isn, cctx); - } - - if (*p != ',' && *skipwhite(p) == ',') - { - semsg(_(e_no_white_space_allowed_before_str_str), ",", p); - p = skipwhite(p); - } - if (*p == ',') - { - ++p; - if (*p != NUL && !VIM_ISWHITE(*p)) - semsg(_(e_white_space_required_after_str_str), ",", p - 1); - } - else - must_end = TRUE; - whitep = p; - p = skipwhite(p); - } -failret: - emsg(_(e_missing_closing_paren)); - return FAIL; -} - -/* - * Compile a function call: name(arg1, arg2) - * "arg" points to "name", "arg + varlen" to the "(". - * "argcount_init" is 1 for "value->method()" - * Instructions: - * EVAL arg1 - * EVAL arg2 - * BCALL / DCALL / UCALL - */ - static int -compile_call( - char_u **arg, - size_t varlen, - cctx_T *cctx, - ppconst_T *ppconst, - int argcount_init) -{ - char_u *name = *arg; - char_u *p; - int argcount = argcount_init; - char_u namebuf[100]; - char_u fname_buf[FLEN_FIXED + 1]; - char_u *tofree = NULL; - int error = FCERR_NONE; - ufunc_T *ufunc = NULL; - int res = FAIL; - int is_autoload; - int is_searchpair; - - // We can evaluate "has('name')" at compile time. - // We always evaluate "exists_compiled()" at compile time. - if ((varlen == 3 && STRNCMP(*arg, "has", 3) == 0) - || (varlen == 15 && STRNCMP(*arg, "exists_compiled", 6) == 0)) - { - char_u *s = skipwhite(*arg + varlen + 1); - typval_T argvars[2]; - int is_has = **arg == 'h'; - - argvars[0].v_type = VAR_UNKNOWN; - if (*s == '"') - (void)eval_string(&s, &argvars[0], TRUE); - else if (*s == '\'') - (void)eval_lit_string(&s, &argvars[0], TRUE); - s = skipwhite(s); - if (*s == ')' && argvars[0].v_type == VAR_STRING - && ((is_has && !dynamic_feature(argvars[0].vval.v_string)) - || !is_has)) - { - typval_T *tv = &ppconst->pp_tv[ppconst->pp_used]; - - *arg = s + 1; - argvars[1].v_type = VAR_UNKNOWN; - tv->v_type = VAR_NUMBER; - tv->vval.v_number = 0; - if (is_has) - f_has(argvars, tv); - else - f_exists(argvars, tv); - clear_tv(&argvars[0]); - ++ppconst->pp_used; - return OK; - } - clear_tv(&argvars[0]); - if (!is_has) - { - emsg(_(e_argument_of_exists_compiled_must_be_literal_string)); - return FAIL; - } - } - - if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - - if (varlen >= sizeof(namebuf)) - { - semsg(_(e_name_too_long_str), name); - return FAIL; - } - vim_strncpy(namebuf, *arg, varlen); - name = fname_trans_sid(namebuf, fname_buf, &tofree, &error); - - // We handle the "skip" argument of searchpair() and searchpairpos() - // differently. - is_searchpair = (varlen == 6 && STRNCMP(*arg, "search", 6) == 0) - || (varlen == 9 && STRNCMP(*arg, "searchpos", 9) == 0) - || (varlen == 10 && STRNCMP(*arg, "searchpair", 10) == 0) - || (varlen == 13 && STRNCMP(*arg, "searchpairpos", 13) == 0); - - *arg = skipwhite(*arg + varlen + 1); - if (compile_arguments(arg, cctx, &argcount, is_searchpair) == FAIL) - goto theend; - - is_autoload = vim_strchr(name, AUTOLOAD_CHAR) != NULL; - if (ASCII_ISLOWER(*name) && name[1] != ':' && !is_autoload) - { - int idx; - - // builtin function - idx = find_internal_func(name); - if (idx >= 0) - { - if (STRCMP(name, "flatten") == 0) - { - emsg(_(e_cannot_use_flatten_in_vim9_script)); - goto theend; - } - - if (STRCMP(name, "add") == 0 && argcount == 2) - { - garray_T *stack = &cctx->ctx_type_stack; - type_T *type = ((type_T **)stack->ga_data)[ - stack->ga_len - 2]; - - // add() can be compiled to instructions if we know the type - if (type->tt_type == VAR_LIST) - { - // inline "add(list, item)" so that the type can be checked - res = generate_LISTAPPEND(cctx); - idx = -1; - } - else if (type->tt_type == VAR_BLOB) - { - // inline "add(blob, nr)" so that the type can be checked - res = generate_BLOBAPPEND(cctx); - idx = -1; - } - } - - if (idx >= 0) - res = generate_BCALL(cctx, idx, argcount, argcount_init == 1); - } - else - semsg(_(e_unknown_function_str), namebuf); - goto theend; - } - - // An argument or local variable can be a function reference, this - // overrules a function name. - if (lookup_local(namebuf, varlen, NULL, cctx) == FAIL - && arg_exists(namebuf, varlen, NULL, NULL, NULL, cctx) != OK) - { - // If we can find the function by name generate the right call. - // Skip global functions here, a local funcref takes precedence. - ufunc = find_func(name, FALSE, cctx); - if (ufunc != NULL && !func_is_global(ufunc)) - { - res = generate_CALL(cctx, ufunc, argcount); - goto theend; - } - } - - // If the name is a variable, load it and use PCALL. - // Not for g:Func(), we don't know if it is a variable or not. - // Not for eome#Func(), it will be loaded later. - p = namebuf; - if (STRNCMP(namebuf, "g:", 2) != 0 && !is_autoload - && compile_load(&p, namebuf + varlen, cctx, FALSE, FALSE) == OK) - { - garray_T *stack = &cctx->ctx_type_stack; - type_T *type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - - res = generate_PCALL(cctx, argcount, namebuf, type, FALSE); - goto theend; - } - - // If we can find a global function by name generate the right call. - if (ufunc != NULL) - { - res = generate_CALL(cctx, ufunc, argcount); - goto theend; - } - - // A global function may be defined only later. Need to figure out at - // runtime. Also handles a FuncRef at runtime. - if (STRNCMP(namebuf, "g:", 2) == 0 || is_autoload) - res = generate_UCALL(cctx, name, argcount); - else - semsg(_(e_unknown_function_str), namebuf); - -theend: - vim_free(tofree); - return res; -} - -// like NAMESPACE_CHAR but with 'a' and 'l'. -#define VIM9_NAMESPACE_CHAR (char_u *)"bgstvw" - -/* - * Find the end of a variable or function name. Unlike find_name_end() this - * does not recognize magic braces. - * When "use_namespace" is TRUE recognize "b:", "s:", etc. - * Return a pointer to just after the name. Equal to "arg" if there is no - * valid name. - */ - char_u * -to_name_end(char_u *arg, int use_namespace) -{ - char_u *p; - - // Quick check for valid starting character. - if (!eval_isnamec1(*arg)) - return arg; - - for (p = arg + 1; *p != NUL && eval_isnamec(*p); MB_PTR_ADV(p)) - // Include a namespace such as "s:var" and "v:var". But "n:" is not - // and can be used in slice "[n:]". - if (*p == ':' && (p != arg + 1 - || !use_namespace - || vim_strchr(VIM9_NAMESPACE_CHAR, *arg) == NULL)) - break; - return p; -} - -/* - * Like to_name_end() but also skip over a list or dict constant. - * Also accept "<SNR>123_Func". - * This intentionally does not handle line continuation. - */ - char_u * -to_name_const_end(char_u *arg) -{ - char_u *p = arg; - typval_T rettv; - - if (STRNCMP(p, "<SNR>", 5) == 0) - p = skipdigits(p + 5); - p = to_name_end(p, TRUE); - if (p == arg && *arg == '[') - { - - // Can be "[1, 2, 3]->Func()". - if (eval_list(&p, &rettv, NULL, FALSE) == FAIL) - p = arg; - } - return p; -} - -/* - * parse a list: [expr, expr] - * "*arg" points to the '['. - * ppconst->pp_is_const is set if all items are a constant. - */ - static int -compile_list(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) -{ - char_u *p = skipwhite(*arg + 1); - char_u *whitep = *arg + 1; - int count = 0; - int is_const; - int is_all_const = TRUE; // reset when non-const encountered - - for (;;) - { - if (may_get_next_line(whitep, &p, cctx) == FAIL) - { - semsg(_(e_list_end), *arg); - return FAIL; - } - if (*p == ',') - { - semsg(_(e_no_white_space_allowed_before_str_str), ",", p); - return FAIL; - } - if (*p == ']') - { - ++p; - break; - } - if (compile_expr0_ext(&p, cctx, &is_const) == FAIL) - return FAIL; - if (!is_const) - is_all_const = FALSE; - ++count; - if (*p == ',') - { - ++p; - if (*p != ']' && !IS_WHITE_OR_NUL(*p)) - { - semsg(_(e_white_space_required_after_str_str), ",", p - 1); - return FAIL; - } - } - whitep = p; - p = skipwhite(p); - } - *arg = p; - - ppconst->pp_is_const = is_all_const; - return generate_NEWLIST(cctx, count); -} - -/* - * Parse a lambda: "(arg, arg) => expr" - * "*arg" points to the '('. - * Returns OK/FAIL when a lambda is recognized, NOTDONE if it's not a lambda. - */ - static int -compile_lambda(char_u **arg, cctx_T *cctx) -{ - int r; - typval_T rettv; - ufunc_T *ufunc; - evalarg_T evalarg; - - init_evalarg(&evalarg); - evalarg.eval_flags = EVAL_EVALUATE; - evalarg.eval_cctx = cctx; - - // Get the funcref in "rettv". - r = get_lambda_tv(arg, &rettv, TRUE, &evalarg); - if (r != OK) - { - clear_evalarg(&evalarg, NULL); - return r; - } - - // "rettv" will now be a partial referencing the function. - ufunc = rettv.vval.v_partial->pt_func; - ++ufunc->uf_refcount; - clear_tv(&rettv); - - // Compile it here to get the return type. The return type is optional, - // when it's missing use t_unknown. This is recognized in - // compile_return(). - if (ufunc->uf_ret_type->tt_type == VAR_VOID) - ufunc->uf_ret_type = &t_unknown; - compile_def_function(ufunc, FALSE, cctx->ctx_compile_type, cctx); - - // When the outer function is compiled for profiling or debugging, the - // lambda may be called without profiling or debugging. Compile it here in - // the right context. - if (cctx->ctx_compile_type == CT_DEBUG -#ifdef FEAT_PROFILE - || cctx->ctx_compile_type == CT_PROFILE -#endif - ) - compile_def_function(ufunc, FALSE, CT_NONE, cctx); - - // The last entry in evalarg.eval_tofree_ga is a copy of the last line and - // "*arg" may point into it. Point into the original line to avoid a - // dangling pointer. - if (evalarg.eval_using_cmdline) - { - garray_T *gap = &evalarg.eval_tofree_ga; - size_t off = *arg - ((char_u **)gap->ga_data)[gap->ga_len - 1]; - - *arg = ((char_u **)cctx->ctx_ufunc->uf_lines.ga_data)[cctx->ctx_lnum] - + off; - } - - clear_evalarg(&evalarg, NULL); - - if (ufunc->uf_def_status == UF_COMPILED) - { - // The return type will now be known. - set_function_type(ufunc); - - // The function reference count will be 1. When the ISN_FUNCREF - // instruction is deleted the reference count is decremented and the - // function is freed. - return generate_FUNCREF(cctx, ufunc); - } - - func_ptr_unref(ufunc); - return FAIL; -} - -/* - * Get a lambda and compile it. Uses Vim9 syntax. - */ - int -get_lambda_tv_and_compile( - char_u **arg, - typval_T *rettv, - int types_optional, - evalarg_T *evalarg) -{ - int r; - ufunc_T *ufunc; - int save_sc_version = current_sctx.sc_version; - - // Get the funcref in "rettv". - current_sctx.sc_version = SCRIPT_VERSION_VIM9; - r = get_lambda_tv(arg, rettv, types_optional, evalarg); - current_sctx.sc_version = save_sc_version; - if (r != OK) - return r; - - // "rettv" will now be a partial referencing the function. - ufunc = rettv->vval.v_partial->pt_func; - - // Compile it here to get the return type. The return type is optional, - // when it's missing use t_unknown. This is recognized in - // compile_return(). - if (ufunc->uf_ret_type == NULL || ufunc->uf_ret_type->tt_type == VAR_VOID) - ufunc->uf_ret_type = &t_unknown; - compile_def_function(ufunc, FALSE, CT_NONE, NULL); - - if (ufunc->uf_def_status == UF_COMPILED) - { - // The return type will now be known. - set_function_type(ufunc); - return OK; - } - clear_tv(rettv); - return FAIL; -} - -/* - * parse a dict: {key: val, [key]: val} - * "*arg" points to the '{'. - * ppconst->pp_is_const is set if all item values are a constant. - */ - static int -compile_dict(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) -{ - garray_T *instr = &cctx->ctx_instr; - int count = 0; - dict_T *d = dict_alloc(); - dictitem_T *item; - char_u *whitep = *arg + 1; - char_u *p; - int is_const; - int is_all_const = TRUE; // reset when non-const encountered - - if (d == NULL) - return FAIL; - if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - for (;;) - { - char_u *key = NULL; - - if (may_get_next_line(whitep, arg, cctx) == FAIL) - { - *arg = NULL; - goto failret; - } - - if (**arg == '}') - break; - - if (**arg == '[') - { - isn_T *isn; - - // {[expr]: value} uses an evaluated key. - *arg = skipwhite(*arg + 1); - if (compile_expr0(arg, cctx) == FAIL) - return FAIL; - isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1; - if (isn->isn_type == ISN_PUSHNR) - { - char buf[NUMBUFLEN]; - - // Convert to string at compile time. - vim_snprintf(buf, NUMBUFLEN, "%lld", isn->isn_arg.number); - isn->isn_type = ISN_PUSHS; - isn->isn_arg.string = vim_strsave((char_u *)buf); - } - if (isn->isn_type == ISN_PUSHS) - key = isn->isn_arg.string; - else if (may_generate_2STRING(-1, FALSE, cctx) == FAIL) - return FAIL; - *arg = skipwhite(*arg); - if (**arg != ']') - { - emsg(_(e_missing_matching_bracket_after_dict_key)); - return FAIL; - } - ++*arg; - } - else - { - // {"name": value}, - // {'name': value}, - // {name: value} use "name" as a literal key - key = get_literal_key(arg); - if (key == NULL) - return FAIL; - if (generate_PUSHS(cctx, &key) == FAIL) - return FAIL; - } - - // Check for duplicate keys, if using string keys. - if (key != NULL) - { - item = dict_find(d, key, -1); - if (item != NULL) - { - semsg(_(e_duplicate_key), key); - goto failret; - } - item = dictitem_alloc(key); - if (item != NULL) - { - item->di_tv.v_type = VAR_UNKNOWN; - item->di_tv.v_lock = 0; - if (dict_add(d, item) == FAIL) - dictitem_free(item); - } - } - - if (**arg != ':') - { - if (*skipwhite(*arg) == ':') - semsg(_(e_no_white_space_allowed_before_str_str), ":", *arg); - else - semsg(_(e_missing_dict_colon), *arg); - return FAIL; - } - whitep = *arg + 1; - if (!IS_WHITE_OR_NUL(*whitep)) - { - semsg(_(e_white_space_required_after_str_str), ":", *arg); - return FAIL; - } - - if (may_get_next_line(whitep, arg, cctx) == FAIL) - { - *arg = NULL; - goto failret; - } - - if (compile_expr0_ext(arg, cctx, &is_const) == FAIL) - return FAIL; - if (!is_const) - is_all_const = FALSE; - ++count; - - whitep = *arg; - if (may_get_next_line(whitep, arg, cctx) == FAIL) - { - *arg = NULL; - goto failret; - } - if (**arg == '}') - break; - if (**arg != ',') - { - semsg(_(e_missing_dict_comma), *arg); - goto failret; - } - if (IS_WHITE_OR_NUL(*whitep)) - { - semsg(_(e_no_white_space_allowed_before_str_str), ",", whitep); - return FAIL; - } - whitep = *arg + 1; - if (!IS_WHITE_OR_NUL(*whitep)) - { - semsg(_(e_white_space_required_after_str_str), ",", *arg); - return FAIL; - } - *arg = skipwhite(whitep); - } - - *arg = *arg + 1; - - // Allow for following comment, after at least one space. - p = skipwhite(*arg); - if (VIM_ISWHITE(**arg) && vim9_comment_start(p)) - *arg += STRLEN(*arg); - - dict_unref(d); - ppconst->pp_is_const = is_all_const; - return generate_NEWDICT(cctx, count); - -failret: - if (*arg == NULL) - { - semsg(_(e_missing_dict_end), _("[end of lines]")); - *arg = (char_u *)""; - } - dict_unref(d); - return FAIL; -} - -/* - * Compile "&option". - */ - static int -compile_get_option(char_u **arg, cctx_T *cctx) -{ - typval_T rettv; - char_u *start = *arg; - int ret; - - // parse the option and get the current value to get the type. - rettv.v_type = VAR_UNKNOWN; - ret = eval_option(arg, &rettv, TRUE); - if (ret == OK) - { - // include the '&' in the name, eval_option() expects it. - char_u *name = vim_strnsave(start, *arg - start); - type_T *type = rettv.v_type == VAR_BOOL ? &t_bool - : rettv.v_type == VAR_NUMBER ? &t_number : &t_string; - - ret = generate_LOAD(cctx, ISN_LOADOPT, 0, name, type); - vim_free(name); - } - clear_tv(&rettv); - - return ret; -} - -/* - * Compile "$VAR". - */ - static int -compile_get_env(char_u **arg, cctx_T *cctx) -{ - char_u *start = *arg; - int len; - int ret; - char_u *name; - - ++*arg; - len = get_env_len(arg); - if (len == 0) - { - semsg(_(e_syntax_error_at_str), start - 1); - return FAIL; - } - - // include the '$' in the name, eval_env_var() expects it. - name = vim_strnsave(start, len + 1); - ret = generate_LOAD(cctx, ISN_LOADENV, 0, name, &t_string); - vim_free(name); - return ret; -} - -/* - * Compile "@r". - */ - static int -compile_get_register(char_u **arg, cctx_T *cctx) -{ - int ret; - - ++*arg; - if (**arg == NUL) - { - semsg(_(e_syntax_error_at_str), *arg - 1); - return FAIL; - } - if (!valid_yank_reg(**arg, FALSE)) - { - 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 -#ifdef FEAT_FLOAT - if (rettv->v_type == VAR_FLOAT) - { - if (*p == '-') - rettv->vval.v_float = -rettv->vval.v_float; - } - else -#endif - { - 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 (!isalnum(i) && i != '_') - { - type = *len == 2 ? EXPR_IS : EXPR_ISNOT; - *type_is = TRUE; - } - } - break; - } - return type; -} - -/* - * Skip over an expression, ignoring most errors. - */ - static 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); -} - -/* - * 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 == '+') - { - int negate = *p == '-'; - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - type_T *type; - - type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - if (type != &t_float && need_type(type, &t_number, - -1, 0, cctx, FALSE, FALSE) == FAIL) - return FAIL; - - while (p > start && (p[-1] == '-' || p[-1] == '+')) - { - --p; - if (*p == '-') - negate = !negate; - } - // only '-' has an effect, for '+' we only check the type - if (negate) - { - isn = generate_instr(cctx, ISN_NEGATENR); - if (isn == 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 (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 (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 (**arg == ')') - ++*arg; - else if (ret == OK) - { - emsg(_(e_missing_closing_paren)); - ret = FAIL; - } - return ret; -} - -/* - * 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); - - 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] == '{' - || 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 == '(') - { - garray_T *stack = &cctx->ctx_type_stack; - type_T *type; - int argcount = 0; - - if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - ppconst->pp_is_const = FALSE; - - // funcref(arg) - type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - - *arg = skipwhite(p + 1); - if (compile_arguments(arg, cctx, &argcount, FALSE) == 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; - - if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - ppconst->pp_is_const = FALSE; - - // something->method() - // 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 "->". - if (**arg == '(') - { - int argcount = 1; - garray_T *stack = &cctx->ctx_type_stack; - int type_idx_start = stack->ga_len; - type_T *type; - int expr_isn_start = cctx->ctx_instr.ga_len; - int expr_isn_end; - int arg_isn_count; - - // 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; - - // Remember the next instruction index, where the instructions - // for arguments are being written. - expr_isn_end = cctx->ctx_instr.ga_len; - - // Compile the arguments. - if (**arg != '(') - { - if (*skipwhite(*arg) == '(') - emsg(_(e_nowhitespace)); - else - semsg(_(e_missing_parenthesis_str), *arg); - return FAIL; - } - *arg = skipwhite(*arg + 1); - if (compile_arguments(arg, cctx, &argcount, FALSE) == 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. - stack = &cctx->ctx_type_stack; - 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); - - 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); - - type = ((type_T **)stack->ga_data)[type_idx_start]; - mch_memmove(((type_T **)stack->ga_data) + type_idx_start, - ((type_T **)stack->ga_data) + type_idx_start + 1, - sizeof(type_T *) - * (stack->ga_len - type_idx_start - 1)); - ((type_T **)stack->ga_data)[stack->ga_len - 1] = type; - } - - type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - if (generate_PCALL(cctx, argcount, p - 2, type, FALSE) == FAIL) - return FAIL; - } - else - { - // method call: list->method() - p = *arg; - if (!eval_isnamec1(*p)) - { - semsg(_(e_trailing_arg), pstart); - return FAIL; - } - if (ASCII_ISALPHA(*p) && p[1] == ':') - p += 2; - for ( ; eval_isnamec(*p); ++p) - ; - if (*p != '(') - { - semsg(_(e_missing_parenthesis_str), *arg); - return FAIL; - } - if (compile_call(arg, p - *arg, cctx, ppconst, 1) == 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] - // 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 (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; - - *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 && 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_expr7( - 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) == FAIL) - return FAIL; - break; - - /* - * Literal string constant: 'str''ing'. - */ - case '\'': if (eval_lit_string(arg, rettv, TRUE) == 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" constant - */ - case 'n': if (STRNCMP(*arg, "null", 4) == 0 - && !eval_isnamec((*arg)[4])) - { - *arg += 4; - rettv->v_type = VAR_SPECIAL; - rettv->vval.v_number = VVAL_NULL; - } - 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. - */ - case '$': if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - 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; - } - - if (*p == '(') - { - r = compile_call(arg, p - *arg, cctx, ppconst, 0); - } - else - { - if (cctx->ctx_skip != SKIP_YES - && generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - r = compile_load(arg, 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) - { - // 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; -} - -/* - * 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); -} - -/* - * <type>expr7: runtime type check / conversion - */ - static int -compile_expr7t(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, 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_expr7(arg, cctx, ppconst) == FAIL) - return FAIL; - - if (want_type != NULL) - { - garray_T *stack = &cctx->ctx_type_stack; - type_T *actual; - where_T where = WHERE_INIT; - - generate_ppconst(cctx, ppconst); - actual = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - if (check_type(want_type, actual, FALSE, where) == FAIL) - { - if (need_type(actual, want_type, -1, 0, cctx, FALSE, FALSE) == FAIL) - return FAIL; - } - } - - return OK; -} - -/* - * * number multiplication - * / number division - * % number modulo - */ - static int -compile_expr6(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_expr7t(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_expr7t(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_expr5(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_expr6(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_expr6(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, FALSE, cctx) == FAIL - || may_generate_2STRING(-1, FALSE, cctx) == FAIL) - return FAIL; - generate_instr_drop(cctx, ISN_CONCAT, 1); - } - else - generate_two_op(cctx, op); - } - } - - 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: - */ - static 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) - { - skip_expr_cctx(arg, cctx); - return OK; - } - - // 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 = ((type_T **)stack->ga_data)[stack->ga_len]; - } - - // 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 - --stack->ga_len; - type1 = ((type_T **)stack->ga_data)[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); - - // If the types differ, the result has a more generic type. - typep = ((type_T **)stack->ga_data) + stack->ga_len - 1; - 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. - */ - static 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. - */ - static int -compile_expr0(char_u **arg, cctx_T *cctx) -{ - return compile_expr0_ext(arg, cctx, NULL); -} - -/* - * Compile "return [expr]". - * When "legacy" is TRUE evaluate [expr] with legacy syntax - */ - static char_u * -compile_return(char_u *arg, int check_return_type, int legacy, cctx_T *cctx) -{ - char_u *p = arg; - garray_T *stack = &cctx->ctx_type_stack; - type_T *stack_type; - - if (*p != NUL && *p != '|' && *p != '\n') - { - if (legacy) - { - int save_flags = cmdmod.cmod_flags; - - generate_LEGACY_EVAL(cctx, p); - if (need_type(&t_any, cctx->ctx_ufunc->uf_ret_type, -1, - 0, cctx, FALSE, FALSE) == FAIL) - return NULL; - cmdmod.cmod_flags |= CMOD_LEGACY; - (void)skip_expr(&p, NULL); - cmdmod.cmod_flags = save_flags; - } - else - { - // compile return argument into instructions - if (compile_expr0(&p, cctx) == FAIL) - return NULL; - } - - if (cctx->ctx_skip != SKIP_YES) - { - // "check_return_type" with uf_ret_type set to &t_unknown is used - // for an inline function without a specified return type. Set the - // return type here. - stack_type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - if ((check_return_type && (cctx->ctx_ufunc->uf_ret_type == NULL - || cctx->ctx_ufunc->uf_ret_type == &t_unknown - || cctx->ctx_ufunc->uf_ret_type == &t_any)) - || (!check_return_type - && cctx->ctx_ufunc->uf_ret_type == &t_unknown)) - { - cctx->ctx_ufunc->uf_ret_type = stack_type; - } - else - { - if (cctx->ctx_ufunc->uf_ret_type->tt_type == VAR_VOID - && stack_type->tt_type != VAR_VOID - && stack_type->tt_type != VAR_UNKNOWN) - { - emsg(_(e_returning_value_in_function_without_return_type)); - return NULL; - } - if (need_type(stack_type, cctx->ctx_ufunc->uf_ret_type, -1, - 0, cctx, FALSE, FALSE) == FAIL) - return NULL; - } - } - } - else - { - // "check_return_type" cannot be TRUE, only used for a lambda which - // always has an argument. - if (cctx->ctx_ufunc->uf_ret_type->tt_type != VAR_VOID - && cctx->ctx_ufunc->uf_ret_type->tt_type != VAR_UNKNOWN) - { - emsg(_(e_missing_return_value)); - return NULL; - } - - // No argument, return zero. - generate_PUSHNR(cctx, 0); - } - - // Undo any command modifiers. - generate_undo_cmdmods(cctx); - - if (cctx->ctx_skip != SKIP_YES && generate_instr(cctx, ISN_RETURN) == NULL) - return NULL; - - // "return val | endif" is possible - return skipwhite(p); -} - /* * Get a line from the compilation context, compatible with exarg_T getline(). * Return a pointer to the line in allocated memory. @@ -5859,6 +769,44 @@ fill_exarg_from_cctx(exarg_T *eap, cctx_T *cctx) } /* + * Return TRUE if "ufunc" should be compiled, taking into account whether + * "profile" indicates profiling is to be done. + */ + int +func_needs_compiling(ufunc_T *ufunc, compiletype_T compile_type) +{ + switch (ufunc->uf_def_status) + { + case UF_TO_BE_COMPILED: + return TRUE; + + case UF_COMPILED: + { + dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + + ufunc->uf_dfunc_idx; + + switch (compile_type) + { + case CT_PROFILE: +#ifdef FEAT_PROFILE + return dfunc->df_instr_prof == NULL; +#endif + case CT_NONE: + return dfunc->df_instr == NULL; + case CT_DEBUG: + return dfunc->df_instr_debug == NULL; + } + } + + case UF_NOT_COMPILED: + case UF_COMPILE_ERROR: + case UF_COMPILING: + break; + } + return FALSE; +} + +/* * Compile a nested :def command. */ static char_u * @@ -6124,7 +1072,7 @@ vim9_declare_error(char_u *name) * "type" is set to the destination type if known, unchanted otherwise. * Return FAIL if an error message was given. */ - static int + int get_var_dest( char_u *name, assign_dest_T *dest, @@ -6243,102 +1191,6 @@ get_var_dest( return OK; } -/* - * Generate a STORE instruction for "dest", not being "dest_local". - * Return FAIL when out of memory. - */ - static int -generate_store_var( - cctx_T *cctx, - assign_dest_T dest, - int opt_flags, - int vimvaridx, - int scriptvar_idx, - int scriptvar_sid, - type_T *type, - char_u *name) -{ - switch (dest) - { - case dest_option: - return generate_STOREOPT(cctx, ISN_STOREOPT, - skip_option_env_lead(name), opt_flags); - case dest_func_option: - return generate_STOREOPT(cctx, ISN_STOREFUNCOPT, - skip_option_env_lead(name), opt_flags); - case dest_global: - // include g: with the name, easier to execute that way - return generate_STORE(cctx, vim_strchr(name, AUTOLOAD_CHAR) == NULL - ? ISN_STOREG : ISN_STOREAUTO, 0, name); - case dest_buffer: - // include b: with the name, easier to execute that way - return generate_STORE(cctx, ISN_STOREB, 0, name); - case dest_window: - // include w: with the name, easier to execute that way - return generate_STORE(cctx, ISN_STOREW, 0, name); - case dest_tab: - // include t: with the name, easier to execute that way - return generate_STORE(cctx, ISN_STORET, 0, name); - case dest_env: - return generate_STORE(cctx, ISN_STOREENV, 0, name + 1); - case dest_reg: - return generate_STORE(cctx, ISN_STOREREG, - name[1] == '@' ? '"' : name[1], NULL); - case dest_vimvar: - return generate_STORE(cctx, ISN_STOREV, vimvaridx, NULL); - case dest_script: - if (scriptvar_idx < 0) - // "s:" may be included in the name. - return generate_OLDSCRIPT(cctx, ISN_STORES, name, - scriptvar_sid, type); - return generate_VIM9SCRIPT(cctx, ISN_STORESCRIPT, - scriptvar_sid, scriptvar_idx, type); - case dest_local: - case dest_expr: - // cannot happen - break; - } - return FAIL; -} - - static int -generate_store_lhs(cctx_T *cctx, lhs_T *lhs, int instr_count) -{ - if (lhs->lhs_dest != dest_local) - return generate_store_var(cctx, lhs->lhs_dest, - lhs->lhs_opt_flags, lhs->lhs_vimvaridx, - lhs->lhs_scriptvar_idx, lhs->lhs_scriptvar_sid, - lhs->lhs_type, lhs->lhs_name); - - if (lhs->lhs_lvar != NULL) - { - garray_T *instr = &cctx->ctx_instr; - isn_T *isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1; - - // optimization: turn "var = 123" from ISN_PUSHNR + ISN_STORE into - // ISN_STORENR - if (lhs->lhs_lvar->lv_from_outer == 0 - && instr->ga_len == instr_count + 1 - && isn->isn_type == ISN_PUSHNR) - { - varnumber_T val = isn->isn_arg.number; - garray_T *stack = &cctx->ctx_type_stack; - - isn->isn_type = ISN_STORENR; - isn->isn_arg.storenr.stnr_idx = lhs->lhs_lvar->lv_idx; - isn->isn_arg.storenr.stnr_val = val; - if (stack->ga_len > 0) - --stack->ga_len; - } - else if (lhs->lhs_lvar->lv_from_outer > 0) - generate_STOREOUTER(cctx, lhs->lhs_lvar->lv_idx, - lhs->lhs_lvar->lv_from_outer); - else - generate_STORE(cctx, ISN_STORE, lhs->lhs_lvar->lv_idx, NULL); - } - return OK; -} - static int is_decl_command(int cmdidx) { @@ -6351,7 +1203,7 @@ is_decl_command(int cmdidx) * of ":unlet" with an index. * Returns OK or FAIL. */ - static int + int compile_lhs( char_u *var_start, lhs_T *lhs, @@ -6637,7 +1489,7 @@ compile_lhs( /* * Figure out the LHS and check a few errors. */ - static int + int compile_assign_lhs( char_u *var_start, lhs_T *lhs, @@ -6809,7 +1661,7 @@ compile_load_lhs( * Produce code for loading "lhs" and also take care of an index. * Return OK/FAIL. */ - static int + int compile_load_lhs_with_index(lhs_T *lhs, char_u *var_start, cctx_T *cctx) { compile_load_lhs(lhs, var_start, NULL, cctx); @@ -6841,7 +1693,7 @@ compile_load_lhs_with_index(lhs_T *lhs, char_u *var_start, cctx_T *cctx) * information in "lhs". * Returns OK or FAIL. */ - static int + int compile_assign_unlet( char_u *var_start, lhs_T *lhs, @@ -7519,2089 +2371,6 @@ may_compile_assignment(exarg_T *eap, char_u **line, cctx_T *cctx) return NOTDONE; } -/* - * Check if "name" can be "unlet". - */ - int -check_vim9_unlet(char_u *name) -{ - if (name[1] != ':' || vim_strchr((char_u *)"gwtb", *name) == NULL) - { - // "unlet s:var" is allowed in legacy script. - if (*name == 's' && !script_is_vim9()) - return OK; - semsg(_(e_cannot_unlet_str), name); - return FAIL; - } - return OK; -} - -/* - * Callback passed to ex_unletlock(). - */ - static int -compile_unlet( - lval_T *lvp, - char_u *name_end, - exarg_T *eap, - int deep UNUSED, - void *coookie) -{ - cctx_T *cctx = coookie; - char_u *p = lvp->ll_name; - int cc = *name_end; - int ret = OK; - - if (cctx->ctx_skip == SKIP_YES) - return OK; - - *name_end = NUL; - if (*p == '$') - { - // :unlet $ENV_VAR - ret = generate_UNLET(cctx, ISN_UNLETENV, p + 1, eap->forceit); - } - else if (vim_strchr(p, '.') != NULL || vim_strchr(p, '[') != NULL) - { - lhs_T lhs; - - // This is similar to assigning: lookup the list/dict, compile the - // idx/key. Then instead of storing the value unlet the item. - // unlet {list}[idx] - // unlet {dict}[key] dict.key - // - // Figure out the LHS type and other properties. - // - ret = compile_lhs(p, &lhs, CMD_unlet, FALSE, 0, cctx); - - // : unlet an indexed item - if (!lhs.lhs_has_index) - { - iemsg("called compile_lhs() without an index"); - ret = FAIL; - } - else - { - // Use the info in "lhs" to unlet the item at the index in the - // list or dict. - ret = compile_assign_unlet(p, &lhs, FALSE, &t_void, cctx); - } - - vim_free(lhs.lhs_name); - } - else if (check_vim9_unlet(p) == FAIL) - { - ret = FAIL; - } - else - { - // Normal name. Only supports g:, w:, t: and b: namespaces. - ret = generate_UNLET(cctx, ISN_UNLET, p, eap->forceit); - } - - *name_end = cc; - return ret; -} - -/* - * Callback passed to ex_unletlock(). - */ - static int -compile_lock_unlock( - lval_T *lvp, - char_u *name_end, - exarg_T *eap, - int deep UNUSED, - void *coookie) -{ - cctx_T *cctx = coookie; - int cc = *name_end; - char_u *p = lvp->ll_name; - int ret = OK; - size_t len; - char_u *buf; - isntype_T isn = ISN_EXEC; - - if (cctx->ctx_skip == SKIP_YES) - return OK; - - // Cannot use :lockvar and :unlockvar on local variables. - if (p[1] != ':') - { - char_u *end = find_name_end(p, NULL, NULL, FNE_CHECK_START); - - if (lookup_local(p, end - p, NULL, cctx) == OK) - { - char_u *s = p; - - if (*end != '.' && *end != '[') - { - emsg(_(e_cannot_lock_unlock_local_variable)); - return FAIL; - } - - // For "d.member" put the local variable on the stack, it will be - // passed to ex_lockvar() indirectly. - if (compile_load(&s, end, cctx, FALSE, FALSE) == FAIL) - return FAIL; - isn = ISN_LOCKUNLOCK; - } - } - - // Checking is done at runtime. - *name_end = NUL; - len = name_end - p + 20; - buf = alloc(len); - if (buf == NULL) - ret = FAIL; - else - { - vim_snprintf((char *)buf, len, "%s %s", - eap->cmdidx == CMD_lockvar ? "lockvar" : "unlockvar", - p); - ret = generate_EXEC_copy(cctx, isn, buf); - - vim_free(buf); - *name_end = cc; - } - return ret; -} - -/* - * compile "unlet var", "lock var" and "unlock var" - * "arg" points to "var". - */ - static char_u * -compile_unletlock(char_u *arg, exarg_T *eap, cctx_T *cctx) -{ - ex_unletlock(eap, arg, 0, GLV_NO_AUTOLOAD | GLV_COMPILING, - eap->cmdidx == CMD_unlet ? compile_unlet : compile_lock_unlock, - cctx); - return eap->nextcmd == NULL ? (char_u *)"" : eap->nextcmd; -} - -/* - * generate a jump to the ":endif"/":endfor"/":endwhile"/":finally"/":endtry". - */ - static int -compile_jump_to_end(endlabel_T **el, jumpwhen_T when, cctx_T *cctx) -{ - garray_T *instr = &cctx->ctx_instr; - endlabel_T *endlabel = ALLOC_CLEAR_ONE(endlabel_T); - - if (endlabel == NULL) - return FAIL; - endlabel->el_next = *el; - *el = endlabel; - endlabel->el_end_label = instr->ga_len; - - generate_JUMP(cctx, when, 0); - return OK; -} - - static void -compile_fill_jump_to_end(endlabel_T **el, int jump_where, cctx_T *cctx) -{ - garray_T *instr = &cctx->ctx_instr; - - while (*el != NULL) - { - endlabel_T *cur = (*el); - isn_T *isn; - - isn = ((isn_T *)instr->ga_data) + cur->el_end_label; - isn->isn_arg.jump.jump_where = jump_where; - *el = cur->el_next; - vim_free(cur); - } -} - - static void -compile_free_jump_to_end(endlabel_T **el) -{ - while (*el != NULL) - { - endlabel_T *cur = (*el); - - *el = cur->el_next; - vim_free(cur); - } -} - -/* - * Create a new scope and set up the generic items. - */ - static scope_T * -new_scope(cctx_T *cctx, scopetype_T type) -{ - scope_T *scope = ALLOC_CLEAR_ONE(scope_T); - - if (scope == NULL) - return NULL; - scope->se_outer = cctx->ctx_scope; - cctx->ctx_scope = scope; - scope->se_type = type; - scope->se_local_count = cctx->ctx_locals.ga_len; - return scope; -} - -/* - * Free the current scope and go back to the outer scope. - */ - static void -drop_scope(cctx_T *cctx) -{ - scope_T *scope = cctx->ctx_scope; - - if (scope == NULL) - { - iemsg("calling drop_scope() without a scope"); - return; - } - cctx->ctx_scope = scope->se_outer; - switch (scope->se_type) - { - case IF_SCOPE: - compile_free_jump_to_end(&scope->se_u.se_if.is_end_label); break; - case FOR_SCOPE: - compile_free_jump_to_end(&scope->se_u.se_for.fs_end_label); break; - case WHILE_SCOPE: - compile_free_jump_to_end(&scope->se_u.se_while.ws_end_label); break; - case TRY_SCOPE: - compile_free_jump_to_end(&scope->se_u.se_try.ts_end_label); break; - case NO_SCOPE: - case BLOCK_SCOPE: - break; - } - vim_free(scope); -} - -/* - * compile "if expr" - * - * "if expr" Produces instructions: - * EVAL expr Push result of "expr" - * JUMP_IF_FALSE end - * ... body ... - * end: - * - * "if expr | else" Produces instructions: - * EVAL expr Push result of "expr" - * JUMP_IF_FALSE else - * ... body ... - * JUMP_ALWAYS end - * else: - * ... body ... - * end: - * - * "if expr1 | elseif expr2 | else" Produces instructions: - * EVAL expr Push result of "expr" - * JUMP_IF_FALSE elseif - * ... body ... - * JUMP_ALWAYS end - * elseif: - * EVAL expr Push result of "expr" - * JUMP_IF_FALSE else - * ... body ... - * JUMP_ALWAYS end - * else: - * ... body ... - * end: - */ - static char_u * -compile_if(char_u *arg, cctx_T *cctx) -{ - char_u *p = arg; - garray_T *instr = &cctx->ctx_instr; - int instr_count = instr->ga_len; - scope_T *scope; - skip_T skip_save = cctx->ctx_skip; - ppconst_T ppconst; - - CLEAR_FIELD(ppconst); - if (compile_expr1(&p, cctx, &ppconst) == FAIL) - { - clear_ppconst(&ppconst); - return NULL; - } - if (!ends_excmd2(arg, skipwhite(p))) - { - semsg(_(e_trailing_arg), p); - return NULL; - } - if (cctx->ctx_skip == SKIP_YES) - clear_ppconst(&ppconst); - else if (instr->ga_len == instr_count && ppconst.pp_used == 1) - { - int error = FALSE; - int v; - - // The expression results in a constant. - v = tv_get_bool_chk(&ppconst.pp_tv[0], &error); - clear_ppconst(&ppconst); - if (error) - return NULL; - cctx->ctx_skip = v ? SKIP_NOT : SKIP_YES; - } - else - { - // Not a constant, generate instructions for the expression. - cctx->ctx_skip = SKIP_UNKNOWN; - if (generate_ppconst(cctx, &ppconst) == FAIL) - return NULL; - if (bool_on_stack(cctx) == FAIL) - return NULL; - } - - // CMDMOD_REV must come before the jump - generate_undo_cmdmods(cctx); - - scope = new_scope(cctx, IF_SCOPE); - if (scope == NULL) - return NULL; - scope->se_skip_save = skip_save; - // "is_had_return" will be reset if any block does not end in :return - scope->se_u.se_if.is_had_return = TRUE; - - if (cctx->ctx_skip == SKIP_UNKNOWN) - { - // "where" is set when ":elseif", "else" or ":endif" is found - scope->se_u.se_if.is_if_label = instr->ga_len; - generate_JUMP(cctx, JUMP_IF_FALSE, 0); - } - else - scope->se_u.se_if.is_if_label = -1; - -#ifdef FEAT_PROFILE - if (cctx->ctx_compile_type == CT_PROFILE && cctx->ctx_skip == SKIP_YES - && skip_save != SKIP_YES) - { - // generated a profile start, need to generate a profile end, since it - // won't be done after returning - cctx->ctx_skip = SKIP_NOT; - generate_instr(cctx, ISN_PROF_END); - cctx->ctx_skip = SKIP_YES; - } -#endif - - return p; -} - - static char_u * -compile_elseif(char_u *arg, cctx_T *cctx) -{ - char_u *p = arg; - garray_T *instr = &cctx->ctx_instr; - int instr_count; - isn_T *isn; - scope_T *scope = cctx->ctx_scope; - ppconst_T ppconst; - skip_T save_skip = cctx->ctx_skip; - - if (scope == NULL || scope->se_type != IF_SCOPE) - { - emsg(_(e_elseif_without_if)); - return NULL; - } - unwind_locals(cctx, scope->se_local_count); - if (!cctx->ctx_had_return) - scope->se_u.se_if.is_had_return = FALSE; - - if (cctx->ctx_skip == SKIP_NOT) - { - // previous block was executed, this one and following will not - cctx->ctx_skip = SKIP_YES; - scope->se_u.se_if.is_seen_skip_not = TRUE; - } - if (scope->se_u.se_if.is_seen_skip_not) - { - // A previous block was executed, skip over expression and bail out. - // Do not count the "elseif" for profiling and cmdmod - instr->ga_len = current_instr_idx(cctx); - - skip_expr_cctx(&p, cctx); - return p; - } - - if (cctx->ctx_skip == SKIP_UNKNOWN) - { - int moved_cmdmod = FALSE; - int saved_debug = FALSE; - isn_T debug_isn; - - // Move any CMDMOD instruction to after the jump - if (((isn_T *)instr->ga_data)[instr->ga_len - 1].isn_type == ISN_CMDMOD) - { - if (GA_GROW_FAILS(instr, 1)) - return NULL; - ((isn_T *)instr->ga_data)[instr->ga_len] = - ((isn_T *)instr->ga_data)[instr->ga_len - 1]; - --instr->ga_len; - moved_cmdmod = TRUE; - } - - // Remove the already generated ISN_DEBUG, it is written below the - // ISN_FOR instruction. - if (cctx->ctx_compile_type == CT_DEBUG && instr->ga_len > 0 - && ((isn_T *)instr->ga_data)[instr->ga_len - 1] - .isn_type == ISN_DEBUG) - { - --instr->ga_len; - debug_isn = ((isn_T *)instr->ga_data)[instr->ga_len]; - saved_debug = TRUE; - } - - if (compile_jump_to_end(&scope->se_u.se_if.is_end_label, - JUMP_ALWAYS, cctx) == FAIL) - return NULL; - // previous "if" or "elseif" jumps here - isn = ((isn_T *)instr->ga_data) + scope->se_u.se_if.is_if_label; - isn->isn_arg.jump.jump_where = instr->ga_len; - - if (moved_cmdmod) - ++instr->ga_len; - - if (saved_debug) - { - // move the debug instruction here - if (GA_GROW_FAILS(instr, 1)) - return NULL; - ((isn_T *)instr->ga_data)[instr->ga_len] = debug_isn; - ++instr->ga_len; - } - } - - // compile "expr"; if we know it evaluates to FALSE skip the block - CLEAR_FIELD(ppconst); - if (cctx->ctx_skip == SKIP_YES) - { - cctx->ctx_skip = SKIP_UNKNOWN; -#ifdef FEAT_PROFILE - if (cctx->ctx_compile_type == CT_PROFILE) - // the previous block was skipped, need to profile this line - generate_instr(cctx, ISN_PROF_START); -#endif - if (cctx->ctx_compile_type == CT_DEBUG) - // the previous block was skipped, may want to debug this line - generate_instr_debug(cctx); - } - - instr_count = instr->ga_len; - if (compile_expr1(&p, cctx, &ppconst) == FAIL) - { - clear_ppconst(&ppconst); - return NULL; - } - cctx->ctx_skip = save_skip; - if (!ends_excmd2(arg, skipwhite(p))) - { - clear_ppconst(&ppconst); - semsg(_(e_trailing_arg), p); - return NULL; - } - if (scope->se_skip_save == SKIP_YES) - clear_ppconst(&ppconst); - else if (instr->ga_len == instr_count && ppconst.pp_used == 1) - { - int error = FALSE; - int v; - - // The expression result is a constant. - v = tv_get_bool_chk(&ppconst.pp_tv[0], &error); - if (error) - { - clear_ppconst(&ppconst); - return NULL; - } - cctx->ctx_skip = v ? SKIP_NOT : SKIP_YES; - clear_ppconst(&ppconst); - scope->se_u.se_if.is_if_label = -1; - } - else - { - // Not a constant, generate instructions for the expression. - cctx->ctx_skip = SKIP_UNKNOWN; - if (generate_ppconst(cctx, &ppconst) == FAIL) - return NULL; - if (bool_on_stack(cctx) == FAIL) - return NULL; - - // CMDMOD_REV must come before the jump - generate_undo_cmdmods(cctx); - - // "where" is set when ":elseif", "else" or ":endif" is found - scope->se_u.se_if.is_if_label = instr->ga_len; - generate_JUMP(cctx, JUMP_IF_FALSE, 0); - } - - return p; -} - - static char_u * -compile_else(char_u *arg, cctx_T *cctx) -{ - char_u *p = arg; - garray_T *instr = &cctx->ctx_instr; - isn_T *isn; - scope_T *scope = cctx->ctx_scope; - - if (scope == NULL || scope->se_type != IF_SCOPE) - { - emsg(_(e_else_without_if)); - return NULL; - } - unwind_locals(cctx, scope->se_local_count); - if (!cctx->ctx_had_return) - scope->se_u.se_if.is_had_return = FALSE; - scope->se_u.se_if.is_seen_else = TRUE; - -#ifdef FEAT_PROFILE - if (cctx->ctx_compile_type == CT_PROFILE) - { - if (cctx->ctx_skip == SKIP_NOT - && ((isn_T *)instr->ga_data)[instr->ga_len - 1] - .isn_type == ISN_PROF_START) - // the previous block was executed, do not count "else" for - // profiling - --instr->ga_len; - if (cctx->ctx_skip == SKIP_YES && !scope->se_u.se_if.is_seen_skip_not) - { - // the previous block was not executed, this one will, do count the - // "else" for profiling - cctx->ctx_skip = SKIP_NOT; - generate_instr(cctx, ISN_PROF_END); - generate_instr(cctx, ISN_PROF_START); - cctx->ctx_skip = SKIP_YES; - } - } -#endif - - if (!scope->se_u.se_if.is_seen_skip_not && scope->se_skip_save != SKIP_YES) - { - // jump from previous block to the end, unless the else block is empty - if (cctx->ctx_skip == SKIP_UNKNOWN) - { - if (!cctx->ctx_had_return - && compile_jump_to_end(&scope->se_u.se_if.is_end_label, - JUMP_ALWAYS, cctx) == FAIL) - return NULL; - } - - if (cctx->ctx_skip == SKIP_UNKNOWN) - { - if (scope->se_u.se_if.is_if_label >= 0) - { - // previous "if" or "elseif" jumps here - isn = ((isn_T *)instr->ga_data) + scope->se_u.se_if.is_if_label; - isn->isn_arg.jump.jump_where = instr->ga_len; - scope->se_u.se_if.is_if_label = -1; - } - } - - if (cctx->ctx_skip != SKIP_UNKNOWN) - cctx->ctx_skip = cctx->ctx_skip == SKIP_YES ? SKIP_NOT : SKIP_YES; - } - - return p; -} - - static char_u * -compile_endif(char_u *arg, cctx_T *cctx) -{ - scope_T *scope = cctx->ctx_scope; - ifscope_T *ifscope; - garray_T *instr = &cctx->ctx_instr; - isn_T *isn; - - if (misplaced_cmdmod(cctx)) - return NULL; - - if (scope == NULL || scope->se_type != IF_SCOPE) - { - emsg(_(e_endif_without_if)); - return NULL; - } - ifscope = &scope->se_u.se_if; - unwind_locals(cctx, scope->se_local_count); - if (!cctx->ctx_had_return) - ifscope->is_had_return = FALSE; - - if (scope->se_u.se_if.is_if_label >= 0) - { - // previous "if" or "elseif" jumps here - isn = ((isn_T *)instr->ga_data) + scope->se_u.se_if.is_if_label; - isn->isn_arg.jump.jump_where = instr->ga_len; - } - // Fill in the "end" label in jumps at the end of the blocks. - compile_fill_jump_to_end(&ifscope->is_end_label, instr->ga_len, cctx); - -#ifdef FEAT_PROFILE - // even when skipping we count the endif as executed, unless the block it's - // in is skipped - if (cctx->ctx_compile_type == CT_PROFILE && cctx->ctx_skip == SKIP_YES - && scope->se_skip_save != SKIP_YES) - { - cctx->ctx_skip = SKIP_NOT; - generate_instr(cctx, ISN_PROF_START); - } -#endif - cctx->ctx_skip = scope->se_skip_save; - - // If all the blocks end in :return and there is an :else then the - // had_return flag is set. - cctx->ctx_had_return = ifscope->is_had_return && ifscope->is_seen_else; - - drop_scope(cctx); - return arg; -} - -/* - * Compile "for var in expr": - * - * Produces instructions: - * PUSHNR -1 - * STORE loop-idx Set index to -1 - * EVAL expr result of "expr" on top of stack - * top: FOR loop-idx, end Increment index, use list on bottom of stack - * - if beyond end, jump to "end" - * - otherwise get item from list and push it - * STORE var Store item in "var" - * ... body ... - * JUMP top Jump back to repeat - * end: DROP Drop the result of "expr" - * - * Compile "for [var1, var2] in expr" - as above, but instead of "STORE var": - * UNPACK 2 Split item in 2 - * STORE var1 Store item in "var1" - * STORE var2 Store item in "var2" - */ - static char_u * -compile_for(char_u *arg_start, cctx_T *cctx) -{ - char_u *arg; - char_u *arg_end; - char_u *name = NULL; - char_u *p; - char_u *wp; - int var_count = 0; - int var_list = FALSE; - int semicolon = FALSE; - size_t varlen; - garray_T *stack = &cctx->ctx_type_stack; - garray_T *instr = &cctx->ctx_instr; - scope_T *scope; - lvar_T *loop_lvar; // loop iteration variable - lvar_T *var_lvar; // variable for "var" - type_T *vartype; - type_T *item_type = &t_any; - int idx; - int prev_lnum = cctx->ctx_prev_lnum; - - p = skip_var_list(arg_start, TRUE, &var_count, &semicolon, FALSE); - if (p == NULL) - return NULL; - if (var_count == 0) - var_count = 1; - else - var_list = TRUE; // can also be a list of one variable - - // consume "in" - wp = p; - if (may_get_next_line_error(wp, &p, cctx) == FAIL) - return NULL; - if (STRNCMP(p, "in", 2) != 0 || !IS_WHITE_OR_NUL(p[2])) - { - if (*p == ':' && wp != p) - semsg(_(e_no_white_space_allowed_before_colon_str), p); - else - emsg(_(e_missing_in)); - return NULL; - } - wp = p + 2; - if (may_get_next_line_error(wp, &p, cctx) == FAIL) - return NULL; - - // Remove the already generated ISN_DEBUG, it is written below the ISN_FOR - // instruction. - if (cctx->ctx_compile_type == CT_DEBUG && instr->ga_len > 0 - && ((isn_T *)instr->ga_data)[instr->ga_len - 1] - .isn_type == ISN_DEBUG) - { - --instr->ga_len; - prev_lnum = ((isn_T *)instr->ga_data)[instr->ga_len] - .isn_arg.debug.dbg_break_lnum; - } - - scope = new_scope(cctx, FOR_SCOPE); - if (scope == NULL) - return NULL; - - // Reserve a variable to store the loop iteration counter and initialize it - // to -1. - loop_lvar = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number); - if (loop_lvar == NULL) - { - // out of memory - drop_scope(cctx); - return NULL; - } - generate_STORENR(cctx, loop_lvar->lv_idx, -1); - - // compile "expr", it remains on the stack until "endfor" - arg = p; - if (compile_expr0(&arg, cctx) == FAIL) - { - drop_scope(cctx); - return NULL; - } - arg_end = arg; - - if (cctx->ctx_skip != SKIP_YES) - { - // If we know the type of "var" and it is a not a supported type we can - // give an error now. - vartype = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - if (vartype->tt_type != VAR_LIST && vartype->tt_type != VAR_STRING - && vartype->tt_type != VAR_BLOB && vartype->tt_type != VAR_ANY) - { - semsg(_(e_for_loop_on_str_not_supported), - vartype_name(vartype->tt_type)); - drop_scope(cctx); - return NULL; - } - - if (vartype->tt_type == VAR_STRING) - item_type = &t_string; - else if (vartype->tt_type == VAR_BLOB) - item_type = &t_number; - else if (vartype->tt_type == VAR_LIST - && vartype->tt_member->tt_type != VAR_ANY) - { - if (!var_list) - item_type = vartype->tt_member; - else if (vartype->tt_member->tt_type == VAR_LIST - && vartype->tt_member->tt_member->tt_type != VAR_ANY) - item_type = vartype->tt_member->tt_member; - } - - // CMDMOD_REV must come before the FOR instruction. - generate_undo_cmdmods(cctx); - - // "for_end" is set when ":endfor" is found - scope->se_u.se_for.fs_top_label = current_instr_idx(cctx); - - generate_FOR(cctx, loop_lvar->lv_idx); - - arg = arg_start; - if (var_list) - { - generate_UNPACK(cctx, var_count, semicolon); - arg = skipwhite(arg + 1); // skip white after '[' - - // the list item is replaced by a number of items - if (GA_GROW_FAILS(stack, var_count - 1)) - { - drop_scope(cctx); - return NULL; - } - --stack->ga_len; - for (idx = 0; idx < var_count; ++idx) - { - ((type_T **)stack->ga_data)[stack->ga_len] = - (semicolon && idx == 0) ? vartype : item_type; - ++stack->ga_len; - } - } - - for (idx = 0; idx < var_count; ++idx) - { - assign_dest_T dest = dest_local; - int opt_flags = 0; - int vimvaridx = -1; - type_T *type = &t_any; - type_T *lhs_type = &t_any; - where_T where = WHERE_INIT; - - p = skip_var_one(arg, FALSE); - varlen = p - arg; - name = vim_strnsave(arg, varlen); - if (name == NULL) - goto failed; - if (*p == ':') - { - p = skipwhite(p + 1); - lhs_type = parse_type(&p, cctx->ctx_type_list, TRUE); - } - - if (get_var_dest(name, &dest, CMD_for, &opt_flags, - &vimvaridx, &type, cctx) == FAIL) - goto failed; - if (dest != dest_local) - { - if (generate_store_var(cctx, dest, opt_flags, vimvaridx, - 0, 0, type, name) == FAIL) - goto failed; - } - else if (varlen == 1 && *arg == '_') - { - // Assigning to "_": drop the value. - if (generate_instr_drop(cctx, ISN_DROP, 1) == NULL) - goto failed; - } - else - { - // Script var is not supported. - if (STRNCMP(name, "s:", 2) == 0) - { - emsg(_(e_cannot_use_script_variable_in_for_loop)); - goto failed; - } - - if (!valid_varname(arg, (int)varlen, FALSE)) - goto failed; - if (lookup_local(arg, varlen, NULL, cctx) == OK) - { - semsg(_(e_variable_already_declared), arg); - goto failed; - } - - // Reserve a variable to store "var". - where.wt_index = var_list ? idx + 1 : 0; - where.wt_variable = TRUE; - if (lhs_type == &t_any) - lhs_type = item_type; - else if (item_type != &t_unknown - && (item_type == &t_any - ? need_type(item_type, lhs_type, - -1, 0, cctx, FALSE, FALSE) - : check_type(lhs_type, item_type, TRUE, where)) - == FAIL) - goto failed; - var_lvar = reserve_local(cctx, arg, varlen, TRUE, lhs_type); - if (var_lvar == NULL) - // out of memory or used as an argument - goto failed; - - if (semicolon && idx == var_count - 1) - var_lvar->lv_type = vartype; - else - var_lvar->lv_type = item_type; - generate_STORE(cctx, ISN_STORE, var_lvar->lv_idx, NULL); - } - - if (*p == ',' || *p == ';') - ++p; - arg = skipwhite(p); - vim_free(name); - } - - if (cctx->ctx_compile_type == CT_DEBUG) - { - int save_prev_lnum = cctx->ctx_prev_lnum; - - // Add ISN_DEBUG here, so that the loop variables can be inspected. - // Use the prev_lnum from the ISN_DEBUG instruction removed above. - cctx->ctx_prev_lnum = prev_lnum; - generate_instr_debug(cctx); - cctx->ctx_prev_lnum = save_prev_lnum; - } - } - - return arg_end; - -failed: - vim_free(name); - drop_scope(cctx); - return NULL; -} - -/* - * compile "endfor" - */ - static char_u * -compile_endfor(char_u *arg, cctx_T *cctx) -{ - garray_T *instr = &cctx->ctx_instr; - scope_T *scope = cctx->ctx_scope; - forscope_T *forscope; - isn_T *isn; - - if (misplaced_cmdmod(cctx)) - return NULL; - - if (scope == NULL || scope->se_type != FOR_SCOPE) - { - emsg(_(e_for)); - return NULL; - } - forscope = &scope->se_u.se_for; - cctx->ctx_scope = scope->se_outer; - if (cctx->ctx_skip != SKIP_YES) - { - unwind_locals(cctx, scope->se_local_count); - - // At end of ":for" scope jump back to the FOR instruction. - generate_JUMP(cctx, JUMP_ALWAYS, forscope->fs_top_label); - - // Fill in the "end" label in the FOR statement so it can jump here. - isn = ((isn_T *)instr->ga_data) + forscope->fs_top_label; - isn->isn_arg.forloop.for_end = instr->ga_len; - - // Fill in the "end" label any BREAK statements - compile_fill_jump_to_end(&forscope->fs_end_label, instr->ga_len, cctx); - - // Below the ":for" scope drop the "expr" list from the stack. - if (generate_instr_drop(cctx, ISN_DROP, 1) == NULL) - return NULL; - } - - vim_free(scope); - - return arg; -} - -/* - * compile "while expr" - * - * Produces instructions: - * top: EVAL expr Push result of "expr" - * JUMP_IF_FALSE end jump if false - * ... body ... - * JUMP top Jump back to repeat - * end: - * - */ - static char_u * -compile_while(char_u *arg, cctx_T *cctx) -{ - char_u *p = arg; - scope_T *scope; - - scope = new_scope(cctx, WHILE_SCOPE); - if (scope == NULL) - return NULL; - - // "endwhile" jumps back here, one before when profiling or using cmdmods - scope->se_u.se_while.ws_top_label = current_instr_idx(cctx); - - // compile "expr" - if (compile_expr0(&p, cctx) == FAIL) - return NULL; - - if (!ends_excmd2(arg, skipwhite(p))) - { - semsg(_(e_trailing_arg), p); - return NULL; - } - - if (cctx->ctx_skip != SKIP_YES) - { - if (bool_on_stack(cctx) == FAIL) - return FAIL; - - // CMDMOD_REV must come before the jump - generate_undo_cmdmods(cctx); - - // "while_end" is set when ":endwhile" is found - if (compile_jump_to_end(&scope->se_u.se_while.ws_end_label, - JUMP_IF_FALSE, cctx) == FAIL) - return FAIL; - } - - return p; -} - -/* - * compile "endwhile" - */ - static char_u * -compile_endwhile(char_u *arg, cctx_T *cctx) -{ - scope_T *scope = cctx->ctx_scope; - garray_T *instr = &cctx->ctx_instr; - - if (misplaced_cmdmod(cctx)) - return NULL; - if (scope == NULL || scope->se_type != WHILE_SCOPE) - { - emsg(_(e_while)); - return NULL; - } - cctx->ctx_scope = scope->se_outer; - if (cctx->ctx_skip != SKIP_YES) - { - unwind_locals(cctx, scope->se_local_count); - -#ifdef FEAT_PROFILE - // count the endwhile before jumping - may_generate_prof_end(cctx, cctx->ctx_lnum); -#endif - - // At end of ":for" scope jump back to the FOR instruction. - generate_JUMP(cctx, JUMP_ALWAYS, scope->se_u.se_while.ws_top_label); - - // Fill in the "end" label in the WHILE statement so it can jump here. - // And in any jumps for ":break" - compile_fill_jump_to_end(&scope->se_u.se_while.ws_end_label, - instr->ga_len, cctx); - } - - vim_free(scope); - - return arg; -} - -/* - * compile "continue" - */ - static char_u * -compile_continue(char_u *arg, cctx_T *cctx) -{ - scope_T *scope = cctx->ctx_scope; - int try_scopes = 0; - int loop_label; - - for (;;) - { - if (scope == NULL) - { - emsg(_(e_continue)); - return NULL; - } - if (scope->se_type == FOR_SCOPE) - { - loop_label = scope->se_u.se_for.fs_top_label; - break; - } - if (scope->se_type == WHILE_SCOPE) - { - loop_label = scope->se_u.se_while.ws_top_label; - break; - } - if (scope->se_type == TRY_SCOPE) - ++try_scopes; - scope = scope->se_outer; - } - - if (try_scopes > 0) - // Inside one or more try/catch blocks we first need to jump to the - // "finally" or "endtry" to cleanup. - generate_TRYCONT(cctx, try_scopes, loop_label); - else - // Jump back to the FOR or WHILE instruction. - generate_JUMP(cctx, JUMP_ALWAYS, loop_label); - - return arg; -} - -/* - * compile "break" - */ - static char_u * -compile_break(char_u *arg, cctx_T *cctx) -{ - scope_T *scope = cctx->ctx_scope; - endlabel_T **el; - - for (;;) - { - if (scope == NULL) - { - emsg(_(e_break)); - return NULL; - } - if (scope->se_type == FOR_SCOPE || scope->se_type == WHILE_SCOPE) - break; - scope = scope->se_outer; - } - - // Jump to the end of the FOR or WHILE loop. - if (scope->se_type == FOR_SCOPE) - el = &scope->se_u.se_for.fs_end_label; - else - el = &scope->se_u.se_while.ws_end_label; - if (compile_jump_to_end(el, JUMP_ALWAYS, cctx) == FAIL) - return FAIL; - - return arg; -} - -/* - * compile "{" start of block - */ - static char_u * -compile_block(char_u *arg, cctx_T *cctx) -{ - if (new_scope(cctx, BLOCK_SCOPE) == NULL) - return NULL; - return skipwhite(arg + 1); -} - -/* - * compile end of block: drop one scope - */ - static void -compile_endblock(cctx_T *cctx) -{ - scope_T *scope = cctx->ctx_scope; - - cctx->ctx_scope = scope->se_outer; - unwind_locals(cctx, scope->se_local_count); - vim_free(scope); -} - -/* - * Compile "try". - * Creates a new scope for the try-endtry, pointing to the first catch and - * finally. - * Creates another scope for the "try" block itself. - * TRY instruction sets up exception handling at runtime. - * - * "try" - * TRY -> catch1, -> finally push trystack entry - * ... try block - * "throw {exception}" - * EVAL {exception} - * THROW create exception - * ... try block - * " catch {expr}" - * JUMP -> finally - * catch1: PUSH exception - * EVAL {expr} - * MATCH - * JUMP nomatch -> catch2 - * CATCH remove exception - * ... catch block - * " catch" - * JUMP -> finally - * catch2: CATCH remove exception - * ... catch block - * " finally" - * finally: - * ... finally block - * " endtry" - * ENDTRY pop trystack entry, may rethrow - */ - static char_u * -compile_try(char_u *arg, cctx_T *cctx) -{ - garray_T *instr = &cctx->ctx_instr; - scope_T *try_scope; - scope_T *scope; - - if (misplaced_cmdmod(cctx)) - return NULL; - - // scope that holds the jumps that go to catch/finally/endtry - try_scope = new_scope(cctx, TRY_SCOPE); - if (try_scope == NULL) - return NULL; - - if (cctx->ctx_skip != SKIP_YES) - { - isn_T *isn; - - // "try_catch" is set when the first ":catch" is found or when no catch - // is found and ":finally" is found. - // "try_finally" is set when ":finally" is found - // "try_endtry" is set when ":endtry" is found - try_scope->se_u.se_try.ts_try_label = instr->ga_len; - if ((isn = generate_instr(cctx, ISN_TRY)) == NULL) - return NULL; - isn->isn_arg.try.try_ref = ALLOC_CLEAR_ONE(tryref_T); - if (isn->isn_arg.try.try_ref == NULL) - return NULL; - } - - // scope for the try block itself - scope = new_scope(cctx, BLOCK_SCOPE); - if (scope == NULL) - return NULL; - - return arg; -} - -/* - * Compile "catch {expr}". - */ - static char_u * -compile_catch(char_u *arg, cctx_T *cctx UNUSED) -{ - scope_T *scope = cctx->ctx_scope; - garray_T *instr = &cctx->ctx_instr; - char_u *p; - isn_T *isn; - - if (misplaced_cmdmod(cctx)) - return NULL; - - // end block scope from :try or :catch - if (scope != NULL && scope->se_type == BLOCK_SCOPE) - compile_endblock(cctx); - scope = cctx->ctx_scope; - - // Error if not in a :try scope - if (scope == NULL || scope->se_type != TRY_SCOPE) - { - emsg(_(e_catch)); - return NULL; - } - - if (scope->se_u.se_try.ts_caught_all) - { - emsg(_(e_catch_unreachable_after_catch_all)); - return NULL; - } - - if (cctx->ctx_skip != SKIP_YES) - { -#ifdef FEAT_PROFILE - // the profile-start should be after the jump - if (cctx->ctx_compile_type == CT_PROFILE - && instr->ga_len > 0 - && ((isn_T *)instr->ga_data)[instr->ga_len - 1] - .isn_type == ISN_PROF_START) - --instr->ga_len; -#endif - // Jump from end of previous block to :finally or :endtry - if (compile_jump_to_end(&scope->se_u.se_try.ts_end_label, - JUMP_ALWAYS, cctx) == FAIL) - return NULL; - - // End :try or :catch scope: set value in ISN_TRY instruction - isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_try_label; - if (isn->isn_arg.try.try_ref->try_catch == 0) - isn->isn_arg.try.try_ref->try_catch = instr->ga_len; - if (scope->se_u.se_try.ts_catch_label != 0) - { - // Previous catch without match jumps here - isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_catch_label; - isn->isn_arg.jump.jump_where = instr->ga_len; - } -#ifdef FEAT_PROFILE - if (cctx->ctx_compile_type == CT_PROFILE) - { - // a "throw" that jumps here needs to be counted - generate_instr(cctx, ISN_PROF_END); - // the "catch" is also counted - generate_instr(cctx, ISN_PROF_START); - } -#endif - if (cctx->ctx_compile_type == CT_DEBUG) - generate_instr_debug(cctx); - } - - p = skipwhite(arg); - if (ends_excmd2(arg, p)) - { - scope->se_u.se_try.ts_caught_all = TRUE; - scope->se_u.se_try.ts_catch_label = 0; - } - else - { - char_u *end; - char_u *pat; - char_u *tofree = NULL; - int dropped = 0; - int len; - - // Push v:exception, push {expr} and MATCH - generate_instr_type(cctx, ISN_PUSHEXC, &t_string); - - end = skip_regexp_ex(p + 1, *p, TRUE, &tofree, &dropped, NULL); - if (*end != *p) - { - semsg(_(e_separator_mismatch_str), p); - vim_free(tofree); - return FAIL; - } - if (tofree == NULL) - len = (int)(end - (p + 1)); - else - len = (int)(end - tofree); - pat = vim_strnsave(tofree == NULL ? p + 1 : tofree, len); - vim_free(tofree); - p += len + 2 + dropped; - if (pat == NULL) - return FAIL; - if (generate_PUSHS(cctx, &pat) == FAIL) - return FAIL; - - if (generate_COMPARE(cctx, EXPR_MATCH, FALSE) == FAIL) - return NULL; - - scope->se_u.se_try.ts_catch_label = instr->ga_len; - if (generate_JUMP(cctx, JUMP_IF_FALSE, 0) == FAIL) - return NULL; - } - - if (cctx->ctx_skip != SKIP_YES && generate_instr(cctx, ISN_CATCH) == NULL) - return NULL; - - if (new_scope(cctx, BLOCK_SCOPE) == NULL) - return NULL; - return p; -} - - static char_u * -compile_finally(char_u *arg, cctx_T *cctx) -{ - scope_T *scope = cctx->ctx_scope; - garray_T *instr = &cctx->ctx_instr; - isn_T *isn; - int this_instr; - - if (misplaced_cmdmod(cctx)) - return NULL; - - // end block scope from :try or :catch - if (scope != NULL && scope->se_type == BLOCK_SCOPE) - compile_endblock(cctx); - scope = cctx->ctx_scope; - - // Error if not in a :try scope - if (scope == NULL || scope->se_type != TRY_SCOPE) - { - emsg(_(e_finally)); - return NULL; - } - - if (cctx->ctx_skip != SKIP_YES) - { - // End :catch or :finally scope: set value in ISN_TRY instruction - isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_try_label; - if (isn->isn_arg.try.try_ref->try_finally != 0) - { - emsg(_(e_finally_dup)); - return NULL; - } - - this_instr = instr->ga_len; -#ifdef FEAT_PROFILE - if (cctx->ctx_compile_type == CT_PROFILE - && ((isn_T *)instr->ga_data)[this_instr - 1] - .isn_type == ISN_PROF_START) - { - // jump to the profile start of the "finally" - --this_instr; - - // jump to the profile end above it - if (this_instr > 0 && ((isn_T *)instr->ga_data)[this_instr - 1] - .isn_type == ISN_PROF_END) - --this_instr; - } -#endif - - // Fill in the "end" label in jumps at the end of the blocks. - compile_fill_jump_to_end(&scope->se_u.se_try.ts_end_label, - this_instr, cctx); - - // If there is no :catch then an exception jumps to :finally. - if (isn->isn_arg.try.try_ref->try_catch == 0) - isn->isn_arg.try.try_ref->try_catch = this_instr; - isn->isn_arg.try.try_ref->try_finally = this_instr; - if (scope->se_u.se_try.ts_catch_label != 0) - { - // Previous catch without match jumps here - isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_catch_label; - isn->isn_arg.jump.jump_where = this_instr; - scope->se_u.se_try.ts_catch_label = 0; - } - if (generate_instr(cctx, ISN_FINALLY) == NULL) - return NULL; - } - - return arg; -} - - static char_u * -compile_endtry(char_u *arg, cctx_T *cctx) -{ - scope_T *scope = cctx->ctx_scope; - garray_T *instr = &cctx->ctx_instr; - isn_T *try_isn; - - if (misplaced_cmdmod(cctx)) - return NULL; - - // end block scope from :catch or :finally - if (scope != NULL && scope->se_type == BLOCK_SCOPE) - compile_endblock(cctx); - scope = cctx->ctx_scope; - - // Error if not in a :try scope - if (scope == NULL || scope->se_type != TRY_SCOPE) - { - if (scope == NULL) - emsg(_(e_no_endtry)); - else if (scope->se_type == WHILE_SCOPE) - emsg(_(e_endwhile)); - else if (scope->se_type == FOR_SCOPE) - emsg(_(e_endfor)); - else - emsg(_(e_endif)); - return NULL; - } - - try_isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_try_label; - if (cctx->ctx_skip != SKIP_YES) - { - if (try_isn->isn_arg.try.try_ref->try_catch == 0 - && try_isn->isn_arg.try.try_ref->try_finally == 0) - { - emsg(_(e_missing_catch_or_finally)); - return NULL; - } - -#ifdef FEAT_PROFILE - if (cctx->ctx_compile_type == CT_PROFILE - && ((isn_T *)instr->ga_data)[instr->ga_len - 1] - .isn_type == ISN_PROF_START) - // move the profile start after "endtry" so that it's not counted when - // the exception is rethrown. - --instr->ga_len; -#endif - - // Fill in the "end" label in jumps at the end of the blocks, if not - // done by ":finally". - compile_fill_jump_to_end(&scope->se_u.se_try.ts_end_label, - instr->ga_len, cctx); - - if (scope->se_u.se_try.ts_catch_label != 0) - { - // Last catch without match jumps here - isn_T *isn = ((isn_T *)instr->ga_data) - + scope->se_u.se_try.ts_catch_label; - isn->isn_arg.jump.jump_where = instr->ga_len; - } - } - - compile_endblock(cctx); - - if (cctx->ctx_skip != SKIP_YES) - { - // End :catch or :finally scope: set instruction index in ISN_TRY - // instruction - try_isn->isn_arg.try.try_ref->try_endtry = instr->ga_len; - if (cctx->ctx_skip != SKIP_YES - && generate_instr(cctx, ISN_ENDTRY) == NULL) - return NULL; -#ifdef FEAT_PROFILE - if (cctx->ctx_compile_type == CT_PROFILE) - generate_instr(cctx, ISN_PROF_START); -#endif - } - return arg; -} - -/* - * compile "throw {expr}" - */ - static char_u * -compile_throw(char_u *arg, cctx_T *cctx UNUSED) -{ - char_u *p = skipwhite(arg); - - if (compile_expr0(&p, cctx) == FAIL) - return NULL; - if (cctx->ctx_skip == SKIP_YES) - return p; - if (may_generate_2STRING(-1, FALSE, cctx) == FAIL) - return NULL; - if (generate_instr_drop(cctx, ISN_THROW, 1) == NULL) - return NULL; - - return p; -} - - static char_u * -compile_eval(char_u *arg, cctx_T *cctx) -{ - char_u *p = arg; - int name_only; - long lnum = SOURCING_LNUM; - - // find_ex_command() will consider a variable name an expression, assuming - // that something follows on the next line. Check that something actually - // follows, otherwise it's probably a misplaced command. - name_only = cmd_is_name_only(arg); - - if (compile_expr0(&p, cctx) == FAIL) - return NULL; - - if (name_only && lnum == SOURCING_LNUM) - { - semsg(_(e_expression_without_effect_str), arg); - return NULL; - } - - // drop the result - generate_instr_drop(cctx, ISN_DROP, 1); - - return skipwhite(p); -} - -/* - * compile "echo expr" - * compile "echomsg expr" - * compile "echoerr expr" - * compile "echoconsole expr" - * compile "execute expr" - */ - static char_u * -compile_mult_expr(char_u *arg, int cmdidx, cctx_T *cctx) -{ - char_u *p = arg; - char_u *prev = arg; - char_u *expr_start; - int count = 0; - int start_ctx_lnum = cctx->ctx_lnum; - garray_T *stack = &cctx->ctx_type_stack; - type_T *type; - - for (;;) - { - if (ends_excmd2(prev, p)) - break; - expr_start = p; - if (compile_expr0(&p, cctx) == FAIL) - return NULL; - - if (cctx->ctx_skip != SKIP_YES) - { - // check for non-void type - type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - if (type->tt_type == VAR_VOID) - { - semsg(_(e_expression_does_not_result_in_value_str), expr_start); - return NULL; - } - } - - ++count; - prev = p; - p = skipwhite(p); - } - - if (count > 0) - { - long save_lnum = cctx->ctx_lnum; - - // Use the line number where the command started. - cctx->ctx_lnum = start_ctx_lnum; - - if (cmdidx == CMD_echo || cmdidx == CMD_echon) - generate_ECHO(cctx, cmdidx == CMD_echo, count); - else if (cmdidx == CMD_execute) - generate_MULT_EXPR(cctx, ISN_EXECUTE, count); - else if (cmdidx == CMD_echomsg) - generate_MULT_EXPR(cctx, ISN_ECHOMSG, count); - else if (cmdidx == CMD_echoconsole) - generate_MULT_EXPR(cctx, ISN_ECHOCONSOLE, count); - else - generate_MULT_EXPR(cctx, ISN_ECHOERR, count); - - cctx->ctx_lnum = save_lnum; - } - return p; -} - -/* - * If "eap" has a range that is not a constant generate an ISN_RANGE - * instruction to compute it and return OK. - * Otherwise return FAIL, the caller must deal with any range. - */ - static int -compile_variable_range(exarg_T *eap, cctx_T *cctx) -{ - char_u *range_end = skip_range(eap->cmd, TRUE, NULL); - char_u *p = skipdigits(eap->cmd); - - if (p == range_end) - return FAIL; - return generate_RANGE(cctx, vim_strnsave(eap->cmd, range_end - eap->cmd)); -} - -/* - * :put r - * :put ={expr} - */ - static char_u * -compile_put(char_u *arg, exarg_T *eap, cctx_T *cctx) -{ - char_u *line = arg; - linenr_T lnum; - char *errormsg; - int above = eap->forceit; - - eap->regname = *line; - - if (eap->regname == '=') - { - char_u *p = line + 1; - - if (compile_expr0(&p, cctx) == FAIL) - return NULL; - line = p; - } - else if (eap->regname != NUL) - ++line; - - if (compile_variable_range(eap, cctx) == OK) - { - lnum = above ? LNUM_VARIABLE_RANGE_ABOVE : LNUM_VARIABLE_RANGE; - } - else - { - // Either no range or a number. - // "errormsg" will not be set because the range is ADDR_LINES. - if (parse_cmd_address(eap, &errormsg, FALSE) == FAIL) - // cannot happen - return NULL; - if (eap->addr_count == 0) - lnum = -1; - else - lnum = eap->line2; - if (above) - --lnum; - } - - generate_PUT(cctx, eap->regname, lnum); - return line; -} - -/* - * A command that is not compiled, execute with legacy code. - */ - static char_u * -compile_exec(char_u *line_arg, exarg_T *eap, cctx_T *cctx) -{ - char_u *line = line_arg; - char_u *p; - int has_expr = FALSE; - char_u *nextcmd = (char_u *)""; - char_u *tofree = NULL; - char_u *cmd_arg = NULL; - - if (cctx->ctx_skip == SKIP_YES) - goto theend; - - // If there was a prececing command modifier, drop it and include it in the - // EXEC command. - if (cctx->ctx_has_cmdmod) - { - garray_T *instr = &cctx->ctx_instr; - isn_T *isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1; - - if (isn->isn_type == ISN_CMDMOD) - { - vim_regfree(isn->isn_arg.cmdmod.cf_cmdmod - ->cmod_filter_regmatch.regprog); - vim_free(isn->isn_arg.cmdmod.cf_cmdmod); - --instr->ga_len; - cctx->ctx_has_cmdmod = FALSE; - } - } - - if (eap->cmdidx >= 0 && eap->cmdidx < CMD_SIZE) - { - long argt = eap->argt; - int usefilter = FALSE; - - has_expr = argt & (EX_XFILE | EX_EXPAND); - - // If the command can be followed by a bar, find the bar and truncate - // it, so that the following command can be compiled. - // The '|' is overwritten with a NUL, it is put back below. - if ((eap->cmdidx == CMD_write || eap->cmdidx == CMD_read) - && *eap->arg == '!') - // :w !filter or :r !filter or :r! filter - usefilter = TRUE; - if ((argt & EX_TRLBAR) && !usefilter) - { - eap->argt = argt; - separate_nextcmd(eap); - if (eap->nextcmd != NULL) - nextcmd = eap->nextcmd; - } - else if (eap->cmdidx == CMD_wincmd) - { - p = eap->arg; - if (*p != NUL) - ++p; - if (*p == 'g' || *p == Ctrl_G) - ++p; - p = skipwhite(p); - if (*p == '|') - { - *p = NUL; - nextcmd = p + 1; - } - } - else if (eap->cmdidx == CMD_command || eap->cmdidx == CMD_autocmd) - { - // If there is a trailing '{' read lines until the '}' - p = eap->arg + STRLEN(eap->arg) - 1; - while (p > eap->arg && VIM_ISWHITE(*p)) - --p; - if (*p == '{') - { - exarg_T ea; - int flags; // unused - int start_lnum = SOURCING_LNUM; - - CLEAR_FIELD(ea); - ea.arg = eap->arg; - fill_exarg_from_cctx(&ea, cctx); - (void)may_get_cmd_block(&ea, p, &tofree, &flags); - if (tofree != NULL) - { - *p = NUL; - line = concat_str(line, tofree); - if (line == NULL) - goto theend; - vim_free(tofree); - tofree = line; - SOURCING_LNUM = start_lnum; - } - } - } - } - - if (eap->cmdidx == CMD_syntax && STRNCMP(eap->arg, "include ", 8) == 0) - { - // expand filename in "syntax include [@group] filename" - has_expr = TRUE; - eap->arg = skipwhite(eap->arg + 7); - if (*eap->arg == '@') - eap->arg = skiptowhite(eap->arg); - } - - if ((eap->cmdidx == CMD_global || eap->cmdidx == CMD_vglobal) - && STRLEN(eap->arg) > 4) - { - int delim = *eap->arg; - - p = skip_regexp_ex(eap->arg + 1, delim, TRUE, NULL, NULL, NULL); - if (*p == delim) - cmd_arg = p + 1; - } - - if (eap->cmdidx == CMD_folddoopen || eap->cmdidx == CMD_folddoclosed) - cmd_arg = eap->arg; - - if (cmd_arg != NULL) - { - exarg_T nea; - - CLEAR_FIELD(nea); - nea.cmd = cmd_arg; - p = find_ex_command(&nea, NULL, lookup_scriptitem, NULL); - if (nea.cmdidx < CMD_SIZE) - { - has_expr = excmd_get_argt(nea.cmdidx) & (EX_XFILE | EX_EXPAND); - if (has_expr) - eap->arg = skiptowhite(eap->arg); - } - } - - if (has_expr && (p = (char_u *)strstr((char *)eap->arg, "`=")) != NULL) - { - int count = 0; - char_u *start = skipwhite(line); - - // :cmd xxx`=expr1`yyy`=expr2`zzz - // PUSHS ":cmd xxx" - // eval expr1 - // PUSHS "yyy" - // eval expr2 - // PUSHS "zzz" - // EXECCONCAT 5 - for (;;) - { - if (p > start) - { - char_u *val = vim_strnsave(start, p - start); - - generate_PUSHS(cctx, &val); - ++count; - } - p += 2; - if (compile_expr0(&p, cctx) == FAIL) - return NULL; - may_generate_2STRING(-1, TRUE, cctx); - ++count; - p = skipwhite(p); - if (*p != '`') - { - emsg(_(e_missing_backtick)); - return NULL; - } - start = p + 1; - - p = (char_u *)strstr((char *)start, "`="); - if (p == NULL) - { - if (*skipwhite(start) != NUL) - { - char_u *val = vim_strsave(start); - - generate_PUSHS(cctx, &val); - ++count; - } - break; - } - } - generate_EXECCONCAT(cctx, count); - } - else - generate_EXEC_copy(cctx, ISN_EXEC, line); - -theend: - if (*nextcmd != NUL) - { - // the parser expects a pointer to the bar, put it back - --nextcmd; - *nextcmd = '|'; - } - vim_free(tofree); - - return nextcmd; -} - -/* - * A script command with heredoc, e.g. - * ruby << EOF - * command - * EOF - * Has been turned into one long line with NL characters by - * get_function_body(): - * ruby << EOF<NL> command<NL>EOF - */ - static char_u * -compile_script(char_u *line, cctx_T *cctx) -{ - if (cctx->ctx_skip != SKIP_YES) - { - isn_T *isn; - - if ((isn = generate_instr(cctx, ISN_EXEC_SPLIT)) == NULL) - return NULL; - isn->isn_arg.string = vim_strsave(line); - } - return (char_u *)""; -} - - -/* - * :s/pat/repl/ - */ - static char_u * -compile_substitute(char_u *arg, exarg_T *eap, cctx_T *cctx) -{ - char_u *cmd = eap->arg; - char_u *expr = (char_u *)strstr((char *)cmd, "\\="); - - if (expr != NULL) - { - int delimiter = *cmd++; - - // There is a \=expr, find it in the substitute part. - cmd = skip_regexp_ex(cmd, delimiter, magic_isset(), NULL, NULL, NULL); - if (cmd[0] == delimiter && cmd[1] == '\\' && cmd[2] == '=') - { - garray_T save_ga = cctx->ctx_instr; - char_u *end; - int expr_res; - int trailing_error; - int instr_count; - isn_T *instr; - isn_T *isn; - - cmd += 3; - end = skip_substitute(cmd, delimiter); - - // Temporarily reset the list of instructions so that the jump - // labels are correct. - cctx->ctx_instr.ga_len = 0; - cctx->ctx_instr.ga_maxlen = 0; - cctx->ctx_instr.ga_data = NULL; - expr_res = compile_expr0(&cmd, cctx); - if (end[-1] == NUL) - end[-1] = delimiter; - cmd = skipwhite(cmd); - trailing_error = *cmd != delimiter && *cmd != NUL; - - if (expr_res == FAIL || trailing_error - || GA_GROW_FAILS(&cctx->ctx_instr, 1)) - { - if (trailing_error) - semsg(_(e_trailing_arg), cmd); - clear_instr_ga(&cctx->ctx_instr); - cctx->ctx_instr = save_ga; - return NULL; - } - - // Move the generated instructions into the ISN_SUBSTITUTE - // instructions, then restore the list of instructions before - // adding the ISN_SUBSTITUTE instruction. - instr_count = cctx->ctx_instr.ga_len; - instr = cctx->ctx_instr.ga_data; - instr[instr_count].isn_type = ISN_FINISH; - - cctx->ctx_instr = save_ga; - if ((isn = generate_instr(cctx, ISN_SUBSTITUTE)) == NULL) - { - int idx; - - for (idx = 0; idx < instr_count; ++idx) - delete_instr(instr + idx); - vim_free(instr); - return NULL; - } - isn->isn_arg.subs.subs_cmd = vim_strsave(arg); - isn->isn_arg.subs.subs_instr = instr; - - // skip over flags - if (*end == '&') - ++end; - while (ASCII_ISALPHA(*end) || *end == '#') - ++end; - return end; - } - } - - return compile_exec(arg, eap, cctx); -} - - static char_u * -compile_redir(char_u *line, exarg_T *eap, cctx_T *cctx) -{ - char_u *arg = eap->arg; - lhs_T *lhs = &cctx->ctx_redir_lhs; - - if (lhs->lhs_name != NULL) - { - if (STRNCMP(arg, "END", 3) == 0) - { - if (lhs->lhs_append) - { - // First load the current variable value. - if (compile_load_lhs_with_index(lhs, lhs->lhs_whole, - cctx) == FAIL) - return NULL; - } - - // Gets the redirected text and put it on the stack, then store it - // in the variable. - generate_instr_type(cctx, ISN_REDIREND, &t_string); - - if (lhs->lhs_append) - generate_instr_drop(cctx, ISN_CONCAT, 1); - - if (lhs->lhs_has_index) - { - // Use the info in "lhs" to store the value at the index in the - // list or dict. - if (compile_assign_unlet(lhs->lhs_whole, lhs, TRUE, - &t_string, cctx) == FAIL) - return NULL; - } - else if (generate_store_lhs(cctx, lhs, -1) == FAIL) - return NULL; - - VIM_CLEAR(lhs->lhs_name); - VIM_CLEAR(lhs->lhs_whole); - return arg + 3; - } - emsg(_(e_cannot_nest_redir)); - return NULL; - } - - if (arg[0] == '=' && arg[1] == '>') - { - int append = FALSE; - - // redirect to a variable is compiled - arg += 2; - if (*arg == '>') - { - ++arg; - append = TRUE; - } - arg = skipwhite(arg); - - if (compile_assign_lhs(arg, lhs, CMD_redir, - FALSE, FALSE, 1, cctx) == FAIL) - return NULL; - if (need_type(&t_string, lhs->lhs_member_type, - -1, 0, cctx, FALSE, FALSE) == FAIL) - return NULL; - generate_instr(cctx, ISN_REDIRSTART); - lhs->lhs_append = append; - if (lhs->lhs_has_index) - { - lhs->lhs_whole = vim_strnsave(arg, lhs->lhs_varlen_total); - if (lhs->lhs_whole == NULL) - return NULL; - } - - return arg + lhs->lhs_varlen_total; - } - - // other redirects are handled like at script level - return compile_exec(line, eap, cctx); -} - -#ifdef FEAT_QUICKFIX - static char_u * -compile_cexpr(char_u *line, exarg_T *eap, cctx_T *cctx) -{ - isn_T *isn; - char_u *p; - - isn = generate_instr(cctx, ISN_CEXPR_AUCMD); - if (isn == NULL) - return NULL; - isn->isn_arg.number = eap->cmdidx; - - p = eap->arg; - if (compile_expr0(&p, cctx) == FAIL) - return NULL; - - isn = generate_instr(cctx, ISN_CEXPR_CORE); - if (isn == NULL) - return NULL; - isn->isn_arg.cexpr.cexpr_ref = ALLOC_ONE(cexprref_T); - if (isn->isn_arg.cexpr.cexpr_ref == NULL) - return NULL; - isn->isn_arg.cexpr.cexpr_ref->cer_cmdidx = eap->cmdidx; - isn->isn_arg.cexpr.cexpr_ref->cer_forceit = eap->forceit; - isn->isn_arg.cexpr.cexpr_ref->cer_cmdline = vim_strsave(skipwhite(line)); - - return p; -} -#endif - -/* - * Check if the separator for a :global or :substitute command is OK. - */ - int -check_global_and_subst(char_u *cmd, char_u *arg) -{ - if (arg == cmd + 1 && vim_strchr((char_u *)":-.", *arg) != NULL) - { - semsg(_(e_separator_not_supported_str), arg); - return FAIL; - } - if (VIM_ISWHITE(cmd[1])) - { - semsg(_(e_no_white_space_allowed_before_separator_str), cmd); - return FAIL; - } - return OK; -} - /* * Add a function to the list of :def functions. @@ -10448,272 +3217,6 @@ set_function_type(ufunc_T *ufunc) argcount, &ufunc->uf_type_list); } - -/* - * Delete an instruction, free what it contains. - */ - void -delete_instr(isn_T *isn) -{ - switch (isn->isn_type) - { - case ISN_DEF: - case ISN_EXEC: - case ISN_EXECRANGE: - case ISN_EXEC_SPLIT: - case ISN_LEGACY_EVAL: - case ISN_LOADAUTO: - case ISN_LOADB: - case ISN_LOADENV: - case ISN_LOADG: - case ISN_LOADOPT: - case ISN_LOADT: - case ISN_LOADW: - case ISN_LOCKUNLOCK: - case ISN_PUSHEXC: - case ISN_PUSHFUNC: - case ISN_PUSHS: - case ISN_RANGE: - case ISN_STOREAUTO: - case ISN_STOREB: - case ISN_STOREENV: - case ISN_STOREG: - case ISN_STORET: - case ISN_STOREW: - case ISN_STRINGMEMBER: - vim_free(isn->isn_arg.string); - break; - - case ISN_SUBSTITUTE: - { - int idx; - isn_T *list = isn->isn_arg.subs.subs_instr; - - vim_free(isn->isn_arg.subs.subs_cmd); - for (idx = 0; list[idx].isn_type != ISN_FINISH; ++idx) - delete_instr(list + idx); - vim_free(list); - } - break; - - case ISN_INSTR: - { - int idx; - isn_T *list = isn->isn_arg.instr; - - for (idx = 0; list[idx].isn_type != ISN_FINISH; ++idx) - delete_instr(list + idx); - vim_free(list); - } - break; - - case ISN_LOADS: - case ISN_STORES: - vim_free(isn->isn_arg.loadstore.ls_name); - break; - - case ISN_UNLET: - case ISN_UNLETENV: - vim_free(isn->isn_arg.unlet.ul_name); - break; - - case ISN_STOREOPT: - case ISN_STOREFUNCOPT: - vim_free(isn->isn_arg.storeopt.so_name); - break; - - case ISN_PUSHBLOB: // push blob isn_arg.blob - blob_unref(isn->isn_arg.blob); - break; - - case ISN_PUSHJOB: -#ifdef FEAT_JOB_CHANNEL - job_unref(isn->isn_arg.job); -#endif - break; - - case ISN_PUSHCHANNEL: -#ifdef FEAT_JOB_CHANNEL - channel_unref(isn->isn_arg.channel); -#endif - break; - - case ISN_UCALL: - vim_free(isn->isn_arg.ufunc.cuf_name); - break; - - case ISN_FUNCREF: - { - if (isn->isn_arg.funcref.fr_func_name == NULL) - { - dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) - + isn->isn_arg.funcref.fr_dfunc_idx; - ufunc_T *ufunc = dfunc->df_ufunc; - - if (ufunc != NULL && func_name_refcount(ufunc->uf_name)) - func_ptr_unref(ufunc); - } - else - { - char_u *name = isn->isn_arg.funcref.fr_func_name; - - if (name != NULL) - func_unref(name); - vim_free(isn->isn_arg.funcref.fr_func_name); - } - } - break; - - case ISN_DCALL: - { - dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) - + isn->isn_arg.dfunc.cdf_idx; - - if (dfunc->df_ufunc != NULL - && func_name_refcount(dfunc->df_ufunc->uf_name)) - func_ptr_unref(dfunc->df_ufunc); - } - break; - - case ISN_NEWFUNC: - { - char_u *lambda = isn->isn_arg.newfunc.nf_lambda; - ufunc_T *ufunc = find_func_even_dead(lambda, TRUE, NULL); - - if (ufunc != NULL) - { - unlink_def_function(ufunc); - func_ptr_unref(ufunc); - } - - vim_free(lambda); - vim_free(isn->isn_arg.newfunc.nf_global); - } - break; - - case ISN_CHECKTYPE: - case ISN_SETTYPE: - free_type(isn->isn_arg.type.ct_type); - break; - - case ISN_CMDMOD: - vim_regfree(isn->isn_arg.cmdmod.cf_cmdmod - ->cmod_filter_regmatch.regprog); - vim_free(isn->isn_arg.cmdmod.cf_cmdmod); - break; - - case ISN_LOADSCRIPT: - case ISN_STORESCRIPT: - vim_free(isn->isn_arg.script.scriptref); - break; - - case ISN_TRY: - vim_free(isn->isn_arg.try.try_ref); - break; - - case ISN_CEXPR_CORE: - vim_free(isn->isn_arg.cexpr.cexpr_ref->cer_cmdline); - vim_free(isn->isn_arg.cexpr.cexpr_ref); - break; - - case ISN_2BOOL: - case ISN_2STRING: - case ISN_2STRING_ANY: - case ISN_ADDBLOB: - case ISN_ADDLIST: - case ISN_ANYINDEX: - case ISN_ANYSLICE: - case ISN_BCALL: - case ISN_BLOBAPPEND: - case ISN_BLOBINDEX: - case ISN_BLOBSLICE: - case ISN_CATCH: - case ISN_CEXPR_AUCMD: - case ISN_CHECKLEN: - case ISN_CHECKNR: - case ISN_CLEARDICT: - case ISN_CMDMOD_REV: - case ISN_COMPAREANY: - case ISN_COMPAREBLOB: - case ISN_COMPAREBOOL: - case ISN_COMPAREDICT: - case ISN_COMPAREFLOAT: - case ISN_COMPAREFUNC: - case ISN_COMPARELIST: - case ISN_COMPARENR: - case ISN_COMPARESPECIAL: - case ISN_COMPARESTRING: - case ISN_CONCAT: - case ISN_COND2BOOL: - case ISN_DEBUG: - case ISN_DROP: - case ISN_ECHO: - case ISN_ECHOCONSOLE: - case ISN_ECHOERR: - case ISN_ECHOMSG: - case ISN_ENDTRY: - case ISN_EXECCONCAT: - case ISN_EXECUTE: - case ISN_FINALLY: - case ISN_FINISH: - case ISN_FOR: - case ISN_GETITEM: - case ISN_JUMP: - case ISN_JUMP_IF_ARG_SET: - case ISN_LISTAPPEND: - case ISN_LISTINDEX: - case ISN_LISTSLICE: - case ISN_LOAD: - case ISN_LOADBDICT: - case ISN_LOADGDICT: - case ISN_LOADOUTER: - case ISN_LOADREG: - case ISN_LOADTDICT: - case ISN_LOADV: - case ISN_LOADWDICT: - case ISN_LOCKCONST: - case ISN_MEMBER: - case ISN_NEGATENR: - case ISN_NEWDICT: - case ISN_NEWLIST: - case ISN_OPANY: - case ISN_OPFLOAT: - case ISN_OPNR: - case ISN_PCALL: - case ISN_PCALL_END: - case ISN_PROF_END: - case ISN_PROF_START: - case ISN_PUSHBOOL: - case ISN_PUSHF: - case ISN_PUSHNR: - case ISN_PUSHSPEC: - case ISN_PUT: - case ISN_REDIREND: - case ISN_REDIRSTART: - case ISN_RETURN: - case ISN_RETURN_VOID: - case ISN_SHUFFLE: - case ISN_SLICE: - case ISN_STORE: - case ISN_STOREINDEX: - case ISN_STORENR: - case ISN_STOREOUTER: - case ISN_STORERANGE: - case ISN_STOREREG: - case ISN_STOREV: - case ISN_STRINDEX: - case ISN_STRSLICE: - case ISN_THROW: - case ISN_TRYCONT: - case ISN_UNLETINDEX: - case ISN_UNLETRANGE: - case ISN_UNPACK: - case ISN_USEDICT: - // nothing allocated - break; - } -} - /* * Free all instructions for "dfunc" except df_name. */ |