diff options
Diffstat (limited to 'src/vim9instr.c')
-rw-r--r-- | src/vim9instr.c | 2208 |
1 files changed, 2208 insertions, 0 deletions
diff --git a/src/vim9instr.c b/src/vim9instr.c new file mode 100644 index 0000000000..d82efe94e5 --- /dev/null +++ b/src/vim9instr.c @@ -0,0 +1,2208 @@ +/* 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. + */ + +/* + * vim9instr.c: Dealing with instructions of a compiled function + */ + +#define USING_FLOAT_STUFF +#include "vim.h" + +#if defined(FEAT_EVAL) || defined(PROTO) + +// When not generating protos this is included in proto.h +#ifdef PROTO +# include "vim9.h" +#endif + + +///////////////////////////////////////////////////////////////////// +// Following generate_ functions expect the caller to call ga_grow(). + +#define RETURN_NULL_IF_SKIP(cctx) if (cctx->ctx_skip == SKIP_YES) return NULL +#define RETURN_OK_IF_SKIP(cctx) if (cctx->ctx_skip == SKIP_YES) return OK + +/* + * Generate an instruction without arguments. + * Returns a pointer to the new instruction, NULL if failed. + */ + isn_T * +generate_instr(cctx_T *cctx, isntype_T isn_type) +{ + garray_T *instr = &cctx->ctx_instr; + isn_T *isn; + + RETURN_NULL_IF_SKIP(cctx); + if (GA_GROW_FAILS(instr, 1)) + return NULL; + isn = ((isn_T *)instr->ga_data) + instr->ga_len; + isn->isn_type = isn_type; + isn->isn_lnum = cctx->ctx_lnum + 1; + ++instr->ga_len; + + return isn; +} + +/* + * Generate an instruction without arguments. + * "drop" will be removed from the stack. + * Returns a pointer to the new instruction, NULL if failed. + */ + isn_T * +generate_instr_drop(cctx_T *cctx, isntype_T isn_type, int drop) +{ + garray_T *stack = &cctx->ctx_type_stack; + + RETURN_NULL_IF_SKIP(cctx); + stack->ga_len -= drop; + return generate_instr(cctx, isn_type); +} + +/* + * Generate instruction "isn_type" and put "type" on the type stack. + */ + isn_T * +generate_instr_type(cctx_T *cctx, isntype_T isn_type, type_T *type) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + if ((isn = generate_instr(cctx, isn_type)) == NULL) + return NULL; + + if (GA_GROW_FAILS(stack, 1)) + return NULL; + ((type_T **)stack->ga_data)[stack->ga_len] = type == NULL ? &t_any : type; + ++stack->ga_len; + + return isn; +} + +/* + * Generate an ISN_DEBUG instruction. + */ + isn_T * +generate_instr_debug(cctx_T *cctx) +{ + isn_T *isn; + dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + + cctx->ctx_ufunc->uf_dfunc_idx; + + if ((isn = generate_instr(cctx, ISN_DEBUG)) == NULL) + return NULL; + isn->isn_arg.debug.dbg_var_names_len = dfunc->df_var_names.ga_len; + isn->isn_arg.debug.dbg_break_lnum = cctx->ctx_prev_lnum; + return isn; +} + +/* + * If type at "offset" isn't already VAR_STRING then generate ISN_2STRING. + * But only for simple types. + * When "tolerant" is TRUE convert most types to string, e.g. a List. + */ + int +may_generate_2STRING(int offset, int tolerant, cctx_T *cctx) +{ + isn_T *isn; + isntype_T isntype = ISN_2STRING; + garray_T *stack = &cctx->ctx_type_stack; + type_T **type; + + RETURN_OK_IF_SKIP(cctx); + type = ((type_T **)stack->ga_data) + stack->ga_len + offset; + switch ((*type)->tt_type) + { + // nothing to be done + case VAR_STRING: return OK; + + // conversion possible + case VAR_SPECIAL: + case VAR_BOOL: + case VAR_NUMBER: + case VAR_FLOAT: + break; + + // conversion possible (with runtime check) + case VAR_ANY: + case VAR_UNKNOWN: + isntype = ISN_2STRING_ANY; + break; + + // conversion possible when tolerant + case VAR_LIST: + if (tolerant) + { + isntype = ISN_2STRING_ANY; + break; + } + // FALLTHROUGH + + // conversion not possible + case VAR_VOID: + case VAR_BLOB: + case VAR_FUNC: + case VAR_PARTIAL: + case VAR_DICT: + case VAR_JOB: + case VAR_CHANNEL: + case VAR_INSTR: + to_string_error((*type)->tt_type); + return FAIL; + } + + *type = &t_string; + if ((isn = generate_instr(cctx, isntype)) == NULL) + return FAIL; + isn->isn_arg.tostring.offset = offset; + isn->isn_arg.tostring.tolerant = tolerant; + + return OK; +} + + static int +check_number_or_float(vartype_T type1, vartype_T type2, char_u *op) +{ + if (!((type1 == VAR_NUMBER || type1 == VAR_FLOAT || type1 == VAR_ANY) + && (type2 == VAR_NUMBER || type2 == VAR_FLOAT + || type2 == VAR_ANY))) + { + if (*op == '+') + emsg(_(e_wrong_argument_type_for_plus)); + else + semsg(_(e_char_requires_number_or_float_arguments), *op); + return FAIL; + } + return OK; +} + +/* + * Generate instruction for "+". For a list this creates a new list. + */ + int +generate_add_instr( + cctx_T *cctx, + vartype_T vartype, + type_T *type1, + type_T *type2, + exprtype_T expr_type) +{ + garray_T *stack = &cctx->ctx_type_stack; + isn_T *isn = generate_instr_drop(cctx, + vartype == VAR_NUMBER ? ISN_OPNR + : vartype == VAR_LIST ? ISN_ADDLIST + : vartype == VAR_BLOB ? ISN_ADDBLOB +#ifdef FEAT_FLOAT + : vartype == VAR_FLOAT ? ISN_OPFLOAT +#endif + : ISN_OPANY, 1); + + if (vartype != VAR_LIST && vartype != VAR_BLOB + && type1->tt_type != VAR_ANY + && type2->tt_type != VAR_ANY + && check_number_or_float( + type1->tt_type, type2->tt_type, (char_u *)"+") == FAIL) + return FAIL; + + if (isn != NULL) + { + if (isn->isn_type == ISN_ADDLIST) + isn->isn_arg.op.op_type = expr_type; + else + isn->isn_arg.op.op_type = EXPR_ADD; + } + + // When concatenating two lists with different member types the member type + // becomes "any". + if (vartype == VAR_LIST + && type1->tt_type == VAR_LIST && type2->tt_type == VAR_LIST + && type1->tt_member != type2->tt_member) + (((type_T **)stack->ga_data)[stack->ga_len - 1]) = &t_list_any; + + return isn == NULL ? FAIL : OK; +} + +/* + * Get the type to use for an instruction for an operation on "type1" and + * "type2". If they are matching use a type-specific instruction. Otherwise + * fall back to runtime type checking. + */ + vartype_T +operator_type(type_T *type1, type_T *type2) +{ + if (type1->tt_type == type2->tt_type + && (type1->tt_type == VAR_NUMBER + || type1->tt_type == VAR_LIST +#ifdef FEAT_FLOAT + || type1->tt_type == VAR_FLOAT +#endif + || type1->tt_type == VAR_BLOB)) + return type1->tt_type; + return VAR_ANY; +} + +/* + * Generate an instruction with two arguments. The instruction depends on the + * type of the arguments. + */ + int +generate_two_op(cctx_T *cctx, char_u *op) +{ + garray_T *stack = &cctx->ctx_type_stack; + type_T *type1; + type_T *type2; + vartype_T vartype; + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + + // Get the known type of the two items on the stack. + type1 = ((type_T **)stack->ga_data)[stack->ga_len - 2]; + type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + vartype = operator_type(type1, type2); + + switch (*op) + { + case '+': + if (generate_add_instr(cctx, vartype, type1, type2, + EXPR_COPY) == FAIL) + return FAIL; + break; + + case '-': + case '*': + case '/': if (check_number_or_float(type1->tt_type, type2->tt_type, + op) == FAIL) + return FAIL; + if (vartype == VAR_NUMBER) + isn = generate_instr_drop(cctx, ISN_OPNR, 1); +#ifdef FEAT_FLOAT + else if (vartype == VAR_FLOAT) + isn = generate_instr_drop(cctx, ISN_OPFLOAT, 1); +#endif + else + isn = generate_instr_drop(cctx, ISN_OPANY, 1); + if (isn != NULL) + isn->isn_arg.op.op_type = *op == '*' + ? EXPR_MULT : *op == '/'? EXPR_DIV : EXPR_SUB; + break; + + case '%': if ((type1->tt_type != VAR_ANY + && type1->tt_type != VAR_NUMBER) + || (type2->tt_type != VAR_ANY + && type2->tt_type != VAR_NUMBER)) + { + emsg(_(e_percent_requires_number_arguments)); + return FAIL; + } + isn = generate_instr_drop(cctx, + vartype == VAR_NUMBER ? ISN_OPNR : ISN_OPANY, 1); + if (isn != NULL) + isn->isn_arg.op.op_type = EXPR_REM; + break; + } + + // correct type of result + if (vartype == VAR_ANY) + { + type_T *type = &t_any; + +#ifdef FEAT_FLOAT + // float+number and number+float results in float + if ((type1->tt_type == VAR_NUMBER || type1->tt_type == VAR_FLOAT) + && (type2->tt_type == VAR_NUMBER || type2->tt_type == VAR_FLOAT)) + type = &t_float; +#endif + ((type_T **)stack->ga_data)[stack->ga_len - 1] = type; + } + + return OK; +} + +/* + * Get the instruction to use for comparing "type1" with "type2" + * Return ISN_DROP when failed. + */ + static isntype_T +get_compare_isn(exprtype_T exprtype, vartype_T type1, vartype_T type2) +{ + isntype_T isntype = ISN_DROP; + + if (type1 == VAR_UNKNOWN) + type1 = VAR_ANY; + if (type2 == VAR_UNKNOWN) + type2 = VAR_ANY; + + if (type1 == type2) + { + switch (type1) + { + case VAR_BOOL: isntype = ISN_COMPAREBOOL; break; + case VAR_SPECIAL: isntype = ISN_COMPARESPECIAL; break; + case VAR_NUMBER: isntype = ISN_COMPARENR; break; + case VAR_FLOAT: isntype = ISN_COMPAREFLOAT; break; + case VAR_STRING: isntype = ISN_COMPARESTRING; break; + case VAR_BLOB: isntype = ISN_COMPAREBLOB; break; + case VAR_LIST: isntype = ISN_COMPARELIST; break; + case VAR_DICT: isntype = ISN_COMPAREDICT; break; + case VAR_FUNC: isntype = ISN_COMPAREFUNC; break; + default: isntype = ISN_COMPAREANY; break; + } + } + else if (type1 == VAR_ANY || type2 == VAR_ANY + || ((type1 == VAR_NUMBER || type1 == VAR_FLOAT) + && (type2 == VAR_NUMBER || type2 == VAR_FLOAT))) + isntype = ISN_COMPAREANY; + + if ((exprtype == EXPR_IS || exprtype == EXPR_ISNOT) + && (isntype == ISN_COMPAREBOOL + || isntype == ISN_COMPARESPECIAL + || isntype == ISN_COMPARENR + || isntype == ISN_COMPAREFLOAT)) + { + semsg(_(e_cannot_use_str_with_str), + exprtype == EXPR_IS ? "is" : "isnot" , vartype_name(type1)); + return ISN_DROP; + } + if (isntype == ISN_DROP + || ((exprtype != EXPR_EQUAL && exprtype != EXPR_NEQUAL + && (type1 == VAR_BOOL || type1 == VAR_SPECIAL + || type2 == VAR_BOOL || type2 == VAR_SPECIAL))) + || ((exprtype != EXPR_EQUAL && exprtype != EXPR_NEQUAL + && exprtype != EXPR_IS && exprtype != EXPR_ISNOT + && (type1 == VAR_BLOB || type2 == VAR_BLOB + || type1 == VAR_LIST || type2 == VAR_LIST)))) + { + semsg(_(e_cannot_compare_str_with_str), + vartype_name(type1), vartype_name(type2)); + return ISN_DROP; + } + return isntype; +} + + int +check_compare_types(exprtype_T type, typval_T *tv1, typval_T *tv2) +{ + if (get_compare_isn(type, tv1->v_type, tv2->v_type) == ISN_DROP) + return FAIL; + return OK; +} + +/* + * Generate an ISN_COMPARE* instruction with a boolean result. + */ + int +generate_COMPARE(cctx_T *cctx, exprtype_T exprtype, int ic) +{ + isntype_T isntype; + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + vartype_T type1; + vartype_T type2; + + RETURN_OK_IF_SKIP(cctx); + + // Get the known type of the two items on the stack. If they are matching + // use a type-specific instruction. Otherwise fall back to runtime type + // checking. + type1 = ((type_T **)stack->ga_data)[stack->ga_len - 2]->tt_type; + type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]->tt_type; + isntype = get_compare_isn(exprtype, type1, type2); + if (isntype == ISN_DROP) + return FAIL; + + if ((isn = generate_instr(cctx, isntype)) == NULL) + return FAIL; + isn->isn_arg.op.op_type = exprtype; + isn->isn_arg.op.op_ic = ic; + + // takes two arguments, puts one bool back + if (stack->ga_len >= 2) + { + --stack->ga_len; + ((type_T **)stack->ga_data)[stack->ga_len - 1] = &t_bool; + } + + return OK; +} + +/* + * Generate an ISN_2BOOL instruction. + * "offset" is the offset in the type stack. + */ + int +generate_2BOOL(cctx_T *cctx, int invert, int offset) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_2BOOL)) == NULL) + return FAIL; + isn->isn_arg.tobool.invert = invert; + isn->isn_arg.tobool.offset = offset; + + // type becomes bool + ((type_T **)stack->ga_data)[stack->ga_len + offset] = &t_bool; + + return OK; +} + +/* + * Generate an ISN_COND2BOOL instruction. + */ + int +generate_COND2BOOL(cctx_T *cctx) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_COND2BOOL)) == NULL) + return FAIL; + + // type becomes bool + ((type_T **)stack->ga_data)[stack->ga_len - 1] = &t_bool; + + return OK; +} + + int +generate_TYPECHECK( + cctx_T *cctx, + type_T *expected, + int offset, + int argidx) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_CHECKTYPE)) == NULL) + return FAIL; + isn->isn_arg.type.ct_type = alloc_type(expected); + isn->isn_arg.type.ct_off = (int8_T)offset; + isn->isn_arg.type.ct_arg_idx = (int8_T)argidx; + + // type becomes expected + ((type_T **)stack->ga_data)[stack->ga_len + offset] = expected; + + return OK; +} + + int +generate_SETTYPE( + cctx_T *cctx, + type_T *expected) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_SETTYPE)) == NULL) + return FAIL; + isn->isn_arg.type.ct_type = alloc_type(expected); + return OK; +} + +/* + * Generate a PUSH instruction for "tv". + * "tv" will be consumed or cleared. + * Nothing happens if "tv" is NULL or of type VAR_UNKNOWN; + */ + int +generate_tv_PUSH(cctx_T *cctx, typval_T *tv) +{ + if (tv != NULL) + { + switch (tv->v_type) + { + case VAR_UNKNOWN: + break; + case VAR_BOOL: + generate_PUSHBOOL(cctx, tv->vval.v_number); + break; + case VAR_SPECIAL: + generate_PUSHSPEC(cctx, tv->vval.v_number); + break; + case VAR_NUMBER: + generate_PUSHNR(cctx, tv->vval.v_number); + break; +#ifdef FEAT_FLOAT + case VAR_FLOAT: + generate_PUSHF(cctx, tv->vval.v_float); + break; +#endif + case VAR_BLOB: + generate_PUSHBLOB(cctx, tv->vval.v_blob); + tv->vval.v_blob = NULL; + break; + case VAR_STRING: + generate_PUSHS(cctx, &tv->vval.v_string); + tv->vval.v_string = NULL; + break; + default: + iemsg("constant type not supported"); + clear_tv(tv); + return FAIL; + } + tv->v_type = VAR_UNKNOWN; + } + return OK; +} + +/* + * Generate an ISN_PUSHNR instruction. + */ + int +generate_PUSHNR(cctx_T *cctx, varnumber_T number) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr_type(cctx, ISN_PUSHNR, &t_number)) == NULL) + return FAIL; + isn->isn_arg.number = number; + + if (number == 0 || number == 1) + // A 0 or 1 number can also be used as a bool. + ((type_T **)stack->ga_data)[stack->ga_len - 1] = &t_number_bool; + return OK; +} + +/* + * Generate an ISN_PUSHBOOL instruction. + */ + int +generate_PUSHBOOL(cctx_T *cctx, varnumber_T number) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr_type(cctx, ISN_PUSHBOOL, &t_bool)) == NULL) + return FAIL; + isn->isn_arg.number = number; + + return OK; +} + +/* + * Generate an ISN_PUSHSPEC instruction. + */ + int +generate_PUSHSPEC(cctx_T *cctx, varnumber_T number) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr_type(cctx, ISN_PUSHSPEC, &t_special)) == NULL) + return FAIL; + isn->isn_arg.number = number; + + return OK; +} + +#if defined(FEAT_FLOAT) || defined(PROTO) +/* + * Generate an ISN_PUSHF instruction. + */ + int +generate_PUSHF(cctx_T *cctx, float_T fnumber) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr_type(cctx, ISN_PUSHF, &t_float)) == NULL) + return FAIL; + isn->isn_arg.fnumber = fnumber; + + return OK; +} +#endif + +/* + * Generate an ISN_PUSHS instruction. + * Consumes "*str". When freed *str is set to NULL, unless "str" is NULL. + */ + int +generate_PUSHS(cctx_T *cctx, char_u **str) +{ + isn_T *isn; + + if (cctx->ctx_skip == SKIP_YES) + { + if (str != NULL) + VIM_CLEAR(*str); + return OK; + } + if ((isn = generate_instr_type(cctx, ISN_PUSHS, &t_string)) == NULL) + { + if (str != NULL) + VIM_CLEAR(*str); + return FAIL; + } + isn->isn_arg.string = str == NULL ? NULL : *str; + + return OK; +} + +/* + * Generate an ISN_PUSHCHANNEL instruction. + * Consumes "channel". + */ + int +generate_PUSHCHANNEL(cctx_T *cctx, channel_T *channel) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr_type(cctx, ISN_PUSHCHANNEL, &t_channel)) == NULL) + return FAIL; + isn->isn_arg.channel = channel; + + return OK; +} + +/* + * Generate an ISN_PUSHJOB instruction. + * Consumes "job". + */ + int +generate_PUSHJOB(cctx_T *cctx, job_T *job) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr_type(cctx, ISN_PUSHJOB, &t_channel)) == NULL) + return FAIL; + isn->isn_arg.job = job; + + return OK; +} + +/* + * Generate an ISN_PUSHBLOB instruction. + * Consumes "blob". + */ + int +generate_PUSHBLOB(cctx_T *cctx, blob_T *blob) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr_type(cctx, ISN_PUSHBLOB, &t_blob)) == NULL) + return FAIL; + isn->isn_arg.blob = blob; + + return OK; +} + +/* + * Generate an ISN_PUSHFUNC instruction with name "name". + * Consumes "name". + */ + int +generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type) +{ + isn_T *isn; + char_u *funcname; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr_type(cctx, ISN_PUSHFUNC, type)) == NULL) + return FAIL; + if (name == NULL) + funcname = NULL; + else if (*name == K_SPECIAL) // script-local + funcname = vim_strsave(name); + else + { + funcname = alloc(STRLEN(name) + 3); + if (funcname != NULL) + { + STRCPY(funcname, "g:"); + STRCPY(funcname + 2, name); + } + } + + isn->isn_arg.string = funcname; + return OK; +} + +/* + * Generate an ISN_GETITEM instruction with "index". + * "with_op" is TRUE for "+=" and other operators, the stack has the current + * value below the list with values. + */ + int +generate_GETITEM(cctx_T *cctx, int index, int with_op) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + type_T *type = ((type_T **)stack->ga_data)[stack->ga_len + - (with_op ? 2 : 1)]; + type_T *item_type = &t_any; + + RETURN_OK_IF_SKIP(cctx); + + if (type->tt_type != VAR_LIST) + { + // cannot happen, caller has checked the type + emsg(_(e_listreq)); + return FAIL; + } + item_type = type->tt_member; + if ((isn = generate_instr(cctx, ISN_GETITEM)) == NULL) + return FAIL; + isn->isn_arg.getitem.gi_index = index; + isn->isn_arg.getitem.gi_with_op = with_op; + + // add the item type to the type stack + if (GA_GROW_FAILS(stack, 1)) + return FAIL; + ((type_T **)stack->ga_data)[stack->ga_len] = item_type; + ++stack->ga_len; + return OK; +} + +/* + * Generate an ISN_SLICE instruction with "count". + */ + int +generate_SLICE(cctx_T *cctx, int count) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_SLICE)) == NULL) + return FAIL; + isn->isn_arg.number = count; + return OK; +} + +/* + * Generate an ISN_CHECKLEN instruction with "min_len". + */ + int +generate_CHECKLEN(cctx_T *cctx, int min_len, int more_OK) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + + if ((isn = generate_instr(cctx, ISN_CHECKLEN)) == NULL) + return FAIL; + isn->isn_arg.checklen.cl_min_len = min_len; + isn->isn_arg.checklen.cl_more_OK = more_OK; + + return OK; +} + +/* + * Generate an ISN_STORE instruction. + */ + int +generate_STORE(cctx_T *cctx, isntype_T isn_type, int idx, char_u *name) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr_drop(cctx, isn_type, 1)) == NULL) + return FAIL; + if (name != NULL) + isn->isn_arg.string = vim_strsave(name); + else + isn->isn_arg.number = idx; + + return OK; +} + +/* + * Generate an ISN_STOREOUTER instruction. + */ + int +generate_STOREOUTER(cctx_T *cctx, int idx, int level) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr_drop(cctx, ISN_STOREOUTER, 1)) == NULL) + return FAIL; + isn->isn_arg.outer.outer_idx = idx; + isn->isn_arg.outer.outer_depth = level; + + return OK; +} + +/* + * Generate an ISN_STORENR instruction (short for ISN_PUSHNR + ISN_STORE) + */ + int +generate_STORENR(cctx_T *cctx, int idx, varnumber_T value) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_STORENR)) == NULL) + return FAIL; + isn->isn_arg.storenr.stnr_idx = idx; + isn->isn_arg.storenr.stnr_val = value; + + return OK; +} + +/* + * Generate an ISN_STOREOPT or ISN_STOREFUNCOPT instruction + */ + int +generate_STOREOPT( + cctx_T *cctx, + isntype_T isn_type, + char_u *name, + int opt_flags) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr_drop(cctx, isn_type, 1)) == NULL) + return FAIL; + isn->isn_arg.storeopt.so_name = vim_strsave(name); + isn->isn_arg.storeopt.so_flags = opt_flags; + + return OK; +} + +/* + * Generate an ISN_LOAD or similar instruction. + */ + int +generate_LOAD( + cctx_T *cctx, + isntype_T isn_type, + int idx, + char_u *name, + type_T *type) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr_type(cctx, isn_type, type)) == NULL) + return FAIL; + if (name != NULL) + isn->isn_arg.string = vim_strsave(name); + else + isn->isn_arg.number = idx; + + return OK; +} + +/* + * Generate an ISN_LOADOUTER instruction + */ + int +generate_LOADOUTER( + cctx_T *cctx, + int idx, + int nesting, + type_T *type) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr_type(cctx, ISN_LOADOUTER, type)) == NULL) + return FAIL; + isn->isn_arg.outer.outer_idx = idx; + isn->isn_arg.outer.outer_depth = nesting; + + return OK; +} + +/* + * Generate an ISN_LOADV instruction for v:var. + */ + int +generate_LOADV( + cctx_T *cctx, + char_u *name, + int error) +{ + int di_flags; + int vidx = find_vim_var(name, &di_flags); + type_T *type; + + RETURN_OK_IF_SKIP(cctx); + if (vidx < 0) + { + if (error) + semsg(_(e_variable_not_found_str), name); + return FAIL; + } + type = typval2type_vimvar(get_vim_var_tv(vidx), cctx->ctx_type_list); + + return generate_LOAD(cctx, ISN_LOADV, vidx, NULL, type); +} + +/* + * Generate an ISN_UNLET instruction. + */ + int +generate_UNLET(cctx_T *cctx, isntype_T isn_type, char_u *name, int forceit) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, isn_type)) == NULL) + return FAIL; + isn->isn_arg.unlet.ul_name = vim_strsave(name); + isn->isn_arg.unlet.ul_forceit = forceit; + + return OK; +} + +/* + * Generate an ISN_LOCKCONST instruction. + */ + int +generate_LOCKCONST(cctx_T *cctx) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_LOCKCONST)) == NULL) + return FAIL; + return OK; +} + +/* + * Generate an ISN_LOADS instruction. + */ + int +generate_OLDSCRIPT( + cctx_T *cctx, + isntype_T isn_type, + char_u *name, + int sid, + type_T *type) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if (isn_type == ISN_LOADS) + isn = generate_instr_type(cctx, isn_type, type); + else + isn = generate_instr_drop(cctx, isn_type, 1); + if (isn == NULL) + return FAIL; + isn->isn_arg.loadstore.ls_name = vim_strsave(name); + isn->isn_arg.loadstore.ls_sid = sid; + + return OK; +} + +/* + * Generate an ISN_LOADSCRIPT or ISN_STORESCRIPT instruction. + */ + int +generate_VIM9SCRIPT( + cctx_T *cctx, + isntype_T isn_type, + int sid, + int idx, + type_T *type) +{ + isn_T *isn; + scriptref_T *sref; + scriptitem_T *si = SCRIPT_ITEM(sid); + + RETURN_OK_IF_SKIP(cctx); + if (isn_type == ISN_LOADSCRIPT) + isn = generate_instr_type(cctx, isn_type, type); + else + isn = generate_instr_drop(cctx, isn_type, 1); + if (isn == NULL) + return FAIL; + + // This requires three arguments, which doesn't fit in an instruction, thus + // we need to allocate a struct for this. + sref = ALLOC_ONE(scriptref_T); + if (sref == NULL) + return FAIL; + isn->isn_arg.script.scriptref = sref; + sref->sref_sid = sid; + sref->sref_idx = idx; + sref->sref_seq = si->sn_script_seq; + sref->sref_type = type; + return OK; +} + +/* + * Generate an ISN_NEWLIST instruction. + */ + int +generate_NEWLIST(cctx_T *cctx, int count) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + type_T *type; + type_T *member; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_NEWLIST)) == NULL) + return FAIL; + isn->isn_arg.number = count; + + // get the member type from all the items on the stack. + if (count == 0) + member = &t_unknown; + else + member = get_member_type_from_stack( + ((type_T **)stack->ga_data) + stack->ga_len, count, 1, + cctx->ctx_type_list); + type = get_list_type(member, cctx->ctx_type_list); + + // drop the value types + stack->ga_len -= count; + + // add the list type to the type stack + if (GA_GROW_FAILS(stack, 1)) + return FAIL; + ((type_T **)stack->ga_data)[stack->ga_len] = type; + ++stack->ga_len; + + return OK; +} + +/* + * Generate an ISN_NEWDICT instruction. + */ + int +generate_NEWDICT(cctx_T *cctx, int count) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + type_T *type; + type_T *member; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_NEWDICT)) == NULL) + return FAIL; + isn->isn_arg.number = count; + + if (count == 0) + member = &t_void; + else + member = get_member_type_from_stack( + ((type_T **)stack->ga_data) + stack->ga_len, count, 2, + cctx->ctx_type_list); + type = get_dict_type(member, cctx->ctx_type_list); + + // drop the key and value types + stack->ga_len -= 2 * count; + + // add the dict type to the type stack + if (GA_GROW_FAILS(stack, 1)) + return FAIL; + ((type_T **)stack->ga_data)[stack->ga_len] = type; + ++stack->ga_len; + + return OK; +} + +/* + * Generate an ISN_FUNCREF instruction. + */ + int +generate_FUNCREF(cctx_T *cctx, ufunc_T *ufunc) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_FUNCREF)) == NULL) + return FAIL; + if (ufunc->uf_def_status == UF_NOT_COMPILED) + isn->isn_arg.funcref.fr_func_name = vim_strsave(ufunc->uf_name); + else + isn->isn_arg.funcref.fr_dfunc_idx = ufunc->uf_dfunc_idx; + cctx->ctx_has_closure = 1; + + // If the referenced function is a closure, it may use items further up in + // the nested context, including this one. + if (ufunc->uf_flags & FC_CLOSURE) + cctx->ctx_ufunc->uf_flags |= FC_CLOSURE; + + if (GA_GROW_FAILS(stack, 1)) + return FAIL; + ((type_T **)stack->ga_data)[stack->ga_len] = + ufunc->uf_func_type == NULL ? &t_func_any : ufunc->uf_func_type; + ++stack->ga_len; + + return OK; +} + +/* + * Generate an ISN_NEWFUNC instruction. + * "lambda_name" and "func_name" must be in allocated memory and will be + * consumed. + */ + int +generate_NEWFUNC(cctx_T *cctx, char_u *lambda_name, char_u *func_name) +{ + isn_T *isn; + + if (cctx->ctx_skip == SKIP_YES) + { + vim_free(lambda_name); + vim_free(func_name); + return OK; + } + if ((isn = generate_instr(cctx, ISN_NEWFUNC)) == NULL) + { + vim_free(lambda_name); + vim_free(func_name); + return FAIL; + } + isn->isn_arg.newfunc.nf_lambda = lambda_name; + isn->isn_arg.newfunc.nf_global = func_name; + + return OK; +} + +/* + * Generate an ISN_DEF instruction: list functions + */ + int +generate_DEF(cctx_T *cctx, char_u *name, size_t len) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_DEF)) == NULL) + return FAIL; + if (len > 0) + { + isn->isn_arg.string = vim_strnsave(name, len); + if (isn->isn_arg.string == NULL) + return FAIL; + } + return OK; +} + +/* + * Generate an ISN_JUMP instruction. + */ + int +generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_JUMP)) == NULL) + return FAIL; + isn->isn_arg.jump.jump_when = when; + isn->isn_arg.jump.jump_where = where; + + if (when != JUMP_ALWAYS && stack->ga_len > 0) + --stack->ga_len; + + return OK; +} + +/* + * Generate an ISN_JUMP_IF_ARG_SET instruction. + */ + int +generate_JUMP_IF_ARG_SET(cctx_T *cctx, int arg_off) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_JUMP_IF_ARG_SET)) == NULL) + return FAIL; + isn->isn_arg.jumparg.jump_arg_off = arg_off; + // jump_where is set later + return OK; +} + + int +generate_FOR(cctx_T *cctx, int loop_idx) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_FOR)) == NULL) + return FAIL; + isn->isn_arg.forloop.for_idx = loop_idx; + + if (GA_GROW_FAILS(stack, 1)) + return FAIL; + // type doesn't matter, will be stored next + ((type_T **)stack->ga_data)[stack->ga_len] = &t_any; + ++stack->ga_len; + + return OK; +} +/* + * Generate an ISN_TRYCONT instruction. + */ + int +generate_TRYCONT(cctx_T *cctx, int levels, int where) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_TRYCONT)) == NULL) + return FAIL; + isn->isn_arg.trycont.tct_levels = levels; + isn->isn_arg.trycont.tct_where = where; + + return OK; +} + + +/* + * Generate an ISN_BCALL instruction. + * "method_call" is TRUE for "value->method()" + * Return FAIL if the number of arguments is wrong. + */ + int +generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call) +{ + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + int argoff; + type_T **argtypes = NULL; + type_T *shuffled_argtypes[MAX_FUNC_ARGS]; + type_T *maptype = NULL; + + RETURN_OK_IF_SKIP(cctx); + argoff = check_internal_func(fun |