Mercurial > vim
diff src/vim9compile.c @ 19181:94eda51ba9ba v8.2.0149
patch 8.2.0149: maintaining a Vim9 branch separately is more work
Commit: https://github.com/vim/vim/commit/8a7d6542b33e5d2b352262305c3bfdb2d14e1cf8
Author: Bram Moolenaar <Bram@vim.org>
Date: Sun Jan 26 15:56:19 2020 +0100
patch 8.2.0149: maintaining a Vim9 branch separately is more work
Problem: Maintaining a Vim9 branch separately is more work.
Solution: Merge the Vim9 script changes.
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Sun, 26 Jan 2020 16:00:05 +0100 |
parents | |
children | 133ef7ba4e4e |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/src/vim9compile.c @@ -0,0 +1,4612 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * vim9compile.c: :def and dealing with instructions + */ + +#define USING_FLOAT_STUFF +#include "vim.h" + +#if defined(FEAT_EVAL) || defined(PROTO) + +#ifdef VMS +# include <float.h> +#endif + +#define DEFINE_VIM9_GLOBALS +#include "vim9.h" + +/* + * 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_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 + union { + ifscope_T se_if; + whilescope_T se_while; + forscope_T se_for; + tryscope_T se_try; + }; +}; + +/* + * Entry for "ctx_locals". Used for arguments and local variables. + */ +typedef struct { + char_u *lv_name; + type_T *lv_type; + int lv_const; // when TRUE cannot be assigned to + int lv_arg; // when TRUE this is an argument +} lvar_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 + garray_T ctx_instr; // generated instructions + + garray_T ctx_locals; // currently visible local variables + int ctx_max_local; // maximum number of locals at one time + + garray_T ctx_imports; // imported items + + scope_T *ctx_scope; // current scope, NULL at toplevel + + garray_T ctx_type_stack; // type of each item on the stack + garray_T *ctx_type_list; // space for adding types +}; + +static char e_var_notfound[] = N_("E1001: variable not found: %s"); +static char e_syntax_at[] = N_("E1002: Syntax error at %s"); + +static int compile_expr1(char_u **arg, cctx_T *cctx); +static int compile_expr2(char_u **arg, cctx_T *cctx); +static int compile_expr3(char_u **arg, cctx_T *cctx); + +/* + * Lookup variable "name" in the local scope and return the index. + */ + static int +lookup_local(char_u *name, size_t len, cctx_T *cctx) +{ + int idx; + + if (len <= 0) + return -1; + for (idx = 0; idx < cctx->ctx_locals.ga_len; ++idx) + { + lvar_T *lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx; + + if (STRNCMP(name, lvar->lv_name, len) == 0 + && STRLEN(lvar->lv_name) == len) + return idx; + } + return -1; +} + +/* + * Lookup an argument in the current function. + * Returns the argument index or -1 if not found. + */ + static int +lookup_arg(char_u *name, size_t len, cctx_T *cctx) +{ + int idx; + + if (len <= 0) + return -1; + for (idx = 0; idx < cctx->ctx_ufunc->uf_args.ga_len; ++idx) + { + char_u *arg = FUNCARG(cctx->ctx_ufunc, idx); + + if (STRNCMP(name, arg, len) == 0 && STRLEN(arg) == len) + return idx; + } + return -1; +} + +/* + * Lookup a vararg argument in the current function. + * Returns TRUE if there is a match. + */ + static int +lookup_vararg(char_u *name, size_t len, cctx_T *cctx) +{ + char_u *va_name = cctx->ctx_ufunc->uf_va_name; + + return len > 0 && va_name != NULL + && STRNCMP(name, va_name, len) == 0 && STRLEN(va_name) == len; +} + +/* + * Lookup a variable in the current script. + * Returns OK or FAIL. + */ + static int +lookup_script(char_u *name, size_t len) +{ + int cc; + hashtab_T *ht = &SCRIPT_VARS(current_sctx.sc_sid); + dictitem_T *di; + + cc = name[len]; + name[len] = NUL; + di = find_var_in_ht(ht, 0, name, TRUE); + name[len] = cc; + return di == NULL ? FAIL: OK; +} + + static type_T * +get_list_type(type_T *member_type, garray_T *type_list) +{ + type_T *type; + + // recognize commonly used types + if (member_type->tt_type == VAR_UNKNOWN) + return &t_list_any; + if (member_type->tt_type == VAR_NUMBER) + return &t_list_number; + if (member_type->tt_type == VAR_STRING) + return &t_list_string; + + // Not a common type, create a new entry. + if (ga_grow(type_list, 1) == FAIL) + return FAIL; + type = ((type_T *)type_list->ga_data) + type_list->ga_len; + ++type_list->ga_len; + type->tt_type = VAR_LIST; + type->tt_member = member_type; + return type; +} + + static type_T * +get_dict_type(type_T *member_type, garray_T *type_list) +{ + type_T *type; + + // recognize commonly used types + if (member_type->tt_type == VAR_UNKNOWN) + return &t_dict_any; + if (member_type->tt_type == VAR_NUMBER) + return &t_dict_number; + if (member_type->tt_type == VAR_STRING) + return &t_dict_string; + + // Not a common type, create a new entry. + if (ga_grow(type_list, 1) == FAIL) + return FAIL; + type = ((type_T *)type_list->ga_data) + type_list->ga_len; + ++type_list->ga_len; + type->tt_type = VAR_DICT; + type->tt_member = member_type; + return type; +} + +///////////////////////////////////////////////////////////////////// +// Following generate_ functions expect the caller to call ga_grow(). + +/* + * 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; + + if (ga_grow(instr, 1) == FAIL) + 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; + + 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(stack, 1) == FAIL) + return NULL; + ((type_T **)stack->ga_data)[stack->ga_len] = type; + ++stack->ga_len; + + return isn; +} + +/* + * If type at "offset" isn't already VAR_STRING then generate ISN_2STRING. + */ + static int +may_generate_2STRING(int offset, cctx_T *cctx) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + type_T **type = ((type_T **)stack->ga_data) + stack->ga_len + offset; + + if ((*type)->tt_type == VAR_STRING) + return OK; + *type = &t_string; + + if ((isn = generate_instr(cctx, ISN_2STRING)) == NULL) + return FAIL; + isn->isn_arg.number = offset; + + 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_UNKNOWN) + && (type2 == VAR_NUMBER || type2 == VAR_FLOAT + || type2 == VAR_UNKNOWN))) + { + if (*op == '+') + semsg(_("E1035: wrong argument type for +")); + else + semsg(_("E1036: %c requires number or float arguments"), *op); + return FAIL; + } + return OK; +} + +/* + * 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; + + // 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]; + type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + vartype = VAR_UNKNOWN; + 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)) + vartype = type1->tt_type; + + switch (*op) + { + case '+': if (vartype != VAR_LIST && vartype != VAR_BLOB + && check_number_or_float( + type1->tt_type, type2->tt_type, op) == FAIL) + return FAIL; + 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 (isn != NULL) + isn->isn_arg.op.op_type = EXPR_ADD; + 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_UNKNOWN + && type1->tt_type != VAR_NUMBER) + || (type2->tt_type != VAR_UNKNOWN + && type2->tt_type != VAR_NUMBER)) + { + emsg(_("E1035: % 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_UNKNOWN) + { + 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; +} + +/* + * Generate an ISN_COMPARE* instruction with a boolean result. + */ + static int +generate_COMPARE(cctx_T *cctx, exptype_T exptype, int ic) +{ + isntype_T isntype = ISN_DROP; + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + vartype_T type1; + vartype_T type2; + + // 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; + 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; + case VAR_PARTIAL: isntype = ISN_COMPAREPARTIAL; break; + default: isntype = ISN_COMPAREANY; break; + } + } + else if (type1 == VAR_UNKNOWN || type2 == VAR_UNKNOWN + || ((type1 == VAR_NUMBER || type1 == VAR_FLOAT) + && (type2 == VAR_NUMBER || type2 ==VAR_FLOAT))) + isntype = ISN_COMPAREANY; + + if ((exptype == EXPR_IS || exptype == EXPR_ISNOT) + && (isntype == ISN_COMPAREBOOL + || isntype == ISN_COMPARESPECIAL + || isntype == ISN_COMPARENR + || isntype == ISN_COMPAREFLOAT)) + { + semsg(_("E1037: Cannot use \"%s\" with %s"), + exptype == EXPR_IS ? "is" : "isnot" , vartype_name(type1)); + return FAIL; + } + if (isntype == ISN_DROP + || ((exptype != EXPR_EQUAL && exptype != EXPR_NEQUAL + && (type1 == VAR_BOOL || type1 == VAR_SPECIAL + || type2 == VAR_BOOL || type2 == VAR_SPECIAL))) + || ((exptype != EXPR_EQUAL && exptype != EXPR_NEQUAL + && exptype != EXPR_IS && exptype != EXPR_ISNOT + && (type1 == VAR_BLOB || type2 == VAR_BLOB + || type1 == VAR_LIST || type2 == VAR_LIST)))) + { + semsg(_("E1037: Cannot compare %s with %s"), + vartype_name(type1), vartype_name(type2)); + return FAIL; + } + + if ((isn = generate_instr(cctx, isntype)) == NULL) + return FAIL; + isn->isn_arg.op.op_type = exptype; + 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. + */ + static int +generate_2BOOL(cctx_T *cctx, int invert) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + if ((isn = generate_instr(cctx, ISN_2BOOL)) == NULL) + return FAIL; + isn->isn_arg.number = invert; + + // 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 *vartype, int offset) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + if ((isn = generate_instr(cctx, ISN_CHECKTYPE)) == NULL) + return FAIL; + isn->isn_arg.type.ct_type = vartype->tt_type; // TODO: whole type + isn->isn_arg.type.ct_off = offset; + + // type becomes vartype + ((type_T **)stack->ga_data)[stack->ga_len - 1] = vartype; + + return OK; +} + +/* + * Generate an ISN_PUSHNR instruction. + */ + static int +generate_PUSHNR(cctx_T *cctx, varnumber_T number) +{ + isn_T *isn; + + if ((isn = generate_instr_type(cctx, ISN_PUSHNR, &t_number)) == NULL) + return FAIL; + isn->isn_arg.number = number; + + return OK; +} + +/* + * Generate an ISN_PUSHBOOL instruction. + */ + static int +generate_PUSHBOOL(cctx_T *cctx, varnumber_T number) +{ + isn_T *isn; + + 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; + + 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; + + 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". + */ + static int +generate_PUSHS(cctx_T *cctx, char_u *str) +{ + isn_T *isn; + + if ((isn = generate_instr_type(cctx, ISN_PUSHS, &t_string)) == NULL) + return FAIL; + isn->isn_arg.string = str; + + return OK; +} + +/* + * Generate an ISN_PUSHBLOB instruction. + * Consumes "blob". + */ + static int +generate_PUSHBLOB(cctx_T *cctx, blob_T *blob) +{ + isn_T *isn; + + if ((isn = generate_instr_type(cctx, ISN_PUSHBLOB, &t_blob)) == NULL) + return FAIL; + isn->isn_arg.blob = blob; + + 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; + + 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_STORENR instruction (short for ISN_PUSHNR + ISN_STORE) + */ + static int +generate_STORENR(cctx_T *cctx, int idx, varnumber_T value) +{ + isn_T *isn; + + if ((isn = generate_instr(cctx, ISN_STORENR)) == NULL) + return FAIL; + isn->isn_arg.storenr.str_idx = idx; + isn->isn_arg.storenr.str_val = value; + + return OK; +} + +/* + * Generate an ISN_STOREOPT instruction + */ + static int +generate_STOREOPT(cctx_T *cctx, char_u *name, int opt_flags) +{ + isn_T *isn; + + if ((isn = generate_instr(cctx, ISN_STOREOPT)) == 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; + + 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_LOADS instruction. + */ + static int +generate_LOADS( + cctx_T *cctx, + char_u *name, + int sid) +{ + isn_T *isn; + + if ((isn = generate_instr_type(cctx, ISN_LOADS, &t_any)) == NULL) + return FAIL; + isn->isn_arg.loads.ls_name = vim_strsave(name); + isn->isn_arg.loads.ls_sid = sid; + + return OK; +} + +/* + * Generate an ISN_LOADSCRIPT or ISN_STORESCRIPT instruction. + */ + static int +generate_SCRIPT( + cctx_T *cctx, + isntype_T isn_type, + int sid, + int idx, + type_T *type) +{ + isn_T *isn; + + 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; + isn->isn_arg.script.script_sid = sid; + isn->isn_arg.script.script_idx = idx; + 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; + garray_T *type_list = cctx->ctx_type_list; + type_T *type; + type_T *member; + + if ((isn = generate_instr(cctx, ISN_NEWLIST)) == NULL) + return FAIL; + isn->isn_arg.number = count; + + // drop the value types + stack->ga_len -= count; + + // use the first value type for the list member type + if (count > 0) + member = ((type_T **)stack->ga_data)[stack->ga_len]; + else + member = &t_any; + type = get_list_type(member, type_list); + + // add the list type to the type stack + if (ga_grow(stack, 1) == FAIL) + 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; + garray_T *type_list = cctx->ctx_type_list; + type_T *type; + type_T *member; + + if ((isn = generate_instr(cctx, ISN_NEWDICT)) == NULL) + return FAIL; + isn->isn_arg.number = count; + + // drop the key and value types + stack->ga_len -= 2 * count; + + // use the first value type for the list member type + if (count > 0) + member = ((type_T **)stack->ga_data)[stack->ga_len + 1]; + else + member = &t_any; + type = get_dict_type(member, type_list); + + // add the dict type to the type stack + if (ga_grow(stack, 1) == FAIL) + 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, int dfunc_idx) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + if ((isn = generate_instr(cctx, ISN_FUNCREF)) == NULL) + return FAIL; + isn->isn_arg.number = dfunc_idx; + + if (ga_grow(stack, 1) == FAIL) + return FAIL; + ((type_T **)stack->ga_data)[stack->ga_len] = &t_partial_any; + // TODO: argument and return types + ++stack->ga_len; + + 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; + + 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; +} + + static int +generate_FOR(cctx_T *cctx, int loop_idx) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + if ((isn = generate_instr(cctx, ISN_FOR)) == NULL) + return FAIL; + isn->isn_arg.forloop.for_idx = loop_idx; + + if (ga_grow(stack, 1) == FAIL) + 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_BCALL instruction. + * Return FAIL if the number of arguments is wrong. + */ + static int +generate_BCALL(cctx_T *cctx, int func_idx, int argcount) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + if (check_internal_func(func_idx, argcount) == FAIL) + return FAIL; + + 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; + + stack->ga_len -= argcount; // drop the arguments + if (ga_grow(stack, 1) == FAIL) + return FAIL; + ((type_T **)stack->ga_data)[stack->ga_len] = + internal_func_ret_type(func_idx, argcount); + ++stack->ga_len; // add return value + + return OK; +} + +/* + * 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 argcount) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + int regular_args = ufunc->uf_args.ga_len; + + if (argcount > regular_args && !has_varargs(ufunc)) + { + semsg(_(e_toomanyarg), ufunc->uf_name); + return FAIL; + } + if (argcount < regular_args - ufunc->uf_def_args.ga_len) + { + semsg(_(e_toofewarg), ufunc->uf_name); + return FAIL; + } + + // Turn varargs into a list. + if (ufunc->uf_va_name != NULL) + { + int count = argcount - regular_args; + + // TODO: add default values for optional arguments? + generate_NEWLIST(cctx, count < 0 ? 0 : count); + argcount = regular_args + 1; + } + + if ((isn = generate_instr(cctx, + ufunc->uf_dfunc_idx >= 0 ? ISN_DCALL : ISN_UCALL)) == NULL) + return FAIL; + if (ufunc->uf_dfunc_idx >= 0) + { + 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(stack, 1) == FAIL) + 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; + + 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 + + // drop the funcref/partial, get back the return value + ((type_T **)stack->ga_data)[stack->ga_len - 1] = &t_any; + + return OK; +} + +/* + * Generate an ISN_PCALL instruction. + */ + static int +generate_PCALL(cctx_T *cctx, int argcount, int at_top) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + 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] = &t_any; + + return OK; +} + +/* + * Generate an ISN_MEMBER instruction. + */ + static int +generate_MEMBER(cctx_T *cctx, char_u *name, size_t len) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + type_T *type; + + if ((isn = generate_instr(cctx, ISN_MEMBER)) == NULL) + return FAIL; + isn->isn_arg.string = vim_strnsave(name, (int)len); + + // change dict type to dict member type + type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + ((type_T **)stack->ga_data)[stack->ga_len - 1] = 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; + + 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; +} + + static int +generate_EXEC(cctx_T *cctx, char_u *line) +{ + isn_T *isn; + + if ((isn = generate_instr(cctx, ISN_EXEC)) == NULL) + return FAIL; + isn->isn_arg.string = vim_strsave(line); + return OK; +} + +static char e_white_both[] = + N_("E1004: white space required before and after '%s'"); + +/* + * Reserve space for a local variable. + * Return the index or -1 if it failed. + */ + static int +reserve_local(cctx_T *cctx, char_u *name, size_t len, int isConst, type_T *type) +{ + int idx; + lvar_T *lvar; + + if (lookup_arg(name, len, cctx) >= 0 || lookup_vararg(name, len, cctx)) + { + emsg_namelen(_("E1006: %s is used as an argument"), name, (int)len); + return -1; + } + + if (ga_grow(&cctx->ctx_locals, 1) == FAIL) + return -1; + idx = cctx->ctx_locals.ga_len; + if (cctx->ctx_max_local < idx + 1) + cctx->ctx_max_local = idx + 1; + ++cctx->ctx_locals.ga_len; + + lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx; + lvar->lv_name = vim_strnsave(name, (int)(len == 0 ? STRLEN(name) : len)); + lvar->lv_const = isConst; + lvar->lv_type = type; + + return idx; +} + +/* + * Skip over a type definition and return a pointer to just after it. + */ + char_u * +skip_type(char_u *start) +{ + char_u *p = start; + + while (ASCII_ISALNUM(*p) || *p == '_') + ++p; + + // Skip over "<type>"; this is permissive about white space. + if (*skipwhite(p) == '<') + { + p = skipwhite(p); + p = skip_type(skipwhite(p + 1)); + p = skipwhite(p); + if (*p == '>') + ++p; + } + return p; +} + +/* + * Parse the member type: "<type>" and return "type" with the member set. + * Use "type_list" if a new type needs to be added. + * Returns NULL in case of failure. + */ + static type_T * +parse_type_member(char_u **arg, type_T *type, garray_T *type_list) +{ + type_T *member_type; + + if (**arg != '<') + { + if (*skipwhite(*arg) == '<') + emsg(_("E1007: No white space allowed before <")); + else + emsg(_("E1008: Missing <type>")); + return NULL; + } + *arg = skipwhite(*arg + 1); + + member_type = parse_type(arg, type_list); + if (member_type == NULL) + return NULL; + + *arg = skipwhite(*arg); + if (**arg != '>') + { + emsg(_("E1009: Missing > after type")); + return NULL; + } + ++*arg; + + if (type->tt_type == VAR_LIST) + return get_list_type(member_type, type_list); + return get_dict_type(member_type, type_list); +} + +/* + * Parse a type at "arg" and advance over it. + * Return NULL for failure. + */ + type_T * +parse_type(char_u **arg, garray_T *type_list) +{ + char_u *p = *arg; + size_t len; + + // skip over the first word + while (ASCII_ISALNUM(*p) || *p == '_') + ++p; + len = p - *arg; + + switch (**arg) + { + case 'a': + if (len == 3 && STRNCMP(*arg, "any", len) == 0) + { + *arg += len; + return &t_any; + } + break; + case 'b': + if (len == 4 && STRNCMP(*arg, "bool", len) == 0) + { + *arg += len; + return &t_bool; + } + if (len == 4 && STRNCMP(*arg, "blob", len) == 0) + { + *arg += len; + return &t_blob; + } + break; + case 'c': + if (len == 7 && STRNCMP(*arg, "channel", len) == 0) + { + *arg += len; + return &t_channel; + } + break; + case 'd': + if (len == 4 && STRNCMP(*arg, "dict", len) == 0) + { + *arg += len; + return parse_type_member(arg, &t_dict_any, type_list); + } + break; + case 'f': + if (len == 5 && STRNCMP(*arg, "float", len) == 0) + { + *arg += len; + return &t_float; + } + if (len == 4 && STRNCMP(*arg, "func", len) == 0) + { + *arg += len; + // TODO: arguments and return type + return &t_func_any; + } + break; + case 'j': + if (len == 3 && STRNCMP(*arg, "job", len) == 0) + { + *arg += len; + return &t_job; + } + break; + case 'l': + if (len == 4 && STRNCMP(*arg, "list", len) == 0) + { + *arg += len; + return parse_type_member(arg, &t_list_any, type_list); + } + break; + case 'n': + if (len == 6 && STRNCMP(*arg, "number", len) == 0) + { + *arg += len; + return &t_number; + } + break; + case 'p': + if (len == 4 && STRNCMP(*arg, "partial", len) == 0) + { + *arg += len; + // TODO: arguments and return type + return &t_partial_any; + } + break; + case 's': + if (len == 6 && STRNCMP(*arg, "string", len) == 0) + { + *arg += len; + return &t_string; + } + break; + case 'v': + if (len == 4 && STRNCMP(*arg, "void", len) == 0) + { + *arg += len; + return &t_void; + } + break; + } + + semsg(_("E1010: Type not recognized: %s"), *arg); + return &t_any; +} + +/* + * Check if "type1" and "type2" are exactly the same. + */ + static int +equal_type(type_T *type1, type_T *type2) +{ + if (type1->tt_type != type2->tt_type) + return FALSE; + switch (type1->tt_type) + { + case VAR_VOID: + case VAR_UNKNOWN: + case VAR_SPECIAL: + case VAR_BOOL: + case VAR_NUMBER: + case VAR_FLOAT: + case VAR_STRING: + case VAR_BLOB: + case VAR_JOB: + case VAR_CHANNEL: + return TRUE; // not composite is always OK + case VAR_LIST: + case VAR_DICT: + return equal_type(type1->tt_member, type2->tt_member); + case VAR_FUNC: + case VAR_PARTIAL: + // TODO; check argument types. + return equal_type(type1->tt_member, type2->tt_member) + && type1->tt_argcount == type2->tt_argcount; + } + return TRUE; +} + +/* + * Find the common type of "type1" and "type2" and put it in "dest". + * "type2" and "dest" may be the same. + */ + static void +common_type(type_T *type1, type_T *type2, type_T *dest) +{ + if (equal_type(type1, type2)) + { + if (dest != type2) + *dest = *type2; + return; + } + + if (type1->tt_type == type2->tt_type) + { + dest->tt_type = type1->tt_type; + if (type1->tt_type == VAR_LIST || type2->tt_type == VAR_DICT) + { + common_type(type1->tt_member, type2->tt_member, dest->tt_member); + return; + } + // TODO: VAR_FUNC and VAR_PARTIAL + } + + dest->tt_type = VAR_UNKNOWN; // "any" +} + + char * +vartype_name(vartype_T type) +{ + switch (type) + { + case VAR_VOID: return "void"; + case VAR_UNKNOWN: return "any"; + case VAR_SPECIAL: return "special"; + case VAR_BOOL: return "bool"; + case VAR_NUMBER: return "number"; + case VAR_FLOAT: return "float"; + case VAR_STRING: return "string"; + case VAR_BLOB: return "blob"; + case VAR_JOB: return "job"; + case VAR_CHANNEL: return "channel"; + case VAR_LIST: return "list"; + case VAR_DICT: return "dict"; + case VAR_FUNC: return "function"; + case VAR_PARTIAL: return "partial"; + } + return "???"; +} + +/* + * Return the name of a type. + * The result may be in allocated memory, in which case "tofree" is set. + */ + char * +type_name(type_T *type, char **tofree) +{ + char *name = vartype_name(type->tt_type); + + *tofree = NULL; + if (type->tt_type == VAR_LIST || type->tt_type == VAR_DICT) + { + char *member_free; + char *member_name = type_name(type->tt_member, &member_free); + size_t len; + + len = STRLEN(name) + STRLEN(member_name) + 3; + *tofree = alloc(len); + if (*tofree != NULL) + { + vim_snprintf(*tofree, len, "%s<%s>", name, member_name); + vim_free(member_free); + return *tofree; + } + } + // TODO: function and partial argument types + + return name; +} + +/* + * Find "name" in script-local items of script "sid". + * Returns the index in "sn_var_vals" if found. + * If found but not in "sn_var_vals" returns -1. + * If not found returns -2. + */ + int +get_script_item_idx(int sid, char_u *name, int check_writable) +{ + hashtab_T *ht; + dictitem_T *di; + scriptitem_T *si = &SCRIPT_ITEM(sid); + int idx; + + // First look the name up in the hashtable. + if (sid <= 0 || sid > script_items.ga_len) + return -1; + ht = &SCRIPT_VARS(sid); + di = find_var_in_ht(ht, 0, name, TRUE); + if (di == NULL) + return -2; + + // Now find the svar_T index in sn_var_vals. + for (idx = 0; idx < si->sn_var_vals.ga_len; ++idx) + { + svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + idx; + + if (sv->sv_tv == &di->di_tv) + { + if (check_writable && sv->sv_const) + semsg(_(e_readonlyvar), name); + return idx; + } + } + return -1; +} + +/* + * Find "name" in imported items of the current script/ + */ + imported_T * +find_imported(char_u *name, cctx_T *cctx) +{ + scriptitem_T *si = &SCRIPT_ITEM(current_sctx.sc_sid); + int idx; + + if (cctx != NULL) + for (idx = 0; idx < cctx->ctx_imports.ga_len; ++idx) + { + imported_T *import = ((imported_T *)cctx->ctx_imports.ga_data) + + idx; + + if (STRCMP(name, import->imp_name) == 0) + return import; + } + + for (idx = 0; idx < si->sn_imports.ga_len; ++idx) + { + imported_T *import = ((imported_T *)si->sn_imports.ga_data) + idx; + + if (STRCMP(name, import->imp_name) == 0) + return import; + } + return NULL; +} + +/* + * Generate an instruction to load script-local variable "name". + */ + static int +compile_load_scriptvar(cctx_T *cctx, char_u *name) +{ + scriptitem_T *si = &SCRIPT_ITEM(current_sctx.sc_sid); + int idx = get_script_item_idx(current_sctx.sc_sid, name, FALSE); + imported_T *import; + + if (idx == -1) + { + // variable exists but is not in sn_var_vals: old style script. + return generate_LOADS(cctx, name, current_sctx.sc_sid); + } + if (idx >= 0) + { + svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + idx; + + generate_SCRIPT(cctx, ISN_LOADSCRIPT, + current_sctx.sc_sid, idx, sv->sv_type); + return OK; + } + + import = find_imported(name, cctx); + if (import != NULL) + { + // TODO: check this is a variable, not a function + generate_SCRIPT(cctx, ISN_LOADSCRIPT, + import->imp_sid, + import->imp_var_vals_idx, + import->imp_type); + return OK; + } + + semsg(_("E1050: Item not found: %s"), name); + return FAIL; +} + +/* + * Compile a variable name into a load instruction. + * "end" points to just after the name. + * When "error" is FALSE do not give an error when not found. + */ + static int +compile_load(char_u **arg, char_u *end, cctx_T *cctx, int error) +{ + type_T *type; + char_u *name; + int res = FAIL; + + if (*(*arg + 1) == ':') + { + // load namespaced variable + name = vim_strnsave(*arg + 2, end - (*arg + 2)); + if (name == NULL) + return FAIL; + + if (**arg == 'v') + { + // load v:var + int vidx = find_vim_var(name); + + if (vidx < 0) + { + if (error) + semsg(_(e_var_notfound), name); + goto theend; + } + + // TODO: get actual type + res = generate_LOAD(cctx, ISN_LOADV, vidx, NULL, &t_any); + } + else if (**arg == 'g') + { + // Global variables can be defined later, thus we don't check if it + // exists, give error at runtime. + res = generate_LOAD(cctx, ISN_LOADG, 0, name, &t_any); + } + else if (**arg == 's') + { + res = compile_load_scriptvar(cctx, name); + } + else + { + semsg("Namespace not supported yet: %s", **arg); + goto theend; + } + } + else + { + size_t len = end - *arg; + int idx; + int gen_load = FALSE; + + name = vim_strnsave(*arg, end - *arg); + if (name == NULL) + return FAIL; + + idx = lookup_arg(*arg, len, cctx); + if (idx >= 0) + { + if (cctx->ctx_ufunc->uf_arg_types != NULL) + type = cctx->ctx_ufunc->uf_arg_types[idx]; + else + type = &t_any; + + // Arguments are located above the frame pointer. + idx -= cctx->ctx_ufunc->uf_args.ga_len + STACK_FRAME_SIZE; + if (cctx->ctx_ufunc->uf_va_name != NULL) + --idx; + gen_load = TRUE; + } + else if (lookup_vararg(*arg, len, cctx)) + { + // varargs is always the last argument + idx = -STACK_FRAME_SIZE - 1; + type = cctx->ctx_ufunc->uf_va_type; + gen_load = TRUE; + } + else + { + idx = lookup_local(*arg, len, cctx); + if (idx >= 0) + { + type = (((lvar_T *)cctx->ctx_locals.ga_data) + idx)->lv_type; + gen_load = TRUE; + } + else + { + if ((len == 4 && STRNCMP("true", *arg, 4) == 0) + || (len == 5 && STRNCMP("false", *arg, 5) == 0)) + res = generate_PUSHBOOL(cctx, **arg == 't' + ? VVAL_TRUE : VVAL_FALSE); + else + res = compile_load_scriptvar(cctx, name); + } + } + if (gen_load) + res = generate_LOAD(cctx, ISN_LOAD, idx, NULL, type); + } + + *arg = end; + +theend: + if (res == FAIL && error) + semsg(_(e_var_notfound), name); + vim_free(name); + return res; +} + +/* + * 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) +{ + char_u *p = *arg; + + while (*p != NUL && *p != ')') + { + if (compile_expr1(&p, cctx) == FAIL) + return FAIL; + ++*argcount; + if (*p == ',') + p = skipwhite(p + 1); + } + if (*p != ')') + { + emsg(_(e_missing_close)); + return FAIL; + } + *arg = p + 1; + return OK; +} + +/* + * 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, int argcount_init) +{ + char_u *name = *arg; + char_u *p = *arg + varlen + 1; + int argcount = argcount_init; + char_u namebuf[100]; + ufunc_T *ufunc; + + if (varlen >= sizeof(namebuf)) + { + semsg(_("E1011: name too long: %s"), name); + return FAIL; + } + vim_strncpy(namebuf, name, varlen); + + *arg = skipwhite(*arg + varlen + 1); + if (compile_arguments(arg, cctx, &argcount) == FAIL) + return FAIL; + + if (ASCII_ISLOWER(*name)) + { + int idx; + + // builtin function + idx = find_internal_func(namebuf); + if (idx >= 0) + return generate_BCALL(cctx, idx, argcount); + semsg(_(e_unknownfunc), namebuf); + } + + // User defined function or variable must start with upper case. + if (!ASCII_ISUPPER(*name)) + { + semsg(_("E1012: Invalid function name: %s"), namebuf); + return FAIL; + } + + // If we can find the function by name generate the right call. + ufunc = find_func(namebuf, cctx); + if (ufunc != NULL) + return generate_CALL(cctx, ufunc, argcount); + + // If the name is a variable, load it and use PCALL. + p = namebuf; + if (compile_load(&p, namebuf + varlen, cctx, FALSE) == OK) + return generate_PCALL(cctx, argcount, FALSE); + + // The function may be defined only later. Need to figure out at runtime. + return generate_UCALL(cctx, namebuf, argcount); +} + +// 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. + * 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) +{ + 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 + || vim_strchr(VIM9_NAMESPACE_CHAR, *arg) == NULL)) + break; + return p; +} + +/* + * Like to_name_end() but also skip over a list or dict constant. + */ + char_u * +to_name_const_end(char_u *arg) +{ + char_u *p = to_name_end(arg); + typval_T rettv; + + if (p == arg && *arg == '[') + { + + // Can be "[1, 2, 3]->Func()". + if (get_list_tv(&p, &rettv, FALSE, FALSE) == FAIL) + p = arg; + } + else if (p == arg && *arg == '#' && arg[1] == '{') + { + ++p; + if (eval_dict(&p, &rettv, FALSE, TRUE) == FAIL) + p = arg; + } + else if (p == arg && *arg == '{') + { + int ret = get_lambda_tv(&p, &rettv, FALSE); + + if (ret == NOTDONE) + ret = eval_dict(&p, &rettv, FALSE, FALSE); + if (ret != OK) + p = arg; + } + + return p; +} + + static void +type_mismatch(type_T *expected, type_T *actual) +{ + char *tofree1, *tofree2; + + semsg(_("E1013: type mismatch, expected %s but got %s"), + type_name(expected, &tofree1), type_name(actual, &tofree2)); + vim_free(tofree1); + vim_free(tofree2); +} + +/* + * Check if the expected and actual types match. + */ + static int +check_type(type_T *expected, type_T *actual, int give_msg) +{ + if (expected->tt_type != VAR_UNKNOWN) + { + if (expected->tt_type != actual->tt_type) + { + if (give_msg) + type_mismatch(expected, actual); + return FAIL; + } + if (expected->tt_type == VAR_DICT || expected->tt_type == VAR_LIST) + { + int ret = check_type(expected->tt_member, actual->tt_member, + FALSE); + if (ret == FAIL && give_msg) + type_mismatch(expected, actual); + return ret; + } + } + return OK; +} + +/* + * Check that + * - "actual" is "expected" type or + * - "actual" is a type that can be "expected" type: add a runtime check; or + * - return FAIL. + */ + static int +need_type(type_T *actual, type_T *expected, int offset, cctx_T *cctx) +{ + if (equal_type(actual, expected) || expected->tt_type == VAR_UNKNOWN) + return OK; + if (actual->tt_type != VAR_UNKNOWN) + { + type_mismatch(expected, actual); + return FAIL; + } + generate_TYPECHECK(cctx, expected, offset); + return OK; +} + +/* + * parse a list: [expr, expr] + * "*arg" points to the '['. + */ + static int +compile_list(char_u **arg, cctx_T *cctx) +{ + char_u *p = skipwhite(*arg + 1); + int count = 0; + + while (*p != ']') + { + if (*p == NUL) + return FAIL; + if (compile_expr1(&p, cctx) == FAIL) + break; + ++count; + if (*p == ',') + ++p; + p = skipwhite(p); + } + *arg = p + 1; + + generate_NEWLIST(cctx, count); + return OK; +} + +/* + * parse a lambda: {arg, arg -> expr} + * "*arg" points to the '{'. + */ + static int +compile_lambda(char_u **arg, cctx_T *cctx) +{ + garray_T *instr = &cctx->ctx_instr; + typval_T rettv; + ufunc_T *ufunc; + + // Get the funcref in "rettv". + if (get_lambda_tv(arg, &rettv, TRUE) == FAIL) + return FAIL; + ufunc = rettv.vval.v_partial->pt_func; + + // The function will have one line: "return {expr}". + // Compile it into instructions. + compile_def_function(ufunc, TRUE); + + if (ufunc->uf_dfunc_idx >= 0) + { + if (ga_grow(instr, 1) == FAIL) + return FAIL; + generate_FUNCREF(cctx, ufunc->uf_dfunc_idx); + return OK; + } + return FAIL; +} + +/* + * Compile a lamda call: expr->{lambda}(args) + * "arg" points to the "{". + */ + static int +compile_lambda_call(char_u **arg, cctx_T *cctx) +{ + ufunc_T *ufunc; + typval_T rettv; + int argcount = 1; + int ret = FAIL; + + // Get the funcref in "rettv". + if (get_lambda_tv(arg, &rettv, TRUE) == FAIL) + return FAIL; + + if (**arg != '(') + { + if (*skipwhite(*arg) == '(') + semsg(_(e_nowhitespace)); + else + semsg(_(e_missing_paren), "lambda"); + clear_tv(&rettv); + return FAIL; + } + + // The function will have one line: "return {expr}". + // Compile it into instructions. + ufunc = rettv.vval.v_partial->pt_func; + ++ufunc->uf_refcount; + compile_def_function(ufunc, TRUE); + + // compile the arguments + *arg = skipwhite(*arg + 1); + if (compile_arguments(arg, cctx, &argcount) == OK) + // call the compiled function + ret = generate_CALL(cctx, ufunc, argcount); + + clear_tv(&rettv); + return ret; +} + +/* + * parse a dict: {'key': val} or #{key: val} + * "*arg" points to the '{'. + */ + static int +compile_dict(char_u **arg, cctx_T *cctx, int literal) +{ + garray_T *instr = &cctx->ctx_instr; + int count = 0; + dict_T *d = dict_alloc(); + dictitem_T *item; + + if (d == NULL) + return FAIL; + *arg = skipwhite(*arg + 1); + while (**arg != '}' && **arg != NUL) + { + char_u *key = NULL; + + if (literal) + { + char_u *p = to_name_end(*arg); + + if (p == *arg) + { + semsg(_("E1014: Invalid key: %s"), *arg); + return FAIL; + } + key = vim_strnsave(*arg, p - *arg); + if (generate_PUSHS(cctx, key) == FAIL) + return FAIL; + *arg = p; + } + else + { + isn_T *isn; + + if (compile_expr1(arg, cctx) == FAIL) + return FAIL; + // TODO: check type is string + isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1; + if (isn->isn_type == ISN_PUSHS) + key = isn->isn_arg.string; + } + + // 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); + } + } + + *arg = skipwhite(*arg); + if (**arg != ':') + { + semsg(_(e_missing_dict_colon), *arg); + return FAIL; + } + + *arg = skipwhite(*arg + 1); + if (compile_expr1(arg, cctx) == FAIL) + return FAIL; + ++count; + + if (**arg == '}') + break; + if (**arg != ',') + { + semsg(_(e_missing_dict_comma), *arg); + goto failret; + } + *arg = skipwhite(*arg + 1); + } + + if (**arg != '}') + { + semsg(_(e_missing_dict_end), *arg); + goto failret; + } + *arg = *arg + 1; + + dict_unref(d); + return generate_NEWDICT(cctx, count); + +failret: + 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 = get_option_tv(arg, &rettv, TRUE); + if (ret == OK) + { + // include the '&' in the name, get_option_tv() expects it. + char_u *name = vim_strnsave(start, *arg - start); + type_T *type = 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; + + start = *arg; + ++*arg; + len = get_env_len(arg); + if (len == 0) + { + semsg(_(e_syntax_at), start - 1); + return FAIL; + } + + // include the '$' in the name, get_env_tv() 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_at), *arg - 1); + return FAIL; + } + if (!valid_yank_reg(**arg, TRUE)) + { + emsg_invreg(**arg); + return FAIL; + } + ret = generate_LOAD(cctx, ISN_LOADREG, **arg, NULL, &t_string); + ++*arg; + return ret; +} + +/* + * Apply leading '!', '-' and '+' to constant "rettv". + */ + static int +apply_leader(typval_T *rettv, 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 + { + 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; + } + } + 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; + } +} + +/* + * Compile code to apply '-', '+' and '!'. + */ + static int +compile_leader(cctx_T *cctx, char_u *start, char_u *end) +{ + char_u *p = end; + + // this works from end to start + while (p > start) + { + --p; + if (*p == '-' || *p == '+') + { + int negate = *p == '-'; + isn_T *isn; + + // TODO: check type + 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); + else + isn = generate_instr(cctx, ISN_CHECKNR); + if (isn == NULL) + return FAIL; + } + else + { + int invert = TRUE; + + while (p > start && p[-1] == '!') + { + --p; + invert = !invert; + } + if (generate_2BOOL(cctx, invert) == FAIL) + return FAIL; + } + } + return OK; +} + +/* + * Compile whatever comes after "name" or "name()". + */ + static int +compile_subscript( + char_u **arg, + cctx_T *cctx, + char_u **start_leader, + char_u *end_leader) +{ + for (;;) + { + if (**arg == '(') + { + int argcount = 0; + + // funcref(arg) + *arg = skipwhite(*arg + 1); + if (compile_arguments(arg, cctx, &argcount) == FAIL) + return FAIL; + if (generate_PCALL(cctx, argcount, TRUE) == FAIL) + return FAIL; + } + else if (**arg == '-' && (*arg)[1] == '>') + { + char_u *p; + + // something->method() + // Apply the '!', '-' and '+' first: + // -1.0->func() works like (-1.0)->func() + if (compile_leader(cctx, *start_leader, end_leader) == FAIL) + return FAIL; + *start_leader = end_leader; // don't apply again later + + *arg = skipwhite(*arg + 2); + if (**arg == '{') + { + // lambda call: list->{lambda} + if (compile_lambda_call(arg, cctx) == FAIL) + return FAIL; + } + else + { + // method call: list->method() + for (p = *arg; eval_isnamec1(*p); ++p) + ; + if (*p != '(') + { + semsg(_(e_missing_paren), arg); + return FAIL; + } + // TODO: base value may not be the first argument + if (compile_call(arg, p - *arg, cctx, 1) == FAIL) + return FAIL; + } + } + else if (**arg == '[') + { + // list index: list[123] + // TODO: more arguments + // TODO: dict member dict['name'] + *arg = skipwhite(*arg + 1); + if (compile_expr1(arg, cctx) == FAIL) + return FAIL; + + if (**arg != ']') + { + emsg(_(e_missbrac)); + return FAIL; + } + *arg = skipwhite(*arg + 1); + + if (generate_instr_drop(cctx, ISN_INDEX, 1) == FAIL) + return FAIL; + } + else if (**arg == '.' && (*arg)[1] != '.') + { + char_u *p; + + ++*arg; + p = *arg; + // dictionary member: dict.name + if (eval_isnamec1(*p)) + while (eval_isnamec(*p)) + MB_PTR_ADV(p); + if (p == *arg) + { + semsg(_(e_syntax_at), *arg); + return FAIL; + } + // TODO: check type is dict + if (generate_MEMBER(cctx, *arg, p - *arg) == FAIL) + return FAIL; + *arg = p; + } + else + break; + } + + // TODO - see handle_subscript(): + // Turn "dict.Func" into a partial for "Func" bound to "dict". + // Don't do this when "Func" is already a partial that was bound + // explicitly (pt_auto is FALSE). + + return OK; +} + +/* + * Compile an expression at "*p" and add instructions to "instr". + * "p" is advanced until after the expression, skipping white space. + * + * This is the 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 + * #{key: val, key: val} Dictionary with literal keys + * + * 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) +{ + typval_T rettv; + char_u *start_leader, *end_leader; + int ret = OK; + + /* + * Skip '!', '-' and '+' characters. They are handled later. + */ + start_leader = *arg; + while (**arg == '!' || **arg == '-' || **arg == '+') + *arg = skipwhite(*arg + 1); + 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 (get_number_tv(arg, &rettv, TRUE, FALSE) == FAIL) + return FAIL; + break; + + /* + * String constant: "string". + */ + case '"': if (get_string_tv(arg, &rettv, TRUE) == FAIL) + return FAIL; + break; + + /* + * Literal string constant: 'str''ing'. + */ + case '\'': if (get_lit_string_tv(arg, &rettv, TRUE) == FAIL) + return FAIL; + break; + + /* + * Constant Vim variable. + */ + case 'v': get_vim_constant(arg, &rettv); + ret = NOTDONE; + break; + + /* + * List: [expr, expr] + */ + case '[': ret = compile_list(arg, cctx); + break; + + /* + * Dictionary: #{key: val, key: val} + */ + case '#': if ((*arg)[1] == '{') + { + ++*arg; + ret = compile_dict(arg, cctx, TRUE); + } + else + ret = NOTDONE; + break; + + /* + * Lambda: {arg, arg -> expr} + * Dictionary: {'key': val, 'key': val} + */ + case '{': { + char_u *start = skipwhite(*arg + 1); + + // Find out what comes after the arguments. + ret = get_function_args(&start, '-', NULL, + NULL, NULL, NULL, TRUE); + if (ret != FAIL && *start == '>') + ret = compile_lambda(arg, cctx); + else + ret = compile_dict(arg, cctx, FALSE); + } + break; + + /* + * Option value: &name + */ + case '&': ret = compile_get_option(arg, cctx); + break; + + /* + * Environment variable: $VAR. + */ + case '$': ret = compile_get_env(arg, cctx); + break; + + /* + * Register contents: @r. + */ + case '@': ret = compile_get_register(arg, cctx); + break; + /* + * nested expression: (expression). + */ + case '(': *arg = skipwhite(*arg + 1); + ret = compile_expr1(arg, cctx); // recursive! + *arg = skipwhite(*arg); + if (**arg == ')') + ++*arg; + else if (ret == OK) + { + emsg(_(e_missing_close)); + ret = FAIL; + } + break; + + default: ret = NOTDONE; + break; + } + if (ret == FAIL) + return FAIL; + + if (rettv.v_type != VAR_UNKNOWN) + { + // apply the '!', '-' and '+' before the constant + if (apply_leader(&rettv, start_leader, end_leader) == FAIL) + { + clear_tv(&rettv); + return FAIL; + } + start_leader = end_leader; // don't apply again below + + // push constant + switch (rettv.v_type) + { + case VAR_BOOL: + generate_PUSHBOOL(cctx, rettv.vval.v_number); + break; + case VAR_SPECIAL: + generate_PUSHSPEC(cctx, rettv.vval.v_number); + break; + case VAR_NUMBER: + generate_PUSHNR(cctx, rettv.vval.v_number); + break; +#ifdef FEAT_FLOAT + case VAR_FLOAT: + generate_PUSHF(cctx, rettv.vval.v_float); + break; +#endif + case VAR_BLOB: + generate_PUSHBLOB(cctx, rettv.vval.v_blob); + rettv.vval.v_blob = NULL; + break; + case VAR_STRING: + generate_PUSHS(cctx, rettv.vval.v_string); + rettv.vval.v_string = NULL; + break; + default: + iemsg("constant type missing"); + return FAIL; + } + } + else if (ret == NOTDONE) + { + char_u *p; + int r; + + if (!eval_isnamec1(**arg)) + { + semsg(_("E1015: Name expected: %s"), *arg); + return FAIL; + } + + // "name" or "name()" + p = to_name_end(*arg); + if (*p == '(') + r = compile_call(arg, p - *arg, cctx, 0); + else + r = compile_load(arg, p, cctx, TRUE); + if (r == FAIL) + return FAIL; + } + + if (compile_subscript(arg, cctx, &start_leader, end_leader) == FAIL) + return FAIL; + + // Now deal with prefixed '-', '+' and '!', if not done already. + return compile_leader(cctx, start_leader, end_leader); +} + +/* + * * number multiplication + * / number division + * % number modulo + */ + static int +compile_expr6(char_u **arg, cctx_T *cctx) +{ + char_u *op; + + // get the first variable + if (compile_expr7(arg, cctx) == FAIL) + return FAIL; + + /* + * Repeat computing, until no "*", "/" or "%" is following. + */ + for (;;) + { + op = skipwhite(*arg); + if (*op != '*' && *op != '/' && *op != '%') + break; + if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(op[1])) + { + char_u buf[3]; + + vim_strncpy(buf, op, 1); + semsg(_(e_white_both), buf); + } + *arg = skipwhite(op + 1); + + // get the second variable + if (compile_expr7(arg, cctx) == FAIL) + return FAIL; + + generate_two_op(cctx, op); + } + + return OK; +} + +/* + * + number addition + * - number subtraction + * .. string concatenation + */ + static int +compile_expr5(char_u **arg, cctx_T *cctx) +{ + char_u *op; + int oplen; + + // get the first variable + if (compile_expr6(arg, cctx) == FAIL) + return FAIL; + + /* + * Repeat computing, until no "+", "-" or ".." is following. + */ + for (;;) + { + op = skipwhite(*arg); + if (*op != '+' && *op != '-' && !(*op == '.' && (*(*arg + 1) == '.'))) + break; + oplen = (*op == '.' ? 2 : 1); + + if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(op[oplen])) + { + char_u buf[3]; + + vim_strncpy(buf, op, oplen); + semsg(_(e_white_both), buf); + } + + *arg = skipwhite(op + oplen); + + // get the second variable + if (compile_expr6(arg, cctx) == FAIL) + return FAIL; + + if (*op == '.') + { + if (may_generate_2STRING(-2, cctx) == FAIL + || may_generate_2STRING(-1, 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) +{ + exptype_T type = EXPR_UNKNOWN; + char_u *p; + int len = 2; + int i; + int type_is = FALSE; + + // get the first variable + if (compile_expr5(arg, cctx) == FAIL) + return FAIL; + + p = skipwhite(*arg); + 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; + } + + /* + * If there is a comparative operator, use it. + */ + if (type != EXPR_UNKNOWN) + { + int ic = FALSE; // Default: do not ignore case + + if (type_is && (p[len] == '?' || p[len] == '#')) + { + semsg(_(e_invexpr2), *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 (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[len])) + { + char_u buf[7]; + + vim_strncpy(buf, p, len); + semsg(_(e_white_both), buf); + } + + // get the second variable + *arg = skipwhite(p + len); + if (compile_expr5(arg, cctx) == FAIL) + return FAIL; + + generate_COMPARE(cctx, type, ic); + } + + return OK; +} + +/* + * Compile || or &&. + */ + static int +compile_and_or(char_u **arg, cctx_T *cctx, char *op) +{ + char_u *p = skipwhite(*arg); + int opchar = *op; + + if (p[0] == opchar && p[1] == opchar) + { + garray_T *instr = &cctx->ctx_instr; + garray_T end_ga; + + /* + * Repeat until there is no following "||" or "&&" + */ + ga_init2(&end_ga, sizeof(int), 10); + while (p[0] == opchar && p[1] == opchar) + { + if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[2])) + semsg(_(e_white_both), op); + + if (ga_grow(&end_ga, 1) == FAIL) + { + ga_clear(&end_ga); + return FAIL; + } + *(((int *)end_ga.ga_data) + end_ga.ga_len) = instr->ga_len; + ++end_ga.ga_len; + generate_JUMP(cctx, opchar == '|' + ? JUMP_AND_KEEP_IF_TRUE : JUMP_AND_KEEP_IF_FALSE, 0); + + // eval the next expression + *arg = skipwhite(p + 2); + if ((opchar == '|' ? compile_expr3(arg, cctx) + : compile_expr4(arg, cctx)) == FAIL) + { + ga_clear(&end_ga); + return FAIL; + } + p = skipwhite(*arg); + } + + // Fill in the end label in all jumps. + 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); + } + + return OK; +} + +/* + * expr4a && expr4a && expr4a logical AND + * + * Produces instructions: + * EVAL expr4a Push result of "expr4a" + * JUMP_AND_KEEP_IF_FALSE end + * EVAL expr4b Push result of "expr4b" + * JUMP_AND_KEEP_IF_FALSE end + * EVAL expr4c Push result of "expr4c" + * end: + */ + static int +compile_expr3(char_u **arg, cctx_T *cctx) +{ + // get the first variable + if (compile_expr4(arg, cctx) == FAIL) + return FAIL; + + // || and && work almost the same + return compile_and_or(arg, cctx, "&&"); +} + +/* + * expr3a || expr3b || expr3c logical OR + * + * Produces instructions: + * EVAL expr3a Push result of "expr3a" + * JUMP_AND_KEEP_IF_TRUE end + * EVAL expr3b Push result of "expr3b" + * JUMP_AND_KEEP_IF_TRUE end + * EVAL expr3c Push result of "expr3c" + * end: + */ + static int +compile_expr2(char_u **arg, cctx_T *cctx) +{ + // eval the first expression + if (compile_expr3(arg, cctx) == FAIL) + return FAIL; + + // || and && work almost the same + return compile_and_or(arg, cctx, "||"); +} + +/* + * Toplevel expression: expr2 ? expr1a : expr1b + * + * Produces instructions: + * EVAL expr2 Push result of "expr" + * JUMP_IF_FALSE alt jump if false + * EVAL expr1a + * JUMP_ALWAYS end + * alt: EVAL expr1b + * end: + */ + static int +compile_expr1(char_u **arg, cctx_T *cctx) +{ + char_u *p; + + // evaluate the first expression + if (compile_expr2(arg, cctx) == FAIL) + return FAIL; + + p = skipwhite(*arg); + if (*p == '?') + { + garray_T *instr = &cctx->ctx_instr; + garray_T *stack = &cctx->ctx_type_stack; + int alt_idx = instr->ga_len; + int end_idx; + isn_T *isn; + type_T *type1; + type_T *type2; + + if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1])) + semsg(_(e_white_both), "?"); + + generate_JUMP(cctx, JUMP_IF_FALSE, 0); + + // evaluate the second expression; any type is accepted + *arg = skipwhite(p + 1); + compile_expr1(arg, cctx); + + // 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; + + // Check for the ":". + p = skipwhite(*arg); + if (*p != ':') + { + emsg(_(e_missing_colon)); + return FAIL; + } + if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1])) + semsg(_(e_white_both), ":"); + + // evaluate the third expression + *arg = skipwhite(p + 1); + compile_expr1(arg, cctx); + + // If the types differ, the result has a more generic type. + type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + common_type(type1, type2, type2); + + // jump here from JUMP_ALWAYS + isn = ((isn_T *)instr->ga_data) + end_idx; + isn->isn_arg.jump.jump_where = instr->ga_len; + } + return OK; +} + +/* + * compile "return [expr]" + */ + static char_u * +compile_return(char_u *arg, int set_return_type, cctx_T *cctx) +{ + char_u *p = arg; + garray_T *stack = &cctx->ctx_type_stack; + type_T *stack_type; + + if (*p != NUL && *p != '|' && *p != '\n') + { + // compile return argument into instructions + if (compile_expr1(&p, cctx) == FAIL) + return NULL; + + stack_type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + if (set_return_type) + cctx->ctx_ufunc->uf_ret_type = stack_type; + else if (need_type(stack_type, cctx->ctx_ufunc->uf_ret_type, -1, cctx) + == FAIL) + return NULL; + } + else + { + if (set_return_type) + cctx->ctx_ufunc->uf_ret_type = &t_void; + else if (cctx->ctx_ufunc->uf_ret_type->tt_type != VAR_VOID) + { + emsg(_("E1003: Missing return value")); + return NULL; + } + + // No argument, return zero. + generate_PUSHNR(cctx, 0); + } + + if (generate_instr(cctx, ISN_RETURN) == NULL) + return NULL; + + // "return val | endif" is possible + return skipwhite(p); +} + +/* + * Return the length of an assignment operator, or zero if there isn't one. + */ + int +assignment_len(char_u *p, int *heredoc) +{ + if (*p == '=') + { + if (p[1] == '<' && p[2] == '<') + { + *heredoc = TRUE; + return 3; + } + return 1; + } + if (vim_strchr((char_u *)"+-*/%", *p) != NULL && p[1] == '=') + return 2; + if (STRNCMP(p, "..=", 3) == 0) + return 3; + return 0; +} + +// words that cannot be used as a variable +static char *reserved[] = { + "true", + "false", + NULL +}; + +/* + * Get a line for "=<<". + * Return a pointer to the line in allocated memory. + * Return NULL for end-of-file or some error. + */ + static char_u * +heredoc_getline( + int c UNUSED, + void *cookie, + int indent UNUSED, + int do_concat UNUSED) +{ + cctx_T *cctx = (cctx_T *)cookie; + + if (cctx->ctx_lnum == cctx->ctx_ufunc->uf_lines.ga_len) + NULL; + ++cctx->ctx_lnum; + return vim_strsave(((char_u **)cctx->ctx_ufunc->uf_lines.ga_data) + [cctx->ctx_lnum]); +} + +/* + * compile "let var [= expr]", "const var = expr" and "var = expr" + * "arg" points to "var". + */ + static char_u * +compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) +{ + char_u *p; + char_u *ret = NULL; + int var_count = 0; + int semicolon = 0; + size_t varlen; + garray_T *instr = &cctx->ctx_instr; + int idx = -1; + char_u *op; + int option = FALSE; + int opt_type; + int opt_flags = 0; + int global = FALSE; + int script = FALSE; + int oplen = 0; + int heredoc = FALSE; + type_T *type; + lvar_T *lvar; + char_u *name; + char_u *sp; + int has_type = FALSE; + int is_decl = cmdidx == CMD_let || cmdidx == CMD_const; + int instr_count = -1; + + p = skip_var_list(arg, FALSE, &var_count, &semicolon); + if (p == NULL) + return NULL; + if (var_count > 0) + { + // TODO: let [var, var] = list + emsg("Cannot handle a list yet"); + return NULL; + } + + varlen = p - arg; + name = vim_strnsave(arg, (int)varlen); + if (name == NULL) + return NULL; + + if (*arg == '&') + { + int cc; + long numval; + char_u *stringval = NULL; + + option = TRUE; + if (cmdidx == CMD_const) + { + emsg(_(e_const_option)); + return NULL; + } + if (is_decl) + { + semsg(_("E1052: Cannot declare an option: %s"), arg); + goto theend; + } + p = arg; + p = find_option_end(&p, &opt_flags); + if (p == NULL) + { + emsg(_(e_letunexp)); + return NULL; + } + cc = *p; + *p = NUL; + opt_type = get_option_value(arg + 1, &numval, &stringval, opt_flags); + *p = cc; + if (opt_type == -3) + { + semsg(_(e_unknown_option), *arg); + return NULL; + } + if (opt_type == -2 || opt_type == 0) + type = &t_string; + else + type = &t_number; // both number and boolean option + } + else if (STRNCMP(arg, "g:", 2) == 0) + { + global = TRUE; + if (is_decl) + { + semsg(_("E1016: Cannot declare a global variable: %s"), name); + goto theend; + } + } + else + { + for (idx = 0; reserved[idx] != NULL; ++idx) + if (STRCMP(reserved[idx], name) == 0) + { + semsg(_("E1034: Cannot use reserved name %s"), name); + goto theend; + } + + idx = lookup_local(arg, varlen, cctx); + if (idx >= 0) + { + if (is_decl) + { + semsg(_("E1017: Variable already declared: %s"), name); + goto theend; + } + else + { + lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx; + if (lvar->lv_const) + { + semsg(_("E1018: Cannot assign to a constant: %s"), name); + goto theend; + } + } + } + else if (lookup_script(arg, varlen) == OK) + { + script = TRUE; + if (is_decl) + { + semsg(_("E1054: Variable already declared in the script: %s"), + name); + goto theend; + } + } + } + + if (!option) + { + if (is_decl && *p == ':') + { + // parse optional type: "let var: type = expr" + p = skipwhite(p + 1); + type = parse_type(&p, cctx->ctx_type_list); + if (type == NULL) + goto theend; + has_type = TRUE; + } + else if (idx < 0) + { + // global and new local default to "any" type + type = &t_any; + } + else + { + lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx; + type = lvar->lv_type; + } + } + + sp = p; + p = skipwhite(p); + op = p; + oplen = assignment_len(p, &heredoc); + if (oplen > 0 && (!VIM_ISWHITE(*sp) || !VIM_ISWHITE(op[oplen]))) + { + char_u buf[4]; + + vim_strncpy(buf, op, oplen); + semsg(_(e_white_both), buf); + } + + if (oplen == 3 && !heredoc && !global && type->tt_type != VAR_STRING + && type->tt_type != VAR_UNKNOWN) + { + emsg("E1019: Can only concatenate to string"); + goto theend; + } + + // +=, /=, etc. require an existing variable + if (idx < 0 && !global && !option) + { + if (oplen > 1 && !heredoc) + { + semsg(_("E1020: cannot use an operator on a new variable: %s"), + name); + goto theend; + } + + // new local variable + idx = reserve_local(cctx, arg, varlen, cmdidx == CMD_const, type); + if (idx < 0) + goto theend; + } + + if (heredoc) + { + list_T *l; + listitem_T *li; + + // [let] varname =<< [trim] {end} + eap->getline = heredoc_getline; + eap->cookie = cctx; + l = heredoc_get(eap, op + 3); + + // Push each line and the create the list. + for (li = l->lv_first; li != NULL; li = li->li_next) + { + generate_PUSHS(cctx, li->li_tv.vval.v_string); + li->li_tv.vval.v_string = NULL; + } + generate_NEWLIST(cctx, l->lv_len); + type = &t_list_string; + list_free(l); + p += STRLEN(p); + } + else if (oplen > 0) + { + // for "+=", "*=", "..=" etc. first load the current value + if (*op != '=') + { + if (option) + generate_LOAD(cctx, ISN_LOADOPT, 0, name + 1, type); + else if (global) + generate_LOAD(cctx, ISN_LOADG, 0, name + 2, type); + else + generate_LOAD(cctx, ISN_LOAD, idx, NULL, type); + } + + // compile the expression + instr_count = instr->ga_len; + p = skipwhite(p + oplen); + if (compile_expr1(&p, cctx) == FAIL) + goto theend; + + if (idx >= 0 && (is_decl || !has_type)) + { + garray_T *stack = &cctx->ctx_type_stack; + type_T *stacktype = + ((type_T **)stack->ga_data)[stack->ga_len - 1]; + + lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx; + if (!has_type) + { + if (stacktype->tt_type == VAR_VOID) + { + emsg(_("E1031: Cannot use void value")); + goto theend; + } + else + lvar->lv_type = stacktype; + } + else + if (check_type(lvar->lv_type, stacktype, TRUE) == FAIL) + goto theend; + } + } + else if (cmdidx == CMD_const) + { + emsg(_("E1021: const requires a value")); + goto theend; + } + else if (!has_type || option) + { + emsg(_("E1022: type or initialization required")); + goto theend; + } + else + { + // variables are always initialized + // TODO: support more types + if (ga_grow(instr, 1) == FAIL) + goto theend; + if (type->tt_type == VAR_STRING) + generate_PUSHS(cctx, vim_strsave((char_u *)"")); + else + generate_PUSHNR(cctx, 0); + } + + if (oplen > 0 && *op != '=') + { + type_T *expected = &t_number; + garray_T *stack = &cctx->ctx_type_stack; + type_T *stacktype; + + // TODO: if type is known use float or any operation + + if (*op == '.') + expected = &t_string; + stacktype = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + if (need_type(stacktype, expected, -1, cctx) == FAIL) + goto theend; + + if (*op == '.') + generate_instr_drop(cctx, ISN_CONCAT, 1); + else + { + isn_T *isn = generate_instr_drop(cctx, ISN_OPNR, 1); + + if (isn == NULL) + goto theend; + switch (*op) + { + case '+': isn->isn_arg.op.op_type = EXPR_ADD; break; + case '-': isn->isn_arg.op.op_type = EXPR_SUB; break; + case '*': isn->isn_arg.op.op_type = EXPR_MULT; break; + case '/': isn->isn_arg.op.op_type = EXPR_DIV; break; + case '%': isn->isn_arg.op.op_type = EXPR_REM; break; + } + } + } + + if (option) + generate_STOREOPT(cctx, name + 1, opt_flags); + else if (global) + generate_STORE(cctx, ISN_STOREG, 0, name + 2); + else if (script) + { + idx = get_script_item_idx(current_sctx.sc_sid, name, TRUE); + // TODO: specific type + generate_SCRIPT(cctx, ISN_STORESCRIPT, + current_sctx.sc_sid, idx, &t_any); + } + else + { + 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 (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.str_idx = idx; + isn->isn_arg.storenr.str_val = val; + if (stack->ga_len > 0) + --stack->ga_len; + } + else + generate_STORE(cctx, ISN_STORE, idx, NULL); + } + ret = p; + +theend: + vim_free(name); + return ret; +} + +/* + * Compile an :import command. + */ + static char_u * +compile_import(char_u *arg, cctx_T *cctx) +{ + return handle_import(arg, &cctx->ctx_imports, 0); +} + +/* + * 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, 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 = instr->ga_len; + *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; +} + +/* + * 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; + scope_T *scope; + + // compile "expr" + if (compile_expr1(&p, cctx) == FAIL) + return NULL; + + scope = new_scope(cctx, IF_SCOPE); + if (scope == NULL) + return NULL; + + // "where" is set when ":elseif", "else" or ":endif" is found + scope->se_if.is_if_label = instr->ga_len; + generate_JUMP(cctx, JUMP_IF_FALSE, 0); + + return p; +} + + static char_u * +compile_elseif(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_elseif_without_if)); + return NULL; + } + cctx->ctx_locals.ga_len = scope->se_local_count; + + // jump from previous block to the end + if (compile_jump_to_end(&scope->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_if.is_if_label; + isn->isn_arg.jump.jump_where = instr->ga_len; + + // compile "expr" + if (compile_expr1(&p, cctx) == FAIL) + return NULL; + + // "where" is set when ":elseif", "else" or ":endif" is found + scope->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; + } + cctx->ctx_locals.ga_len = scope->se_local_count; + + // jump from previous block to the end + if (compile_jump_to_end(&scope->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_if.is_if_label; + isn->isn_arg.jump.jump_where = instr->ga_len; + + 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 (scope == NULL || scope->se_type != IF_SCOPE) + { + emsg(_(e_endif_without_if)); + return NULL; + } + ifscope = &scope->se_if; + cctx->ctx_scope = scope->se_outer; + cctx->ctx_locals.ga_len = scope->se_local_count; + + // previous "if" or "elseif" jumps here + isn = ((isn_T *)instr->ga_data) + scope->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, cctx); + + vim_free(scope); + return arg; +} + +/* + * compile "for var in expr" + * + * Produces instructions: + * PUSHNR -1 + * STORE loop-idx Set index to -1 + * EVAL expr Push result of "expr" + * 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" + * + */ + static char_u * +compile_for(char_u *arg, cctx_T *cctx) +{ + char_u *p; + size_t varlen; + garray_T *instr = &cctx->ctx_instr; + garray_T *stack = &cctx->ctx_type_stack; + scope_T *scope; + int loop_idx; // index of loop iteration variable + int var_idx; // index of "var" + type_T *vartype; + + // TODO: list of variables: "for [key, value] in dict" + // parse "var" + for (p = arg; eval_isnamec1(*p); ++p) + ; + varlen = p - arg; + var_idx = lookup_local(arg, varlen, cctx); + if (var_idx >= 0) + { + semsg(_("E1023: variable already defined: %s"), arg); + return NULL; + } + + // consume "in" + p = skipwhite(p); + if (STRNCMP(p, "in", 2) != 0 || !VIM_ISWHITE(p[2])) + { + emsg(_(e_missing_in)); + return NULL; + } + p = skipwhite(p + 2); + + + scope = new_scope(cctx, FOR_SCOPE); + if (scope == NULL) + return NULL; + + // Reserve a variable to store the loop iteration counter. + loop_idx = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number); + if (loop_idx < 0) + return NULL; + + // Reserve a variable to store "var" + var_idx = reserve_local(cctx, arg, varlen, FALSE, &t_any); + if (var_idx < 0) + return NULL; + + generate_STORENR(cctx, loop_idx, -1); + + // compile "expr", it remains on the stack until "endfor" + arg = p; + if (compile_expr1(&arg, cctx) == FAIL) + return NULL; + + // now we know the type of "var" + vartype = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + if (vartype->tt_type != VAR_LIST) + { + emsg(_("E1024: need a List to iterate over")); + return NULL; + } + if (vartype->tt_member->tt_type != VAR_UNKNOWN) + { + lvar_T *lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + var_idx; + + lvar->lv_type = vartype->tt_member; + } + + // "for_end" is set when ":endfor" is found + scope->se_for.fs_top_label = instr->ga_len; + + generate_FOR(cctx, loop_idx); + generate_STORE(cctx, ISN_STORE, var_idx, NULL); + + return arg; +} + +/* + * 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 (scope == NULL || scope->se_type != FOR_SCOPE) + { + emsg(_(e_for)); + return NULL; + } + forscope = &scope->se_for; + cctx->ctx_scope = scope->se_outer; + cctx->ctx_locals.ga_len = 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, 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; + garray_T *instr = &cctx->ctx_instr; + scope_T *scope; + + scope = new_scope(cctx, WHILE_SCOPE); + if (scope == NULL) + return NULL; + + scope->se_while.ws_top_label = instr->ga_len; + + // compile "expr" + if (compile_expr1(&p, cctx) == FAIL) + return NULL; + + // "while_end" is set when ":endwhile" is found + if (compile_jump_to_end(&scope->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; + + if (scope == NULL || scope->se_type != WHILE_SCOPE) + { + emsg(_(e_while)); + return NULL; + } + cctx->ctx_scope = scope->se_outer; + cctx->ctx_locals.ga_len = scope->se_local_count; + + // At end of ":for" scope jump back to the FOR instruction. + generate_JUMP(cctx, JUMP_ALWAYS, scope->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_while.ws_end_label, 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; + + for (;;) + { + if (scope == NULL) + { + emsg(_(e_continue)); + return NULL; + } + if (scope->se_type == FOR_SCOPE || scope->se_type == WHILE_SCOPE) + break; + scope = scope->se_outer; + } + + // Jump back to the FOR or WHILE instruction. + generate_JUMP(cctx, JUMP_ALWAYS, + scope->se_type == FOR_SCOPE ? scope->se_for.fs_top_label + : scope->se_while.ws_top_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_for.fs_end_label; + else + el = &scope->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; + cctx->ctx_locals.ga_len = 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 exeception + * 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; + + // scope that holds the jumps that go to catch/finally/endtry + try_scope = new_scope(cctx, TRY_SCOPE); + if (try_scope == NULL) + return NULL; + + // "catch" is set when the first ":catch" is found. + // "finally" is set when ":finally" or ":endtry" is found + try_scope->se_try.ts_try_label = instr->ga_len; + if (generate_instr(cctx, ISN_TRY) == 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; + + // 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_try.ts_caught_all) + { + emsg(_("E1033: catch unreachable after catch-all")); + return NULL; + } + + // Jump from end of previous block to :finally or :endtry + if (compile_jump_to_end(&scope->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_try.ts_try_label; + if (isn->isn_arg.try.try_catch == 0) + isn->isn_arg.try.try_catch = instr->ga_len; + if (scope->se_try.ts_catch_label != 0) + { + // Previous catch without match jumps here + isn = ((isn_T *)instr->ga_data) + scope->se_try.ts_catch_label; + isn->isn_arg.jump.jump_where = instr->ga_len; + } + + p = skipwhite(arg); + if (ends_excmd(*p)) + { + scope->se_try.ts_caught_all = TRUE; + scope->se_try.ts_catch_label = 0; + } + else + { + // Push v:exception, push {expr} and MATCH + generate_instr_type(cctx, ISN_PUSHEXC, &t_string); + + if (compile_expr1(&p, cctx) == FAIL) + return NULL; + + // TODO: check for strings? + if (generate_COMPARE(cctx, EXPR_MATCH, FALSE) == FAIL) + return NULL; + + scope->se_try.ts_catch_label = instr->ga_len; + if (generate_JUMP(cctx, JUMP_IF_FALSE, 0) == FAIL) + return NULL; + } + + if (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; + + // 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; + } + + // End :catch or :finally scope: set value in ISN_TRY instruction + isn = ((isn_T *)instr->ga_data) + scope->se_try.ts_try_label; + if (isn->isn_arg.try.try_finally != 0) + { + emsg(_(e_finally_dup)); + return NULL; + } + + // Fill in the "end" label in jumps at the end of the blocks. + compile_fill_jump_to_end(&scope->se_try.ts_end_label, cctx); + + if (scope->se_try.ts_catch_label != 0) + { + // Previous catch without match jumps here + isn = ((isn_T *)instr->ga_data) + scope->se_try.ts_catch_label; + isn->isn_arg.jump.jump_where = instr->ga_len; + } + + isn->isn_arg.try.try_finally = instr->ga_len; + // TODO: set index in ts_finally_label jumps + + 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 *isn; + + // 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)); + if (scope->se_type == FOR_SCOPE) + emsg(_(e_endfor)); + else + emsg(_(e_endif)); + return NULL; + } + + isn = ((isn_T *)instr->ga_data) + scope->se_try.ts_try_label; + if (isn->isn_arg.try.try_catch == 0 && isn->isn_arg.try.try_finally == 0) + { + emsg(_("E1032: missing :catch or :finally")); + return NULL; + } + + // 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_try.ts_end_label, cctx); + + // End :catch or :finally scope: set value in ISN_TRY instruction + if (isn->isn_arg.try.try_finally == 0) + isn->isn_arg.try.try_finally = instr->ga_len; + compile_endblock(cctx); + + if (generate_instr(cctx, ISN_ENDTRY) == NULL) + return NULL; + return arg; +} + +/* + * compile "throw {expr}" + */ + static char_u * +compile_throw(char_u *arg, cctx_T *cctx UNUSED) +{ + char_u *p = skipwhite(arg); + + if (ends_excmd(*p)) + { + emsg(_(e_argreq)); + return NULL; + } + if (compile_expr1(&p, cctx) == FAIL) + return NULL; + if (may_generate_2STRING(-1, cctx) == FAIL) + return NULL; + if (generate_instr_drop(cctx, ISN_THROW, 1) == NULL) + return NULL; + + return p; +} + +/* + * compile "echo expr" + */ + static char_u * +compile_echo(char_u *arg, int with_white, cctx_T *cctx) +{ + char_u *p = arg; + int count = 0; + + // for () + { + if (compile_expr1(&p, cctx) == FAIL) + return NULL; + ++count; + } + + generate_ECHO(cctx, with_white, count); + + return p; +} + +/* + * After ex_function() has collected all the function lines: parse and compile + * the lines into instructions. + * Adds the function to "def_functions". + * When "set_return_type" is set then set ufunc->uf_ret_type to the type of the + * return statement (used for lambda). + */ + void +compile_def_function(ufunc_T *ufunc, int set_return_type) +{ + dfunc_T *dfunc; + char_u *line = NULL; + char_u *p; + exarg_T ea; + char *errormsg = NULL; // error message + int had_return = FALSE; + cctx_T cctx; + garray_T *instr; + int called_emsg_before = called_emsg; + int ret = FAIL; + sctx_T save_current_sctx = current_sctx; + + if (ufunc->uf_dfunc_idx >= 0) + { + // redefining a function that was compiled before + dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx; + dfunc->df_deleted = FALSE; + } + else + { + // Add the function to "def_functions". + if (ga_grow(&def_functions, 1) == FAIL) + return; + dfunc = ((dfunc_T *)def_functions.ga_data) + def_functions.ga_len; + vim_memset(dfunc, 0, sizeof(dfunc_T)); + dfunc->df_idx = def_functions.ga_len; + ufunc->uf_dfunc_idx = dfunc->df_idx; + dfunc->df_ufunc = ufunc; + ++def_functions.ga_len; + } + + vim_memset(&cctx, 0, sizeof(cctx)); + cctx.ctx_ufunc = ufunc; + cctx.ctx_lnum = -1; + ga_init2(&cctx.ctx_locals, sizeof(lvar_T), 10); + ga_init2(&cctx.ctx_type_stack, sizeof(type_T *), 50); + ga_init2(&cctx.ctx_imports, sizeof(imported_T), 10); + cctx.ctx_type_list = &ufunc->uf_type_list; + ga_init2(&cctx.ctx_instr, sizeof(isn_T), 50); + instr = &cctx.ctx_instr; + + // Most modern script version. + current_sctx.sc_version = SCRIPT_VERSION_VIM9; + + for (;;) + { + if (line != NULL && *line == '|') + // the line continues after a '|' + ++line; + else if (line != NULL && *line != NUL) + { + semsg(_("E488: Trailing characters: %s"), line); + goto erret; + } + else + { + do + { + ++cctx.ctx_lnum; + if (cctx.ctx_lnum == ufunc->uf_lines.ga_len) + break; + line = ((char_u **)ufunc->uf_lines.ga_data)[cctx.ctx_lnum]; + } while (line == NULL); + if (cctx.ctx_lnum == ufunc->uf_lines.ga_len) + break; + SOURCING_LNUM = ufunc->uf_script_ctx.sc_lnum + cctx.ctx_lnum + 1; + } + + had_return = FALSE; + vim_memset(&ea, 0, sizeof(ea)); + ea.cmdlinep = &line; + ea.cmd = skipwhite(line); + + // "}" ends a block scope + if (*ea.cmd == '}') + { + scopetype_T stype = cctx.ctx_scope == NULL + ? NO_SCOPE : cctx.ctx_scope->se_type; + + if (stype == BLOCK_SCOPE) + { + compile_endblock(&cctx); + line = ea.cmd; + } + else + { + emsg("E1025: using } outside of a block scope"); + goto erret; + } + if (line != NULL) + line = skipwhite(ea.cmd + 1); + continue; + } + + // "{" starts a block scope + if (*ea.cmd == '{') + { + line = compile_block(ea.cmd, &cctx); + continue; + } + + /* + * COMMAND MODIFIERS + */ + if (parse_command_modifiers(&ea, &errormsg, FALSE) == FAIL) + { + if (errormsg != NULL) + goto erret; + // empty line or comment + line = (char_u *)""; + continue; + } + + // Skip ":call" to get to the function name. + if (checkforcmd(&ea.cmd, "call", 3)) + ea.cmd = skipwhite(ea.cmd); + + // Assuming the command starts with a variable or function name, find + // what follows. Also "&opt = value". + p = (*ea.cmd == '&') ? ea.cmd + 1 : ea.cmd; + p = to_name_end(p); + if (p > ea.cmd && *p != NUL) + { + int oplen; + int heredoc; + + // "funcname(" is always a function call. + // "varname[]" is an expression. + // "g:varname" is an expression. + // "varname->expr" is an expression. + if (*p == '(' + || *p == '[' + || ((p - ea.cmd) > 2 && ea.cmd[1] == ':') + || (*p == '-' && p[1] == '>')) + { + // TODO + } + + oplen = assignment_len(skipwhite(p), &heredoc); + if (oplen > 0) + { + // Recognize an assignment if we recognize the variable name: + // "g:var = expr" + // "var = expr" where "var" is a local var name. + // "&opt = expr" + if (*ea.cmd == '&' + || ((p - ea.cmd) > 2 && ea.cmd[1] == ':') + || lookup_local(ea.cmd, p - ea.cmd, &cctx) >= 0 + || lookup_script(ea.cmd, p - ea.cmd) == OK) + { + line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx); + if (line == NULL) + goto erret; + continue; + } + } + } + + /* + * COMMAND after range + */ + ea.cmd = skip_range(ea.cmd, NULL); + p = find_ex_command(&ea, NULL, lookup_local, &cctx); + + if (p == ea.cmd && ea.cmdidx != CMD_SIZE) + { + // Expression or function call. + if (ea.cmdidx == CMD_eval) + { + p = ea.cmd; + if (compile_expr1(&p, &cctx) == FAIL) + goto erret; + + // drop the return value + generate_instr_drop(&cctx, ISN_DROP, 1); + line = p; + continue; + } + if (ea.cmdidx == CMD_let) + { + line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx); + if (line == NULL) + goto erret; + continue; + } + iemsg("Command from find_ex_command() not handled"); + goto erret; + } + + p = skipwhite(p); + + switch (ea.cmdidx) + { + case CMD_def: + case CMD_function: + // TODO: Nested function + emsg("Nested function not implemented yet"); + goto erret; + + case CMD_return: + line = compile_return(p, set_return_type, &cctx); + had_return = TRUE; + break; + + case CMD_let: + case CMD_const: + line = compile_assignment(p, &ea, ea.cmdidx, &cctx); + break; + + case CMD_import: + line = compile_import(p, &cctx); + break; + + case CMD_if: + line = compile_if(p, &cctx); + break; + case CMD_elseif: + line = compile_elseif(p, &cctx); + break; + case CMD_else: + line = compile_else(p, &cctx); + break; + case CMD_endif: + line = compile_endif(p, &cctx); + break; + + case CMD_while: + line = compile_while(p, &cctx); + break; + case CMD_endwhile: + line = compile_endwhile(p, &cctx); + break; + + case CMD_for: + line = compile_for(p, &cctx); + break; + case CMD_endfor: + line = compile_endfor(p, &cctx); + break; + case CMD_continue: + line = compile_continue(p, &cctx); + break; + case CMD_break: + line = compile_break(p, &cctx); + break; + + case CMD_try: + line = compile_try(p, &cctx); + break; + case CMD_catch: + line = compile_catch(p, &cctx); + break; + case CMD_finally: + line = compile_finally(p, &cctx); + break; + case CMD_endtry: + line = compile_endtry(p, &cctx); + break; + case CMD_throw: + line = compile_throw(p, &cctx); + break; + + case CMD_echo: + line = compile_echo(p, TRUE, &cctx); + break; + case CMD_echon: + line = compile_echo(p, FALSE, &cctx); + break; + + default: + // Not recognized, execute with do_cmdline_cmd(). + generate_EXEC(&cctx, line); + line = (char_u *)""; + break; + } + if (line == NULL) + goto erret; + + if (cctx.ctx_type_stack.ga_len < 0) + { + iemsg("Type stack underflow"); + goto erret; + } + } + + if (cctx.ctx_scope != NULL) + { + if (cctx.ctx_scope->se_type == IF_SCOPE) + emsg(_(e_endif)); + else if (cctx.ctx_scope->se_type == WHILE_SCOPE) + emsg(_(e_endwhile)); + else if (cctx.ctx_scope->se_type == FOR_SCOPE) + emsg(_(e_endfor)); + else + emsg(_("E1026: Missing }")); + goto erret; + } + + if (!had_return) + { + if (ufunc->uf_ret_type->tt_type != VAR_VOID) + { + emsg(_("E1027: Missing return statement")); + goto erret; + } + + // Return zero if there is no return at the end. + generate_PUSHNR(&cctx, 0); + generate_instr(&cctx, ISN_RETURN); + } + + dfunc->df_instr = instr->ga_data; + dfunc->df_instr_count = instr->ga_len; + dfunc->df_varcount = cctx.ctx_max_local; + + ret = OK; + +erret: + if (ret == FAIL) + { + ga_clear(instr); + ufunc->uf_dfunc_idx = -1; + --def_functions.ga_len; + if (errormsg != NULL) + emsg(errormsg); + else if (called_emsg == called_emsg_before) + emsg("E1028: compile_def_function failed"); + + // don't execute this function body + ufunc->uf_lines.ga_len = 0; + } + + current_sctx = save_current_sctx; + ga_clear(&cctx.ctx_type_stack); + ga_clear(&cctx.ctx_locals); +} + +/* + * Delete an instruction, free what it contains. + */ + static void +delete_instr(isn_T *isn) +{ + switch (isn->isn_type) + { + case ISN_EXEC: + case ISN_LOADENV: + case ISN_LOADG: + case ISN_LOADOPT: + case ISN_MEMBER: + case ISN_PUSHEXC: + case ISN_PUSHS: + case ISN_STOREG: + vim_free(isn->isn_arg.string); + break; + + case ISN_LOADS: + vim_free(isn->isn_arg.loads.ls_name); + break; + + case ISN_STOREOPT: + 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_UCALL: + vim_free(isn->isn_arg.ufunc.cuf_name); + break; + + case ISN_2BOOL: + case ISN_2STRING: + case ISN_ADDBLOB: + case ISN_ADDLIST: + case ISN_BCALL: + case ISN_CATCH: + case ISN_CHECKNR: + case ISN_CHECKTYPE: + 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_COMPAREPARTIAL: + case ISN_COMPARESPECIAL: + case ISN_COMPARESTRING: + case ISN_CONCAT: + case ISN_DCALL: + case ISN_DROP: + case ISN_ECHO: + case ISN_ENDTRY: + case ISN_FOR: + case ISN_FUNCREF: + case ISN_INDEX: + case ISN_JUMP: + case ISN_LOAD: + case ISN_LOADSCRIPT: + case ISN_LOADREG: + case ISN_LOADV: + case ISN_NEGATENR: + case ISN_NEWDICT: + case ISN_NEWLIST: + case ISN_OPNR: + case ISN_OPFLOAT: + case ISN_OPANY: + case ISN_PCALL: + case ISN_PUSHF: + case ISN_PUSHNR: + case ISN_PUSHBOOL: + case ISN_PUSHSPEC: + case ISN_RETURN: + case ISN_STORE: + case ISN_STORENR: + case ISN_STORESCRIPT: + case ISN_THROW: + case ISN_TRY: + // nothing allocated + break; + } +} + +/* + * When a user function is deleted, delete any associated def function. + */ + void +delete_def_function(ufunc_T *ufunc) +{ + int idx; + + if (ufunc->uf_dfunc_idx >= 0) + { + dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + + ufunc->uf_dfunc_idx; + ga_clear(&dfunc->df_def_args_isn); + + for (idx = 0; idx < dfunc->df_instr_count; ++idx) + delete_instr(dfunc->df_instr + idx); + VIM_CLEAR(dfunc->df_instr); + + dfunc->df_deleted = TRUE; + } +} + +#if defined(EXITFREE) || defined(PROTO) + void +free_def_functions(void) +{ + vim_free(def_functions.ga_data); +} +#endif + + +#endif // FEAT_EVAL