From 75a9fa68f6cdd6769813a8c5e055bfb00a08c089 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 20 Jun 2024 05:56:56 +0200 Subject: Adding upstream version 2:9.1.0496. Signed-off-by: Daniel Baumann --- src/eval.c | 3665 ++++++++++++++++++++++++++++-------------------------------- 1 file changed, 1738 insertions(+), 1927 deletions(-) (limited to 'src/eval.c') diff --git a/src/eval.c b/src/eval.c index 51ee604..7848fea 100644 --- a/src/eval.c +++ b/src/eval.c @@ -22,13 +22,6 @@ #define NAMESPACE_CHAR (char_u *)"abglstvw" -/* - * When recursively copying lists and dicts we need to remember which ones we - * have done to avoid endless recursiveness. This unique ID is used for that. - * The last bit is used for previous_funccal, ignored when comparing. - */ -static int current_copyID = 0; - static int eval2(char_u **arg, typval_T *rettv, evalarg_T *evalarg); static int eval3(char_u **arg, typval_T *rettv, evalarg_T *evalarg); static int eval4(char_u **arg, typval_T *rettv, evalarg_T *evalarg); @@ -39,7 +32,6 @@ static int eval8(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int want_str static int eval9(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int want_string); static int eval9_leader(typval_T *rettv, int numeric_only, char_u *start_leader, char_u **end_leaderp); -static int free_unref_items(int copyID); static char_u *make_expanded_name(char_u *in_start, char_u *expr_start, char_u *expr_end, char_u *in_end); /* @@ -250,93 +242,146 @@ eval_expr_get_funccal(typval_T *expr, typval_T *rettv) } /* - * Evaluate an expression, which can be a function, partial or string. + * Evaluate a partial. * Pass arguments "argv[argc]". - * If "want_func" is TRUE treat a string as a function name, not an expression. * "fc_arg" is from eval_expr_get_funccal() or NULL; * Return the result in "rettv" and OK or FAIL. */ - int -eval_expr_typval( - typval_T *expr, - int want_func, - typval_T *argv, - int argc, - funccall_T *fc_arg, - typval_T *rettv) + static int +eval_expr_partial( + typval_T *expr, + typval_T *argv, + int argc, + funccall_T *fc_arg, + typval_T *rettv) { - char_u *s; - char_u buf[NUMBUFLEN]; - funcexe_T funcexe; + partial_T *partial = expr->vval.v_partial; - if (expr->v_type == VAR_PARTIAL) + if (partial == NULL) + return FAIL; + + if (partial->pt_func != NULL + && partial->pt_func->uf_def_status != UF_NOT_COMPILED) { - partial_T *partial = expr->vval.v_partial; + funccall_T *fc = fc_arg != NULL ? fc_arg + : create_funccal(partial->pt_func, rettv); + int r; - if (partial == NULL) + if (fc == NULL) return FAIL; - if (partial->pt_func != NULL - && partial->pt_func->uf_def_status != UF_NOT_COMPILED) - { - funccall_T *fc = fc_arg != NULL ? fc_arg - : create_funccal(partial->pt_func, rettv); - int r; - - if (fc == NULL) - return FAIL; - - // Shortcut to call a compiled function with minimal overhead. - r = call_def_function(partial->pt_func, argc, argv, - DEF_USE_PT_ARGV, partial, NULL, fc, rettv); - if (fc_arg == NULL) - remove_funccal(); - if (r == FAIL) - return FAIL; - } - else - { - s = partial_name(partial); - if (s == NULL || *s == NUL) - return FAIL; - CLEAR_FIELD(funcexe); - funcexe.fe_evaluate = TRUE; - funcexe.fe_partial = partial; - if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) - return FAIL; - } - } - else if (expr->v_type == VAR_INSTR) - { - return exe_typval_instr(expr, rettv); + // Shortcut to call a compiled function with minimal overhead. + r = call_def_function(partial->pt_func, argc, argv, DEF_USE_PT_ARGV, + partial, NULL, fc, rettv); + if (fc_arg == NULL) + remove_funccal(); + if (r == FAIL) + return FAIL; } - else if (expr->v_type == VAR_FUNC || want_func) + else { - s = expr->v_type == VAR_FUNC - ? expr->vval.v_string - : tv_get_string_buf_chk_strict(expr, buf, in_vim9script()); + char_u *s = partial_name(partial); + funcexe_T funcexe; + if (s == NULL || *s == NUL) return FAIL; + CLEAR_FIELD(funcexe); funcexe.fe_evaluate = TRUE; + funcexe.fe_partial = partial; if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) return FAIL; } + + return OK; +} + +/* + * Evaluate an expression which is a function. + * Pass arguments "argv[argc]". + * Return the result in "rettv" and OK or FAIL. + */ + static int +eval_expr_func( + typval_T *expr, + typval_T *argv, + int argc, + typval_T *rettv) +{ + funcexe_T funcexe; + char_u buf[NUMBUFLEN]; + char_u *s; + + if (expr->v_type == VAR_FUNC) + s = expr->vval.v_string; else - { s = tv_get_string_buf_chk_strict(expr, buf, in_vim9script()); - if (s == NULL) - return FAIL; - s = skipwhite(s); - if (eval1_emsg(&s, rettv, NULL) == FAIL) - return FAIL; - if (*skipwhite(s) != NUL) // check for trailing chars after expr - { - clear_tv(rettv); - semsg(_(e_invalid_expression_str), s); - return FAIL; - } + if (s == NULL || *s == NUL) + return FAIL; + + CLEAR_FIELD(funcexe); + funcexe.fe_evaluate = TRUE; + if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) + return FAIL; + + return OK; +} + +/* + * Evaluate an expression, which is a string. + * Return the result in "rettv" and OK or FAIL. + */ + static int +eval_expr_string( + typval_T *expr, + typval_T *rettv) +{ + char_u *s; + char_u buf[NUMBUFLEN]; + + s = tv_get_string_buf_chk_strict(expr, buf, in_vim9script()); + if (s == NULL) + return FAIL; + + s = skipwhite(s); + if (eval1_emsg(&s, rettv, NULL) == FAIL) + return FAIL; + + if (*skipwhite(s) != NUL) // check for trailing chars after expr + { + clear_tv(rettv); + semsg(_(e_invalid_expression_str), s); + return FAIL; } + + return OK; +} + +/* + * Evaluate an expression, which can be a function, partial or string. + * Pass arguments "argv[argc]". + * If "want_func" is TRUE treat a string as a function name, not an expression. + * "fc_arg" is from eval_expr_get_funccal() or NULL; + * Return the result in "rettv" and OK or FAIL. + */ + int +eval_expr_typval( + typval_T *expr, + int want_func, + typval_T *argv, + int argc, + funccall_T *fc_arg, + typval_T *rettv) +{ + if (expr->v_type == VAR_PARTIAL) + return eval_expr_partial(expr, argv, argc, fc_arg, rettv); + else if (expr->v_type == VAR_INSTR) + return exe_typval_instr(expr, rettv); + else if (expr->v_type == VAR_FUNC || want_func) + return eval_expr_func(expr, argv, argc, rettv); + else + return eval_expr_string(expr, rettv); + return OK; } @@ -1117,41 +1162,40 @@ get_lval_check_access( ch_log(NULL, "LKVAR: get_lval_check_access(), cl_exec %p, cl %p, %c", (void*)cl_exec, (void*)cl, *p); #endif - if (cl_exec == NULL || cl_exec != cl) + if (cl_exec != NULL && cl_exec == cl) + return OK; + + char *msg = NULL; + switch (om->ocm_access) { - char *msg = NULL; - switch (om->ocm_access) - { - case VIM_ACCESS_PRIVATE: - msg = e_cannot_access_protected_variable_str; + case VIM_ACCESS_PRIVATE: + msg = e_cannot_access_protected_variable_str; + break; + case VIM_ACCESS_READ: + // If [idx] or .key following, read only OK. + if (*p == '[' || *p == '.') break; - case VIM_ACCESS_READ: - // If [idx] or .key following, read only OK. - if (*p == '[' || *p == '.') - break; - if ((flags & GLV_READ_ONLY) == 0) + if ((flags & GLV_READ_ONLY) == 0) + { + if (IS_ENUM(cl)) { - if (IS_ENUM(cl)) - { - if (om->ocm_type->tt_type == VAR_OBJECT) - semsg(_(e_enumvalue_str_cannot_be_modified), - cl->class_name, om->ocm_name); - else - msg = e_variable_is_not_writable_str; - } + if (om->ocm_type->tt_type == VAR_OBJECT) + semsg(_(e_enumvalue_str_cannot_be_modified), + cl->class_name, om->ocm_name); else msg = e_variable_is_not_writable_str; } - break; - case VIM_ACCESS_ALL: - break; - } - if (msg != NULL) - { - emsg_var_cl_define(msg, om->ocm_name, 0, cl); - return FAIL; - } - + else + msg = e_variable_is_not_writable_str; + } + break; + case VIM_ACCESS_ALL: + break; + } + if (msg != NULL) + { + emsg_var_cl_define(msg, om->ocm_name, 0, cl); + return FAIL; } return OK; } @@ -1170,12 +1214,10 @@ get_lval_check_access( static char_u * get_lval_imported( lval_T *lp, - typval_T *rettv, scid_T imp_sid, char_u *p, dictitem_T **dip, - int fne_flags, - int vim9script) + int fne_flags) { ufunc_T *ufunc; type_T *type = NULL; @@ -1197,16 +1239,6 @@ get_lval_imported( TRUE) == -1) goto failed; - if (vim9script && type != NULL) - { - where_T where = WHERE_INIT; - - // In a vim9 script, do type check and make sure the variable is - // writable. - if (check_typval_type(type, rettv, where) == FAIL) - goto failed; - } - // Get the typval for the exported item hashtab_T *ht = &SCRIPT_VARS(imp_sid); if (ht == NULL) @@ -1232,6 +1264,7 @@ get_lval_imported( goto failed; lp->ll_tv = &di->di_tv; + lp->ll_valtype = type; success: rc = OK; @@ -1241,11 +1274,15 @@ failed: return rc == OK ? p : NULL; } +typedef enum { + GLV_FAIL, + GLV_OK, + GLV_STOP +} glv_status_T; + /* - * Get an lval: variable, Dict item or List item that can be assigned a value - * to: "name", "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", - * "name.key", "name.key[expr]" etc. - * Indexing only works if "name" is an existing List or Dictionary. + * Get an Dict lval variable that can be assigned a value to: "name", + * "name[expr]", "name[expr][expr]", "name.key", "name.key[expr]" etc. * "name" points to the start of the name. * If "rettv" is not NULL it points to the value to be assigned. * "unlet" is TRUE for ":unlet": slightly different behavior when something is @@ -1256,235 +1293,485 @@ failed: * GLV_READ_ONLY: will not change the variable * GLV_NO_AUTOLOAD: do not use script autoloading * - * Returns a pointer to just after the name, including indexes. - * When an evaluation error occurs "lp->ll_name" is NULL; - * Returns NULL for a parsing error. Still need to free items in "lp"! + * The Dict is returned in 'lp'. Returns GLV_OK on success and GLV_FAIL on + * failure. Returns GLV_STOP to stop processing the characters following + * 'key_end'. */ - char_u * -get_lval( - char_u *name, - typval_T *rettv, + static int +get_lval_dict_item( lval_T *lp, + char_u *name, + char_u *key, + int len, + char_u **key_end, + typval_T *var1, + int flags, int unlet, - int skip, - int flags, // GLV_ values - int fne_flags) // flags for find_name_end() + typval_T *rettv) { - char_u *p; - char_u *expr_start, *expr_end; - int cc; - dictitem_T *v = NULL; - typval_T var1; - typval_T var2; - int empty1 = FALSE; - char_u *key = NULL; - int len; - hashtab_T *ht = NULL; int quiet = flags & GLV_QUIET; - int writing = 0; - int vim9script = in_vim9script(); - class_T *cl_exec = NULL; // class that is executing, or NULL. - -#ifdef LOG_LOCKVAR - if (lval_root == NULL) - ch_log(NULL, "LKVAR: get_lval(): name: %s, lval_root (nil)", name); - else - ch_log(NULL, "LKVAR: get_lval(): name: %s, lr_tv %p lr_is_arg %d", - name, (void*)lval_root->lr_tv, lval_root->lr_is_arg); - char buf[80]; - ch_log(NULL, "LKVAR: ...: GLV flags: %s", - flags_tostring(flags, glv_flag_strings, buf, sizeof(buf))); -#endif + char_u *p = *key_end; - // Clear everything in "lp". - CLEAR_POINTER(lp); + if (len == -1) + { + // "[key]": get key from "var1" + key = tv_get_string_chk(var1); // is number or string + if (key == NULL) + return GLV_FAIL; + } + lp->ll_list = NULL; + lp->ll_object = NULL; + lp->ll_class = NULL; - if (skip || (flags & GLV_COMPILING)) + // a NULL dict is equivalent with an empty dict + if (lp->ll_tv->vval.v_dict == NULL) { - // When skipping or compiling just find the end of the name. - lp->ll_name = name; - lp->ll_name_end = find_name_end(name, NULL, NULL, - FNE_INCL_BR | fne_flags); - return lp->ll_name_end; + lp->ll_tv->vval.v_dict = dict_alloc(); + if (lp->ll_tv->vval.v_dict == NULL) + return GLV_FAIL; + ++lp->ll_tv->vval.v_dict->dv_refcount; } + lp->ll_dict = lp->ll_tv->vval.v_dict; - // Cannot use "s:var" at the Vim9 script level. "s: type" is OK. - if (vim9script && at_script_level() - && name[0] == 's' && name[1] == ':' && !VIM_ISWHITE(name[2])) + lp->ll_di = dict_find(lp->ll_dict, key, len); + + // When assigning to a scope dictionary check that a function and + // variable name is valid (only variable name unless it is l: or + // g: dictionary). Disallow overwriting a builtin function. + if (rettv != NULL && lp->ll_dict->dv_scope != 0) { - semsg(_(e_cannot_use_s_colon_in_vim9_script_str), name); - return NULL; + int prevval; + + if (len != -1) + { + prevval = key[len]; + key[len] = NUL; + } + else + prevval = 0; // avoid compiler warning + int wrong = (lp->ll_dict->dv_scope == VAR_DEF_SCOPE + && (rettv->v_type == VAR_FUNC + || rettv->v_type == VAR_PARTIAL) + && var_wrong_func_name(key, lp->ll_di == NULL)) + || !valid_varname(key, -1, TRUE); + if (len != -1) + key[len] = prevval; + if (wrong) + return GLV_FAIL; } - // Find the end of the name. - p = find_name_end(name, &expr_start, &expr_end, fne_flags); - lp->ll_name_end = p; - if (expr_start != NULL) + if (lp->ll_valtype != NULL) + // use the type of the member + lp->ll_valtype = lp->ll_valtype->tt_member; + + if (lp->ll_di == NULL) { - // Don't expand the name when we already know there is an error. - if (unlet && !VIM_ISWHITE(*p) && !ends_excmd(*p) - && *p != '[' && *p != '.') + // Can't add "v:" or "a:" variable. + if (lp->ll_dict == get_vimvar_dict() + || &lp->ll_dict->dv_hashtab == get_funccal_args_ht()) { - semsg(_(e_trailing_characters_str), p); - return NULL; + semsg(_(e_illegal_variable_name_str), name); + return GLV_FAIL; } - lp->ll_exp_name = make_expanded_name(name, expr_start, expr_end, p); - if (lp->ll_exp_name == NULL) + // Key does not exist in dict: may need to add it. + if (*p == '[' || *p == '.' || unlet) { - // Report an invalid expression in braces, unless the - // expression evaluation has been cancelled due to an - // aborting error, an interrupt, or an exception. - if (!aborting() && !quiet) - { - emsg_severe = TRUE; - semsg(_(e_invalid_argument_str), name); - return NULL; - } + if (!quiet) + semsg(_(e_key_not_present_in_dictionary_str), key); + return GLV_FAIL; } - lp->ll_name = lp->ll_exp_name; + if (len == -1) + lp->ll_newkey = vim_strsave(key); + else + lp->ll_newkey = vim_strnsave(key, len); + if (lp->ll_newkey == NULL) + p = NULL; + + *key_end = p; + return GLV_STOP; } - else - { - lp->ll_name = name; + // existing variable, need to check if it can be changed + else if ((flags & GLV_READ_ONLY) == 0 + && (var_check_ro(lp->ll_di->di_flags, name, FALSE) + || var_check_lock(lp->ll_di->di_flags, name, FALSE))) + return GLV_FAIL; - if (vim9script) - { - // "a: type" is declaring variable "a" with a type, not "a:". - // However, "g:[key]" is indexing a dictionary. - if (p == name + 2 && p[-1] == ':' && *p != '[') - { - --p; - lp->ll_name_end = p; - } - if (*skipwhite(p) == ':') - { - char_u *tp = skipwhite(p + 1); + lp->ll_tv = &lp->ll_di->di_tv; - if (is_scoped_variable(name)) - { - semsg(_(e_cannot_use_type_with_this_variable_str), name); - return NULL; - } - if (VIM_ISWHITE(*p)) - { - semsg(_(e_no_white_space_allowed_before_colon_str), p); - return NULL; - } - if (tp == p + 1 && !quiet) - { - semsg(_(e_white_space_required_after_str_str), ":", p); - return NULL; - } - if (!SCRIPT_ID_VALID(current_sctx.sc_sid)) - { - semsg(_(e_using_type_not_in_script_context_str), p); - return NULL; - } - if (vim9script && (flags & GLV_NO_DECL) && - !(flags & GLV_FOR_LOOP)) - { - // Using a type and not in a "var" declaration. - semsg(_(e_trailing_characters_str), p); - return NULL; - } + return GLV_OK; +} + +/* + * Get an blob lval variable that can be assigned a value to: "name", + * "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", etc. + * + * 'var1' specifies the starting blob index and 'var2' specifies the ending + * blob index. If the first index is not specified in a range, then 'empty1' + * is TRUE. If 'quiet' is TRUE, then error messages are not displayed for + * invalid indexes. + * + * The blob is returned in 'lp'. Returns OK on success and FAIL on failure. + */ + static int +get_lval_blob( + lval_T *lp, + typval_T *var1, + typval_T *var2, + int empty1, + int quiet) +{ + long bloblen = blob_len(lp->ll_tv->vval.v_blob); + // Get the number and item for the only or first index of the List. + if (empty1) + lp->ll_n1 = 0; + else + // is number or string + lp->ll_n1 = (long)tv_get_number(var1); - // parse the type after the name - lp->ll_type = parse_type(&tp, - &SCRIPT_ITEM(current_sctx.sc_sid)->sn_type_list, - !quiet); - if (lp->ll_type == NULL && !quiet) - return NULL; - lp->ll_name_end = tp; - } - // TODO: check inside class? + if (check_blob_index(bloblen, lp->ll_n1, quiet) == FAIL) + return FAIL; + if (lp->ll_range && !lp->ll_empty2) + { + lp->ll_n2 = (long)tv_get_number(var2); + if (check_blob_range(bloblen, lp->ll_n1, lp->ll_n2, quiet) == FAIL) + return FAIL; + } + + if (!lp->ll_range) + // Indexing a single byte in a blob. So the rhs type is a + // number. + lp->ll_valtype = &t_number; + + lp->ll_blob = lp->ll_tv->vval.v_blob; + lp->ll_tv = NULL; + + return OK; +} + +/* + * Get a List lval variable that can be assigned a value to: "name", + * "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", etc. + * + * 'var1' specifies the starting List index and 'var2' specifies the ending + * List index. If the first index is not specified in a range, then 'empty1' + * is TRUE. If 'quiet' is TRUE, then error messages are not displayed for + * invalid indexes. + * + * The List is returned in 'lp'. Returns OK on success and FAIL on failure. + */ + static int +get_lval_list( + lval_T *lp, + typval_T *var1, + typval_T *var2, + int empty1, + int flags, + int quiet) +{ + /* + * Get the number and item for the only or first index of the List. + */ + if (empty1) + lp->ll_n1 = 0; + else + // is number or string + lp->ll_n1 = (long)tv_get_number(var1); + + lp->ll_dict = NULL; + lp->ll_object = NULL; + lp->ll_class = NULL; + lp->ll_list = lp->ll_tv->vval.v_list; + lp->ll_li = check_range_index_one(lp->ll_list, &lp->ll_n1, + (flags & GLV_ASSIGN_WITH_OP) == 0, quiet); + if (lp->ll_li == NULL) + return FAIL; + + if (lp->ll_valtype != NULL && !lp->ll_range) + // use the type of the member + lp->ll_valtype = lp->ll_valtype->tt_member; + + /* + * May need to find the item or absolute index for the second + * index of a range. + * When no index given: "lp->ll_empty2" is TRUE. + * Otherwise "lp->ll_n2" is set to the second index. + */ + if (lp->ll_range && !lp->ll_empty2) + { + lp->ll_n2 = (long)tv_get_number(var2); + // is number or string + if (check_range_index_two(lp->ll_list, + &lp->ll_n1, lp->ll_li, &lp->ll_n2, quiet) == FAIL) + return FAIL; + } + + lp->ll_tv = &lp->ll_li->li_tv; + + return OK; +} + +/* + * Get a class or object lval method in class "cl". The 'key' argument points + * to the method name and 'key_end' points to the character after 'key'. + * 'v_type' is VAR_CLASS or VAR_OBJECT. + * + * The method index, method function pointer and method type are returned in + * "lp". + */ + static void +get_lval_oc_method( + lval_T *lp, + class_T *cl, + char_u *key, + char_u *key_end, + vartype_T v_type) +{ + // Look for a method with this name. + // round 1: class functions (skipped for an object) + // round 2: object methods + for (int round = v_type == VAR_OBJECT ? 2 : 1; round <= 2; ++round) + { + int m_idx; + ufunc_T *fp; + + fp = method_lookup(cl, round == 1 ? VAR_CLASS : VAR_OBJECT, + key, key_end - key, &m_idx); + lp->ll_oi = m_idx; + if (fp != NULL) + { + lp->ll_ufunc = fp; + lp->ll_valtype = fp->uf_func_type; + break; } } - if (lp->ll_name == NULL) - return p; +} - if (*p == '.') +/* + * Get a class or object lval variable in class "cl". The "key" argument + * points to the variable name and "key_end" points to the character after + * "key". "v_type" is VAR_CLASS or VAR_OBJECT. "cl_exec" is the class that is + * executing, or NULL. + * + * The variable index, typval and type are returned in "lp". Returns FAIL if + * the variable is not writable. Otherwise returns OK. + */ + static int +get_lval_oc_variable( + lval_T *lp, + class_T *cl, + char_u *key, + char_u *key_end, + vartype_T v_type, + class_T *cl_exec, + int flags) +{ + int m_idx; + ocmember_T *om; + + om = member_lookup(cl, v_type, key, key_end - key, &m_idx); + lp->ll_oi = m_idx; + if (om == NULL) + return OK; + + // Check variable is accessible + if (get_lval_check_access(cl_exec, cl, om, key_end, flags) == FAIL) + return FAIL; + + // When lhs is used to modify the variable, check it is not a read-only + // variable. + if ((flags & GLV_READ_ONLY) == 0 && (*key_end != '.' && *key_end != '[') + && oc_var_check_ro(cl, om)) + return FAIL; + + lp->ll_valtype = om->ocm_type; + + if (v_type == VAR_OBJECT) + lp->ll_tv = ((typval_T *)(lp->ll_tv->vval.v_object + 1)) + m_idx; + else + lp->ll_tv = &cl->class_members_tv[m_idx]; + + return OK; +} + +/* + * Get a Class or Object lval variable or method that can be assigned a value + * to: "name", "name.key", "name.key[expr]" etc. + * + * The 'key' argument points to the member name and 'key_end' points to the + * character after 'key'. 'v_type' is VAR_CLASS or VAR_OBJECT. 'cl_exec' is + * the class that is executing, or NULL. If 'quiet' is TRUE, then error + * messages are not displayed for invalid indexes. + * + * The Class or Object is returned in 'lp'. Returns OK on success and FAIL on + * failure. + */ + static int +get_lval_class_or_obj( + lval_T *lp, + char_u *key, + char_u *key_end, + vartype_T v_type, + class_T *cl_exec, + int flags, + int quiet) +{ + lp->ll_dict = NULL; + lp->ll_list = NULL; + + class_T *cl; + if (v_type == VAR_OBJECT) { - imported_T *import = find_imported(lp->ll_name, p - lp->ll_name, TRUE); - if (import != NULL) + if (lp->ll_tv->vval.v_object == NULL) { - p++; // skip '.' - p = get_lval_imported(lp, rettv, import->imp_sid, p, &v, - fne_flags, vim9script); - if (p == NULL) - return NULL; + if (!quiet) + emsg(_(e_using_null_object)); + return FAIL; } + cl = lp->ll_tv->vval.v_object->obj_class; + lp->ll_object = lp->ll_tv->vval.v_object; + } + else + { + cl = lp->ll_tv->vval.v_class; + lp->ll_object = NULL; } + lp->ll_class = cl; - // Without [idx] or .key we are done. - if (*p != '[' && *p != '.') + if (cl == NULL) + // TODO: what if class is NULL? + return OK; + + lp->ll_valtype = NULL; + + if (flags & GLV_PREFER_FUNC) + get_lval_oc_method(lp, cl, key, key_end, v_type); + + // Look for object/class member variable + if (lp->ll_valtype == NULL) { - if (lval_root != NULL) - fill_lval_from_lval_root(lp, lval_root); - return p; + if (get_lval_oc_variable(lp, cl, key, key_end, v_type, cl_exec, flags) + == FAIL) + return FAIL; } - if (vim9script && lval_root != NULL) - cl_exec = lval_root->lr_cl_exec; - if (vim9script && lval_root != NULL && lval_root->lr_tv != NULL) + if (lp->ll_valtype == NULL) { - // using local variable - lp->ll_tv = lval_root->lr_tv; - v = NULL; + member_not_found_msg(cl, v_type, key, key_end - key); + return FAIL; } - else if (lp->ll_tv == NULL) + + return OK; +} + +/* + * Check whether dot (".") is allowed after the variable "name" with type + * "v_type". Only Dict, Class and Object types support a dot after the name. + * Returns TRUE if dot is allowed after the name. + */ + static int +dot_allowed_after_type(char_u *name, vartype_T v_type, int quiet) +{ + if (v_type != VAR_DICT && v_type != VAR_OBJECT && v_type != VAR_CLASS) { - cc = *p; - *p = NUL; - // When we would write to the variable pass &ht and prevent autoload. - writing = !(flags & GLV_READ_ONLY); - v = find_var(lp->ll_name, writing ? &ht : NULL, - (flags & GLV_NO_AUTOLOAD) || writing); - if (v == NULL && !quiet) - semsg(_(e_undefined_variable_str), lp->ll_name); - *p = cc; - if (v == NULL) - return NULL; - lp->ll_tv = &v->di_tv; + if (!quiet) + semsg(_(e_dot_not_allowed_after_str_str), + vartype_name(v_type), name); + return FALSE; } - if (vim9script && (flags & GLV_NO_DECL) == 0) + return TRUE; +} + +/* + * Check whether left bracket ("[") is allowed after the variable "name" with + * type "v_type". Only Dict, List and Blob types support a bracket after the + * variable name. Returns TRUE if bracket is allowed after the name. + */ + static int +bracket_allowed_after_type(char_u *name, vartype_T v_type, int quiet) +{ + if (v_type == VAR_CLASS || v_type == VAR_OBJECT) { if (!quiet) - semsg(_(e_variable_already_declared_str), lp->ll_name); - return NULL; + semsg(_(e_index_not_allowed_after_str_str), + vartype_name(v_type), name); + return FALSE; + } + + return TRUE; +} + +/* + * Check whether the variable "name" with type "v_type" can be followed by an + * index. Only Dict, List, Blob, Object and Class types support indexing. + * Returns TRUE if indexing is allowed after the name. + */ + static int +index_allowed_after_type(char_u *name, vartype_T v_type, int quiet) +{ + if (v_type != VAR_LIST && v_type != VAR_DICT && v_type != VAR_BLOB && + v_type != VAR_OBJECT && v_type != VAR_CLASS) + { + if (!quiet) + semsg(_(e_index_not_allowed_after_str_str), + vartype_name(v_type), name); + return FALSE; } + return TRUE; +} + +/* + * Get the lval of a list/dict/blob/object/class subitem starting at "p". Loop + * until no more [idx] or .key is following. + * + * If "rettv" is not NULL it points to the value to be assigned. + * "unlet" is TRUE for ":unlet". + * + * Returns a pointer to the character after the subscript on success or NULL on + * failure. + */ + static char_u * +get_lval_subscript( + lval_T *lp, + char_u *p, + char_u *name, + typval_T *rettv, + hashtab_T *ht, + dictitem_T *v, + int unlet, + int flags, // GLV_ values + class_T *cl_exec) +{ + int vim9script = in_vim9script(); + int quiet = flags & GLV_QUIET; + char_u *key = NULL; + int len; + typval_T var1; + typval_T var2; + int empty1 = FALSE; + int rc = FAIL; + /* * Loop until no more [idx] or .key is following. */ var1.v_type = VAR_UNKNOWN; var2.v_type = VAR_UNKNOWN; + while (*p == '[' || (*p == '.' && p[1] != '=' && p[1] != '.')) { vartype_T v_type = lp->ll_tv->v_type; - if (*p == '.' && v_type != VAR_DICT - && v_type != VAR_OBJECT - && v_type != VAR_CLASS) - { - if (!quiet) - semsg(_(e_dot_not_allowed_after_str_str), - vartype_name(v_type), name); - return NULL; - } - if (v_type != VAR_LIST - && v_type != VAR_DICT - && v_type != VAR_BLOB - && v_type != VAR_OBJECT - && v_type != VAR_CLASS) - { - if (!quiet) - semsg(_(e_index_not_allowed_after_str_str), - vartype_name(v_type), name); - return NULL; - } + if (*p == '.' && !dot_allowed_after_type(name, v_type, quiet)) + goto done; + + if (*p == '[' && !bracket_allowed_after_type(name, v_type, quiet)) + goto done; + + if (!index_allowed_after_type(name, v_type, quiet)) + goto done; // A NULL list/blob works like an empty list/blob, allocate one now. int r = OK; @@ -1493,17 +1780,17 @@ get_lval( else if (v_type == VAR_BLOB && lp->ll_tv->vval.v_blob == NULL) r = rettv_blob_alloc(lp->ll_tv); if (r == FAIL) - return NULL; + goto done; if (lp->ll_range) { if (!quiet) emsg(_(e_slice_must_come_last)); - return NULL; + goto done; } #ifdef LOG_LOCKVAR ch_log(NULL, "LKVAR: get_lval() loop: p: %s, type: %s", p, - vartype_name(v_type)); + vartype_name(v_type)); #endif if (vim9script && lp->ll_valtype == NULL @@ -1519,7 +1806,7 @@ get_lval( lp->ll_valtype = sv->sv_type; #ifdef LOG_LOCKVAR ch_log(NULL, "LKVAR: ... loop: vim9 assign type: %s", - vartype_name(lp->ll_valtype->tt_type)); + vartype_name(lp->ll_valtype->tt_type)); #endif } } @@ -1528,13 +1815,14 @@ get_lval( if (*p == '.') { key = p + 1; + for (len = 0; ASCII_ISALNUM(key[len]) || key[len] == '_'; ++len) ; if (len == 0) { if (!quiet) emsg(_(e_cannot_use_empty_key_for_dictionary)); - return NULL; + goto done; } p = key + len; } @@ -1548,13 +1836,10 @@ get_lval( { empty1 = FALSE; if (eval1(&p, &var1, &EVALARG_EVALUATE) == FAIL) // recursive! - return NULL; + goto done; if (tv_get_string_chk(&var1) == NULL) - { // not a number or string - clear_tv(&var1); - return NULL; - } + goto done; p = skipwhite(p); } @@ -1565,19 +1850,17 @@ get_lval( { if (!quiet) emsg(_(e_cannot_slice_dictionary)); - clear_tv(&var1); - return NULL; + goto done; } if (rettv != NULL && !(rettv->v_type == VAR_LIST - && rettv->vval.v_list != NULL) + && rettv->vval.v_list != NULL) && !(rettv->v_type == VAR_BLOB - && rettv->vval.v_blob != NULL)) + && rettv->vval.v_blob != NULL)) { if (!quiet) emsg(_(e_slice_requires_list_or_blob_value)); - clear_tv(&var1); - return NULL; + goto done; } p = skipwhite(p + 1); if (*p == ']') @@ -1587,17 +1870,10 @@ get_lval( lp->ll_empty2 = FALSE; // recursive! if (eval1(&p, &var2, &EVALARG_EVALUATE) == FAIL) - { - clear_tv(&var1); - return NULL; - } + goto done; if (tv_get_string_chk(&var2) == NULL) - { // not a number or string - clear_tv(&var1); - clear_tv(&var2); - return NULL; - } + goto done; } lp->ll_range = TRUE; } @@ -1608,9 +1884,7 @@ get_lval( { if (!quiet) emsg(_(e_missing_closing_square_brace)); - clear_tv(&var1); - clear_tv(&var2); - return NULL; + goto done; } // Skip to past ']'. @@ -1619,284 +1893,279 @@ get_lval( #ifdef LOG_LOCKVAR if (len == -1) ch_log(NULL, "LKVAR: ... loop: p: %s, '[' key: %s", p, - empty1 ? ":" : (char*)tv_get_string(&var1)); + empty1 ? ":" : (char*)tv_get_string(&var1)); else ch_log(NULL, "LKVAR: ... loop: p: %s, '.' key: %s", p, key); #endif if (v_type == VAR_DICT) { - if (len == -1) - { - // "[key]": get key from "var1" - key = tv_get_string_chk(&var1); // is number or string - if (key == NULL) - { - clear_tv(&var1); - return NULL; - } - } - lp->ll_list = NULL; - lp->ll_object = NULL; - lp->ll_class = NULL; + glv_status_T glv_status; - // a NULL dict is equivalent with an empty dict - if (lp->ll_tv->vval.v_dict == NULL) - { - lp->ll_tv->vval.v_dict = dict_alloc(); - if (lp->ll_tv->vval.v_dict == NULL) - { - clear_tv(&var1); - return NULL; - } - ++lp->ll_tv->vval.v_dict->dv_refcount; - } - lp->ll_dict = lp->ll_tv->vval.v_dict; + glv_status = get_lval_dict_item(lp, name, key, len, &p, &var1, + flags, unlet, rettv); + if (glv_status == GLV_FAIL) + goto done; + if (glv_status == GLV_STOP) + break; + } + else if (v_type == VAR_BLOB) + { + if (get_lval_blob(lp, &var1, &var2, empty1, quiet) == FAIL) + goto done; + + break; + } + else if (v_type == VAR_LIST) + { + if (get_lval_list(lp, &var1, &var2, empty1, flags, quiet) == FAIL) + goto done; + } + else // v_type == VAR_CLASS || v_type == VAR_OBJECT + { + if (get_lval_class_or_obj(lp, key, p, v_type, cl_exec, flags, + quiet) == FAIL) + goto done; + } + + clear_tv(&var1); + clear_tv(&var2); + var1.v_type = VAR_UNKNOWN; + var2.v_type = VAR_UNKNOWN; + } - lp->ll_di = dict_find(lp->ll_dict, key, len); + rc = OK; - // When assigning to a scope dictionary check that a function and - // variable name is valid (only variable name unless it is l: or - // g: dictionary). Disallow overwriting a builtin function. - if (rettv != NULL && lp->ll_dict->dv_scope != 0) +done: + clear_tv(&var1); + clear_tv(&var2); + return rc == OK ? p : NULL; +} + +/* + * Get an lval: variable, Dict item or List item that can be assigned a value + * to: "name", "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", + * "name.key", "name.key[expr]" etc. + * Indexing only works if "name" is an existing List or Dictionary. + * "name" points to the start of the name. + * If "rettv" is not NULL it points to the value to be assigned. + * "unlet" is TRUE for ":unlet": slightly different behavior when something is + * wrong; must end in space or cmd separator. + * + * flags: + * GLV_QUIET: do not give error messages + * GLV_READ_ONLY: will not change the variable + * GLV_NO_AUTOLOAD: do not use script autoloading + * + * Returns a pointer to just after the name, including indexes. + * When an evaluation error occurs "lp->ll_name" is NULL; + * Returns NULL for a parsing error. Still need to free items in "lp"! + */ + char_u * +get_lval( + char_u *name, + typval_T *rettv, + lval_T *lp, + int unlet, + int skip, + int flags, // GLV_ values + int fne_flags) // flags for find_name_end() +{ + char_u *p; + char_u *expr_start, *expr_end; + int cc; + dictitem_T *v = NULL; + hashtab_T *ht = NULL; + int quiet = flags & GLV_QUIET; + int writing = 0; + int vim9script = in_vim9script(); + class_T *cl_exec = NULL; // class that is executing, or NULL. + +#ifdef LOG_LOCKVAR + if (lval_root == NULL) + ch_log(NULL, "LKVAR: get_lval(): name: %s, lval_root (nil)", name); + else + ch_log(NULL, "LKVAR: get_lval(): name: %s, lr_tv %p lr_is_arg %d", + name, (void*)lval_root->lr_tv, lval_root->lr_is_arg); + char buf[80]; + ch_log(NULL, "LKVAR: ...: GLV flags: %s", + flags_tostring(flags, glv_flag_strings, buf, sizeof(buf))); +#endif + + // Clear everything in "lp". + CLEAR_POINTER(lp); + + if (skip || (flags & GLV_COMPILING)) + { + // When skipping or compiling just find the end of the name. + lp->ll_name = name; + lp->ll_name_end = find_name_end(name, NULL, NULL, + FNE_INCL_BR | fne_flags); + return lp->ll_name_end; + } + + // Cannot use "s:var" at the Vim9 script level. "s: type" is OK. + if (vim9script && at_script_level() + && name[0] == 's' && name[1] == ':' && !VIM_ISWHITE(name[2])) + { + semsg(_(e_cannot_use_s_colon_in_vim9_script_str), name); + return NULL; + } + + // Find the end of the name. + p = find_name_end(name, &expr_start, &expr_end, fne_flags); + lp->ll_name_end = p; + if (expr_start != NULL) + { + // Don't expand the name when we already know there is an error. + if (unlet && !VIM_ISWHITE(*p) && !ends_excmd(*p) + && *p != '[' && *p != '.') + { + semsg(_(e_trailing_characters_str), p); + return NULL; + } + + lp->ll_exp_name = make_expanded_name(name, expr_start, expr_end, p); + if (lp->ll_exp_name == NULL) + { + // Report an invalid expression in braces, unless the + // expression evaluation has been cancelled due to an + // aborting error, an interrupt, or an exception. + if (!aborting() && !quiet) { - int prevval; + emsg_severe = TRUE; + semsg(_(e_invalid_argument_str), name); + return NULL; + } + } + lp->ll_name = lp->ll_exp_name; + } + else + { + lp->ll_name = name; - if (len != -1) + if (vim9script) + { + // "a: type" is declaring variable "a" with a type, not "a:". + // However, "g:[key]" is indexing a dictionary. + if (p == name + 2 && p[-1] == ':' && *p != '[') + { + --p; + lp->ll_name_end = p; + } + if (*skipwhite(p) == ':') + { + char_u *tp = skipwhite(p + 1); + + if (is_scoped_variable(name)) { - prevval = key[len]; - key[len] = NUL; + semsg(_(e_cannot_use_type_with_this_variable_str), name); + return NULL; } - else - prevval = 0; // avoid compiler warning - int wrong = (lp->ll_dict->dv_scope == VAR_DEF_SCOPE - && (rettv->v_type == VAR_FUNC - || rettv->v_type == VAR_PARTIAL) - && var_wrong_func_name(key, lp->ll_di == NULL)) - || !valid_varname(key, -1, TRUE); - if (len != -1) - key[len] = prevval; - if (wrong) + if (VIM_ISWHITE(*p)) { - clear_tv(&var1); + semsg(_(e_no_white_space_allowed_before_colon_str), p); return NULL; } - } - - if (lp->ll_valtype != NULL) - // use the type of the member - lp->ll_valtype = lp->ll_valtype->tt_member; - - if (lp->ll_di == NULL) - { - // Can't add "v:" or "a:" variable. - if (lp->ll_dict == get_vimvar_dict() - || &lp->ll_dict->dv_hashtab == get_funccal_args_ht()) + if (tp == p + 1 && !quiet) { - semsg(_(e_illegal_variable_name_str), name); - clear_tv(&var1); + semsg(_(e_white_space_required_after_str_str), ":", p); return NULL; } - - // Key does not exist in dict: may need to add it. - if (*p == '[' || *p == '.' || unlet) + if (!SCRIPT_ID_VALID(current_sctx.sc_sid)) { - if (!quiet) - semsg(_(e_key_not_present_in_dictionary_str), key); - clear_tv(&var1); + semsg(_(e_using_type_not_in_script_context_str), p); + return NULL; + } + if (vim9script && (flags & GLV_NO_DECL) && + !(flags & GLV_FOR_LOOP)) + { + // Using a type and not in a "var" declaration. + semsg(_(e_trailing_characters_str), p); return NULL; } - if (len == -1) - lp->ll_newkey = vim_strsave(key); - else - lp->ll_newkey = vim_strnsave(key, len); - clear_tv(&var1); - if (lp->ll_newkey == NULL) - p = NULL; - break; - } - // existing variable, need to check if it can be changed - else if ((flags & GLV_READ_ONLY) == 0 - && (var_check_ro(lp->ll_di->di_flags, name, FALSE) - || var_check_lock(lp->ll_di->di_flags, name, FALSE))) - { - clear_tv(&var1); - return NULL; - } - - clear_tv(&var1); - lp->ll_tv = &lp->ll_di->di_tv; - } - else if (v_type == VAR_BLOB) - { - long bloblen = blob_len(lp->ll_tv->vval.v_blob); - /* - * Get the number and item for the only or first index of the List. - */ - if (empty1) - lp->ll_n1 = 0; - else - // is number or string - lp->ll_n1 = (long)tv_get_number(&var1); - clear_tv(&var1); - if (check_blob_index(bloblen, lp->ll_n1, quiet) == FAIL) - { - clear_tv(&var2); - return NULL; - } - if (lp->ll_range && !lp->ll_empty2) - { - lp->ll_n2 = (long)tv_get_number(&var2); - clear_tv(&var2); - if (check_blob_range(bloblen, lp->ll_n1, lp->ll_n2, quiet) - == FAIL) + // parse the type after the name + lp->ll_type = parse_type(&tp, + &SCRIPT_ITEM(current_sctx.sc_sid)->sn_type_list, + !quiet); + if (lp->ll_type == NULL && !quiet) return NULL; + lp->ll_name_end = tp; } - lp->ll_blob = lp->ll_tv->vval.v_blob; - lp->ll_tv = NULL; - break; + // TODO: check inside class? } - else if (v_type == VAR_LIST) - { - /* - * Get the number and item for the only or first index of the List. - */ - if (empty1) - lp->ll_n1 = 0; - else - // is number or string - lp->ll_n1 = (long)tv_get_number(&var1); - clear_tv(&var1); + } + if (lp->ll_name == NULL) + return p; - lp->ll_dict = NULL; - lp->ll_object = NULL; - lp->ll_class = NULL; - lp->ll_list = lp->ll_tv->vval.v_list; - lp->ll_li = check_range_index_one(lp->ll_list, &lp->ll_n1, - (flags & GLV_ASSIGN_WITH_OP) == 0, quiet); - if (lp->ll_li == NULL) - { - clear_tv(&var2); + if (*p == '.') + { + imported_T *import = find_imported(lp->ll_name, p - lp->ll_name, TRUE); + if (import != NULL) + { + p++; // skip '.' + p = get_lval_imported(lp, import->imp_sid, p, &v, fne_flags); + if (p == NULL) return NULL; - } - - if (lp->ll_valtype != NULL) - // use the type of the member - lp->ll_valtype = lp->ll_valtype->tt_member; - - /* - * May need to find the item or absolute index for the second - * index of a range. - * When no index given: "lp->ll_empty2" is TRUE. - * Otherwise "lp->ll_n2" is set to the second index. - */ - if (lp->ll_range && !lp->ll_empty2) - { - lp->ll_n2 = (long)tv_get_number(&var2); - // is number or string - clear_tv(&var2); - if (check_range_index_two(lp->ll_list, - &lp->ll_n1, lp->ll_li, - &lp->ll_n2, quiet) == FAIL) - return NULL; - } - - lp->ll_tv = &lp->ll_li->li_tv; } - else // v_type == VAR_CLASS || v_type == VAR_OBJECT - { - lp->ll_dict = NULL; - lp->ll_list = NULL; + } - class_T *cl; - if (v_type == VAR_OBJECT) - { - if (lp->ll_tv->vval.v_object == NULL) - { - if (!quiet) - emsg(_(e_using_null_object)); - return NULL; - } - cl = lp->ll_tv->vval.v_object->obj_class; - lp->ll_object = lp->ll_tv->vval.v_object; - } - else - { - cl = lp->ll_tv->vval.v_class; - lp->ll_object = NULL; - } - lp->ll_class = cl; + // Without [idx] or .key we are done. + if (*p != '[' && *p != '.') + { + if (lval_root != NULL) + fill_lval_from_lval_root(lp, lval_root); + return p; + } - // TODO: what if class is NULL? - if (cl != NULL) - { - lp->ll_valtype = NULL; + if (vim9script && lval_root != NULL) + cl_exec = lval_root->lr_cl_exec; + if (vim9script && lval_root != NULL && lval_root->lr_tv != NULL) + { + // using local variable + lp->ll_tv = lval_root->lr_tv; + v = NULL; + } + else if (lp->ll_tv == NULL) + { + cc = *p; + *p = NUL; + // When we would write to the variable pass &ht and prevent autoload. + writing = !(flags & GLV_READ_ONLY); + v = find_var(lp->ll_name, writing ? &ht : NULL, + (flags & GLV_NO_AUTOLOAD) || writing); + if (v == NULL && !quiet) + semsg(_(e_undefined_variable_str), lp->ll_name); + *p = cc; + if (v == NULL) + return NULL; + lp->ll_tv = &v->di_tv; + } - if (flags & GLV_PREFER_FUNC) - { - // First look for a function with this name. - // round 1: class functions (skipped for an object) - // round 2: object methods - for (int round = v_type == VAR_OBJECT ? 2 : 1; - round <= 2; ++round) - { - int m_idx; - ufunc_T *fp; - - fp = method_lookup(cl, - round == 1 ? VAR_CLASS : VAR_OBJECT, - key, p - key, &m_idx); - lp->ll_oi = m_idx; - if (fp != NULL) - { - lp->ll_ufunc = fp; - lp->ll_valtype = fp->uf_func_type; - break; - } - } - } + if (vim9script && (flags & GLV_NO_DECL) == 0) + { + if (!quiet) + semsg(_(e_variable_already_declared_str), lp->ll_name); + return NULL; + } + + // If the next character is a "." or a "[", then process the subitem. + p = get_lval_subscript(lp, p, name, rettv, ht, v, unlet, flags, cl_exec); + if (p == NULL) + return NULL; - if (lp->ll_valtype == NULL) - { - int m_idx; - ocmember_T *om - = member_lookup(cl, v_type, key, p - key, &m_idx); - lp->ll_oi = m_idx; - if (om != NULL) - { - if (get_lval_check_access(cl_exec, cl, om, - p, flags) == FAIL) - return NULL; - - // When lhs is used to modify the variable, check it is - // not a read-only variable. - if ((flags & GLV_READ_ONLY) == 0 - && (*p != '.' && *p != '[') - && oc_var_check_ro(cl, om)) - return NULL; - - lp->ll_valtype = om->ocm_type; - - if (v_type == VAR_OBJECT) - lp->ll_tv = ((typval_T *)( - lp->ll_tv->vval.v_object + 1)) + m_idx; - else - lp->ll_tv = &cl->class_members_tv[m_idx]; - } - } + if (vim9script && lp->ll_valtype != NULL && rettv != NULL) + { + where_T where = WHERE_INIT; - if (lp->ll_valtype == NULL) - { - member_not_found_msg(cl, v_type, key, p - key); - return NULL; - } - } - } + // In a vim9 script, do type check and make sure the variable is + // writable. + if (check_typval_type(lp->ll_valtype, rettv, where) == FAIL) + return NULL; } - clear_tv(&var1); lp->ll_name_end = p; return p; } @@ -2085,159 +2354,238 @@ set_var_lval( } /* - * Handle "tv1 += tv2", "tv1 -= tv2", "tv1 *= tv2", "tv1 /= tv2", "tv1 %= tv2" - * and "tv1 .= tv2" + * Handle "blob1 += blob2". * Returns OK or FAIL. */ - int -tv_op(typval_T *tv1, typval_T *tv2, char_u *op) + static int +tv_op_blob(typval_T *tv1, typval_T *tv2, char_u *op) +{ + if (*op != '+' || tv2->v_type != VAR_BLOB) + return FAIL; + + // Blob += Blob + if (tv2->vval.v_blob == NULL) + return OK; + + if (tv1->vval.v_blob == NULL) + { + tv1->vval.v_blob = tv2->vval.v_blob; + ++tv1->vval.v_blob->bv_refcount; + return OK; + } + + blob_T *b1 = tv1->vval.v_blob; + blob_T *b2 = tv2->vval.v_blob; + int len = blob_len(b2); + + for (int i = 0; i < len; i++) + ga_append(&b1->bv_ga, blob_get(b2, i)); + + return OK; +} + +/* + * Handle "list1 += list2". + * Returns OK or FAIL. + */ + static int +tv_op_list(typval_T *tv1, typval_T *tv2, char_u *op) +{ + if (*op != '+' || tv2->v_type != VAR_LIST) + return FAIL; + + // List += List + if (tv2->vval.v_list == NULL) + return OK; + + if (tv1->vval.v_list == NULL) + { + tv1->vval.v_list = tv2->vval.v_list; + ++tv1->vval.v_list->lv_refcount; + } + else + list_extend(tv1->vval.v_list, tv2->vval.v_list, NULL); + + return OK; +} + +/* + * Handle number operations: + * nr += nr , nr -= nr , nr *=nr , nr /= nr , nr %= nr + * + * Returns OK or FAIL. + */ + static int +tv_op_number(typval_T *tv1, typval_T *tv2, char_u *op) { varnumber_T n; + int failed = FALSE; + + n = tv_get_number(tv1); + if (tv2->v_type == VAR_FLOAT) + { + float_T f = n; + + if (*op == '%') + return FAIL; + switch (*op) + { + case '+': f += tv2->vval.v_float; break; + case '-': f -= tv2->vval.v_float; break; + case '*': f *= tv2->vval.v_float; break; + case '/': f /= tv2->vval.v_float; break; + } + clear_tv(tv1); + tv1->v_type = VAR_FLOAT; + tv1->vval.v_float = f; + } + else + { + switch (*op) + { + case '+': n += tv_get_number(tv2); break; + case '-': n -= tv_get_number(tv2); break; + case '*': n *= tv_get_number(tv2); break; + case '/': n = num_divide(n, tv_get_number(tv2), &failed); break; + case '%': n = num_modulus(n, tv_get_number(tv2), &failed); break; + } + clear_tv(tv1); + tv1->v_type = VAR_NUMBER; + tv1->vval.v_number = n; + } + + return failed ? FAIL : OK; +} + +/* + * Handle "str1 .= str2" + * Returns OK or FAIL. + */ + static int +tv_op_string(typval_T *tv1, typval_T *tv2, char_u *op UNUSED) +{ char_u numbuf[NUMBUFLEN]; char_u *s; - int failed = FALSE; - // Can't do anything with a Funcref or Dict or Type on the right. + if (tv2->v_type == VAR_FLOAT) + return FAIL; + + // str .= str + s = tv_get_string(tv1); + s = concat_str(s, tv_get_string_buf(tv2, numbuf)); + clear_tv(tv1); + tv1->v_type = VAR_STRING; + tv1->vval.v_string = s; + + return OK; +} + +/* + * Handle "tv1 += tv2", "tv1 -= tv2", "tv1 *= tv2", "tv1 /= tv2", "tv1 %= tv2" + * and "tv1 .= tv2" + * Returns OK or FAIL. + */ + static int +tv_op_nr_or_string(typval_T *tv1, typval_T *tv2, char_u *op) +{ + if (tv2->v_type == VAR_LIST) + return FAIL; + + if (vim_strchr((char_u *)"+-*/%", *op) != NULL) + return tv_op_number(tv1, tv2, op); + + return tv_op_string(tv1, tv2, op); +} + +/* + * Handle "f1 += f2", "f1 -= f2", "f1 *= f2", "f1 /= f2". + * Returns OK or FAIL. + */ + static int +tv_op_float(typval_T *tv1, typval_T *tv2, char_u *op) +{ + float_T f; + + if (*op == '%' || *op == '.' + || (tv2->v_type != VAR_FLOAT + && tv2->v_type != VAR_NUMBER + && tv2->v_type != VAR_STRING)) + return FAIL; + + if (tv2->v_type == VAR_FLOAT) + f = tv2->vval.v_float; + else + f = tv_get_number(tv2); + switch (*op) + { + case '+': tv1->vval.v_float += f; break; + case '-': tv1->vval.v_float -= f; break; + case '*': tv1->vval.v_float *= f; break; + case '/': tv1->vval.v_float /= f; break; + } + + return OK; +} + +/* + * Handle "tv1 += tv2", "tv1 -= tv2", "tv1 *= tv2", "tv1 /= tv2", "tv1 %= tv2" + * and "tv1 .= tv2" + * Returns OK or FAIL. + */ + int +tv_op(typval_T *tv1, typval_T *tv2, char_u *op) +{ + // Can't do anything with a Funcref or Dict on the right. // v:true and friends only work with "..=". - if (tv2->v_type != VAR_FUNC && tv2->v_type != VAR_DICT - && tv2->v_type != VAR_CLASS && tv2->v_type != VAR_TYPEALIAS - && ((tv2->v_type != VAR_BOOL && tv2->v_type != VAR_SPECIAL) - || *op == '.')) - { - switch (tv1->v_type) - { - case VAR_UNKNOWN: - case VAR_ANY: - case VAR_VOID: - case VAR_DICT: - case VAR_FUNC: - case VAR_PARTIAL: - case VAR_BOOL: - case VAR_SPECIAL: - case VAR_JOB: - case VAR_CHANNEL: - case VAR_INSTR: - case VAR_OBJECT: - break; - case VAR_CLASS: - case VAR_TYPEALIAS: - check_typval_is_value(tv1); - return FAIL; + if (tv2->v_type == VAR_FUNC || tv2->v_type == VAR_DICT + || ((tv2->v_type == VAR_BOOL || tv2->v_type == VAR_SPECIAL) + && *op != '.')) + { + semsg(_(e_wrong_variable_type_for_str_equal), op); + return FAIL; + } - case VAR_BLOB: - if (*op != '+' || tv2->v_type != VAR_BLOB) - break; - // BLOB += BLOB - if (tv1->vval.v_blob != NULL && tv2->vval.v_blob != NULL) - { - blob_T *b1 = tv1->vval.v_blob; - blob_T *b2 = tv2->vval.v_blob; - int i, len = blob_len(b2); - for (i = 0; i < len; i++) - ga_append(&b1->bv_ga, blob_get(b2, i)); - } - return OK; + int retval = FAIL; + switch (tv1->v_type) + { + case VAR_UNKNOWN: + case VAR_ANY: + case VAR_VOID: + case VAR_DICT: + case VAR_FUNC: + case VAR_PARTIAL: + case VAR_BOOL: + case VAR_SPECIAL: + case VAR_JOB: + case VAR_CHANNEL: + case VAR_INSTR: + case VAR_OBJECT: + case VAR_CLASS: + case VAR_TYPEALIAS: + break; - case VAR_LIST: - if (*op != '+' || tv2->v_type != VAR_LIST) - break; - // List += List - if (tv2->vval.v_list != NULL) - { - if (tv1->vval.v_list == NULL) - { - tv1->vval.v_list = tv2->vval.v_list; - ++tv1->vval.v_list->lv_refcount; - } - else - list_extend(tv1->vval.v_list, tv2->vval.v_list, NULL); - } - return OK; + case VAR_BLOB: + retval = tv_op_blob(tv1, tv2, op); + break; - case VAR_NUMBER: - case VAR_STRING: - if (tv2->v_type == VAR_LIST) - break; - if (vim_strchr((char_u *)"+-*/%", *op) != NULL) - { - // nr += nr , nr -= nr , nr *=nr , nr /= nr , nr %= nr - n = tv_get_number(tv1); - if (tv2->v_type == VAR_FLOAT) - { - float_T f = n; - - if (*op == '%') - break; - switch (*op) - { - case '+': f += tv2->vval.v_float; break; - case '-': f -= tv2->vval.v_float; break; - case '*': f *= tv2->vval.v_float; break; - case '/': f /= tv2->vval.v_float; break; - } - clear_tv(tv1); - tv1->v_type = VAR_FLOAT; - tv1->vval.v_float = f; - } - else - { - switch (*op) - { - case '+': n += tv_get_number(tv2); break; - case '-': n -= tv_get_number(tv2); break; - case '*': n *= tv_get_number(tv2); break; - case '/': n = num_divide(n, tv_get_number(tv2), - &failed); break; - case '%': n = num_modulus(n, tv_get_number(tv2), - &failed); break; - } - clear_tv(tv1); - tv1->v_type = VAR_NUMBER; - tv1->vval.v_number = n; - } - } - else - { - if (tv2->v_type == VAR_FLOAT) - break; - - // str .= str - s = tv_get_string(tv1); - s = concat_str(s, tv_get_string_buf(tv2, numbuf)); - clear_tv(tv1); - tv1->v_type = VAR_STRING; - tv1->vval.v_string = s; - } - return failed ? FAIL : OK; + case VAR_LIST: + retval = tv_op_list(tv1, tv2, op); + break; - case VAR_FLOAT: - { - float_T f; - - if (*op == '%' || *op == '.' - || (tv2->v_type != VAR_FLOAT - && tv2->v_type != VAR_NUMBER - && tv2->v_type != VAR_STRING)) - break; - if (tv2->v_type == VAR_FLOAT) - f = tv2->vval.v_float; - else - f = tv_get_number(tv2); - switch (*op) - { - case '+': tv1->vval.v_float += f; break; - case '-': tv1->vval.v_float -= f; break; - case '*': tv1->vval.v_float *= f; break; - case '/': tv1->vval.v_float /= f; break; - } - } - return OK; - } + case VAR_NUMBER: + case VAR_STRING: + retval = tv_op_nr_or_string(tv1, tv2, op); + break; + + case VAR_FLOAT: + retval = tv_op_float(tv1, tv2, op); + break; } - if (check_typval_is_value(tv2) == OK) + if (retval != OK) semsg(_(e_wrong_variable_type_for_str_equal), op); - return FAIL; + + return retval; } /* @@ -2714,7 +3062,7 @@ newline_skip_comments(char_u *arg) char_u *nl = vim_strchr(p, NL); if (nl == NULL) - break; + break; p = nl; } if (*p != NL) @@ -3598,6 +3946,40 @@ eval_addlist(typval_T *tv1, typval_T *tv2) return OK; } +/* + * Left or right shift the number "tv1" by the number "tv2" and store the + * result in "tv1". + * + * Return OK or FAIL. + */ + static int +eval_shift_number(typval_T *tv1, typval_T *tv2, int shift_type) +{ + if (tv2->v_type != VAR_NUMBER || tv2->vval.v_number < 0) + { + // right operand should be a positive number + if (tv2->v_type != VAR_NUMBER) + emsg(_(e_bitshift_ops_must_be_number)); + else + emsg(_(e_bitshift_ops_must_be_positive)); + clear_tv(tv1); + clear_tv(tv2); + return FAIL; + } + + if (tv2->vval.v_number > MAX_LSHIFT_BITS) + // shifting more bits than we have always results in zero + tv1->vval.v_number = 0; + else if (shift_type == EXPR_LSHIFT) + tv1->vval.v_number = + (uvarnumber_T)tv1->vval.v_number << tv2->vval.v_number; + else + tv1->vval.v_number = + (uvarnumber_T)tv1->vval.v_number >> tv2->vval.v_number; + + return OK; +} + /* * Handle the bitwise left/right shift operator expression: * var1 << var2 @@ -3624,16 +4006,16 @@ eval5(char_u **arg, typval_T *rettv, evalarg_T *evalarg) { char_u *p; int getnext; - exprtype_T type; + exprtype_T exprtype; int evaluate; typval_T var2; int vim9script; p = eval_next_non_blank(*arg, evalarg, &getnext); if (p[0] == '<' && p[1] == '<') - type = EXPR_LSHIFT; + exprtype = EXPR_LSHIFT; else if (p[0] == '>' && p[1] == '>') - type = EXPR_RSHIFT; + exprtype = EXPR_RSHIFT; else return OK; @@ -3678,30 +4060,125 @@ eval5(char_u **arg, typval_T *rettv, evalarg_T *evalarg) if (evaluate) { - if (var2.v_type != VAR_NUMBER || var2.vval.v_number < 0) - { - // right operand should be a positive number - if (var2.v_type != VAR_NUMBER) - emsg(_(e_bitshift_ops_must_be_number)); - else - emsg(_(e_bitshift_ops_must_be_positive)); - clear_tv(rettv); - clear_tv(&var2); + if (eval_shift_number(rettv, &var2, exprtype) == FAIL) return FAIL; - } + } - if (var2.vval.v_number > MAX_LSHIFT_BITS) - // shifting more bits than we have always results in zero - rettv->vval.v_number = 0; - else if (type == EXPR_LSHIFT) - rettv->vval.v_number = - (uvarnumber_T)rettv->vval.v_number << var2.vval.v_number; - else - rettv->vval.v_number = - (uvarnumber_T)rettv->vval.v_number >> var2.vval.v_number; + clear_tv(&var2); + } + + return OK; +} + +/* + * Concatenate strings "tv1" and "tv2" and store the result in "tv1". + */ + static int +eval_concat_str(typval_T *tv1, typval_T *tv2) +{ + char_u buf1[NUMBUFLEN], buf2[NUMBUFLEN]; + char_u *s1 = tv_get_string_buf(tv1, buf1); + char_u *s2 = NULL; + char_u *p; + int vim9script = in_vim9script(); + + if (vim9script && (tv2->v_type == VAR_VOID + || tv2->v_type == VAR_CHANNEL + || tv2->v_type == VAR_JOB)) + semsg(_(e_using_invalid_value_as_string_str), + vartype_name(tv2->v_type)); + else if (vim9script && tv2->v_type == VAR_FLOAT) + { + vim_snprintf((char *)buf2, NUMBUFLEN, "%g", + tv2->vval.v_float); + s2 = buf2; + } + else + s2 = tv_get_string_buf_chk(tv2, buf2); + if (s2 == NULL) // type error ? + { + clear_tv(tv1); + clear_tv(tv2); + return FAIL; + } + + p = concat_str(s1, s2); + clear_tv(tv1); + tv1->v_type = VAR_STRING; + tv1->vval.v_string = p; + + return OK; +} + +/* + * Add or subtract numbers "tv1" and "tv2" and store the result in "tv1". + * The numbers can be whole numbers or floats. + */ + static int +eval_addsub_number(typval_T *tv1, typval_T *tv2, int op) +{ + int error = FALSE; + varnumber_T n1, n2; + float_T f1 = 0, f2 = 0; + + if (tv1->v_type == VAR_FLOAT) + { + f1 = tv1->vval.v_float; + n1 = 0; + } + else + { + n1 = tv_get_number_chk(tv1, &error); + if (error) + { + // This can only happen for "list + non-list" or + // "blob + non-blob". For "non-list + ..." or + // "something - ...", we returned before evaluating the + // 2nd operand. + clear_tv(tv1); + clear_tv(tv2); + return FAIL; + } + if (tv2->v_type == VAR_FLOAT) + f1 = n1; + } + if (tv2->v_type == VAR_FLOAT) + { + f2 = tv2->vval.v_float; + n2 = 0; + } + else + { + n2 = tv_get_number_chk(tv2, &error); + if (error) + { + clear_tv(tv1); + clear_tv(tv2); + return FAIL; } + if (tv1->v_type == VAR_FLOAT) + f2 = n2; + } + clear_tv(tv1); - clear_tv(&var2); + // If there is a float on either side the result is a float. + if (tv1->v_type == VAR_FLOAT || tv2->v_type == VAR_FLOAT) + { + if (op == '+') + f1 = f1 + f2; + else + f1 = f1 - f2; + tv1->v_type = VAR_FLOAT; + tv1->vval.v_float = f1; + } + else + { + if (op == '+') + n1 = n1 + n2; + else + n1 = n1 - n2; + tv1->v_type = VAR_NUMBER; + tv1->vval.v_number = n1; } return OK; @@ -3814,33 +4291,8 @@ eval6(char_u **arg, typval_T *rettv, evalarg_T *evalarg) */ if (op == '.') { - char_u buf1[NUMBUFLEN], buf2[NUMBUFLEN]; - char_u *s1 = tv_get_string_buf(rettv, buf1); - char_u *s2 = NULL; - - if (vim9script && (var2.v_type == VAR_VOID - || var2.v_type == VAR_CHANNEL - || var2.v_type == VAR_JOB)) - semsg(_(e_using_invalid_value_as_string_str), - vartype_name(var2.v_type)); - else if (vim9script && var2.v_type == VAR_FLOAT) - { - vim_snprintf((char *)buf2, NUMBUFLEN, "%g", - var2.vval.v_float); - s2 = buf2; - } - else - s2 = tv_get_string_buf_chk(&var2, buf2); - if (s2 == NULL) // type error ? - { - clear_tv(rettv); - clear_tv(&var2); + if (eval_concat_str(rettv, &var2) == FAIL) return FAIL; - } - p = concat_str(s1, s2); - clear_tv(rettv); - rettv->v_type = VAR_STRING; - rettv->vval.v_string = p; } else if (op == '+' && rettv->v_type == VAR_BLOB && var2.v_type == VAR_BLOB) @@ -3853,73 +4305,119 @@ eval6(char_u **arg, typval_T *rettv, evalarg_T *evalarg) } else { - int error = FALSE; - varnumber_T n1, n2; - float_T f1 = 0, f2 = 0; + if (eval_addsub_number(rettv, &var2, op) == FAIL) + return FAIL; + } + clear_tv(&var2); + } + } + return OK; +} - if (rettv->v_type == VAR_FLOAT) - { - f1 = rettv->vval.v_float; - n1 = 0; - } - else - { - n1 = tv_get_number_chk(rettv, &error); - if (error) - { - // This can only happen for "list + non-list" or - // "blob + non-blob". For "non-list + ..." or - // "something - ...", we returned before evaluating the - // 2nd operand. - clear_tv(rettv); - clear_tv(&var2); - return FAIL; - } - if (var2.v_type == VAR_FLOAT) - f1 = n1; - } - if (var2.v_type == VAR_FLOAT) - { - f2 = var2.vval.v_float; - n2 = 0; - } - else - { - n2 = tv_get_number_chk(&var2, &error); - if (error) - { - clear_tv(rettv); - clear_tv(&var2); - return FAIL; - } - if (rettv->v_type == VAR_FLOAT) - f2 = n2; - } - clear_tv(rettv); +/* + * Multiply or divide or compute the modulo of numbers "tv1" and "tv2" and + * store the result in "tv1". The numbers can be whole numbers or floats. + */ + static int +eval_multdiv_number(typval_T *tv1, typval_T *tv2, int op) +{ + varnumber_T n1, n2; + float_T f1, f2; + int error; + int use_float = FALSE; - // If there is a float on either side the result is a float. - if (rettv->v_type == VAR_FLOAT || var2.v_type == VAR_FLOAT) - { - if (op == '+') - f1 = f1 + f2; - else - f1 = f1 - f2; - rettv->v_type = VAR_FLOAT; - rettv->vval.v_float = f1; - } + f1 = 0; + f2 = 0; + error = FALSE; + if (tv1->v_type == VAR_FLOAT) + { + f1 = tv1->vval.v_float; + use_float = TRUE; + n1 = 0; + } + else + n1 = tv_get_number_chk(tv1, &error); + clear_tv(tv1); + if (error) + { + clear_tv(tv2); + return FAIL; + } + + if (tv2->v_type == VAR_FLOAT) + { + if (!use_float) + { + f1 = n1; + use_float = TRUE; + } + f2 = tv2->vval.v_float; + n2 = 0; + } + else + { + n2 = tv_get_number_chk(tv2, &error); + clear_tv(tv2); + if (error) + return FAIL; + if (use_float) + f2 = n2; + } + + /* + * Compute the result. + * When either side is a float the result is a float. + */ + if (use_float) + { + if (op == '*') + f1 = f1 * f2; + else if (op == '/') + { +#ifdef VMS + // VMS crashes on divide by zero, work around it + if (f2 == 0.0) + { + if (f1 == 0) + f1 = -1 * __F_FLT_MAX - 1L; // similar to NaN + else if (f1 < 0) + f1 = -1 * __F_FLT_MAX; else - { - if (op == '+') - n1 = n1 + n2; - else - n1 = n1 - n2; - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = n1; - } + f1 = __F_FLT_MAX; } - clear_tv(&var2); + else + f1 = f1 / f2; +#else + // We rely on the floating point library to handle divide + // by zero to result in "inf" and not a crash. + f1 = f1 / f2; +#endif + } + else + { + emsg(_(e_cannot_use_percent_with_float)); + return FAIL; } + tv1->v_type = VAR_FLOAT; + tv1->vval.v_float = f1; + } + else + { + int failed = FALSE; + + if (op == '*') + n1 = n1 * n2; + else if (op == '/') + n1 = num_divide(n1, n2, &failed); + else + n1 = num_modulus(n1, n2, &failed); + if (failed) + return FAIL; + + tv1->v_type = VAR_NUMBER; + tv1->vval.v_number = n1; } + return OK; } @@ -3941,8 +4439,6 @@ eval7( evalarg_T *evalarg, int want_string) // after "." operator { - int use_float = FALSE; - /* * Get the first expression. */ @@ -3959,9 +4455,6 @@ eval7( typval_T var2; char_u *p; int op; - varnumber_T n1, n2; - float_T f1, f2; - int error; // "*=", "/=" and "%=" are assignments p = eval_next_non_blank(*arg, evalarg, &getnext); @@ -3983,26 +4476,6 @@ eval7( *arg = p; } - f1 = 0; - f2 = 0; - error = FALSE; - if (evaluate) - { - if (rettv->v_type == VAR_FLOAT) - { - f1 = rettv->vval.v_float; - use_float = TRUE; - n1 = 0; - } - else - n1 = tv_get_number_chk(rettv, &error); - clear_tv(rettv); - if (error) - return FAIL; - } - else - n1 = 0; - /* * Get the second variable. */ @@ -4017,81 +4490,9 @@ eval7( return FAIL; if (evaluate) - { - if (var2.v_type == VAR_FLOAT) - { - if (!use_float) - { - f1 = n1; - use_float = TRUE; - } - f2 = var2.vval.v_float; - n2 = 0; - } - else - { - n2 = tv_get_number_chk(&var2, &error); - clear_tv(&var2); - if (error) - return FAIL; - if (use_float) - f2 = n2; - } - - /* - * Compute the result. - * When either side is a float the result is a float. - */ - if (use_float) - { - if (op == '*') - f1 = f1 * f2; - else if (op == '/') - { -#ifdef VMS - // VMS crashes on divide by zero, work around it - if (f2 == 0.0) - { - if (f1 == 0) - f1 = -1 * __F_FLT_MAX - 1L; // similar to NaN - else if (f1 < 0) - f1 = -1 * __F_FLT_MAX; - else - f1 = __F_FLT_MAX; - } - else - f1 = f1 / f2; -#else - // We rely on the floating point library to handle divide - // by zero to result in "inf" and not a crash. - f1 = f1 / f2; -#endif - } - else - { - emsg(_(e_cannot_use_percent_with_float)); - return FAIL; - } - rettv->v_type = VAR_FLOAT; - rettv->vval.v_float = f1; - } - else - { - int failed = FALSE; - - if (op == '*') - n1 = n1 * n2; - else if (op == '/') - n1 = num_divide(n1, n2, &failed); - else - n1 = num_modulus(n1, n2, &failed); - if (failed) - return FAIL; - - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = n1; - } - } + // Compute the result. + if (eval_multdiv_number(rettv, &var2, op) == FAIL) + return FAIL; } return OK; @@ -4244,18 +4645,21 @@ handle_predefined(char_u *s, int len, typval_T *rettv) case 9: if (STRNCMP(s, "null_", 5) != 0) break; + // null_list if (STRNCMP(s + 5, "list", 4) == 0) { rettv->v_type = VAR_LIST; rettv->vval.v_list = NULL; return OK; } + // null_dict if (STRNCMP(s + 5, "dict", 4) == 0) { rettv->v_type = VAR_DICT; rettv->vval.v_dict = NULL; return OK; } + // null_blob if (STRNCMP(s + 5, "blob", 4) == 0) { rettv->v_type = VAR_BLOB; @@ -4313,6 +4717,158 @@ handle_predefined(char_u *s, int len, typval_T *rettv) return FAIL; } +/* + * Handle register contents: @r. + */ + static void +eval9_reg_contents( + char_u **arg, + typval_T *rettv, + int evaluate) +{ + int vim9script = in_vim9script(); + + ++*arg; // skip '@' + if (evaluate) + { + if (vim9script && IS_WHITE_OR_NUL(**arg)) + semsg(_(e_syntax_error_at_str), *arg); + else if (vim9script && !valid_yank_reg(**arg, FALSE)) + emsg_invreg(**arg); + else + { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = get_reg_contents(**arg, + GREG_EXPR_SRC); + } + } + if (**arg != NUL) + ++*arg; +} + +/* + * Handle a nested expression: (expression) or lambda: (arg) => expr + */ + static int +eval9_nested_expr( + char_u **arg, + typval_T *rettv, + evalarg_T *evalarg, + int evaluate) +{ + int ret = NOTDONE; + int vim9script = in_vim9script(); + + if (vim9script) + { + ret = get_lambda_tv(arg, rettv, TRUE, evalarg); + if (ret == OK && evaluate) + { + ufunc_T *ufunc = rettv->vval.v_partial->pt_func; + + // Compile it here to get the return type. The return + // type is optional, when it's missing use t_unknown. + // This is recognized in compile_return(). + if (ufunc->uf_ret_type->tt_type == VAR_VOID) + ufunc->uf_ret_type = &t_unknown; + if (compile_def_function(ufunc, FALSE, + get_compile_type(ufunc), NULL) == FAIL) + { + clear_tv(rettv); + ret = FAIL; + } + } + } + if (ret == NOTDONE) + { + *arg = skipwhite_and_linebreak(*arg + 1, evalarg); + ret = eval1(arg, rettv, evalarg); // recursive! + + *arg = skipwhite_and_linebreak(*arg, evalarg); + if (**arg == ')') + ++*arg; + else if (ret == OK) + { + emsg(_(e_missing_closing_paren)); + clear_tv(rettv); + ret = FAIL; + } + } + + return ret; +} + +/* +* Handle be a variable or function name. +* Can also be a curly-braces kind of name: {expr}. +*/ + static int +eval9_var_func_name( + char_u **arg, + typval_T *rettv, + evalarg_T *evalarg, + int evaluate, + char_u **name_start) +{ + char_u *s; + int len; + char_u *alias; + int ret = OK; + int vim9script = in_vim9script(); + + s = *arg; + len = get_name_len(arg, &alias, evaluate, TRUE); + if (alias != NULL) + s = alias; + + if (len <= 0) + ret = FAIL; + else + { + int flags = evalarg == NULL ? 0 : evalarg->eval_flags; + + if (evaluate && vim9script && len == 1 && *s == '_') + { + emsg(_(e_cannot_use_underscore_here)); + ret = FAIL; + } + else if (evaluate && vim9script && len > 2 + && s[0] == 's' && s[1] == ':') + { + semsg(_(e_cannot_use_s_colon_in_vim9_script_str), s); + ret = FAIL; + } + else if ((vim9script ? **arg : *skipwhite(*arg)) == '(') + { + // "name(..." recursive! + *arg = skipwhite(*arg); + ret = eval_func(arg, evalarg, s, len, rettv, flags, NULL); + } + else if (evaluate) + { + // get the value of "true", "false", etc. or a variable + ret = FAIL; + if (vim9script) + ret = handle_predefined(s, len, rettv); + if (ret == FAIL) + { + *name_start = s; + ret = eval_variable(s, len, 0, rettv, NULL, + EVAL_VAR_VERBOSE + EVAL_VAR_IMPORT); + } + } + else + { + // skip the name + check_vars(s, len); + ret = OK; + } + } + vim_free(alias); + + return ret; +} + /* * Handle sixth level expression: * number number constant @@ -4352,12 +4908,9 @@ eval9( { int evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE); - int len; - char_u *s; char_u *name_start = NULL; char_u *start_leader, *end_leader; int ret = OK; - char_u *alias; static int recurse = 0; int vim9script = in_vim9script(); @@ -4439,20 +4992,10 @@ eval9( case '[': ret = eval_list(arg, rettv, evalarg, TRUE); break; - /* - * Dictionary: #{key: val, key: val} - */ - case '#': if (vim9script) - { - ret = vim9_bad_comment(*arg) ? FAIL : NOTDONE; - } - else if ((*arg)[1] == '{') - { - ++*arg; - ret = eval_dict(arg, rettv, evalarg, TRUE); - } - else - ret = NOTDONE; + /* + * Literal Dictionary: #{key: val, key: val} + */ + case '#': ret = eval_lit_dict(arg, rettv, evalarg); break; /* @@ -4486,64 +5029,14 @@ eval9( /* * Register contents: @r. */ - case '@': ++*arg; - if (evaluate) - { - if (vim9script && IS_WHITE_OR_NUL(**arg)) - semsg(_(e_syntax_error_at_str), *arg); - else if (vim9script && !valid_yank_reg(**arg, FALSE)) - emsg_invreg(**arg); - else - { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = get_reg_contents(**arg, - GREG_EXPR_SRC); - } - } - if (**arg != NUL) - ++*arg; + case '@': eval9_reg_contents(arg, rettv, evaluate); break; /* * nested expression: (expression). * or lambda: (arg) => expr */ - case '(': ret = NOTDONE; - if (vim9script) - { - ret = get_lambda_tv(arg, rettv, TRUE, evalarg); - if (ret == OK && evaluate) - { - ufunc_T *ufunc = rettv->vval.v_partial->pt_func; - - // Compile it here to get the return type. The return - // type is optional, when it's missing use t_unknown. - // This is recognized in compile_return(). - if (ufunc->uf_ret_type->tt_type == VAR_VOID) - ufunc->uf_ret_type = &t_unknown; - if (compile_def_function(ufunc, FALSE, - get_compile_type(ufunc), NULL) == FAIL) - { - clear_tv(rettv); - ret = FAIL; - } - } - } - if (ret == NOTDONE) - { - *arg = skipwhite_and_linebreak(*arg + 1, evalarg); - ret = eval1(arg, rettv, evalarg); // recursive! - - *arg = skipwhite_and_linebreak(*arg, evalarg); - if (**arg == ')') - ++*arg; - else if (ret == OK) - { - emsg(_(e_missing_closing_paren)); - clear_tv(rettv); - ret = FAIL; - } - } + case '(': ret = eval9_nested_expr(arg, rettv, evalarg, evaluate); break; default: ret = NOTDONE; @@ -4556,55 +5049,7 @@ eval9( * Must be a variable or function name. * Can also be a curly-braces kind of name: {expr}. */ - s = *arg; - len = get_name_len(arg, &alias, evaluate, TRUE); - if (alias != NULL) - s = alias; - - if (len <= 0) - ret = FAIL; - else - { - int flags = evalarg == NULL ? 0 : evalarg->eval_flags; - - if (evaluate && vim9script && len == 1 && *s == '_') - { - emsg(_(e_cannot_use_underscore_here)); - ret = FAIL; - } - else if (evaluate && vim9script && len > 2 - && s[0] == 's' && s[1] == ':') - { - semsg(_(e_cannot_use_s_colon_in_vim9_script_str), s); - ret = FAIL; - } - else if ((vim9script ? **arg : *skipwhite(*arg)) == '(') - { - // "name(..." recursive! - *arg = skipwhite(*arg); - ret = eval_func(arg, evalarg, s, len, rettv, flags, NULL); - } - else if (evaluate) - { - // get the value of "true", "false", etc. or a variable - ret = FAIL; - if (vim9script) - ret = handle_predefined(s, len, rettv); - if (ret == FAIL) - { - name_start = s; - ret = eval_variable(s, len, 0, rettv, NULL, - EVAL_VAR_VERBOSE + EVAL_VAR_IMPORT); - } - } - else - { - // skip the name - check_vars(s, len); - ret = OK; - } - } - vim_free(alias); + ret = eval9_var_func_name(arg, rettv, evalarg, evaluate, &name_start); } // Handle following '[', '(' and '.' for expr[expr], expr.name, @@ -4895,7 +5340,7 @@ eval_method( { *arg = name; - // Truncate the name a the "(". Avoid trying to get another line + // Truncate the name at the "(". Avoid trying to get another line // by making "getline" NULL. *paren = NUL; char_u *(*getline)(int, void *, int, getline_opt_T) = NULL; @@ -4950,6 +5395,9 @@ eval_method( clear_tv(&base); vim_free(tofree); + if (alias != NULL) + vim_free(alias); + return ret; } @@ -5152,30 +5600,6 @@ check_can_index(typval_T *rettv, int evaluate, int verbose) return OK; } -/* - * slice() function - */ - void -f_slice(typval_T *argvars, typval_T *rettv) -{ - if (in_vim9script() - && ((argvars[0].v_type != VAR_STRING - && argvars[0].v_type != VAR_LIST - && argvars[0].v_type != VAR_BLOB - && check_for_list_arg(argvars, 0) == FAIL) - || check_for_number_arg(argvars, 1) == FAIL - || check_for_opt_number_arg(argvars, 2) == FAIL)) - return; - - if (check_can_index(argvars, TRUE, FALSE) != OK) - return; - - copy_tv(argvars, rettv); - eval_index_inner(rettv, TRUE, argvars + 1, - argvars[2].v_type == VAR_UNKNOWN ? NULL : argvars + 2, - TRUE, NULL, 0, FALSE); -} - /* * Apply index or range to "rettv". * "var1" is the first index, NULL for [:expr]. @@ -5386,803 +5810,339 @@ partial_free(partial_T *pt) loopvars_check_refcount(pt->pt_loopvars[i]); } - vim_free(pt); -} - -/* - * Unreference a closure: decrement the reference count and free it when it - * becomes zero. - */ - void -partial_unref(partial_T *pt) -{ - if (pt == NULL) - return; - - int done = FALSE; - - if (--pt->pt_refcount <= 0) - partial_free(pt); - - // If the reference count goes down to one, the funcstack may be the - // only reference and can be freed if no other partials reference it. - else if (pt->pt_refcount == 1) - { - // careful: if the funcstack is freed it may contain this partial - // and it gets freed as well - if (pt->pt_funcstack != NULL) - done = funcstack_check_refcount(pt->pt_funcstack); - - if (!done) - { - int depth; - - for (depth = 0; depth < MAX_LOOP_DEPTH; ++depth) - if (pt->pt_loopvars[depth] != NULL - && loopvars_check_refcount(pt->pt_loopvars[depth])) - break; - } - } -} - -/* - * Return the next (unique) copy ID. - * Used for serializing nested structures. - */ - int -get_copyID(void) -{ - current_copyID += COPYID_INC; - return current_copyID; -} - -/* - * Garbage collection for lists and dictionaries. - * - * We use reference counts to be able to free most items right away when they - * are no longer used. But for composite items it's possible that it becomes - * unused while the reference count is > 0: When there is a recursive - * reference. Example: - * :let l = [1, 2, 3] - * :let d = {9: l} - * :let l[1] = d - * - * Since this is quite unusual we handle this with garbage collection: every - * once in a while find out which lists and dicts are not referenced from any - * variable. - * - * Here is a good reference text about garbage collection (refers to Python - * but it applies to all reference-counting mechanisms): - * http://python.ca/nas/python/gc/ - */ - -/* - * Do garbage collection for lists and dicts. - * When "testing" is TRUE this is called from test_garbagecollect_now(). - * Return TRUE if some memory was freed. - */ - int -garbage_collect(int testing) -{ - int copyID; - int abort = FALSE; - buf_T *buf; - win_T *wp; - int did_free = FALSE; - tabpage_T *tp; - - if (!testing) - { - // Only do this once. - want_garbage_collect = FALSE; - may_garbage_collect = FALSE; - garbage_collect_at_exit = FALSE; - } - - // The execution stack can grow big, limit the size. - if (exestack.ga_maxlen - exestack.ga_len > 500) - { - size_t new_len; - char_u *pp; - int n; - - // Keep 150% of the current size, with a minimum of the growth size. - n = exestack.ga_len / 2; - if (n < exestack.ga_growsize) - n = exestack.ga_growsize; - - // Don't make it bigger though. - if (exestack.ga_len + n < exestack.ga_maxlen) - { - new_len = (size_t)exestack.ga_itemsize * (exestack.ga_len + n); - pp = vim_realloc(exestack.ga_data, new_len); - if (pp == NULL) - return FAIL; - exestack.ga_maxlen = exestack.ga_len + n; - exestack.ga_data = pp; - } - } - - // We advance by two because we add one for items referenced through - // previous_funccal. - copyID = get_copyID(); - - /* - * 1. Go through all accessible variables and mark all lists and dicts - * with copyID. - */ - - // Don't free variables in the previous_funccal list unless they are only - // referenced through previous_funccal. This must be first, because if - // the item is referenced elsewhere the funccal must not be freed. - abort = abort || set_ref_in_previous_funccal(copyID); - - // script-local variables - abort = abort || garbage_collect_scriptvars(copyID); - - // buffer-local variables - FOR_ALL_BUFFERS(buf) - abort = abort || set_ref_in_item(&buf->b_bufvar.di_tv, copyID, - NULL, NULL); - - // window-local variables - FOR_ALL_TAB_WINDOWS(tp, wp) - abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, - NULL, NULL); - // window-local variables in autocmd windows - for (int i = 0; i < AUCMD_WIN_COUNT; ++i) - if (aucmd_win[i].auc_win != NULL) - abort = abort || set_ref_in_item( - &aucmd_win[i].auc_win->w_winvar.di_tv, copyID, NULL, NULL); -#ifdef FEAT_PROP_POPUP - FOR_ALL_POPUPWINS(wp) - abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, - NULL, NULL); - FOR_ALL_TABPAGES(tp) - FOR_ALL_POPUPWINS_IN_TAB(tp, wp) - abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, - NULL, NULL); -#endif - - // tabpage-local variables - FOR_ALL_TABPAGES(tp) - abort = abort || set_ref_in_item(&tp->tp_winvar.di_tv, copyID, - NULL, NULL); - // global variables - abort = abort || garbage_collect_globvars(copyID); - - // function-local variables - abort = abort || set_ref_in_call_stack(copyID); - - // named functions (matters for closures) - abort = abort || set_ref_in_functions(copyID); - - // function call arguments, if v:testing is set. - abort = abort || set_ref_in_func_args(copyID); - - // funcstacks keep variables for closures - abort = abort || set_ref_in_funcstacks(copyID); - - // loopvars keep variables for loop blocks - abort = abort || set_ref_in_loopvars(copyID); - - // v: vars - abort = abort || garbage_collect_vimvars(copyID); - - // callbacks in buffers - abort = abort || set_ref_in_buffers(copyID); - - // 'completefunc', 'omnifunc' and 'thesaurusfunc' callbacks - abort = abort || set_ref_in_insexpand_funcs(copyID); - - // 'operatorfunc' callback - abort = abort || set_ref_in_opfunc(copyID); - - // 'tagfunc' callback - abort = abort || set_ref_in_tagfunc(copyID); - - // 'imactivatefunc' and 'imstatusfunc' callbacks - abort = abort || set_ref_in_im_funcs(copyID); - -#ifdef FEAT_LUA - abort = abort || set_ref_in_lua(copyID); -#endif - -#ifdef FEAT_PYTHON - abort = abort || set_ref_in_python(copyID); -#endif - -#ifdef FEAT_PYTHON3 - abort = abort || set_ref_in_python3(copyID); -#endif - -#ifdef FEAT_JOB_CHANNEL - abort = abort || set_ref_in_channel(copyID); - abort = abort || set_ref_in_job(copyID); -#endif -#ifdef FEAT_NETBEANS_INTG - abort = abort || set_ref_in_nb_channel(copyID); -#endif - -#ifdef FEAT_TIMERS - abort = abort || set_ref_in_timer(copyID); -#endif - -#ifdef FEAT_QUICKFIX - abort = abort || set_ref_in_quickfix(copyID); -#endif - -#ifdef FEAT_TERMINAL - abort = abort || set_ref_in_term(copyID); -#endif - -#ifdef FEAT_PROP_POPUP - abort = abort || set_ref_in_popups(copyID); -#endif - - abort = abort || set_ref_in_classes(copyID); - - if (!abort) - { - /* - * 2. Free lists and dictionaries that are not referenced. - */ - did_free = free_unref_items(copyID); - - /* - * 3. Check if any funccal can be freed now. - * This may call us back recursively. - */ - free_unref_funccal(copyID, testing); - } - else if (p_verbose > 0) - { - verb_msg(_("Not enough memory to set references, garbage collection aborted!")); - } - - return did_free; -} - -/* - * Free lists, dictionaries, channels and jobs that are no longer referenced. - */ - static int -free_unref_items(int copyID) -{ - int did_free = FALSE; - - // Let all "free" functions know that we are here. This means no - // dictionaries, lists, channels or jobs are to be freed, because we will - // do that here. - in_free_unref_items = TRUE; - - /* - * PASS 1: free the contents of the items. We don't free the items - * themselves yet, so that it is possible to decrement refcount counters - */ - - // Go through the list of dicts and free items without this copyID. - did_free |= dict_free_nonref(copyID); - - // Go through the list of lists and free items without this copyID. - did_free |= list_free_nonref(copyID); - - // Go through the list of objects and free items without this copyID. - did_free |= object_free_nonref(copyID); - - // Go through the list of classes and free items without this copyID. - did_free |= class_free_nonref(copyID); - -#ifdef FEAT_JOB_CHANNEL - // Go through the list of jobs and free items without the copyID. This - // must happen before doing channels, because jobs refer to channels, but - // the reference from the channel to the job isn't tracked. - did_free |= free_unused_jobs_contents(copyID, COPYID_MASK); - - // Go through the list of channels and free items without the copyID. - did_free |= free_unused_channels_contents(copyID, COPYID_MASK); -#endif - - /* - * PASS 2: free the items themselves. - */ - object_free_items(copyID); - dict_free_items(copyID); - list_free_items(copyID); - -#ifdef FEAT_JOB_CHANNEL - // Go through the list of jobs and free items without the copyID. This - // must happen before doing channels, because jobs refer to channels, but - // the reference from the channel to the job isn't tracked. - free_unused_jobs(copyID, COPYID_MASK); - - // Go through the list of channels and free items without the copyID. - free_unused_channels(copyID, COPYID_MASK); -#endif - - in_free_unref_items = FALSE; - - return did_free; -} - -/* - * Mark all lists and dicts referenced through hashtab "ht" with "copyID". - * "list_stack" is used to add lists to be marked. Can be NULL. - * - * Returns TRUE if setting references failed somehow. - */ - int -set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack) -{ - int todo; - int abort = FALSE; - hashitem_T *hi; - hashtab_T *cur_ht; - ht_stack_T *ht_stack = NULL; - ht_stack_T *tempitem; - - cur_ht = ht; - for (;;) - { - if (!abort) - { - // Mark each item in the hashtab. If the item contains a hashtab - // it is added to ht_stack, if it contains a list it is added to - // list_stack. - todo = (int)cur_ht->ht_used; - FOR_ALL_HASHTAB_ITEMS(cur_ht, hi, todo) - if (!HASHITEM_EMPTY(hi)) - { - --todo; - abort = abort || set_ref_in_item(&HI2DI(hi)->di_tv, copyID, - &ht_stack, list_stack); - } - } - - if (ht_stack == NULL) - break; - - // take an item from the stack - cur_ht = ht_stack->ht; - tempitem = ht_stack; - ht_stack = ht_stack->prev; - free(tempitem); - } - - return abort; -} - -#if defined(FEAT_LUA) || defined(FEAT_PYTHON) || defined(FEAT_PYTHON3) \ - || defined(PROTO) -/* - * Mark a dict and its items with "copyID". - * Returns TRUE if setting references failed somehow. - */ - int -set_ref_in_dict(dict_T *d, int copyID) -{ - if (d != NULL && d->dv_copyID != copyID) - { - d->dv_copyID = copyID; - return set_ref_in_ht(&d->dv_hashtab, copyID, NULL); - } - return FALSE; -} -#endif - -/* - * Mark a list and its items with "copyID". - * Returns TRUE if setting references failed somehow. - */ - int -set_ref_in_list(list_T *ll, int copyID) -{ - if (ll != NULL && ll->lv_copyID != copyID) - { - ll->lv_copyID = copyID; - return set_ref_in_list_items(ll, copyID, NULL); - } - return FALSE; -} - -/* - * Mark all lists and dicts referenced through list "l" with "copyID". - * "ht_stack" is used to add hashtabs to be marked. Can be NULL. - * - * Returns TRUE if setting references failed somehow. - */ - int -set_ref_in_list_items(list_T *l, int copyID, ht_stack_T **ht_stack) -{ - listitem_T *li; - int abort = FALSE; - list_T *cur_l; - list_stack_T *list_stack = NULL; - list_stack_T *tempitem; - - cur_l = l; - for (;;) - { - if (!abort && cur_l->lv_first != &range_list_item) - // Mark each item in the list. If the item contains a hashtab - // it is added to ht_stack, if it contains a list it is added to - // list_stack. - for (li = cur_l->lv_first; !abort && li != NULL; li = li->li_next) - abort = abort || set_ref_in_item(&li->li_tv, copyID, - ht_stack, &list_stack); - if (list_stack == NULL) - break; - - // take an item from the stack - cur_l = list_stack->list; - tempitem = list_stack; - list_stack = list_stack->prev; - free(tempitem); - } - - return abort; -} - -/* - * Mark the partial in callback 'cb' with "copyID". - */ - int -set_ref_in_callback(callback_T *cb, int copyID) -{ - typval_T tv; - - if (cb->cb_name == NULL || *cb->cb_name == NUL || cb->cb_partial == NULL) - return FALSE; - - tv.v_type = VAR_PARTIAL; - tv.vval.v_partial = cb->cb_partial; - return set_ref_in_item(&tv, copyID, NULL, NULL); + vim_free(pt); } /* - * Mark the dict "dd" with "copyID". - * Also see set_ref_in_item(). + * Unreference a closure: decrement the reference count and free it when it + * becomes zero. */ - static int -set_ref_in_item_dict( - dict_T *dd, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) + void +partial_unref(partial_T *pt) { - if (dd == NULL || dd->dv_copyID == copyID) - return FALSE; + if (pt == NULL) + return; + + int done = FALSE; - // Didn't see this dict yet. - dd->dv_copyID = copyID; - if (ht_stack == NULL) - return set_ref_in_ht(&dd->dv_hashtab, copyID, list_stack); + if (--pt->pt_refcount <= 0) + partial_free(pt); - ht_stack_T *newitem = ALLOC_ONE(ht_stack_T); - if (newitem == NULL) - return TRUE; + // If the reference count goes down to one, the funcstack may be the + // only reference and can be freed if no other partials reference it. + else if (pt->pt_refcount == 1) + { + // careful: if the funcstack is freed it may contain this partial + // and it gets freed as well + if (pt->pt_funcstack != NULL) + done = funcstack_check_refcount(pt->pt_funcstack); - newitem->ht = &dd->dv_hashtab; - newitem->prev = *ht_stack; - *ht_stack = newitem; + if (!done) + { + int depth; - return FALSE; + for (depth = 0; depth < MAX_LOOP_DEPTH; ++depth) + if (pt->pt_loopvars[depth] != NULL + && loopvars_check_refcount(pt->pt_loopvars[depth])) + break; + } + } } /* - * Mark the list "ll" with "copyID". - * Also see set_ref_in_item(). + * Return a textual representation of a string in "tv". + * If the memory is allocated "tofree" is set to it, otherwise NULL. + * When both "echo_style" and "composite_val" are FALSE, put quotes around + * strings as "string()", otherwise does not put quotes around strings. + * May return NULL. */ - static int -set_ref_in_item_list( - list_T *ll, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) + static char_u * +string_tv2string( + typval_T *tv, + char_u **tofree, + int echo_style, + int composite_val) { - if (ll == NULL || ll->lv_copyID == copyID) - return FALSE; - - // Didn't see this list yet. - ll->lv_copyID = copyID; - if (list_stack == NULL) - return set_ref_in_list_items(ll, copyID, ht_stack); - - list_stack_T *newitem = ALLOC_ONE(list_stack_T); - if (newitem == NULL) - return TRUE; + char_u *r = NULL; - newitem->list = ll; - newitem->prev = *list_stack; - *list_stack = newitem; + if (echo_style && !composite_val) + { + *tofree = NULL; + r = tv->vval.v_string; + if (r == NULL) + r = (char_u *)""; + } + else + { + *tofree = string_quote(tv->vval.v_string, FALSE); + r = *tofree; + } - return FALSE; + return r; } /* - * Mark the partial "pt" with "copyID". - * Also see set_ref_in_item(). + * Return a textual representation of a function in "tv". + * If the memory is allocated "tofree" is set to it, otherwise NULL. + * When "echo_style" is FALSE, put quotes around the function name as + * "function()", otherwise does not put quotes around function name. + * May return NULL. */ - static int -set_ref_in_item_partial( - partial_T *pt, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) + static char_u * +func_tv2string(typval_T *tv, char_u **tofree, int echo_style) { - if (pt == NULL || pt->pt_copyID == copyID) - return FALSE; - - // Didn't see this partial yet. - pt->pt_copyID = copyID; - - int abort = set_ref_in_func(pt->pt_name, pt->pt_func, copyID); + char_u *r = NULL; + char_u buf[MAX_FUNC_NAME_LEN]; - if (pt->pt_dict != NULL) + if (echo_style) { - typval_T dtv; + *tofree = NULL; - dtv.v_type = VAR_DICT; - dtv.vval.v_dict = pt->pt_dict; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + if (tv->vval.v_string == NULL) + r = (char_u *)"function()"; + else + { + r = make_ufunc_name_readable(tv->vval.v_string, buf, + MAX_FUNC_NAME_LEN); + if (r == buf) + r = *tofree = vim_strsave(buf); + } } - - if (pt->pt_obj != NULL) + else { - typval_T objtv; + char_u *s = NULL; - objtv.v_type = VAR_OBJECT; - objtv.vval.v_object = pt->pt_obj; - set_ref_in_item(&objtv, copyID, ht_stack, list_stack); - } + if (tv->vval.v_string != NULL) + s = make_ufunc_name_readable(tv->vval.v_string, buf, + MAX_FUNC_NAME_LEN); - for (int i = 0; i < pt->pt_argc; ++i) - abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID, - ht_stack, list_stack); - // pt_funcstack is handled in set_ref_in_funcstacks() - // pt_loopvars is handled in set_ref_in_loopvars() + r = *tofree = string_quote(s, TRUE); + } - return abort; + return r; } -#ifdef FEAT_JOB_CHANNEL /* - * Mark the job "pt" with "copyID". - * Also see set_ref_in_item(). + * Return a textual representation of a partial in "tv". + * If the memory is allocated "tofree" is set to it, otherwise NULL. + * "numbuf" is used for a number. May return NULL. */ - static int -set_ref_in_item_job( - job_T *job, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) + static char_u * +partial_tv2string( + typval_T *tv, + char_u **tofree, + char_u *numbuf, + int copyID) { - typval_T dtv; + char_u *r = NULL; + partial_T *pt; + char_u *fname; + garray_T ga; + int i; + char_u *tf; - if (job == NULL || job->jv_copyID == copyID) - return FALSE; + pt = tv->vval.v_partial; + fname = string_quote(pt == NULL ? NULL : partial_name(pt), FALSE); - job->jv_copyID = copyID; - if (job->jv_channel != NULL) + ga_init2(&ga, 1, 100); + ga_concat(&ga, (char_u *)"function("); + if (fname != NULL) + { + // When using uf_name prepend "g:" for a global function. + if (pt != NULL && pt->pt_name == NULL && fname[0] == '\'' + && vim_isupper(fname[1])) + { + ga_concat(&ga, (char_u *)"'g:"); + ga_concat(&ga, fname + 1); + } + else + ga_concat(&ga, fname); + vim_free(fname); + } + if (pt != NULL && pt->pt_argc > 0) { - dtv.v_type = VAR_CHANNEL; - dtv.vval.v_channel = job->jv_channel; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + ga_concat(&ga, (char_u *)", ["); + for (i = 0; i < pt->pt_argc; ++i) + { + if (i > 0) + ga_concat(&ga, (char_u *)", "); + ga_concat(&ga, tv2string(&pt->pt_argv[i], &tf, numbuf, copyID)); + vim_free(tf); + } + ga_concat(&ga, (char_u *)"]"); } - if (job->jv_exit_cb.cb_partial != NULL) + if (pt != NULL && pt->pt_dict != NULL) { - dtv.v_type = VAR_PARTIAL; - dtv.vval.v_partial = job->jv_exit_cb.cb_partial; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + typval_T dtv; + + ga_concat(&ga, (char_u *)", "); + dtv.v_type = VAR_DICT; + dtv.vval.v_dict = pt->pt_dict; + ga_concat(&ga, tv2string(&dtv, &tf, numbuf, copyID)); + vim_free(tf); } + // terminate with ')' and a NUL + ga_concat_len(&ga, (char_u *)")", 2); + + *tofree = ga.ga_data; + r = *tofree; - return FALSE; + return r; } /* - * Mark the channel "ch" with "copyID". - * Also see set_ref_in_item(). + * Return a textual representation of a List in "tv". + * If the memory is allocated "tofree" is set to it, otherwise NULL. + * When "copyID" is not zero replace recursive lists with "...". When + * "restore_copyID" is FALSE, repeated items in lists are replaced with "...". + * May return NULL. */ - static int -set_ref_in_item_channel( - channel_T *ch, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) + static char_u * +list_tv2string( + typval_T *tv, + char_u **tofree, + int copyID, + int restore_copyID) { - typval_T dtv; - - if (ch == NULL || ch->ch_copyID == copyID) - return FALSE; + char_u *r = NULL; - ch->ch_copyID = copyID; - for (ch_part_T part = PART_SOCK; part < PART_COUNT; ++part) + if (tv->vval.v_list == NULL) { - for (jsonq_T *jq = ch->ch_part[part].ch_json_head.jq_next; - jq != NULL; jq = jq->jq_next) - set_ref_in_item(jq->jq_value, copyID, ht_stack, list_stack); - for (cbq_T *cq = ch->ch_part[part].ch_cb_head.cq_next; cq != NULL; - cq = cq->cq_next) - if (cq->cq_callback.cb_partial != NULL) - { - dtv.v_type = VAR_PARTIAL; - dtv.vval.v_partial = cq->cq_callback.cb_partial; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); - } - if (ch->ch_part[part].ch_callback.cb_partial != NULL) - { - dtv.v_type = VAR_PARTIAL; - dtv.vval.v_partial = ch->ch_part[part].ch_callback.cb_partial; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); - } + // NULL list is equivalent to empty list. + *tofree = NULL; + r = (char_u *)"[]"; } - if (ch->ch_callback.cb_partial != NULL) + else if (copyID != 0 && tv->vval.v_list->lv_copyID == copyID + && tv->vval.v_list->lv_len > 0) { - dtv.v_type = VAR_PARTIAL; - dtv.vval.v_partial = ch->ch_callback.cb_partial; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + *tofree = NULL; + r = (char_u *)"[...]"; } - if (ch->ch_close_cb.cb_partial != NULL) + else { - dtv.v_type = VAR_PARTIAL; - dtv.vval.v_partial = ch->ch_close_cb.cb_partial; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + int old_copyID; + if (restore_copyID) + old_copyID = tv->vval.v_list->lv_copyID; + + tv->vval.v_list->lv_copyID = copyID; + *tofree = list2string(tv, copyID, restore_copyID); + if (restore_copyID) + tv->vval.v_list->lv_copyID = old_copyID; + r = *tofree; } - return FALSE; + return r; } -#endif /* - * Mark the class "cl" with "copyID". - * Also see set_ref_in_item(). + * Return a textual representation of a Dict in "tv". + * If the memory is allocated "tofree" is set to it, otherwise NULL. + * When "copyID" is not zero replace recursive dicts with "...". + * When "restore_copyID" is FALSE, repeated items in the dictionary are + * replaced with "...". May return NULL. */ - int -set_ref_in_item_class( - class_T *cl, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) + static char_u * +dict_tv2string( + typval_T *tv, + char_u **tofree, + int copyID, + int restore_copyID) { - int abort = FALSE; - - if (cl == NULL || cl->class_copyID == copyID) - return FALSE; + char_u *r = NULL; - cl->class_copyID = copyID; - if (cl->class_members_tv != NULL) + if (tv->vval.v_dict == NULL) { - // The "class_members_tv" table is allocated only for regular classes - // and not for interfaces. - for (int i = 0; !abort && i < cl->class_class_member_count; ++i) - abort = abort || set_ref_in_item( - &cl->class_members_tv[i], - copyID, ht_stack, list_stack); + // NULL dict is equivalent to empty dict. + *tofree = NULL; + r = (char_u *)"{}"; } + else if (copyID != 0 && tv->vval.v_dict->dv_copyID == copyID + && tv->vval.v_dict->dv_hashtab.ht_used != 0) + { + *tofree = NULL; + r = (char_u *)"{...}"; + } + else + { + int old_copyID; + if (restore_copyID) + old_copyID = tv->vval.v_dict->dv_copyID; - for (int i = 0; !abort && i < cl->class_class_function_count; ++i) - abort = abort || set_ref_in_func(NULL, - cl->class_class_functions[i], copyID); - - for (int i = 0; !abort && i < cl->class_obj_method_count; ++i) - abort = abort || set_ref_in_func(NULL, - cl->class_obj_methods[i], copyID); + tv->vval.v_dict->dv_copyID = copyID; + *tofree = dict2string(tv, copyID, restore_copyID); + if (restore_copyID) + tv->vval.v_dict->dv_copyID = old_copyID; + r = *tofree; + } - return abort; + return r; } /* - * Mark the object "cl" with "copyID". - * Also see set_ref_in_item(). + * Return a textual representation of a job or a channel in "tv". + * If the memory is allocated "tofree" is set to it, otherwise NULL. + * "numbuf" is used for a number. + * When "composite_val" is FALSE, put quotes around strings as "string()", + * otherwise does not put quotes around strings. + * May return NULL. */ - static int -set_ref_in_item_object( - object_T *obj, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) + static char_u * +jobchan_tv2string( + typval_T *tv, + char_u **tofree, + char_u *numbuf, + int composite_val) { - int abort = FALSE; + char_u *r = NULL; - if (obj == NULL || obj->obj_copyID == copyID) - return FALSE; +#ifdef FEAT_JOB_CHANNEL + *tofree = NULL; - obj->obj_copyID = copyID; + if (tv->v_type == VAR_JOB) + r = job_to_string_buf(tv, numbuf); + else + r = channel_to_string_buf(tv, numbuf); - // The typval_T array is right after the object_T. - typval_T *mtv = (typval_T *)(obj + 1); - for (int i = 0; !abort - && i < obj->obj_class->class_obj_member_count; ++i) - abort = abort || set_ref_in_item(mtv + i, copyID, - ht_stack, list_stack); + if (composite_val) + { + *tofree = string_quote(r, FALSE); + r = *tofree; + } +#endif - return abort; + return r; } /* - * Mark all lists, dicts and other container types referenced through typval - * "tv" with "copyID". - * "list_stack" is used to add lists to be marked. Can be NULL. - * "ht_stack" is used to add hashtabs to be marked. Can be NULL. - * - * Returns TRUE if setting references failed somehow. + * Return a textual representation of a class in "tv". + * If the memory is allocated "tofree" is set to it, otherwise NULL. + * May return NULL. */ - int -set_ref_in_item( - typval_T *tv, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) + static char_u * +class_tv2string(typval_T *tv, char_u **tofree) { - int abort = FALSE; - - switch (tv->v_type) - { - case VAR_DICT: - return set_ref_in_item_dict(tv->vval.v_dict, copyID, - ht_stack, list_stack); - - case VAR_LIST: - return set_ref_in_item_list(tv->vval.v_list, copyID, - ht_stack, list_stack); - - case VAR_FUNC: - { - abort = set_ref_in_func(tv->vval.v_string, NULL, copyID); - break; - } - - case VAR_PARTIAL: - return set_ref_in_item_partial(tv->vval.v_partial, copyID, - ht_stack, list_stack); - - case VAR_JOB: -#ifdef FEAT_JOB_CHANNEL - return set_ref_in_item_job(tv->vval.v_job, copyID, - ht_stack, list_stack); -#else - break; -#endif - - case VAR_CHANNEL: -#ifdef FEAT_JOB_CHANNEL - return set_ref_in_item_channel(tv->vval.v_channel, copyID, - ht_stack, list_stack); -#else - break; -#endif - - case VAR_CLASS: - return set_ref_in_item_class(tv->vval.v_class, copyID, - ht_stack, list_stack); - - case VAR_OBJECT: - return set_ref_in_item_object(tv->vval.v_object, copyID, - ht_stack, list_stack); - - case VAR_UNKNOWN: - case VAR_ANY: - case VAR_VOID: - case VAR_BOOL: - case VAR_SPECIAL: - case VAR_NUMBER: - case VAR_FLOAT: - case VAR_STRING: - case VAR_BLOB: - case VAR_TYPEALIAS: - case VAR_INSTR: - // Types that do not contain any other item - break; - } + char_u *r = NULL; + class_T *cl = tv->vval.v_class; + char *s = "class"; + + if (cl != NULL && IS_INTERFACE(cl)) + s = "interface"; + else if (cl != NULL && IS_ENUM(cl)) + s = "enum"; + size_t len = STRLEN(s) + 1 + + (cl == NULL ? 9 : STRLEN(cl->class_name)) + 1; + r = *tofree = alloc(len); + vim_snprintf((char *)r, len, "%s %s", s, + cl == NULL ? "[unknown]" : (char *)cl->class_name); - return abort; + return r; } /* * Return a string with the string representation of a variable. * If the memory is allocated "tofree" is set to it, otherwise NULL. * "numbuf" is used for a number. - * When "copyID" is not NULL replace recursive lists and dicts with "...". + * When "copyID" is not zero replace recursive lists and dicts with "...". * When both "echo_style" and "composite_val" are FALSE, put quotes around * strings as "string()", otherwise does not put quotes around strings, as * ":echo" displays values. @@ -6221,155 +6181,27 @@ echo_string_core( switch (tv->v_type) { case VAR_STRING: - if (echo_style && !composite_val) - { - *tofree = NULL; - r = tv->vval.v_string; - if (r == NULL) - r = (char_u *)""; - } - else - { - *tofree = string_quote(tv->vval.v_string, FALSE); - r = *tofree; - } + r = string_tv2string(tv, tofree, echo_style, composite_val); break; case VAR_FUNC: - { - char_u buf[MAX_FUNC_NAME_LEN]; - - if (echo_style) - { - r = tv->vval.v_string == NULL ? (char_u *)"function()" - : make_ufunc_name_readable(tv->vval.v_string, - buf, MAX_FUNC_NAME_LEN); - if (r == buf) - { - r = vim_strsave(buf); - *tofree = r; - } - else - *tofree = NULL; - } - else - { - *tofree = string_quote(tv->vval.v_string == NULL ? NULL - : make_ufunc_name_readable( - tv->vval.v_string, buf, MAX_FUNC_NAME_LEN), - TRUE); - r = *tofree; - } - } + r = func_tv2string(tv, tofree, echo_style); break; case VAR_PARTIAL: - { - partial_T *pt = tv->vval.v_partial; - char_u *fname = string_quote(pt == NULL ? NULL - : partial_name(pt), FALSE); - garray_T ga; - int i; - char_u *tf; - - ga_init2(&ga, 1, 100); - ga_concat(&ga, (char_u *)"function("); - if (fname != NULL) - { - // When using uf_name prepend "g:" for a global function. - if (pt != NULL && pt->pt_name == NULL && fname[0] == '\'' - && vim_isupper(fname[1])) - { - ga_concat(&ga, (char_u *)"'g:"); - ga_concat(&ga, fname + 1); - } - else - ga_concat(&ga, fname); - vim_free(fname); - } - if (pt != NULL && pt->pt_argc > 0) - { - ga_concat(&ga, (char_u *)", ["); - for (i = 0; i < pt->pt_argc; ++i) - { - if (i > 0) - ga_concat(&ga, (char_u *)", "); - ga_concat(&ga, - tv2string(&pt->pt_argv[i], &tf, numbuf, copyID)); - vim_free(tf); - } - ga_concat(&ga, (char_u *)"]"); - } - if (pt != NULL && pt->pt_dict != NULL) - { - typval_T dtv; - - ga_concat(&ga, (char_u *)", "); - dtv.v_type = VAR_DICT; - dtv.vval.v_dict = pt->pt_dict; - ga_concat(&ga, tv2string(&dtv, &tf, numbuf, copyID)); - vim_free(tf); - } - // terminate with ')' and a NUL - ga_concat_len(&ga, (char_u *)")", 2); - - *tofree = ga.ga_data; - r = *tofree; - break; - } + r = partial_tv2string(tv, tofree, numbuf, copyID); + break; case VAR_BLOB: r = blob2string(tv->vval.v_blob, tofree, numbuf); break; case VAR_LIST: - if (tv->vval.v_list == NULL) - { - // NULL list is equivalent to empty list. - *tofree = NULL; - r = (char_u *)"[]"; - } - else if (copyID != 0 && tv->vval.v_list->lv_copyID == copyID - && tv->vval.v_list->lv_len > 0) - { - *tofree = NULL; - r = (char_u *)"[...]"; - } - else - { - int old_copyID = tv->vval.v_list->lv_copyID; - - tv->vval.v_list->lv_copyID = copyID; - *tofree = list2string(tv, copyID, restore_copyID); - if (restore_copyID) - tv->vval.v_list->lv_copyID = old_copyID; - r = *tofree; - } + r = list_tv2string(tv, tofree, copyID, restore_copyID); break; case VAR_DICT: - if (tv->vval.v_dict == NULL) - { - // NULL dict is equivalent to empty dict. - *tofree = NULL; - r = (char_u *)"{}"; - } - else if (copyID != 0 && tv->vval.v_dict->dv_copyID == copyID - && tv->vval.v_dict->dv_hashtab.ht_used != 0) - { - *tofree = NULL; - r = (char_u *)"{...}"; - } - else - { - int old_copyID = tv->vval.v_dict->dv_copyID; - - tv->vval.v_dict->dv_copyID = copyID; - *tofree = dict2string(tv, copyID, restore_copyID); - if (restore_copyID) - tv->vval.v_dict->dv_copyID = old_copyID; - r = *tofree; - } + r = dict_tv2string(tv, tofree, copyID, restore_copyID); break; case VAR_NUMBER: @@ -6382,16 +6214,7 @@ echo_string_core( case VAR_JOB: case VAR_CHANNEL: -#ifdef FEAT_JOB_CHANNEL - *tofree = NULL; - r = tv->v_type == VAR_JOB ? job_to_string_buf(tv, numbuf) - : channel_to_string_buf(tv, numbuf); - if (composite_val) - { - *tofree = string_quote(r, FALSE); - r = *tofree; - } -#endif + r = jobchan_tv2string(tv, tofree, numbuf, composite_val); break; case VAR_INSTR: @@ -6400,23 +6223,11 @@ echo_string_core( break; case VAR_CLASS: - { - class_T *cl = tv->vval.v_class; - char *s = "class"; - if (IS_INTERFACE(cl)) - s = "interface"; - else if (IS_ENUM(cl)) - s = "enum"; - size_t len = STRLEN(s) + 1 + - (cl == NULL ? 9 : STRLEN(cl->class_name)) + 1; - r = *tofree = alloc(len); - vim_snprintf((char *)r, len, "%s %s", s, - cl == NULL ? "[unknown]" : (char *)cl->class_name); - } + r = class_tv2string(tv, tofree); break; case VAR_OBJECT: - *tofree = r = object_string(tv->vval.v_object, numbuf, copyID, + *tofree = r = object2string(tv->vval.v_object, numbuf, copyID, echo_style, restore_copyID, composite_val); break; @@ -6451,7 +6262,7 @@ echo_string_core( * If the memory is allocated "tofree" is set to it, otherwise NULL. * "numbuf" is used for a number. * Does not put quotes around strings, as ":echo" displays values. - * When "copyID" is not NULL replace recursive lists and dicts with "...". + * When "copyID" is not zero replace recursive lists and dicts with "...". * May return NULL. */ char_u * -- cgit v1.2.3