patch 9.1.0920: Vim9: compile_assignment() too long
Problem: Vim9: compile_assignment() too long
Solution: refactor compile_assignment() function and split up into
smaller parts (Yegappan Lakshmanan)
closes: #16209
Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/vim9compile.c b/src/vim9compile.c
index e75462f..64e2195 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -2391,11 +2391,11 @@
*/
int
compile_assign_unlet(
- char_u *var_start,
- lhs_T *lhs,
- int is_assign,
- type_T *rhs_type,
- cctx_T *cctx)
+ char_u *var_start,
+ lhs_T *lhs,
+ int is_assign,
+ type_T *rhs_type,
+ cctx_T *cctx)
{
vartype_T dest_type;
int range = FALSE;
@@ -2599,7 +2599,7 @@
* Returns OK on success.
*/
static int
-compile_assignment_obj_new_arg(char_u **argp, cctx_T *cctx)
+compile_assign_obj_new_arg(char_u **argp, cctx_T *cctx)
{
char_u *arg = *argp;
@@ -2621,17 +2621,57 @@
}
/*
- * Convert the increment (++) or decrement (--) operator to the corresponding
- * compound operator.
+ * Compile assignment context. Used when compiling an assignment statement.
+ */
+typedef struct cac_S cac_T;
+struct cac_S
+{
+ cmdidx_T cac_cmdidx; // assignment command
+ char_u *cac_nextc; // next character to parse
+ lhs_T cac_lhs; // lhs of the assignment
+ type_T *cac_rhs_type; // rhs type of an assignment
+ char_u *cac_op; // assignment operator
+ int cac_oplen; // assignment operator length
+ char_u *cac_var_start; // start of the variable names
+ char_u *cac_var_end; // end of the variable names
+ int cac_var_count; // number of variables in assignment
+ int cac_var_idx; // variable index in a list
+ int cac_semicolon; // semicolon in [var1, var2; var3]
+ garray_T *cac_instr;
+ int cac_instr_count;
+ int cac_incdec;
+ int cac_did_generate_slice;
+ int cac_is_decl;
+ int cac_is_const;
+ int cac_start_lnum;
+ type_T *cac_inferred_type;
+ int cac_skip_store;
+};
+
+/*
+ * Initialize the compile assignment context.
+ */
+ static void
+compile_assign_context_init(cac_T *cac, cctx_T *cctx, int cmdidx, char_u *arg)
+{
+ CLEAR_FIELD(*cac);
+ cac->cac_cmdidx = cmdidx;
+ cac->cac_instr = &cctx->ctx_instr;
+ cac->cac_rhs_type = &t_any;
+ cac->cac_is_decl = is_decl_command(cmdidx);
+ cac->cac_start_lnum = SOURCING_LNUM;
+ cac->cac_instr_count = -1;
+ cac->cac_var_end = arg;
+}
+
+/*
+ * Translate the increment (++) and decrement (--) operators to the
+ * corresponding compound operators (+= or -=).
*
* Returns OK on success and FAIL on syntax error.
*/
static int
-incdec_op_translate(
- exarg_T *eap,
- char_u **op,
- int *oplen,
- int *incdec)
+translate_incdec_op(exarg_T *eap, cac_T *cac)
{
if (VIM_ISWHITE(eap->cmd[2]))
{
@@ -2639,9 +2679,56 @@
eap->cmdidx == CMD_increment ? "++" : "--", eap->cmd);
return FAIL;
}
- *op = (char_u *)(eap->cmdidx == CMD_increment ? "+=" : "-=");
- *oplen = 2;
- *incdec = TRUE;
+ cac->cac_op = (char_u *)(eap->cmdidx == CMD_increment ? "+=" : "-=");
+ cac->cac_oplen = 2;
+ cac->cac_incdec = TRUE;
+
+ return OK;
+}
+
+/*
+ * Process the operator in an assignment statement.
+ */
+ static int
+compile_assign_process_operator(
+ exarg_T *eap,
+ char_u *arg,
+ cac_T *cac,
+ int *heredoc,
+ char_u **retstr)
+{
+ *retstr = NULL;
+
+ if (eap->cmdidx == CMD_increment || eap->cmdidx == CMD_decrement)
+ {
+ // Change an unary operator to a compound operator
+ if (translate_incdec_op(eap, cac) == FAIL)
+ return FAIL;
+ }
+ else
+ {
+ char_u *sp;
+
+ sp = cac->cac_nextc;
+ cac->cac_nextc = skipwhite(cac->cac_nextc);
+ cac->cac_op = cac->cac_nextc;
+ cac->cac_oplen = assignment_len(cac->cac_nextc, heredoc);
+
+ if (cac->cac_var_count > 0 && cac->cac_oplen == 0)
+ {
+ // can be something like "[1, 2]->func()"
+ *retstr = arg;
+ return FAIL;
+ }
+
+ // need white space before and after the operator
+ if (cac->cac_oplen > 0 && (!VIM_ISWHITE(*sp)
+ || !IS_WHITE_OR_NUL(cac->cac_op[cac->cac_oplen])))
+ {
+ error_white_both(cac->cac_op, cac->cac_oplen);
+ return FAIL;
+ }
+ }
return OK;
}
@@ -2651,86 +2738,538 @@
* beginning of the heredoc content.
*/
static char_u *
-heredoc_assign_stmt_end_get(char_u *p, exarg_T *eap, cctx_T *cctx)
+parse_heredoc_assignment(exarg_T *eap, cctx_T *cctx, cac_T *cac)
{
// [let] varname =<< [trim] {end}
eap->ea_getline = exarg_getline;
eap->cookie = cctx;
- list_T *l = heredoc_get(eap, p + 3, FALSE, TRUE);
+ list_T *l = heredoc_get(eap, cac->cac_nextc + 3, FALSE, TRUE);
if (l == NULL)
return NULL;
list_free(l);
- p += STRLEN(p);
+ cac->cac_nextc += STRLEN(cac->cac_nextc);
- return p;
+ return cac->cac_nextc;
}
+/*
+ * Evaluate the expression for "[var, var] = expr" assignment.
+ * A line break may follow the assignment operator "=".
+ */
static char_u *
-compile_list_assignment(
- char_u *p,
- char_u *op,
- int oplen,
- int var_count,
- int semicolon,
- garray_T *instr,
- type_T **rhs_type,
- cctx_T *cctx)
+compile_list_assignment_expr(cctx_T *cctx, cac_T *cac)
{
char_u *wp;
- // for "[var, var] = expr" evaluate the expression here, loop over the
- // list of variables below.
- // A line break may follow the "=".
+ wp = cac->cac_op + cac->cac_oplen;
- wp = op + oplen;
- if (may_get_next_line_error(wp, &p, cctx) == FAIL)
- return NULL;
- if (compile_expr0(&p, cctx) == FAIL)
+ if (may_get_next_line_error(wp, &cac->cac_nextc, cctx) == FAIL)
return NULL;
- if (cctx->ctx_skip != SKIP_YES)
- {
- type_T *stacktype;
- int needed_list_len;
- int did_check = FALSE;
+ if (compile_expr0(&cac->cac_nextc, cctx) == FAIL)
+ return NULL;
- stacktype = cctx->ctx_type_stack.ga_len == 0 ? &t_void
+ if (cctx->ctx_skip == SKIP_YES)
+ // no need to parse more when skipping
+ return cac->cac_nextc;
+
+ type_T *stacktype;
+ int needed_list_len;
+ int did_check = FALSE;
+
+ stacktype = cctx->ctx_type_stack.ga_len == 0 ? &t_void
: get_type_on_stack(cctx, 0);
- if (stacktype->tt_type == VAR_VOID)
- {
- emsg(_(e_cannot_use_void_value));
- return NULL;
- }
- if (need_type(stacktype, &t_list_any, FALSE, -1, 0, cctx, FALSE,
- FALSE) == FAIL)
- return NULL;
- // If a constant list was used we can check the length right here.
- needed_list_len = semicolon ? var_count - 1 : var_count;
- if (instr->ga_len > 0)
- {
- isn_T *isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1;
-
- if (isn->isn_type == ISN_NEWLIST)
- {
- did_check = TRUE;
- if (semicolon ? isn->isn_arg.number < needed_list_len
- : isn->isn_arg.number != needed_list_len)
- {
- semsg(_(e_expected_nr_items_but_got_nr),
- needed_list_len, (int)isn->isn_arg.number);
- return NULL;
- }
- }
- }
- if (!did_check)
- generate_CHECKLEN(cctx, needed_list_len, semicolon);
- if (stacktype->tt_member != NULL)
- *rhs_type = stacktype->tt_member;
+ if (stacktype->tt_type == VAR_VOID)
+ {
+ emsg(_(e_cannot_use_void_value));
+ return NULL;
}
- return p;
+ if (need_type(stacktype, &t_list_any, FALSE, -1, 0, cctx,
+ FALSE, FALSE) == FAIL)
+ return NULL;
+
+ // If a constant list was used we can check the length right here.
+ needed_list_len = cac->cac_semicolon
+ ? cac->cac_var_count - 1
+ : cac->cac_var_count;
+ if (cac->cac_instr->ga_len > 0)
+ {
+ isn_T *isn = ((isn_T *)cac->cac_instr->ga_data) +
+ cac->cac_instr->ga_len - 1;
+
+ if (isn->isn_type == ISN_NEWLIST)
+ {
+ did_check = TRUE;
+ if (cac->cac_semicolon ? isn->isn_arg.number <
+ needed_list_len
+ : isn->isn_arg.number != needed_list_len)
+ {
+ semsg(_(e_expected_nr_items_but_got_nr),
+ needed_list_len, (int)isn->isn_arg.number);
+ return NULL;
+ }
+ }
+ }
+
+ if (!did_check)
+ generate_CHECKLEN(cctx, needed_list_len, cac->cac_semicolon);
+
+ if (stacktype->tt_member != NULL)
+ cac->cac_rhs_type = stacktype->tt_member;
+
+ return cac->cac_nextc;
+}
+
+/*
+ * Find and return the end of a heredoc or a list of variables assignment
+ * statement. For a single variable assignment statement, returns the current
+ * end.
+ * Returns NULL on failure.
+ */
+ static char_u *
+compile_assign_compute_end(
+ exarg_T *eap,
+ cctx_T *cctx,
+ cac_T *cac,
+ int heredoc)
+{
+ if (heredoc)
+ {
+ cac->cac_nextc = parse_heredoc_assignment(eap, cctx, cac);
+ return cac->cac_nextc;
+ }
+ else if (cac->cac_var_count > 0)
+ {
+ // for "[var, var] = expr" evaluate the expression. The list of
+ // variables are processed later.
+ // A line break may follow the "=".
+ cac->cac_nextc = compile_list_assignment_expr(cctx, cac);
+ return cac->cac_nextc;
+ }
+
+ return cac->cac_var_end;
+}
+
+/*
+ * For "var = expr" evaluate the expression.
+ */
+ static int
+compile_assign_single_eval_expr(cctx_T *cctx, cac_T *cac)
+{
+ int ret = OK;
+ char_u *wp;
+ lhs_T *lhs = &cac->cac_lhs;
+
+ // Compile the expression.
+ if (cac->cac_incdec)
+ return generate_PUSHNR(cctx, 1);
+
+ // Temporarily hide the new local variable here, it is
+ // not available to this expression.
+ if (lhs->lhs_new_local)
+ --cctx->ctx_locals.ga_len;
+ wp = cac->cac_op + cac->cac_oplen;
+
+ if (may_get_next_line_error(wp, &cac->cac_nextc, cctx) == FAIL)
+ {
+ if (lhs->lhs_new_local)
+ ++cctx->ctx_locals.ga_len;
+ return FAIL;
+ }
+
+ ret = compile_expr0_ext(&cac->cac_nextc, cctx, &cac->cac_is_const);
+ if (lhs->lhs_new_local)
+ ++cctx->ctx_locals.ga_len;
+
+ return ret;
+}
+
+/*
+ * Compare the LHS type with the RHS type in an assignment.
+ */
+ static int
+compile_assign_check_type(cctx_T *cctx, cac_T *cac)
+{
+ lhs_T *lhs = &cac->cac_lhs;
+ type_T *rhs_type;
+
+ rhs_type = cctx->ctx_type_stack.ga_len == 0 ?
+ &t_void : get_type_on_stack(cctx, 0);
+ cac->cac_rhs_type = rhs_type;
+
+ if (check_type_is_value(rhs_type) == FAIL)
+ return FAIL;
+
+ if (lhs->lhs_lvar != NULL && (cac->cac_is_decl || !lhs->lhs_has_type))
+ {
+ if ((rhs_type->tt_type == VAR_FUNC
+ || rhs_type->tt_type == VAR_PARTIAL)
+ && !lhs->lhs_has_index
+ && var_wrong_func_name(lhs->lhs_name, TRUE))
+ return FAIL;
+
+ if (lhs->lhs_new_local && !lhs->lhs_has_type)
+ {
+ if (rhs_type->tt_type == VAR_VOID)
+ {
+ emsg(_(e_cannot_use_void_value));
+ return FAIL;
+ }
+ else
+ {
+ type_T *type;
+
+ // An empty list or dict has a &t_unknown member,
+ // for a variable that implies &t_any.
+ if (rhs_type == &t_list_empty)
+ type = &t_list_any;
+ else if (rhs_type == &t_dict_empty)
+ type = &t_dict_any;
+ else if (rhs_type == &t_unknown)
+ type = &t_any;
+ else
+ {
+ type = rhs_type;
+ cac->cac_inferred_type = rhs_type;
+ }
+ set_var_type(lhs->lhs_lvar, type, cctx);
+ }
+ }
+ else if (*cac->cac_op == '=')
+ {
+ type_T *use_type = lhs->lhs_lvar->lv_type;
+ where_T where = WHERE_INIT;
+
+ // Without operator check type here, otherwise below.
+ // Use the line number of the assignment.
+ SOURCING_LNUM = cac->cac_start_lnum;
+ if (cac->cac_var_count > 0)
+ {
+ where.wt_index = cac->cac_var_idx + 1;
+ where.wt_kind = WT_VARIABLE;
+ }
+ // If assigning to a list or dict member, use the
+ // member type. Not for "list[:] =".
+ if (lhs->lhs_has_index &&
+ !has_list_index(cac->cac_var_start +
+ lhs->lhs_varlen, cctx))
+ use_type = lhs->lhs_member_type;
+ if (need_type_where(rhs_type, use_type, FALSE, -1,
+ where, cctx, FALSE, cac->cac_is_const) == FAIL)
+ return FAIL;
+ }
+ }
+ else
+ {
+ type_T *lhs_type = lhs->lhs_member_type;
+
+ // Special case: assigning to @# can use a number or a
+ // string.
+ // Also: can assign a number to a float.
+ if ((lhs_type == &t_number_or_string || lhs_type == &t_float)
+ && rhs_type->tt_type == VAR_NUMBER)
+ lhs_type = &t_number;
+ if (*cac->cac_nextc != '=' && need_type(rhs_type,
+ lhs_type, FALSE, -1, 0, cctx, FALSE, FALSE) == FAIL)
+ return FAIL;
+ }
+
+ return OK;
+}
+
+/*
+ * Compile the RHS expression in an assignment
+ */
+ static int
+compile_assign_rhs(cctx_T *cctx, cac_T *cac)
+{
+ lhs_T *lhs = &cac->cac_lhs;
+
+ if (cctx->ctx_skip == SKIP_YES)
+ {
+ if (cac->cac_oplen > 0 && cac->cac_var_count == 0)
+ {
+ // skip over the "=" and the expression
+ cac->cac_nextc = skipwhite(cac->cac_op + cac->cac_oplen);
+ (void)compile_expr0(&cac->cac_nextc, cctx);
+ }
+ return OK;
+ }
+
+ if (cac->cac_oplen > 0)
+ {
+ cac->cac_is_const = FALSE;
+
+ // for "+=", "*=", "..=" etc. first load the current value
+ if (*cac->cac_op != '='
+ && compile_load_lhs_with_index(&cac->cac_lhs,
+ cac->cac_var_start,
+ cctx) == FAIL)
+ return FAIL;
+
+ // For "var = expr" evaluate the expression.
+ if (cac->cac_var_count == 0)
+ {
+ int ret;
+
+ // Compile the expression.
+ cac->cac_instr_count = cac->cac_instr->ga_len;
+ ret = compile_assign_single_eval_expr(cctx, cac);
+ if (ret == FAIL)
+ return FAIL;
+ }
+ else if (cac->cac_semicolon &&
+ cac->cac_var_idx == cac->cac_var_count - 1)
+ {
+ // For "[var; var] = expr" get the rest of the list
+ cac->cac_did_generate_slice = TRUE;
+ if (generate_SLICE(cctx, cac->cac_var_count - 1) == FAIL)
+ return FAIL;
+ }
+ else
+ {
+ // For "[var, var] = expr" get the "var_idx" item from the
+ // list.
+ if (generate_GETITEM(cctx, cac->cac_var_idx,
+ *cac->cac_op != '=') == FAIL)
+ return FAIL;
+ }
+
+ if (compile_assign_check_type(cctx, cac) == FAIL)
+ return FAIL;
+
+ return OK;
+ }
+
+ if (cac->cac_cmdidx == CMD_final)
+ {
+ emsg(_(e_final_requires_a_value));
+ return FAIL;
+ }
+
+ if (cac->cac_cmdidx == CMD_const)
+ {
+ emsg(_(e_const_requires_a_value));
+ return FAIL;
+ }
+
+ if (!lhs->lhs_has_type || lhs->lhs_dest == dest_option
+ || lhs->lhs_dest == dest_func_option)
+ {
+ emsg(_(e_type_or_initialization_required));
+ return FAIL;
+ }
+
+ // variables are always initialized
+ if (GA_GROW_FAILS(cac->cac_instr, 1))
+ return FAIL;
+
+ cac->cac_instr_count = cac->cac_instr->ga_len;
+
+ return push_default_value(cctx, lhs->lhs_member_type->tt_type,
+ lhs->lhs_dest == dest_local,
+ &cac->cac_skip_store);
+}
+
+/*
+ * Compile a compound op assignment statement (+=, -=, *=, %=, etc.)
+ */
+ static int
+compile_assign_compound_op(cctx_T *cctx, cac_T *cac)
+{
+ lhs_T *lhs = &cac->cac_lhs;
+ type_T *expected;
+ type_T *stacktype = NULL;
+
+ if (*cac->cac_op == '.')
+ {
+ if (may_generate_2STRING(-1, TOSTRING_NONE, cctx) == FAIL)
+ return FAIL;
+ }
+ else
+ {
+ expected = lhs->lhs_member_type;
+ stacktype = get_type_on_stack(cctx, 0);
+ if (
+ // If variable is float operation with number is OK.
+ !(expected == &t_float && (stacktype == &t_number
+ || stacktype == &t_number_bool))
+ && need_type(stacktype, expected, TRUE, -1, 0, cctx,
+ FALSE, FALSE) == FAIL)
+ return FAIL;
+ }
+
+ if (*cac->cac_op == '.')
+ {
+ if (generate_CONCAT(cctx, 2) == FAIL)
+ return FAIL;
+ }
+ else if (*cac->cac_op == '+')
+ {
+ if (generate_add_instr(cctx,
+ operator_type(lhs->lhs_member_type, stacktype),
+ lhs->lhs_member_type, stacktype,
+ EXPR_APPEND) == FAIL)
+ return FAIL;
+ }
+ else if (generate_two_op(cctx, cac->cac_op) == FAIL)
+ return FAIL;
+
+ return OK;
+}
+
+/*
+ * Generate the STORE and SETTYPE instructions for an assignment statement.
+ */
+ static int
+compile_assign_generate_store(cctx_T *cctx, cac_T *cac)
+{
+ lhs_T *lhs = &cac->cac_lhs;
+ int save_lnum;
+
+ // Use the line number of the assignment for store instruction.
+ save_lnum = cctx->ctx_lnum;
+ cctx->ctx_lnum = cac->cac_start_lnum - 1;
+
+ if (lhs->lhs_has_index)
+ {
+ // Use the info in "lhs" to store the value at the index in the
+ // list, dict or object.
+ if (compile_assign_unlet(cac->cac_var_start, &cac->cac_lhs,
+ TRUE, cac->cac_rhs_type, cctx) == FAIL)
+ {
+ cctx->ctx_lnum = save_lnum;
+ return FAIL;
+ }
+ }
+ else
+ {
+ if (cac->cac_is_decl && cac->cac_cmdidx == CMD_const &&
+ (lhs->lhs_dest == dest_script
+ || lhs->lhs_dest == dest_script_v9
+ || lhs->lhs_dest == dest_global
+ || lhs->lhs_dest == dest_local))
+ // ":const var": lock the value, but not referenced variables
+ generate_LOCKCONST(cctx);
+
+ type_T *inferred_type = cac->cac_inferred_type;
+
+ if ((lhs->lhs_type->tt_type == VAR_DICT
+ || lhs->lhs_type->tt_type == VAR_LIST)
+ && lhs->lhs_type->tt_member != NULL
+ && lhs->lhs_type->tt_member != &t_any
+ && lhs->lhs_type->tt_member != &t_unknown)
+ // Set the type in the list or dict, so that it can be checked,
+ // also in legacy script.
+ generate_SETTYPE(cctx, lhs->lhs_type);
+ else if (inferred_type != NULL
+ && (inferred_type->tt_type == VAR_DICT
+ || inferred_type->tt_type == VAR_LIST)
+ && inferred_type->tt_member != NULL
+ && inferred_type->tt_member != &t_unknown
+ && inferred_type->tt_member != &t_any)
+ // Set the type in the list or dict, so that it can be checked,
+ // also in legacy script.
+ generate_SETTYPE(cctx, inferred_type);
+
+ if (!cac->cac_skip_store &&
+ generate_store_lhs(cctx, &cac->cac_lhs,
+ cac->cac_instr_count,
+ cac->cac_is_decl) == FAIL)
+ {
+ cctx->ctx_lnum = save_lnum;
+ return FAIL;
+ }
+ }
+
+ cctx->ctx_lnum = save_lnum;
+ return OK;
+}
+
+/*
+ * Process the variable(s) in an assignment statement
+ */
+ static int
+compile_assign_process_variables(
+ cctx_T *cctx,
+ cac_T *cac,
+ int cmdidx,
+ int heredoc,
+ int has_cmd,
+ int has_argisset_prefix,
+ int jump_instr_idx)
+{
+ for (cac->cac_var_idx = 0; cac->cac_var_idx == 0 ||
+ cac->cac_var_idx < cac->cac_var_count; cac->cac_var_idx++)
+ {
+ if (cac->cac_var_start[0] == '_'
+ && !eval_isnamec(cac->cac_var_start[1]))
+ {
+ // Ignore underscore in "[a, _, b] = list".
+ if (cac->cac_var_count > 0)
+ {
+ cac->cac_var_start = skipwhite(cac->cac_var_start + 2);
+ continue;
+ }
+ emsg(_(e_cannot_use_underscore_here));
+ return FAIL;
+ }
+ vim_free(cac->cac_lhs.lhs_name);
+
+ /*
+ * Figure out the LHS type and other properties.
+ */
+ if (compile_assign_lhs(cac->cac_var_start, &cac->cac_lhs, cmdidx,
+ cac->cac_is_decl, heredoc, has_cmd,
+ cac->cac_oplen, cctx) == FAIL)
+ return FAIL;
+
+ // Compile the RHS expression
+ if (heredoc)
+ {
+ SOURCING_LNUM = cac->cac_start_lnum;
+ if (cac->cac_lhs.lhs_has_type
+ && need_type(&t_list_string, cac->cac_lhs.lhs_type,
+ FALSE, -1, 0, cctx, FALSE, FALSE) == FAIL)
+ return FAIL;
+ }
+ else
+ {
+ if (compile_assign_rhs(cctx, cac) == FAIL)
+ return FAIL;
+ if (cac->cac_var_count == 0)
+ cac->cac_var_end = cac->cac_nextc;
+ }
+
+ // no need to parse more when skipping
+ if (cctx->ctx_skip == SKIP_YES)
+ break;
+
+ if (cac->cac_oplen > 0 && *cac->cac_op != '=')
+ {
+ if (compile_assign_compound_op(cctx, cac) == FAIL)
+ return FAIL;
+ }
+
+ // generate the store instructions
+ if (compile_assign_generate_store(cctx, cac) == FAIL)
+ return FAIL;
+
+ if (cac->cac_var_idx + 1 < cac->cac_var_count)
+ cac->cac_var_start = skipwhite(cac->cac_lhs.lhs_end + 1);
+
+ if (has_argisset_prefix)
+ {
+ // set instruction index in JUMP_IF_ARG_SET to here
+ isn_T *isn = ((isn_T *)cac->cac_instr->ga_data) + jump_instr_idx;
+ isn->isn_arg.jumparg.jump_where = cac->cac_instr->ga_len;
+ }
+ }
+
+ return OK;
}
/*
@@ -2752,418 +3291,68 @@
cmdidx_T cmdidx,
cctx_T *cctx)
{
+ cac_T cac;
char_u *arg = arg_start;
- char_u *var_start;
- char_u *p;
- char_u *end = arg;
char_u *ret = NULL;
- int var_count = 0;
- int var_idx;
- int semicolon = 0;
- int did_generate_slice = FALSE;
- garray_T *instr = &cctx->ctx_instr;
- int jump_instr_idx = instr->ga_len;
- char_u *op;
- int oplen = 0;
int heredoc = FALSE;
- int incdec = FALSE;
- type_T *rhs_type = &t_any;
- char_u *sp;
- int is_decl = is_decl_command(cmdidx);
- lhs_T lhs;
- CLEAR_FIELD(lhs);
- long start_lnum = SOURCING_LNUM;
+ int jump_instr_idx;
- int has_arg_is_set_prefix = STRNCMP(arg, "ifargisset ", 11) == 0;
- if (has_arg_is_set_prefix &&
- compile_assignment_obj_new_arg(&arg, cctx) == FAIL)
+ compile_assign_context_init(&cac, cctx, cmdidx, arg);
+
+ jump_instr_idx = cac.cac_instr->ga_len;
+
+ // process object variable initialization in a new() constructor method
+ int has_argisset_prefix = STRNCMP(arg, "ifargisset ", 11) == 0;
+ if (has_argisset_prefix &&
+ compile_assign_obj_new_arg(&arg, cctx) == FAIL)
goto theend;
// Skip over the "varname" or "[varname, varname]" to get to any "=".
- p = skip_var_list(arg, TRUE, &var_count, &semicolon, TRUE);
- if (p == NULL)
+ cac.cac_nextc = skip_var_list(arg, TRUE, &cac.cac_var_count,
+ &cac.cac_semicolon, TRUE);
+ if (cac.cac_nextc == NULL)
return *arg == '[' ? arg : NULL;
+ char_u *retstr;
+ if (compile_assign_process_operator(eap, arg, &cac, &heredoc,
+ &retstr) == FAIL)
+ return retstr;
- if (eap->cmdidx == CMD_increment || eap->cmdidx == CMD_decrement)
- {
- if (incdec_op_translate(eap, &op, &oplen, &incdec) == FAIL)
- return NULL;
- }
- else
- {
- sp = p;
- p = skipwhite(p);
- op = p;
- oplen = assignment_len(p, &heredoc);
-
- if (var_count > 0 && oplen == 0)
- // can be something like "[1, 2]->func()"
- return arg;
-
- if (oplen > 0 && (!VIM_ISWHITE(*sp) || !IS_WHITE_OR_NUL(op[oplen])))
- {
- error_white_both(op, oplen);
- return NULL;
- }
- }
-
- if (heredoc)
- {
- p = heredoc_assign_stmt_end_get(p, eap, cctx);
- if (p == NULL)
- return NULL;
- end = p;
- }
- else if (var_count > 0)
- {
- // "[var, var] = expr"
- p = compile_list_assignment(p, op, oplen, var_count, semicolon,
- instr, &rhs_type, cctx);
- if (p == NULL)
- goto theend;
- end = p;
- }
+ // Compute the end of the assignment
+ cac.cac_var_end = compile_assign_compute_end(eap, cctx, &cac, heredoc);
+ if (cac.cac_var_end == NULL)
+ return NULL;
/*
* Loop over variables in "[var, var] = expr".
- * For "var = expr" and "let var: type" this is done only once.
+ * For "name = expr" and "var name: type" this is done only once.
*/
- if (var_count > 0)
- var_start = skipwhite(arg + 1); // skip over the "["
+ if (cac.cac_var_count > 0)
+ cac.cac_var_start = skipwhite(arg + 1); // skip over the "["
else
- var_start = arg;
- for (var_idx = 0; var_idx == 0 || var_idx < var_count; var_idx++)
- {
- int instr_count = -1;
- int save_lnum;
- int skip_store = FALSE;
- type_T *inferred_type = NULL;
+ cac.cac_var_start = arg;
- if (var_start[0] == '_' && !eval_isnamec(var_start[1]))
- {
- // Ignore underscore in "[a, _, b] = list".
- if (var_count > 0)
- {
- var_start = skipwhite(var_start + 2);
- continue;
- }
- emsg(_(e_cannot_use_underscore_here));
- goto theend;
- }
- vim_free(lhs.lhs_name);
+ int has_cmd = cac.cac_var_start > eap->cmd;
- /*
- * Figure out the LHS type and other properties.
- */
- if (compile_assign_lhs(var_start, &lhs, cmdidx,
- is_decl, heredoc, var_start > eap->cmd,
- oplen, cctx) == FAIL)
- goto theend;
- if (heredoc)
- {
- SOURCING_LNUM = start_lnum;
- if (lhs.lhs_has_type
- && need_type(&t_list_string, lhs.lhs_type, FALSE,
- -1, 0, cctx, FALSE, FALSE) == FAIL)
- goto theend;
- }
- else
- {
- if (cctx->ctx_skip == SKIP_YES)
- {
- if (oplen > 0 && var_count == 0)
- {
- // skip over the "=" and the expression
- p = skipwhite(op + oplen);
- (void)compile_expr0(&p, cctx);
- }
- }
- else if (oplen > 0)
- {
- int is_const = FALSE;
- char_u *wp;
-
- // for "+=", "*=", "..=" etc. first load the current value
- if (*op != '='
- && compile_load_lhs_with_index(&lhs, var_start,
- cctx) == FAIL)
- goto theend;
-
- // For "var = expr" evaluate the expression.
- if (var_count == 0)
- {
- int r;
-
- // Compile the expression.
- instr_count = instr->ga_len;
- if (incdec)
- {
- r = generate_PUSHNR(cctx, 1);
- }
- else
- {
- // Temporarily hide the new local variable here, it is
- // not available to this expression.
- if (lhs.lhs_new_local)
- --cctx->ctx_locals.ga_len;
- wp = op + oplen;
- if (may_get_next_line_error(wp, &p, cctx) == FAIL)
- {
- if (lhs.lhs_new_local)
- ++cctx->ctx_locals.ga_len;
- goto theend;
- }
- r = compile_expr0_ext(&p, cctx, &is_const);
- if (lhs.lhs_new_local)
- ++cctx->ctx_locals.ga_len;
- }
- if (r == FAIL)
- goto theend;
- }
- else if (semicolon && var_idx == var_count - 1)
- {
- // For "[var; var] = expr" get the rest of the list
- did_generate_slice = TRUE;
- if (generate_SLICE(cctx, var_count - 1) == FAIL)
- goto theend;
- }
- else
- {
- // For "[var, var] = expr" get the "var_idx" item from the
- // list.
- if (generate_GETITEM(cctx, var_idx, *op != '=') == FAIL)
- goto theend;
- }
-
- rhs_type = cctx->ctx_type_stack.ga_len == 0 ? &t_void
- : get_type_on_stack(cctx, 0);
- if (check_type_is_value(rhs_type) == FAIL)
- goto theend;
- if (lhs.lhs_lvar != NULL && (is_decl || !lhs.lhs_has_type))
- {
- if ((rhs_type->tt_type == VAR_FUNC
- || rhs_type->tt_type == VAR_PARTIAL)
- && !lhs.lhs_has_index
- && var_wrong_func_name(lhs.lhs_name, TRUE))
- goto theend;
-
- if (lhs.lhs_new_local && !lhs.lhs_has_type)
- {
- if (rhs_type->tt_type == VAR_VOID)
- {
- emsg(_(e_cannot_use_void_value));
- goto theend;
- }
- else
- {
- type_T *type;
-
- // An empty list or dict has a &t_unknown member,
- // for a variable that implies &t_any.
- if (rhs_type == &t_list_empty)
- type = &t_list_any;
- else if (rhs_type == &t_dict_empty)
- type = &t_dict_any;
- else if (rhs_type == &t_unknown)
- type = &t_any;
- else
- {
- type = rhs_type;
- inferred_type = rhs_type;
- }
- set_var_type(lhs.lhs_lvar, type, cctx);
- }
- }
- else if (*op == '=')
- {
- type_T *use_type = lhs.lhs_lvar->lv_type;
- where_T where = WHERE_INIT;
-
- // Without operator check type here, otherwise below.
- // Use the line number of the assignment.
- SOURCING_LNUM = start_lnum;
- if (var_count > 0)
- {
- where.wt_index = var_idx + 1;
- where.wt_kind = WT_VARIABLE;
- }
- // If assigning to a list or dict member, use the
- // member type. Not for "list[:] =".
- if (lhs.lhs_has_index
- && !has_list_index(var_start + lhs.lhs_varlen,
- cctx))
- use_type = lhs.lhs_member_type;
- if (need_type_where(rhs_type, use_type, FALSE, -1,
- where, cctx, FALSE, is_const) == FAIL)
- goto theend;
- }
- }
- else
- {
- type_T *lhs_type = lhs.lhs_member_type;
-
- // Special case: assigning to @# can use a number or a
- // string.
- // Also: can assign a number to a float.
- if ((lhs_type == &t_number_or_string
- || lhs_type == &t_float)
- && rhs_type->tt_type == VAR_NUMBER)
- lhs_type = &t_number;
- if (*p != '=' && need_type(rhs_type, lhs_type, FALSE,
- -1, 0, cctx, FALSE, FALSE) == FAIL)
- goto theend;
- }
- }
- else if (cmdidx == CMD_final)
- {
- emsg(_(e_final_requires_a_value));
- goto theend;
- }
- else if (cmdidx == CMD_const)
- {
- emsg(_(e_const_requires_a_value));
- goto theend;
- }
- else if (!lhs.lhs_has_type || lhs.lhs_dest == dest_option
- || lhs.lhs_dest == dest_func_option)
- {
- emsg(_(e_type_or_initialization_required));
- goto theend;
- }
- else
- {
- // variables are always initialized
- if (GA_GROW_FAILS(instr, 1))
- goto theend;
- instr_count = instr->ga_len;
- int r = push_default_value(cctx, lhs.lhs_member_type->tt_type,
- lhs.lhs_dest == dest_local, &skip_store);
- if (r == FAIL)
- goto theend;
- }
- if (var_count == 0)
- end = p;
- }
-
- // no need to parse more when skipping
- if (cctx->ctx_skip == SKIP_YES)
- break;
-
- if (oplen > 0 && *op != '=')
- {
- type_T *expected;
- type_T *stacktype = NULL;
-
- if (*op == '.')
- {
- if (may_generate_2STRING(-1, TOSTRING_NONE, cctx) == FAIL)
- goto theend;
- }
- else
- {
- expected = lhs.lhs_member_type;
- stacktype = get_type_on_stack(cctx, 0);
- if (
- // If variable is float operation with number is OK.
- !(expected == &t_float && (stacktype == &t_number
- || stacktype == &t_number_bool))
- && need_type(stacktype, expected, TRUE, -1, 0, cctx,
- FALSE, FALSE) == FAIL)
- goto theend;
- }
-
- if (*op == '.')
- {
- if (generate_CONCAT(cctx, 2) == FAIL)
- goto theend;
- }
- else if (*op == '+')
- {
- if (generate_add_instr(cctx,
- operator_type(lhs.lhs_member_type, stacktype),
- lhs.lhs_member_type, stacktype,
- EXPR_APPEND) == FAIL)
- goto theend;
- }
- else if (generate_two_op(cctx, op) == FAIL)
- goto theend;
- }
-
- // Use the line number of the assignment for store instruction.
- save_lnum = cctx->ctx_lnum;
- cctx->ctx_lnum = start_lnum - 1;
-
- if (lhs.lhs_has_index)
- {
- // Use the info in "lhs" to store the value at the index in the
- // list, dict or object.
- if (compile_assign_unlet(var_start, &lhs, TRUE, rhs_type, cctx)
- == FAIL)
- {
- cctx->ctx_lnum = save_lnum;
- goto theend;
- }
- }
- else
- {
- if (is_decl && cmdidx == CMD_const && (lhs.lhs_dest == dest_script
- || lhs.lhs_dest == dest_script_v9
- || lhs.lhs_dest == dest_global
- || lhs.lhs_dest == dest_local))
- // ":const var": lock the value, but not referenced variables
- generate_LOCKCONST(cctx);
-
- if ((lhs.lhs_type->tt_type == VAR_DICT
- || lhs.lhs_type->tt_type == VAR_LIST)
- && lhs.lhs_type->tt_member != NULL
- && lhs.lhs_type->tt_member != &t_any
- && lhs.lhs_type->tt_member != &t_unknown)
- // Set the type in the list or dict, so that it can be checked,
- // also in legacy script.
- generate_SETTYPE(cctx, lhs.lhs_type);
- else if (inferred_type != NULL
- && (inferred_type->tt_type == VAR_DICT
- || inferred_type->tt_type == VAR_LIST)
- && inferred_type->tt_member != NULL
- && inferred_type->tt_member != &t_unknown
- && inferred_type->tt_member != &t_any)
- // Set the type in the list or dict, so that it can be checked,
- // also in legacy script.
- generate_SETTYPE(cctx, inferred_type);
-
- if (!skip_store && generate_store_lhs(cctx, &lhs,
- instr_count, is_decl) == FAIL)
- {
- cctx->ctx_lnum = save_lnum;
- goto theend;
- }
- }
- cctx->ctx_lnum = save_lnum;
-
- if (var_idx + 1 < var_count)
- var_start = skipwhite(lhs.lhs_end + 1);
-
- if (has_arg_is_set_prefix)
- {
- // set instruction index in JUMP_IF_ARG_SET to here
- isn_T *isn = ((isn_T *)instr->ga_data) + jump_instr_idx;
- isn->isn_arg.jumparg.jump_where = instr->ga_len;
- }
- }
+ /* process the variable(s) */
+ if (compile_assign_process_variables(cctx, &cac, cmdidx, heredoc,
+ has_cmd, has_argisset_prefix,
+ jump_instr_idx) == FAIL)
+ goto theend;
// For "[var, var] = expr" drop the "expr" value.
// Also for "[var, var; _] = expr".
- if (var_count > 0 && (!semicolon || !did_generate_slice))
+ if (cac.cac_var_count > 0 &&
+ (!cac.cac_semicolon || !cac.cac_did_generate_slice))
{
if (generate_instr_drop(cctx, ISN_DROP, 1) == NULL)
goto theend;
}
- ret = skipwhite(end);
+ ret = skipwhite(cac.cac_var_end);
theend:
- vim_free(lhs.lhs_name);
+ vim_free(cac.cac_lhs.lhs_name);
return ret;
}