# HG changeset patch # User Bram Moolenaar # Date 1589031903 -7200 # Node ID 445c2b2ea44bdb591909d7159147730fba2bf3da # Parent fdd4ca33af69efa19aaf2f7ebb139be9be74d08c patch 8.2.0719: Vim9: more expressions can be evaluated at compile time Commit: https://github.com/vim/vim/commit/a5565e4189b7c4d3f03d1f5405fc64d5dc00f717 Author: Bram Moolenaar Date: Sat May 9 15:44:01 2020 +0200 patch 8.2.0719: Vim9: more expressions can be evaluated at compile time Problem: Vim9: more expressions can be evaluated at compile time Solution: Recognize has('name'). diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -814,46 +814,45 @@ def Test_disassemble_invert_bool() enddef def Test_disassemble_compare() - " TODO: COMPAREFUNC let cases = [ - ['true == false', 'COMPAREBOOL =='], - ['true != false', 'COMPAREBOOL !='], - ['v:none == v:null', 'COMPARESPECIAL =='], - ['v:none != v:null', 'COMPARESPECIAL !='], + ['true == isFalse', 'COMPAREBOOL =='], + ['true != isFalse', 'COMPAREBOOL !='], + ['v:none == isNull', 'COMPARESPECIAL =='], + ['v:none != isNull', 'COMPARESPECIAL !='], - ['111 == 222', 'COMPARENR =='], - ['111 != 222', 'COMPARENR !='], - ['111 > 222', 'COMPARENR >'], - ['111 < 222', 'COMPARENR <'], - ['111 >= 222', 'COMPARENR >='], - ['111 <= 222', 'COMPARENR <='], - ['111 =~ 222', 'COMPARENR =\~'], - ['111 !~ 222', 'COMPARENR !\~'], + ['111 == aNumber', 'COMPARENR =='], + ['111 != aNumber', 'COMPARENR !='], + ['111 > aNumber', 'COMPARENR >'], + ['111 < aNumber', 'COMPARENR <'], + ['111 >= aNumber', 'COMPARENR >='], + ['111 <= aNumber', 'COMPARENR <='], + ['111 =~ aNumber', 'COMPARENR =\~'], + ['111 !~ aNumber', 'COMPARENR !\~'], - ['"xx" != "yy"', 'COMPARESTRING !='], - ['"xx" > "yy"', 'COMPARESTRING >'], - ['"xx" < "yy"', 'COMPARESTRING <'], - ['"xx" >= "yy"', 'COMPARESTRING >='], - ['"xx" <= "yy"', 'COMPARESTRING <='], - ['"xx" =~ "yy"', 'COMPARESTRING =\~'], - ['"xx" !~ "yy"', 'COMPARESTRING !\~'], - ['"xx" is "yy"', 'COMPARESTRING is'], - ['"xx" isnot "yy"', 'COMPARESTRING isnot'], + ['"xx" != aString', 'COMPARESTRING !='], + ['"xx" > aString', 'COMPARESTRING >'], + ['"xx" < aString', 'COMPARESTRING <'], + ['"xx" >= aString', 'COMPARESTRING >='], + ['"xx" <= aString', 'COMPARESTRING <='], + ['"xx" =~ aString', 'COMPARESTRING =\~'], + ['"xx" !~ aString', 'COMPARESTRING !\~'], + ['"xx" is aString', 'COMPARESTRING is'], + ['"xx" isnot aString', 'COMPARESTRING isnot'], - ['0z11 == 0z22', 'COMPAREBLOB =='], - ['0z11 != 0z22', 'COMPAREBLOB !='], - ['0z11 is 0z22', 'COMPAREBLOB is'], - ['0z11 isnot 0z22', 'COMPAREBLOB isnot'], + ['0z11 == aBlob', 'COMPAREBLOB =='], + ['0z11 != aBlob', 'COMPAREBLOB !='], + ['0z11 is aBlob', 'COMPAREBLOB is'], + ['0z11 isnot aBlob', 'COMPAREBLOB isnot'], - ['[1,2] == [3,4]', 'COMPARELIST =='], - ['[1,2] != [3,4]', 'COMPARELIST !='], - ['[1,2] is [3,4]', 'COMPARELIST is'], - ['[1,2] isnot [3,4]', 'COMPARELIST isnot'], + ['[1, 2] == aList', 'COMPARELIST =='], + ['[1, 2] != aList', 'COMPARELIST !='], + ['[1, 2] is aList', 'COMPARELIST is'], + ['[1, 2] isnot aList', 'COMPARELIST isnot'], - ['#{a:1} == #{x:2}', 'COMPAREDICT =='], - ['#{a:1} != #{x:2}', 'COMPAREDICT !='], - ['#{a:1} is #{x:2}', 'COMPAREDICT is'], - ['#{a:1} isnot #{x:2}', 'COMPAREDICT isnot'], + ['#{a: 1} == aDict', 'COMPAREDICT =='], + ['#{a: 1} != aDict', 'COMPAREDICT !='], + ['#{a: 1} is aDict', 'COMPAREDICT is'], + ['#{a: 1} isnot aDict', 'COMPAREDICT isnot'], ['{->33} == {->44}', 'COMPAREFUNC =='], ['{->33} != {->44}', 'COMPAREFUNC !='], @@ -871,22 +870,33 @@ def Test_disassemble_compare() ['77 is g:xx', 'COMPAREANY is'], ['77 isnot g:xx', 'COMPAREANY isnot'], ] + let floatDecl = '' if has('float') cases->extend([ - ['1.1 == 2.2', 'COMPAREFLOAT =='], - ['1.1 != 2.2', 'COMPAREFLOAT !='], - ['1.1 > 2.2', 'COMPAREFLOAT >'], - ['1.1 < 2.2', 'COMPAREFLOAT <'], - ['1.1 >= 2.2', 'COMPAREFLOAT >='], - ['1.1 <= 2.2', 'COMPAREFLOAT <='], - ['1.1 =~ 2.2', 'COMPAREFLOAT =\~'], - ['1.1 !~ 2.2', 'COMPAREFLOAT !\~'], + ['1.1 == aFloat', 'COMPAREFLOAT =='], + ['1.1 != aFloat', 'COMPAREFLOAT !='], + ['1.1 > aFloat', 'COMPAREFLOAT >'], + ['1.1 < aFloat', 'COMPAREFLOAT <'], + ['1.1 >= aFloat', 'COMPAREFLOAT >='], + ['1.1 <= aFloat', 'COMPAREFLOAT <='], + ['1.1 =~ aFloat', 'COMPAREFLOAT =\~'], + ['1.1 !~ aFloat', 'COMPAREFLOAT !\~'], ]) + floatDecl = 'let aFloat = 2.2' endif let nr = 1 for case in cases + " declare local variables to get a non-constant with the right type writefile(['def TestCase' .. nr .. '()', + ' let isFalse = false', + ' let isNull = v:null', + ' let aNumber = 222', + ' let aString = "yy"', + ' let aBlob = 0z22', + ' let aList = [3, 4]', + ' let aDict = #{x: 2}', + floatDecl, ' if ' .. case[0], ' echo 42' ' endif', @@ -896,7 +906,7 @@ def Test_disassemble_compare() assert_match('TestCase' .. nr .. '.*' .. 'if ' .. substitute(case[0], '[[~]', '\\\0', 'g') .. '.*' .. '\d \(PUSH\|FUNCREF\).*' .. - '\d \(PUSH\|FUNCREF\|LOADG\).*' .. + '\d \(PUSH\|FUNCREF\|LOAD\).*' .. '\d ' .. case[1] .. '.*' .. '\d JUMP_IF_FALSE -> \d\+.*', instr) diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -429,7 +429,7 @@ func Test_expr4_fails() call CheckDefFailure(["let x = 1 == '2'"], 'Cannot compare number with string') call CheckDefFailure(["let x = '1' == 2"], 'Cannot compare string with number') - call CheckDefFailure(["let x = 1 == RetVoid()"], 'Cannot use void value') + call CheckDefFailure(["let x = 1 == RetVoid()"], 'Cannot compare number with void') call CheckDefFailure(["let x = RetVoid() == 1"], 'Cannot compare void with number') call CheckDefFailure(["let x = true > false"], 'Cannot compare bool with bool') diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -747,6 +747,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 719, +/**/ 718, /**/ 717, diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -136,9 +136,7 @@ struct cctx_S { static char e_var_notfound[] = N_("E1001: variable not found: %s"); static char e_syntax_at[] = N_("E1002: Syntax error at %s"); -static int compile_expr1(char_u **arg, cctx_T *cctx); -static int compile_expr2(char_u **arg, cctx_T *cctx); -static int compile_expr3(char_u **arg, cctx_T *cctx); +static int compile_expr0(char_u **arg, cctx_T *cctx); static void delete_def_function_contents(dfunc_T *dfunc); static void arg_type_mismatch(type_T *expected, type_T *actual, int argidx); static int check_type(type_T *expected, type_T *actual, int give_msg); @@ -744,24 +742,14 @@ generate_two_op(cctx_T *cctx, char_u *op } /* - * Generate an ISN_COMPARE* instruction with a boolean result. - */ - static int -generate_COMPARE(cctx_T *cctx, exptype_T exptype, int ic) + * Get the instruction to use for comparing "type1" with "type2" + * Return ISN_DROP when failed. + */ + static isntype_T +get_compare_isn(exptype_T exptype, vartype_T type1, vartype_T type2) { isntype_T isntype = ISN_DROP; - isn_T *isn; - garray_T *stack = &cctx->ctx_type_stack; - vartype_T type1; - vartype_T type2; - - RETURN_OK_IF_SKIP(cctx); - - // Get the known type of the two items on the stack. If they are matching - // use a type-specific instruction. Otherwise fall back to runtime type - // checking. - type1 = ((type_T **)stack->ga_data)[stack->ga_len - 2]->tt_type; - type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]->tt_type; + if (type1 == VAR_UNKNOWN) type1 = VAR_ANY; if (type2 == VAR_UNKNOWN) @@ -796,7 +784,7 @@ generate_COMPARE(cctx_T *cctx, exptype_T { semsg(_("E1037: Cannot use \"%s\" with %s"), exptype == EXPR_IS ? "is" : "isnot" , vartype_name(type1)); - return FAIL; + return ISN_DROP; } if (isntype == ISN_DROP || ((exptype != EXPR_EQUAL && exptype != EXPR_NEQUAL @@ -809,8 +797,33 @@ generate_COMPARE(cctx_T *cctx, exptype_T { semsg(_("E1072: Cannot compare %s with %s"), vartype_name(type1), vartype_name(type2)); + return ISN_DROP; + } + return isntype; +} + +/* + * Generate an ISN_COMPARE* instruction with a boolean result. + */ + static int +generate_COMPARE(cctx_T *cctx, exptype_T exptype, int ic) +{ + isntype_T isntype; + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + vartype_T type1; + vartype_T type2; + + RETURN_OK_IF_SKIP(cctx); + + // Get the known type of the two items on the stack. If they are matching + // use a type-specific instruction. Otherwise fall back to runtime type + // checking. + type1 = ((type_T **)stack->ga_data)[stack->ga_len - 2]->tt_type; + type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]->tt_type; + isntype = get_compare_isn(exptype, type1, type2); + if (isntype == ISN_DROP) return FAIL; - } if ((isn = generate_instr(cctx, isntype)) == NULL) return FAIL; @@ -2340,6 +2353,91 @@ may_get_next_line(char_u *whitep, char_u return OK; } +// Structure passed between the compile_expr* functions to keep track of +// constants that have been parsed but for which no code was produced yet. If +// possible expressions on these constants are applied at compile time. If +// that is not possible, the code to push the constants needs to be generated +// before other instructions. +typedef struct { + typval_T pp_tv[10]; // stack of ppconst constants + int pp_used; // active entries in pp_tv[] +} ppconst_T; + +/* + * Generate a PUSH instruction for "tv". + * "tv" will be consumed or cleared. + * Nothing happens if "tv" is NULL or of type VAR_UNKNOWN; + */ + static int +generate_tv_PUSH(cctx_T *cctx, typval_T *tv) +{ + if (tv != NULL) + { + switch (tv->v_type) + { + case VAR_UNKNOWN: + break; + case VAR_BOOL: + generate_PUSHBOOL(cctx, tv->vval.v_number); + break; + case VAR_SPECIAL: + generate_PUSHSPEC(cctx, tv->vval.v_number); + break; + case VAR_NUMBER: + generate_PUSHNR(cctx, tv->vval.v_number); + break; +#ifdef FEAT_FLOAT + case VAR_FLOAT: + generate_PUSHF(cctx, tv->vval.v_float); + break; +#endif + case VAR_BLOB: + generate_PUSHBLOB(cctx, tv->vval.v_blob); + tv->vval.v_blob = NULL; + break; + case VAR_STRING: + generate_PUSHS(cctx, tv->vval.v_string); + tv->vval.v_string = NULL; + break; + default: + iemsg("constant type not supported"); + clear_tv(tv); + return FAIL; + } + tv->v_type = VAR_UNKNOWN; + } + return OK; +} + +/* + * Generate code for any ppconst entries. + */ + static int +generate_ppconst(cctx_T *cctx, ppconst_T *ppconst) +{ + int i; + int ret = OK; + + for (i = 0; i < ppconst->pp_used; ++i) + if (generate_tv_PUSH(cctx, &ppconst->pp_tv[i]) == FAIL) + ret = FAIL; + ppconst->pp_used = 0; + return ret; +} + +/* + * Clear ppconst constants. Used when failing. + */ + static void +clear_ppconst(ppconst_T *ppconst) +{ + int i; + + for (i = 0; i < ppconst->pp_used; ++i) + clear_tv(&ppconst->pp_tv[i]); + ppconst->pp_used = 0; +} + /* * Generate an instruction to load script-local variable "name", without the * leading "s:". @@ -2526,25 +2624,18 @@ compile_load(char_u **arg, char_u *end_a } else { - if ((len == 4 && STRNCMP("true", *arg, 4) == 0) - || (len == 5 && STRNCMP("false", *arg, 5) == 0)) - res = generate_PUSHBOOL(cctx, **arg == 't' - ? VVAL_TRUE : VVAL_FALSE); - else - { - // "var" can be script-local even without using "s:" if it - // already exists. - if (SCRIPT_ITEM(current_sctx.sc_sid)->sn_version - == SCRIPT_VERSION_VIM9 - || lookup_script(*arg, len) == OK) - res = compile_load_scriptvar(cctx, name, *arg, &end, - FALSE); - - // When the name starts with an uppercase letter or "x:" it - // can be a user defined function. - if (res == FAIL && (ASCII_ISUPPER(*name) || name[1] == ':')) - res = generate_funcref(cctx, name); - } + // "var" can be script-local even without using "s:" if it + // already exists. + if (SCRIPT_ITEM(current_sctx.sc_sid)->sn_version + == SCRIPT_VERSION_VIM9 + || lookup_script(*arg, len) == OK) + res = compile_load_scriptvar(cctx, name, *arg, &end, + FALSE); + + // When the name starts with an uppercase letter or "x:" it + // can be a user defined function. + if (res == FAIL && (ASCII_ISUPPER(*name) || name[1] == ':')) + res = generate_funcref(cctx, name); } } if (gen_load) @@ -2588,7 +2679,7 @@ compile_arguments(char_u **arg, cctx_T * return OK; } - if (compile_expr1(&p, cctx) == FAIL) + if (compile_expr0(&p, cctx) == FAIL) return FAIL; ++*argcount; @@ -2621,7 +2712,12 @@ failret: * BCALL / DCALL / UCALL */ static int -compile_call(char_u **arg, size_t varlen, cctx_T *cctx, int argcount_init) +compile_call( + char_u **arg, + size_t varlen, + cctx_T *cctx, + ppconst_T *ppconst, + int argcount_init) { char_u *name = *arg; char_u *p; @@ -2633,6 +2729,36 @@ compile_call(char_u **arg, size_t varlen ufunc_T *ufunc; int res = FAIL; + // we can evaluate "has('name')" at compile time + if (varlen == 3 && STRNCMP(*arg, "has", 3) == 0) + { + char_u *s = skipwhite(*arg + varlen + 1); + typval_T argvars[2]; + + argvars[0].v_type = VAR_UNKNOWN; + if (*s == '"') + (void)get_string_tv(&s, &argvars[0], TRUE); + else if (*s == '\'') + (void)get_lit_string_tv(&s, &argvars[0], TRUE); + s = skipwhite(s); + if (*s == ')' && argvars[0].v_type == VAR_STRING) + { + typval_T *tv = &ppconst->pp_tv[ppconst->pp_used]; + + *arg = s + 1; + argvars[1].v_type = VAR_UNKNOWN; + tv->v_type = VAR_NUMBER; + tv->vval.v_number = 0; + f_has(argvars, tv); + clear_tv(&argvars[0]); + ++ppconst->pp_used; + return OK; + } + } + + if (generate_ppconst(cctx, ppconst) == FAIL) + return FAIL; + if (varlen >= sizeof(namebuf)) { semsg(_("E1011: name too long: %s"), name); @@ -2791,7 +2917,7 @@ compile_list(char_u **arg, cctx_T *cctx) p += STRLEN(p); break; } - if (compile_expr1(&p, cctx) == FAIL) + if (compile_expr0(&p, cctx) == FAIL) break; ++count; if (*p == ',') @@ -2929,7 +3055,7 @@ compile_dict(char_u **arg, cctx_T *cctx, { isn_T *isn; - if (compile_expr1(arg, cctx) == FAIL) + if (compile_expr0(arg, cctx) == FAIL) return FAIL; // TODO: check type is string isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1; @@ -2974,7 +3100,7 @@ compile_dict(char_u **arg, cctx_T *cctx, *arg = skipwhite(*arg); } - if (compile_expr1(arg, cctx) == FAIL) + if (compile_expr0(arg, cctx) == FAIL) return FAIL; ++count; @@ -3626,90 +3752,7 @@ evaluate_const_expr1(char_u **arg, cctx_ return OK; } -// Structure passed between the compile_expr* functions to keep track of -// constants that have been parsed but for which no code was produced yet. If -// possible expressions on these constants are applied at compile time. If -// that is not possible, the code to push the constants needs to be generated -// before other instructions. -typedef struct { - typval_T pp_tv[10]; // stack of ppconst constants - int pp_used; // active entries in pp_tv[] -} ppconst_T; - -/* - * Generate a PUSH instruction for "tv". - * "tv" will be consumed or cleared. - * Nothing happens if "tv" is NULL or of type VAR_UNKNOWN; - */ - static int -generate_tv_PUSH(cctx_T *cctx, typval_T *tv) -{ - if (tv != NULL) - { - switch (tv->v_type) - { - case VAR_UNKNOWN: - break; - case VAR_BOOL: - generate_PUSHBOOL(cctx, tv->vval.v_number); - break; - case VAR_SPECIAL: - generate_PUSHSPEC(cctx, tv->vval.v_number); - break; - case VAR_NUMBER: - generate_PUSHNR(cctx, tv->vval.v_number); - break; -#ifdef FEAT_FLOAT - case VAR_FLOAT: - generate_PUSHF(cctx, tv->vval.v_float); - break; -#endif - case VAR_BLOB: - generate_PUSHBLOB(cctx, tv->vval.v_blob); - tv->vval.v_blob = NULL; - break; - case VAR_STRING: - generate_PUSHS(cctx, tv->vval.v_string); - tv->vval.v_string = NULL; - break; - default: - iemsg("constant type not supported"); - clear_tv(tv); - return FAIL; - } - tv->v_type = VAR_UNKNOWN; - } - return OK; -} - -/* - * Generate code for any ppconst entries. - */ - static int -generate_ppconst(cctx_T *cctx, ppconst_T *ppconst) -{ - int i; - int ret = OK; - - for (i = 0; i < ppconst->pp_used; ++i) - if (generate_tv_PUSH(cctx, &ppconst->pp_tv[i]) == FAIL) - ret = FAIL; - ppconst->pp_used = 0; - return ret; -} - -/* - * Clear ppconst constants. Used when failing. - */ - static void -clear_ppconst(ppconst_T *ppconst) -{ - int i; - - for (i = 0; i < ppconst->pp_used; ++i) - clear_tv(&ppconst->pp_tv[i]); - ppconst->pp_used = 0; -} +static int compile_expr3(char_u **arg, cctx_T *cctx, ppconst_T *ppconst); /* * Compile code to apply '-', '+' and '!'. @@ -3825,7 +3868,7 @@ compile_subscript( return FAIL; } // TODO: base value may not be the first argument - if (compile_call(arg, p - *arg, cctx, 1) == FAIL) + if (compile_call(arg, p - *arg, cctx, ppconst, 1) == FAIL) return FAIL; } } @@ -3841,7 +3884,7 @@ compile_subscript( // TODO: more arguments // TODO: dict member dict['name'] *arg = skipwhite(*arg + 1); - if (compile_expr1(arg, cctx) == FAIL) + if (compile_expr0(arg, cctx) == FAIL) return FAIL; if (**arg != ']') @@ -3991,6 +4034,34 @@ compile_expr7( break; /* + * "true" constant + */ + case 't': if (STRNCMP(*arg, "true", 4) == 0 + && !eval_isnamec((*arg)[4])) + { + *arg += 4; + rettv->v_type = VAR_BOOL; + rettv->vval.v_number = VVAL_TRUE; + } + else + ret = NOTDONE; + break; + + /* + * "false" constant + */ + case 'f': if (STRNCMP(*arg, "false", 5) == 0 + && !eval_isnamec((*arg)[5])) + { + *arg += 5; + rettv->v_type = VAR_BOOL; + rettv->vval.v_number = VVAL_FALSE; + } + else + ret = NOTDONE; + break; + + /* * List: [expr, expr] */ case '[': ret = compile_list(arg, cctx); @@ -4047,7 +4118,7 @@ compile_expr7( * nested expression: (expression). */ case '(': *arg = skipwhite(*arg + 1); - ret = compile_expr1(arg, cctx); // recursive! + ret = compile_expr0(arg, cctx); // recursive! *arg = skipwhite(*arg); if (**arg == ')') ++*arg; @@ -4074,18 +4145,18 @@ compile_expr7( } start_leader = end_leader; // don't apply again below - // A constant expression can possibly be handled compile time, return - // the value instead of generating code. - ++ppconst->pp_used; + if (cctx->ctx_skip == TRUE) + clear_tv(rettv); + else + // A constant expression can possibly be handled compile time, + // return the value instead of generating code. + ++ppconst->pp_used; } else if (ret == NOTDONE) { char_u *p; int r; - if (generate_ppconst(cctx, ppconst) == FAIL) - return FAIL; - if (!eval_isnamec1(**arg)) { semsg(_("E1015: Name expected: %s"), *arg); @@ -4095,9 +4166,15 @@ compile_expr7( // "name" or "name()" p = to_name_end(*arg, TRUE); if (*p == '(') - r = compile_call(arg, p - *arg, cctx, 0); + { + r = compile_call(arg, p - *arg, cctx, ppconst, 0); + } else + { + if (generate_ppconst(cctx, ppconst) == FAIL) + return FAIL; r = compile_load(arg, p, cctx, TRUE); + } if (r == FAIL) return FAIL; } @@ -4105,8 +4182,17 @@ compile_expr7( // Handle following "[]", ".member", etc. // Then deal with prefixed '-', '+' and '!', if not done already. if (compile_subscript(arg, cctx, &start_leader, end_leader, - ppconst) == FAIL - || compile_leader(cctx, start_leader, end_leader) == FAIL) + ppconst) == FAIL) + return FAIL; + if (ppconst->pp_used > 0) + { + // apply the '!', '-' and '+' before the constant + rettv = &ppconst->pp_tv[ppconst->pp_used - 1]; + if (apply_leader(rettv, start_leader, end_leader) == FAIL) + return FAIL; + return OK; + } + if (compile_leader(cctx, start_leader, end_leader) == FAIL) return FAIL; return OK; } @@ -4117,10 +4203,7 @@ compile_expr7( * % number modulo */ static int -compile_expr6( - char_u **arg, - cctx_T *cctx, - ppconst_T *ppconst) +compile_expr6(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) { char_u *op; int ppconst_used = ppconst->pp_used; @@ -4191,21 +4274,15 @@ compile_expr6( * .. string concatenation */ static int -compile_expr5(char_u **arg, cctx_T *cctx) +compile_expr5(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) { char_u *op; int oplen; - ppconst_T ppconst; - int ppconst_used = 0; - - CLEAR_FIELD(ppconst); + int ppconst_used = ppconst->pp_used; // get the first variable - if (compile_expr6(arg, cctx, &ppconst) == FAIL) - { - clear_ppconst(&ppconst); + if (compile_expr6(arg, cctx, ppconst) == FAIL) return FAIL; - } /* * Repeat computing, until no "+", "-" or ".." is following. @@ -4221,7 +4298,6 @@ compile_expr5(char_u **arg, cctx_T *cctx { char_u buf[3]; - clear_ppconst(&ppconst); vim_strncpy(buf, op, oplen); semsg(_(e_white_both), buf); return FAIL; @@ -4229,27 +4305,21 @@ compile_expr5(char_u **arg, cctx_T *cctx *arg = skipwhite(op + oplen); if (may_get_next_line(op + oplen, arg, cctx) == FAIL) - { - clear_ppconst(&ppconst); return FAIL; - } // get the second expression - if (compile_expr6(arg, cctx, &ppconst) == FAIL) - { - clear_ppconst(&ppconst); + if (compile_expr6(arg, cctx, ppconst) == FAIL) return FAIL; - } - - if (ppconst.pp_used == ppconst_used + 2 + + if (ppconst->pp_used == ppconst_used + 2 && (*op == '.' - ? (ppconst.pp_tv[ppconst_used].v_type == VAR_STRING - && ppconst.pp_tv[ppconst_used + 1].v_type == VAR_STRING) - : (ppconst.pp_tv[ppconst_used].v_type == VAR_NUMBER - && ppconst.pp_tv[ppconst_used + 1].v_type == VAR_NUMBER))) + ? (ppconst->pp_tv[ppconst_used].v_type == VAR_STRING + && ppconst->pp_tv[ppconst_used + 1].v_type == VAR_STRING) + : (ppconst->pp_tv[ppconst_used].v_type == VAR_NUMBER + && ppconst->pp_tv[ppconst_used + 1].v_type == VAR_NUMBER))) { - typval_T *tv1 = &ppconst.pp_tv[ppconst_used]; - typval_T *tv2 = &ppconst.pp_tv[ppconst_used + 1]; + typval_T *tv1 = &ppconst->pp_tv[ppconst_used]; + typval_T *tv2 = &ppconst->pp_tv[ppconst_used + 1]; // concat/subtract/add constant numbers if (*op == '+') @@ -4266,7 +4336,7 @@ compile_expr5(char_u **arg, cctx_T *cctx tv1->vval.v_string = alloc((int)(len1 + STRLEN(s2) + 1)); if (tv1->vval.v_string == NULL) { - clear_ppconst(&ppconst); + clear_ppconst(ppconst); return FAIL; } mch_memmove(tv1->vval.v_string, s1, len1); @@ -4274,19 +4344,16 @@ compile_expr5(char_u **arg, cctx_T *cctx vim_free(s1); vim_free(s2); } - --ppconst.pp_used; + --ppconst->pp_used; } else { - generate_ppconst(cctx, &ppconst); + generate_ppconst(cctx, ppconst); if (*op == '.') { if (may_generate_2STRING(-2, cctx) == FAIL || may_generate_2STRING(-1, cctx) == FAIL) - { - clear_ppconst(&ppconst); return FAIL; - } generate_instr_drop(cctx, ISN_CONCAT, 1); } else @@ -4294,9 +4361,6 @@ compile_expr5(char_u **arg, cctx_T *cctx } } - // TODO: move to caller - generate_ppconst(cctx, &ppconst); - return OK; } @@ -4318,15 +4382,16 @@ compile_expr5(char_u **arg, cctx_T *cctx * COMPARE one of the compare instructions */ static int -compile_expr4(char_u **arg, cctx_T *cctx) +compile_expr4(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) { exptype_T type = EXPR_UNKNOWN; char_u *p; int len = 2; int type_is = FALSE; + int ppconst_used = ppconst->pp_used; // get the first variable - if (compile_expr5(arg, cctx) == FAIL) + if (compile_expr5(arg, cctx, ppconst) == FAIL) return FAIL; p = skipwhite(*arg); @@ -4369,10 +4434,34 @@ compile_expr4(char_u **arg, cctx_T *cctx if (may_get_next_line(p + len, arg, cctx) == FAIL) return FAIL; - if (compile_expr5(arg, cctx) == FAIL) + if (compile_expr5(arg, cctx, ppconst) == FAIL) return FAIL; - generate_COMPARE(cctx, type, ic); + if (ppconst->pp_used == ppconst_used + 2) + { + typval_T * tv1 = &ppconst->pp_tv[ppconst->pp_used - 2]; + typval_T *tv2 = &ppconst->pp_tv[ppconst->pp_used - 1]; + int ret; + + // Both sides are a constant, compute the result now. + // First check for a valid combination of types, this is more + // strict than typval_compare(). + if (get_compare_isn(type, tv1->v_type, tv2->v_type) == ISN_DROP) + ret = FAIL; + else + { + ret = typval_compare(tv1, tv2, type, ic); + tv1->v_type = VAR_BOOL; + tv1->vval.v_number = tv1->vval.v_number + ? VVAL_TRUE : VVAL_FALSE; + clear_tv(tv2); + --ppconst->pp_used; + } + return ret; + } + + generate_ppconst(cctx, ppconst); + return generate_COMPARE(cctx, type, ic); } return OK; @@ -4382,7 +4471,12 @@ compile_expr4(char_u **arg, cctx_T *cctx * Compile || or &&. */ static int -compile_and_or(char_u **arg, cctx_T *cctx, char *op) +compile_and_or( + char_u **arg, + cctx_T *cctx, + char *op, + ppconst_T *ppconst, + int ppconst_used UNUSED) { char_u *p = skipwhite(*arg); int opchar = *op; @@ -4404,6 +4498,9 @@ compile_and_or(char_u **arg, cctx_T *cct return FAIL; } + // TODO: use ppconst if the value is a constant + generate_ppconst(cctx, ppconst); + if (ga_grow(&end_ga, 1) == FAIL) { ga_clear(&end_ga); @@ -4419,14 +4516,15 @@ compile_and_or(char_u **arg, cctx_T *cct if (may_get_next_line(p + 2, arg, cctx) == FAIL) return FAIL; - if ((opchar == '|' ? compile_expr3(arg, cctx) - : compile_expr4(arg, cctx)) == FAIL) + if ((opchar == '|' ? compile_expr3(arg, cctx, ppconst) + : compile_expr4(arg, cctx, ppconst)) == FAIL) { ga_clear(&end_ga); return FAIL; } p = skipwhite(*arg); } + generate_ppconst(cctx, ppconst); // Fill in the end label in all jumps. while (end_ga.ga_len > 0) @@ -4456,14 +4554,16 @@ compile_and_or(char_u **arg, cctx_T *cct * end: */ static int -compile_expr3(char_u **arg, cctx_T *cctx) -{ +compile_expr3(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) +{ + int ppconst_used = ppconst->pp_used; + // get the first variable - if (compile_expr4(arg, cctx) == FAIL) + if (compile_expr4(arg, cctx, ppconst) == FAIL) return FAIL; // || and && work almost the same - return compile_and_or(arg, cctx, "&&"); + return compile_and_or(arg, cctx, "&&", ppconst, ppconst_used); } /* @@ -4478,14 +4578,16 @@ compile_expr3(char_u **arg, cctx_T *cctx * end: */ static int -compile_expr2(char_u **arg, cctx_T *cctx) -{ +compile_expr2(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) +{ + int ppconst_used = ppconst->pp_used; + // eval the first expression - if (compile_expr3(arg, cctx) == FAIL) + if (compile_expr3(arg, cctx, ppconst) == FAIL) return FAIL; // || and && work almost the same - return compile_and_or(arg, cctx, "||"); + return compile_and_or(arg, cctx, "||", ppconst, ppconst_used); } /* @@ -4500,12 +4602,13 @@ compile_expr2(char_u **arg, cctx_T *cctx * end: */ static int -compile_expr1(char_u **arg, cctx_T *cctx) +compile_expr1(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) { char_u *p; + int ppconst_used = ppconst->pp_used; // Evaluate the first expression. - if (compile_expr2(arg, cctx) == FAIL) + if (compile_expr2(arg, cctx, ppconst) == FAIL) return FAIL; p = skipwhite(*arg); @@ -4518,6 +4621,9 @@ compile_expr1(char_u **arg, cctx_T *cct isn_T *isn; type_T *type1; type_T *type2; + int has_const_expr = FALSE; + int const_value = FALSE; + int save_skip = cctx->ctx_skip; if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[1])) { @@ -4525,26 +4631,44 @@ compile_expr1(char_u **arg, cctx_T *cct return FAIL; } - generate_JUMP(cctx, JUMP_IF_FALSE, 0); + if (ppconst->pp_used == ppconst_used + 1) + { + // the condition is a constant, we know whether the ? or the : + // expression is to be evaluated. + has_const_expr = TRUE; + const_value = tv2bool(&ppconst->pp_tv[ppconst_used]); + clear_tv(&ppconst->pp_tv[ppconst_used]); + --ppconst->pp_used; + cctx->ctx_skip = save_skip == TRUE || !const_value; + } + else + { + generate_ppconst(cctx, ppconst); + generate_JUMP(cctx, JUMP_IF_FALSE, 0); + } // evaluate the second expression; any type is accepted *arg = skipwhite(p + 1); if (may_get_next_line(p + 1, arg, cctx) == FAIL) return FAIL; - - if (compile_expr1(arg, cctx) == FAIL) + if (compile_expr1(arg, cctx, ppconst) == FAIL) return FAIL; - // remember the type and drop it - --stack->ga_len; - type1 = ((type_T **)stack->ga_data)[stack->ga_len]; - - end_idx = instr->ga_len; - generate_JUMP(cctx, JUMP_ALWAYS, 0); - - // jump here from JUMP_IF_FALSE - isn = ((isn_T *)instr->ga_data) + alt_idx; - isn->isn_arg.jump.jump_where = instr->ga_len; + if (!has_const_expr) + { + generate_ppconst(cctx, ppconst); + + // remember the type and drop it + --stack->ga_len; + type1 = ((type_T **)stack->ga_data)[stack->ga_len]; + + end_idx = instr->ga_len; + generate_JUMP(cctx, JUMP_ALWAYS, 0); + + // jump here from JUMP_IF_FALSE + isn = ((isn_T *)instr->ga_data) + alt_idx; + isn->isn_arg.jump.jump_where = instr->ga_len; + } // Check for the ":". p = skipwhite(*arg); @@ -4560,21 +4684,48 @@ compile_expr1(char_u **arg, cctx_T *cct } // evaluate the third expression + if (has_const_expr) + cctx->ctx_skip = save_skip == TRUE || const_value; *arg = skipwhite(p + 1); if (may_get_next_line(p + 1, arg, cctx) == FAIL) return FAIL; - - if (compile_expr1(arg, cctx) == FAIL) + if (compile_expr1(arg, cctx, ppconst) == FAIL) return FAIL; - // If the types differ, the result has a more generic type. - type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]; - common_type(type1, type2, &type2, cctx->ctx_type_list); - - // jump here from JUMP_ALWAYS - isn = ((isn_T *)instr->ga_data) + end_idx; - isn->isn_arg.jump.jump_where = instr->ga_len; - } + if (!has_const_expr) + { + generate_ppconst(cctx, ppconst); + + // If the types differ, the result has a more generic type. + type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + common_type(type1, type2, &type2, cctx->ctx_type_list); + + // jump here from JUMP_ALWAYS + isn = ((isn_T *)instr->ga_data) + end_idx; + isn->isn_arg.jump.jump_where = instr->ga_len; + } + + cctx->ctx_skip = save_skip; + } + return OK; +} + +/* + * Toplevel expression. + */ + static int +compile_expr0(char_u **arg, cctx_T *cctx) +{ + ppconst_T ppconst; + + CLEAR_FIELD(ppconst); + if (compile_expr1(arg, cctx, &ppconst) == FAIL) + { + clear_ppconst(&ppconst); + return FAIL; + } + if (generate_ppconst(cctx, &ppconst) == FAIL) + return FAIL; return OK; } @@ -4591,7 +4742,7 @@ compile_return(char_u *arg, int set_retu if (*p != NUL && *p != '|' && *p != '\n') { // compile return argument into instructions - if (compile_expr1(&p, cctx) == FAIL) + if (compile_expr0(&p, cctx) == FAIL) return NULL; stack_type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; @@ -5075,7 +5226,7 @@ compile_assignment(char_u *arg, exarg_T --cctx->ctx_locals.ga_len; instr_count = instr->ga_len; p = skipwhite(p + oplen); - r = compile_expr1(&p, cctx); + r = compile_expr0(&p, cctx); if (new_local) ++cctx->ctx_locals.ga_len; if (r == FAIL) @@ -5526,20 +5677,28 @@ compile_if(char_u *arg, cctx_T *cctx) { char_u *p = arg; garray_T *instr = &cctx->ctx_instr; + int instr_count = instr->ga_len; scope_T *scope; - typval_T tv; - - // compile "expr"; if we know it evaluates to FALSE skip the block - tv.v_type = VAR_UNKNOWN; - if (evaluate_const_expr1(&p, cctx, &tv) == OK) - cctx->ctx_skip = tv2bool(&tv) ? FALSE : TRUE; + ppconst_T ppconst; + + CLEAR_FIELD(ppconst); + if (compile_expr1(&p, cctx, &ppconst) == FAIL) + { + clear_ppconst(&ppconst); + return NULL; + } + if (instr->ga_len == instr_count && ppconst.pp_used == 1) + { + // The expression results in a constant. + // TODO: how about nesting? + cctx->ctx_skip = tv2bool(&ppconst.pp_tv[0]) ? FALSE : TRUE; + clear_ppconst(&ppconst); + } else + { + // Not a constant, generate instructions for the expression. cctx->ctx_skip = MAYBE; - clear_tv(&tv); - if (cctx->ctx_skip == MAYBE) - { - p = arg; - if (compile_expr1(&p, cctx) == FAIL) + if (generate_ppconst(cctx, &ppconst) == FAIL) return NULL; } @@ -5595,7 +5754,7 @@ compile_elseif(char_u *arg, cctx_T *cctx if (cctx->ctx_skip == MAYBE) { p = arg; - if (compile_expr1(&p, cctx) == FAIL) + if (compile_expr0(&p, cctx) == FAIL) return NULL; // "where" is set when ":elseif", "else" or ":endif" is found @@ -5754,7 +5913,7 @@ compile_for(char_u *arg, cctx_T *cctx) // compile "expr", it remains on the stack until "endfor" arg = p; - if (compile_expr1(&arg, cctx) == FAIL) + if (compile_expr0(&arg, cctx) == FAIL) { drop_scope(cctx); return NULL; @@ -5844,7 +6003,7 @@ compile_while(char_u *arg, cctx_T *cctx) scope->se_u.se_while.ws_top_label = instr->ga_len; // compile "expr" - if (compile_expr1(&p, cctx) == FAIL) + if (compile_expr0(&p, cctx) == FAIL) return NULL; // "while_end" is set when ":endwhile" is found @@ -6219,7 +6378,7 @@ compile_throw(char_u *arg, cctx_T *cctx { char_u *p = skipwhite(arg); - if (compile_expr1(&p, cctx) == FAIL) + if (compile_expr0(&p, cctx) == FAIL) return NULL; if (may_generate_2STRING(-1, cctx) == FAIL) return NULL; @@ -6243,7 +6402,7 @@ compile_mult_expr(char_u *arg, int cmdid for (;;) { - if (compile_expr1(&p, cctx) == FAIL) + if (compile_expr0(&p, cctx) == FAIL) return NULL; ++count; p = skipwhite(p); @@ -6305,7 +6464,7 @@ compile_exec(char_u *line, exarg_T *eap, ++count; } p += 2; - if (compile_expr1(&p, cctx) == FAIL) + if (compile_expr0(&p, cctx) == FAIL) return NULL; may_generate_2STRING(-1, cctx); ++count; @@ -6423,7 +6582,7 @@ compile_def_function(ufunc_T *ufunc, int ufunc->uf_def_arg_idx[i] = instr->ga_len; arg = ((char_u **)(ufunc->uf_def_args.ga_data))[i]; - if (compile_expr1(&arg, &cctx) == FAIL) + if (compile_expr0(&arg, &cctx) == FAIL) goto erret; // If no type specified use the type of the default value. @@ -6609,7 +6768,7 @@ compile_def_function(ufunc_T *ufunc, int if (ea.cmdidx == CMD_eval) { p = ea.cmd; - if (compile_expr1(&p, &cctx) == FAIL) + if (compile_expr0(&p, &cctx) == FAIL) goto erret; // drop the return value