diff options
Diffstat (limited to 'src/vim9compile.c')
-rw-r--r-- | src/vim9compile.c | 4612 |
1 files changed, 4612 insertions, 0 deletions
diff --git a/src/vim9compile.c b/src/vim9compile.c new file mode 100644 index 0000000000..9f273c579a --- /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 ret |