To: vim_dev@googlegroups.com Subject: Patch 8.2.0708 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.0708 Problem: Vim9: constant expressions are not simplified. Solution: Simplify string concatenation. Files: src/vim9compile.c, src/testdir/test_vim9_disassemble.vim, src/testdir/test_vim9_expr.vim *** ../vim-8.2.0707/src/vim9compile.c 2020-05-06 21:06:26.421435642 +0200 --- src/vim9compile.c 2020-05-07 16:33:42.968965021 +0200 *************** *** 1041,1046 **** --- 1041,1083 ---- } /* + * Generate a PUSH instruction for "tv". + */ + static int + generate_tv_PUSH(cctx_T *cctx, typval_T *tv) + { + switch (tv->v_type) + { + case VAR_BOOL: + generate_PUSHBOOL(cctx, tv->vval.v_number); + break; + case VAR_SPECIAL: + generate_PUSHSPEC(cctx, tv->vval.v_number); + break; + case VAR_NUMBER: + generate_PUSHNR(cctx, tv->vval.v_number); + break; + #ifdef FEAT_FLOAT + case VAR_FLOAT: + generate_PUSHF(cctx, tv->vval.v_float); + break; + #endif + case VAR_BLOB: + generate_PUSHBLOB(cctx, tv->vval.v_blob); + tv->vval.v_blob = NULL; + break; + case VAR_STRING: + generate_PUSHS(cctx, tv->vval.v_string); + tv->vval.v_string = NULL; + break; + default: + iemsg("constant type not supported"); + return FAIL; + } + return OK; + } + + /* * Generate an ISN_STORE instruction. */ static int *************** *** 3181,3186 **** --- 3218,3669 ---- } /* + * Evaluate an expression that is a constant: + * has(arg) + * + * Also handle: + * ! in front logical NOT + * + * Return FAIL if the expression is not a constant. + */ + static int + evaluate_const_expr7(char_u **arg, cctx_T *cctx UNUSED, typval_T *tv) + { + typval_T argvars[2]; + char_u *start_leader, *end_leader; + int has_call = FALSE; + + /* + * Skip '!' characters. They are handled later. + * TODO: '-' and '+' characters + */ + start_leader = *arg; + while (**arg == '!') + *arg = skipwhite(*arg + 1); + end_leader = *arg; + + /* + * Recognize only a few types of constants for now. + */ + if (STRNCMP("true", *arg, 4) == 0 && !ASCII_ISALNUM((*arg)[4])) + { + tv->v_type = VAR_BOOL; + tv->vval.v_number = VVAL_TRUE; + *arg += 4; + return OK; + } + if (STRNCMP("false", *arg, 5) == 0 && !ASCII_ISALNUM((*arg)[5])) + { + tv->v_type = VAR_BOOL; + tv->vval.v_number = VVAL_FALSE; + *arg += 5; + return OK; + } + + if (STRNCMP("has(", *arg, 4) == 0) + { + has_call = TRUE; + *arg = skipwhite(*arg + 4); + } + + if (**arg == '"') + { + if (get_string_tv(arg, tv, TRUE) == FAIL) + return FAIL; + } + else if (**arg == '\'') + { + if (get_lit_string_tv(arg, tv, TRUE) == FAIL) + return FAIL; + } + else + return FAIL; + + if (has_call) + { + *arg = skipwhite(*arg); + if (**arg != ')') + return FAIL; + *arg = *arg + 1; + + argvars[0] = *tv; + argvars[1].v_type = VAR_UNKNOWN; + tv->v_type = VAR_NUMBER; + tv->vval.v_number = 0; + f_has(argvars, tv); + clear_tv(&argvars[0]); + + while (start_leader < end_leader) + { + if (*start_leader == '!') + tv->vval.v_number = !tv->vval.v_number; + ++start_leader; + } + } + else if (end_leader > start_leader) + { + clear_tv(tv); + return FAIL; + } + + return OK; + } + + /* + * * number multiplication + * / number division + * % number modulo + */ + static int + evaluate_const_expr6(char_u **arg, cctx_T *cctx, typval_T *tv) + { + char_u *op; + + // get the first variable + if (evaluate_const_expr7(arg, cctx, tv) == FAIL) + return FAIL; + + /* + * Repeat computing, until no "*", "/" or "%" is following. + */ + for (;;) + { + op = skipwhite(*arg); + if (*op != '*' && *op != '/' && *op != '%') + break; + // TODO: not implemented yet. + clear_tv(tv); + return FAIL; + } + return OK; + } + + /* + * + number addition + * - number subtraction + * .. string concatenation + */ + static int + evaluate_const_expr5(char_u **arg, cctx_T *cctx, typval_T *tv) + { + char_u *op; + int oplen; + + // get the first variable + if (evaluate_const_expr6(arg, cctx, tv) == FAIL) + return FAIL; + + /* + * Repeat computing, until no "+", "-" or ".." is following. + */ + for (;;) + { + op = skipwhite(*arg); + if (*op != '+' && *op != '-' && !(*op == '.' && (*(*arg + 1) == '.'))) + break; + oplen = (*op == '.' ? 2 : 1); + + if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(op[oplen])) + { + clear_tv(tv); + return FAIL; + } + + if (*op == '.' && tv->v_type == VAR_STRING) + { + typval_T tv2; + size_t len1; + char_u *s1, *s2; + + tv2.v_type = VAR_UNKNOWN; + *arg = skipwhite(op + oplen); + + // TODO: what if we fail??? + if (may_get_next_line(op + oplen, arg, cctx) == FAIL) + return FAIL; + + // get the second variable + if (evaluate_const_expr6(arg, cctx, &tv2) == FAIL) + { + clear_tv(tv); + return FAIL; + } + if (tv2.v_type != VAR_STRING) + { + clear_tv(tv); + clear_tv(&tv2); + return FAIL; + } + s1 = tv->vval.v_string; + len1 = STRLEN(s1); + s2 = tv2.vval.v_string; + tv->vval.v_string = alloc((int)(len1 + STRLEN(s2) + 1)); + if (tv->vval.v_string == NULL) + { + vim_free(s1); + vim_free(s2); + return FAIL; + } + mch_memmove(tv->vval.v_string, s1, len1); + STRCPY(tv->vval.v_string + len1, s2); + continue; + } + + // TODO: Not implemented yet. + clear_tv(tv); + return FAIL; + } + return OK; + } + + static exptype_T + get_compare_type(char_u *p, int *len, int *type_is) + { + exptype_T type = EXPR_UNKNOWN; + int i; + + switch (p[0]) + { + case '=': if (p[1] == '=') + type = EXPR_EQUAL; + else if (p[1] == '~') + type = EXPR_MATCH; + break; + case '!': if (p[1] == '=') + type = EXPR_NEQUAL; + else if (p[1] == '~') + type = EXPR_NOMATCH; + break; + case '>': if (p[1] != '=') + { + type = EXPR_GREATER; + *len = 1; + } + else + type = EXPR_GEQUAL; + break; + case '<': if (p[1] != '=') + { + type = EXPR_SMALLER; + *len = 1; + } + else + type = EXPR_SEQUAL; + break; + case 'i': if (p[1] == 's') + { + // "is" and "isnot"; but not a prefix of a name + if (p[2] == 'n' && p[3] == 'o' && p[4] == 't') + *len = 5; + i = p[*len]; + if (!isalnum(i) && i != '_') + { + type = *len == 2 ? EXPR_IS : EXPR_ISNOT; + *type_is = TRUE; + } + } + break; + } + return type; + } + + /* + * Only comparing strings is supported right now. + * expr5a == expr5b + */ + static int + evaluate_const_expr4(char_u **arg, cctx_T *cctx UNUSED, typval_T *tv) + { + exptype_T type = EXPR_UNKNOWN; + char_u *p; + int len = 2; + int type_is = FALSE; + + // get the first variable + if (evaluate_const_expr5(arg, cctx, tv) == FAIL) + return FAIL; + + p = skipwhite(*arg); + type = get_compare_type(p, &len, &type_is); + + /* + * If there is a comparative operator, use it. + */ + if (type != EXPR_UNKNOWN) + { + typval_T tv2; + char_u *s1, *s2; + char_u buf1[NUMBUFLEN], buf2[NUMBUFLEN]; + int n; + + // TODO: Only string == string is supported now + if (tv->v_type != VAR_STRING) + return FAIL; + if (type != EXPR_EQUAL) + return FAIL; + + // get the second variable + init_tv(&tv2); + *arg = skipwhite(p + len); + if (evaluate_const_expr5(arg, cctx, &tv2) == FAIL + || tv2.v_type != VAR_STRING) + { + clear_tv(&tv2); + return FAIL; + } + s1 = tv_get_string_buf(tv, buf1); + s2 = tv_get_string_buf(&tv2, buf2); + n = STRCMP(s1, s2); + clear_tv(tv); + clear_tv(&tv2); + tv->v_type = VAR_BOOL; + tv->vval.v_number = n == 0 ? VVAL_TRUE : VVAL_FALSE; + } + + return OK; + } + + static int evaluate_const_expr3(char_u **arg, cctx_T *cctx, typval_T *tv); + + /* + * Compile constant || or &&. + */ + static int + evaluate_const_and_or(char_u **arg, cctx_T *cctx, char *op, typval_T *tv) + { + char_u *p = skipwhite(*arg); + int opchar = *op; + + if (p[0] == opchar && p[1] == opchar) + { + int val = tv2bool(tv); + + /* + * Repeat until there is no following "||" or "&&" + */ + while (p[0] == opchar && p[1] == opchar) + { + typval_T tv2; + + if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[2])) + return FAIL; + + // eval the next expression + *arg = skipwhite(p + 2); + tv2.v_type = VAR_UNKNOWN; + tv2.v_lock = 0; + if ((opchar == '|' ? evaluate_const_expr3(arg, cctx, &tv2) + : evaluate_const_expr4(arg, cctx, &tv2)) == FAIL) + { + clear_tv(&tv2); + return FAIL; + } + if ((opchar == '&') == val) + { + // false || tv2 or true && tv2: use tv2 + clear_tv(tv); + *tv = tv2; + val = tv2bool(tv); + } + else + clear_tv(&tv2); + p = skipwhite(*arg); + } + } + + return OK; + } + + /* + * Evaluate an expression that is a constant: expr4 && expr4 && expr4 + * Return FAIL if the expression is not a constant. + */ + static int + evaluate_const_expr3(char_u **arg, cctx_T *cctx, typval_T *tv) + { + // evaluate the first expression + if (evaluate_const_expr4(arg, cctx, tv) == FAIL) + return FAIL; + + // || and && work almost the same + return evaluate_const_and_or(arg, cctx, "&&", tv); + } + + /* + * Evaluate an expression that is a constant: expr3 || expr3 || expr3 + * Return FAIL if the expression is not a constant. + */ + static int + evaluate_const_expr2(char_u **arg, cctx_T *cctx, typval_T *tv) + { + // evaluate the first expression + if (evaluate_const_expr3(arg, cctx, tv) == FAIL) + return FAIL; + + // || and && work almost the same + return evaluate_const_and_or(arg, cctx, "||", tv); + } + + /* + * Evaluate an expression that is a constant: expr2 ? expr1 : expr1 + * E.g. for "has('feature')". + * This does not produce error messages. "tv" should be cleared afterwards. + * Return FAIL if the expression is not a constant. + */ + static int + evaluate_const_expr1(char_u **arg, cctx_T *cctx, typval_T *tv) + { + char_u *p; + + // evaluate the first expression + if (evaluate_const_expr2(arg, cctx, tv) == FAIL) + return FAIL; + + p = skipwhite(*arg); + if (*p == '?') + { + int val = tv2bool(tv); + typval_T tv2; + + // require space before and after the ? + if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1])) + return FAIL; + + // evaluate the second expression; any type is accepted + clear_tv(tv); + *arg = skipwhite(p + 1); + if (evaluate_const_expr1(arg, cctx, tv) == FAIL) + return FAIL; + + // Check for the ":". + p = skipwhite(*arg); + if (*p != ':' || !VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1])) + return FAIL; + + // evaluate the third expression + *arg = skipwhite(p + 1); + tv2.v_type = VAR_UNKNOWN; + if (evaluate_const_expr1(arg, cctx, &tv2) == FAIL) + { + clear_tv(&tv2); + return FAIL; + } + if (val) + { + // use the expr after "?" + clear_tv(&tv2); + } + else + { + // use the expr after ":" + clear_tv(tv); + *tv = tv2; + } + } + return OK; + } + + /* * Compile code to apply '-', '+' and '!'. */ static int *************** *** 3525,3558 **** start_leader = end_leader; // don't apply again below // push constant ! switch (rettv.v_type) ! { ! case VAR_BOOL: ! generate_PUSHBOOL(cctx, rettv.vval.v_number); ! break; ! case VAR_SPECIAL: ! generate_PUSHSPEC(cctx, rettv.vval.v_number); ! break; ! case VAR_NUMBER: ! generate_PUSHNR(cctx, rettv.vval.v_number); ! break; ! #ifdef FEAT_FLOAT ! case VAR_FLOAT: ! generate_PUSHF(cctx, rettv.vval.v_float); ! break; ! #endif ! case VAR_BLOB: ! generate_PUSHBLOB(cctx, rettv.vval.v_blob); ! rettv.vval.v_blob = NULL; ! break; ! case VAR_STRING: ! generate_PUSHS(cctx, rettv.vval.v_string); ! rettv.vval.v_string = NULL; ! break; ! default: ! iemsg("constant type missing"); ! return FAIL; ! } } else if (ret == NOTDONE) { --- 4008,4015 ---- start_leader = end_leader; // don't apply again below // push constant ! if (generate_tv_PUSH(cctx, &rettv) == FAIL) ! return FAIL; } else if (ret == NOTDONE) { *************** *** 3682,3738 **** return OK; } - static exptype_T - get_compare_type(char_u *p, int *len, int *type_is) - { - exptype_T type = EXPR_UNKNOWN; - int i; - - switch (p[0]) - { - case '=': if (p[1] == '=') - type = EXPR_EQUAL; - else if (p[1] == '~') - type = EXPR_MATCH; - break; - case '!': if (p[1] == '=') - type = EXPR_NEQUAL; - else if (p[1] == '~') - type = EXPR_NOMATCH; - break; - case '>': if (p[1] != '=') - { - type = EXPR_GREATER; - *len = 1; - } - else - type = EXPR_GEQUAL; - break; - case '<': if (p[1] != '=') - { - type = EXPR_SMALLER; - *len = 1; - } - else - type = EXPR_SEQUAL; - break; - case 'i': if (p[1] == 's') - { - // "is" and "isnot"; but not a prefix of a name - if (p[2] == 'n' && p[3] == 'o' && p[4] == 't') - *len = 5; - i = p[*len]; - if (!isalnum(i) && i != '_') - { - type = *len == 2 ? EXPR_IS : EXPR_ISNOT; - *type_is = TRUE; - } - } - break; - } - return type; - } - /* * expr5a == expr5b * expr5a =~ expr5b --- 4139,4144 ---- *************** *** 3936,3947 **** compile_expr1(char_u **arg, cctx_T *cctx) { char_u *p; ! // TODO: Try parsing as a constant. If that works just one PUSH // instruction needs to be generated. ! ! // evaluate the first expression ! if (compile_expr2(arg, cctx) == FAIL) return FAIL; p = skipwhite(*arg); --- 4342,4360 ---- compile_expr1(char_u **arg, cctx_T *cctx) { char_u *p; + typval_T tv; ! // Evaluate the first expression. ! // First try parsing as a constant. If that works just one PUSH // instruction needs to be generated. ! tv.v_type = VAR_UNKNOWN; ! p = *arg; ! if (evaluate_const_expr2(&p, cctx, &tv) == OK) ! { ! *arg = p; ! generate_tv_PUSH(cctx, &tv); ! } ! else if (compile_expr2(arg, cctx) == FAIL) return FAIL; p = skipwhite(*arg); *************** *** 4113,4119 **** || generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL) == FAIL) return NULL; ! // TODO: warning for trailing? return (char_u *)""; } --- 4526,4532 ---- || generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL) == FAIL) return NULL; ! // TODO: warning for trailing text? return (char_u *)""; } *************** *** 4926,5209 **** } /* - * Evaluate an expression that is a constant: - * has(arg) - * - * Also handle: - * ! in front logical NOT - * - * Return FAIL if the expression is not a constant. - */ - static int - evaluate_const_expr7(char_u **arg, cctx_T *cctx UNUSED, typval_T *tv) - { - typval_T argvars[2]; - char_u *start_leader, *end_leader; - int has_call = FALSE; - - /* - * Skip '!' characters. They are handled later. - */ - start_leader = *arg; - while (**arg == '!') - *arg = skipwhite(*arg + 1); - end_leader = *arg; - - /* - * Recognize only a few types of constants for now. - */ - if (STRNCMP("true", *arg, 4) == 0 && !ASCII_ISALNUM((*arg)[4])) - { - tv->v_type = VAR_SPECIAL; - tv->vval.v_number = VVAL_TRUE; - *arg += 4; - return OK; - } - if (STRNCMP("false", *arg, 5) == 0 && !ASCII_ISALNUM((*arg)[5])) - { - tv->v_type = VAR_SPECIAL; - tv->vval.v_number = VVAL_FALSE; - *arg += 5; - return OK; - } - - if (STRNCMP("has(", *arg, 4) == 0) - { - has_call = TRUE; - *arg = skipwhite(*arg + 4); - } - - if (**arg == '"') - { - if (get_string_tv(arg, tv, TRUE) == FAIL) - return FAIL; - } - else if (**arg == '\'') - { - if (get_lit_string_tv(arg, tv, TRUE) == FAIL) - return FAIL; - } - else - return FAIL; - - if (has_call) - { - *arg = skipwhite(*arg); - if (**arg != ')') - return FAIL; - *arg = *arg + 1; - - argvars[0] = *tv; - argvars[1].v_type = VAR_UNKNOWN; - tv->v_type = VAR_NUMBER; - tv->vval.v_number = 0; - f_has(argvars, tv); - clear_tv(&argvars[0]); - - while (start_leader < end_leader) - { - if (*start_leader == '!') - tv->vval.v_number = !tv->vval.v_number; - ++start_leader; - } - } - - return OK; - } - - static int - evaluate_const_expr4(char_u **arg, cctx_T *cctx UNUSED, typval_T *tv) - { - exptype_T type = EXPR_UNKNOWN; - char_u *p; - int len = 2; - int type_is = FALSE; - - // get the first variable - if (evaluate_const_expr7(arg, cctx, tv) == FAIL) - return FAIL; - - p = skipwhite(*arg); - type = get_compare_type(p, &len, &type_is); - - /* - * If there is a comparative operator, use it. - */ - if (type != EXPR_UNKNOWN) - { - typval_T tv2; - char_u *s1, *s2; - char_u buf1[NUMBUFLEN], buf2[NUMBUFLEN]; - int n; - - // TODO: Only string == string is supported now - if (tv->v_type != VAR_STRING) - return FAIL; - if (type != EXPR_EQUAL) - return FAIL; - - // get the second variable - init_tv(&tv2); - *arg = skipwhite(p + len); - if (evaluate_const_expr7(arg, cctx, &tv2) == FAIL - || tv2.v_type != VAR_STRING) - { - clear_tv(&tv2); - return FAIL; - } - s1 = tv_get_string_buf(tv, buf1); - s2 = tv_get_string_buf(&tv2, buf2); - n = STRCMP(s1, s2); - clear_tv(tv); - clear_tv(&tv2); - tv->v_type = VAR_BOOL; - tv->vval.v_number = n == 0 ? VVAL_TRUE : VVAL_FALSE; - } - - return OK; - } - - static int evaluate_const_expr3(char_u **arg, cctx_T *cctx, typval_T *tv); - - /* - * Compile constant || or &&. - */ - static int - evaluate_const_and_or(char_u **arg, cctx_T *cctx, char *op, typval_T *tv) - { - char_u *p = skipwhite(*arg); - int opchar = *op; - - if (p[0] == opchar && p[1] == opchar) - { - int val = tv2bool(tv); - - /* - * Repeat until there is no following "||" or "&&" - */ - while (p[0] == opchar && p[1] == opchar) - { - typval_T tv2; - - if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[2])) - return FAIL; - - // eval the next expression - *arg = skipwhite(p + 2); - tv2.v_type = VAR_UNKNOWN; - tv2.v_lock = 0; - if ((opchar == '|' ? evaluate_const_expr3(arg, cctx, &tv2) - : evaluate_const_expr4(arg, cctx, &tv2)) == FAIL) - { - clear_tv(&tv2); - return FAIL; - } - if ((opchar == '&') == val) - { - // false || tv2 or true && tv2: use tv2 - clear_tv(tv); - *tv = tv2; - val = tv2bool(tv); - } - else - clear_tv(&tv2); - p = skipwhite(*arg); - } - } - - return OK; - } - - /* - * Evaluate an expression that is a constant: expr4 && expr4 && expr4 - * Return FAIL if the expression is not a constant. - */ - static int - evaluate_const_expr3(char_u **arg, cctx_T *cctx, typval_T *tv) - { - // evaluate the first expression - if (evaluate_const_expr4(arg, cctx, tv) == FAIL) - return FAIL; - - // || and && work almost the same - return evaluate_const_and_or(arg, cctx, "&&", tv); - } - - /* - * Evaluate an expression that is a constant: expr3 || expr3 || expr3 - * Return FAIL if the expression is not a constant. - */ - static int - evaluate_const_expr2(char_u **arg, cctx_T *cctx, typval_T *tv) - { - // evaluate the first expression - if (evaluate_const_expr3(arg, cctx, tv) == FAIL) - return FAIL; - - // || and && work almost the same - return evaluate_const_and_or(arg, cctx, "||", tv); - } - - /* - * Evaluate an expression that is a constant: expr2 ? expr1 : expr1 - * E.g. for "has('feature')". - * This does not produce error messages. "tv" should be cleared afterwards. - * Return FAIL if the expression is not a constant. - */ - static int - evaluate_const_expr1(char_u **arg, cctx_T *cctx, typval_T *tv) - { - char_u *p; - - // evaluate the first expression - if (evaluate_const_expr2(arg, cctx, tv) == FAIL) - return FAIL; - - p = skipwhite(*arg); - if (*p == '?') - { - int val = tv2bool(tv); - typval_T tv2; - - // require space before and after the ? - if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1])) - return FAIL; - - // evaluate the second expression; any type is accepted - clear_tv(tv); - *arg = skipwhite(p + 1); - if (evaluate_const_expr1(arg, cctx, tv) == FAIL) - return FAIL; - - // Check for the ":". - p = skipwhite(*arg); - if (*p != ':' || !VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1])) - return FAIL; - - // evaluate the third expression - *arg = skipwhite(p + 1); - tv2.v_type = VAR_UNKNOWN; - if (evaluate_const_expr1(arg, cctx, &tv2) == FAIL) - { - clear_tv(&tv2); - return FAIL; - } - if (val) - { - // use the expr after "?" - clear_tv(&tv2); - } - else - { - // use the expr after ":" - clear_tv(tv); - *tv = tv2; - } - } - return OK; - } - - /* * compile "if expr" * * "if expr" Produces instructions: --- 5339,5344 ---- *** ../vim-8.2.0707/src/testdir/test_vim9_disassemble.vim 2020-05-07 14:07:19.952220393 +0200 --- src/testdir/test_vim9_disassemble.vim 2020-05-07 16:40:36.903451970 +0200 *************** *** 996,1004 **** '\d PUSHS "message".*' .. '\d ECHOMSG 2.*' .. "echoerr 'went' .. 'wrong'.*" .. ! '\d PUSHS "went".*' .. ! '\d PUSHS "wrong".*' .. ! '\d CONCAT.*' .. '\d ECHOERR 1.*' .. '\d PUSHNR 0.*' .. '\d RETURN', --- 996,1002 ---- '\d PUSHS "message".*' .. '\d ECHOMSG 2.*' .. "echoerr 'went' .. 'wrong'.*" .. ! '\d PUSHS "wentwrong".*' .. '\d ECHOERR 1.*' .. '\d PUSHNR 0.*' .. '\d RETURN', *************** *** 1037,1040 **** --- 1035,1050 ---- res3) enddef + def s:ConcatStrings(): string + return 'one' .. 'two' .. 'three' + enddef + + def Test_simplify_const_expr() + let res = execute('disass s:ConcatStrings') + assert_match('\\d*_ConcatStrings.*' .. + '\d PUSHS "onetwothree".*' .. + '\d RETURN', + res) + enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker *** ../vim-8.2.0707/src/testdir/test_vim9_expr.vim 2020-05-05 21:25:19.141316752 +0200 --- src/testdir/test_vim9_expr.vim 2020-05-07 16:38:24.607934052 +0200 *************** *** 485,492 **** assert_equal(-6, g:alsoint - g:anint) assert_equal('hello', 'hel' .. 'lo') ! assert_equal('hello 123', 'hello ' .. ! 123) assert_equal('123 hello', 123 .. ' hello') assert_equal('123456', 123 .. 456) --- 485,494 ---- assert_equal(-6, g:alsoint - g:anint) assert_equal('hello', 'hel' .. 'lo') ! " TODO: a line break here doesn't work ! " assert_equal('hello 123', 'hello ' .. ! " 123) ! assert_equal('hello 123', 'hello ' .. 123) assert_equal('123 hello', 123 .. ' hello') assert_equal('123456', 123 .. 456) *** ../vim-8.2.0707/src/version.c 2020-05-07 14:37:15.949321720 +0200 --- src/version.c 2020-05-07 15:16:16.905703351 +0200 *************** *** 748,749 **** --- 748,751 ---- { /* Add new patch number below this line */ + /**/ + 708, /**/ -- hundred-and-one symptoms of being an internet addict: 57. You begin to wonder how on earth your service provider is allowed to call 200 hours per month "unlimited." /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///