Mercurial > vim
changeset 35291:02fe9a889b04 v9.1.0450
patch 9.1.0450: evalc. code too complex
Commit: https://github.com/vim/vim/commit/51c45e89b50a4841147b9fbd7c6095ab79a10c71
Author: Yegappan Lakshmanan <yegappan@yahoo.com>
Date: Thu May 30 07:50:08 2024 +0200
patch 9.1.0450: evalc. code too complex
Problem: eval.c code too complex
Solution: refactor eval6() and eval9() functions into several smaller
functions (Yegappan Lakshmanan)
closes: #14875
Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Thu, 30 May 2024 08:00:07 +0200 |
parents | 7fdc42388d71 |
children | 2ee0f1da194f |
files | src/dict.c src/eval.c src/proto/dict.pro src/version.c |
diffstat | 4 files changed, 429 insertions(+), 294 deletions(-) [+] |
line wrap: on
line diff
--- a/src/dict.c +++ b/src/dict.c @@ -1092,6 +1092,33 @@ failret: } /* + * Evaluate a literal dictionary: #{key: val, key: val} + * "*arg" points to the "#". + * On return, "*arg" points to the character after the Dict. + * Return OK or FAIL. Returns NOTDONE for {expr}. + */ + int +eval_lit_dict(char_u **arg, typval_T *rettv, evalarg_T *evalarg) +{ + int vim9script = in_vim9script(); + int ret = OK; + + if (vim9script) + { + ret = vim9_bad_comment(*arg) ? FAIL : NOTDONE; + } + else if ((*arg)[1] == '{') + { + ++*arg; + ret = eval_dict(arg, rettv, evalarg, TRUE); + } + else + ret = NOTDONE; + + return ret; +} + +/* * Go over all entries in "d2" and add them to "d1". * When "action" is "error" then a duplicate key is an error. * When "action" is "force" then a duplicate key is overwritten.
--- a/src/eval.c +++ b/src/eval.c @@ -1299,8 +1299,8 @@ typedef enum { */ static int get_lval_dict_item( + lval_T *lp, char_u *name, - lval_T *lp, char_u *key, int len, char_u **key_end, @@ -1513,24 +1513,107 @@ get_lval_list( } /* - * Get a Class or Object lval variable that can be assigned a value to: - * "name", "name.key", "name.key[expr]" etc. + * 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; + } + } +} + +/* + * 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. * - * 'cl_exec' is the class that is executing, or NULL. 'v_type' is VAR_CLASS or - * VAR_OBJECT. 'key' points to the member variable name and 'key_end' points - * to the character after 'key'. If 'quiet' is TRUE, then error messages - * are not displayed for invalid indexes. + * 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( - class_T *cl_exec, - vartype_T v_type, lval_T *lp, char_u *key, char_u *key_end, + vartype_T v_type, + class_T *cl_exec, int flags, int quiet) { @@ -1556,69 +1639,27 @@ get_lval_class_or_obj( } lp->ll_class = cl; - // TODO: what if class is NULL? - if (cl != NULL) - { - lp->ll_valtype = NULL; - - 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, 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_valtype == NULL) - { - int m_idx; - ocmember_T *om - = member_lookup(cl, v_type, key, key_end - key, &m_idx); - lp->ll_oi = m_idx; - if (om != NULL) - { - 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]; - } - } - - if (lp->ll_valtype == NULL) - { - member_not_found_msg(cl, v_type, key, key_end - key); + 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 (get_lval_oc_variable(lp, cl, key, key_end, v_type, cl_exec, flags) + == FAIL) return FAIL; - } + } + + if (lp->ll_valtype == NULL) + { + member_not_found_msg(cl, v_type, key, key_end - key); + return FAIL; } return OK; @@ -1861,7 +1902,7 @@ get_lval_subscript( { glv_status_T glv_status; - glv_status = get_lval_dict_item(name, lp, key, len, &p, &var1, + glv_status = get_lval_dict_item(lp, name, key, len, &p, &var1, flags, unlet, rettv); if (glv_status == GLV_FAIL) goto done; @@ -1882,8 +1923,8 @@ get_lval_subscript( } else // v_type == VAR_CLASS || v_type == VAR_OBJECT { - if (get_lval_class_or_obj(cl_exec, v_type, lp, key, p, flags, - quiet) == FAIL) + if (get_lval_class_or_obj(lp, key, p, v_type, cl_exec, flags, + quiet) == FAIL) goto done; } @@ -4015,6 +4056,120 @@ eval5(char_u **arg, typval_T *rettv, eva } /* + * 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_num(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); + + // 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; +} + +/* * Handle fifth level expression: * + number addition, concatenation of list or blob * - number subtraction @@ -4121,33 +4276,8 @@ eval6(char_u **arg, typval_T *rettv, eva */ 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) @@ -4160,69 +4290,8 @@ eval6(char_u **arg, typval_T *rettv, eva } else { - int error = FALSE; - varnumber_T n1, n2; - float_T f1 = 0, f2 = 0; - - 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); - - // 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; - } - else - { - if (op == '+') - n1 = n1 + n2; - else - n1 = n1 - n2; - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = n1; - } + if (eval_addsub_num(rettv, &var2, op) == FAIL) + return FAIL; } clear_tv(&var2); } @@ -4624,6 +4693,158 @@ handle_predefined(char_u *s, int len, ty } /* + * 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 * 0zFFFFFFFF Blob constant @@ -4662,12 +4883,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(); @@ -4750,19 +4968,9 @@ eval9( break; /* - * Dictionary: #{key: val, key: val} + * Literal 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; + case '#': ret = eval_lit_dict(arg, rettv, evalarg); break; /* @@ -4796,64 +5004,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; @@ -4866,55 +5024,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, @@ -5761,32 +5871,27 @@ func_tv2string(typval_T *tv, char_u **to if (echo_style) { + *tofree = NULL; + if (tv->vval.v_string == NULL) - { r = (char_u *)"function()"; - *tofree = NULL; - } else { r = 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; + r = *tofree = vim_strsave(buf); } } else { - if (tv->vval.v_string == NULL) - *tofree = string_quote(NULL, TRUE); - else - *tofree = string_quote(make_ufunc_name_readable(tv->vval.v_string, - buf, MAX_FUNC_NAME_LEN), TRUE); - r = *tofree; + char_u *s = NULL; + + if (tv->vval.v_string != NULL) + s = make_ufunc_name_readable(tv->vval.v_string, buf, + MAX_FUNC_NAME_LEN); + + r = *tofree = string_quote(s, TRUE); } return r;
--- a/src/proto/dict.pro +++ b/src/proto/dict.pro @@ -37,6 +37,7 @@ varnumber_T dict_get_bool(dict_T *d, cha char_u *dict2string(typval_T *tv, int copyID, int restore_copyID); char_u *get_literal_key(char_u **arg); int eval_dict(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int literal); +int eval_lit_dict(char_u **arg, typval_T *rettv, evalarg_T *evalarg); void dict_extend(dict_T *d1, dict_T *d2, char_u *action, char *func_name); dictitem_T *dict_lookup(hashitem_T *hi); int dict_equal(dict_T *d1, dict_T *d2, int ic, int recursive);