summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2020-05-09 15:44:01 +0200
committerBram Moolenaar <Bram@vim.org>2020-05-09 15:44:01 +0200
commita5565e4189b7c4d3f03d1f5405fc64d5dc00f717 (patch)
tree78a36791a9413f6de67c3f4930daab0925df65a8
parent7d3664df9008bd5f39f8a2555843d1b269946e4d (diff)
downloadvim-git-a5565e4189b7c4d3f03d1f5405fc64d5dc00f717.tar.gz
patch 8.2.0719: Vim9: more expressions can be evaluated at compile timev8.2.0719
Problem: Vim9: more expressions can be evaluated at compile time Solution: Recognize has('name').
-rw-r--r--src/testdir/test_vim9_disassemble.vim106
-rw-r--r--src/testdir/test_vim9_expr.vim2
-rw-r--r--src/version.c2
-rw-r--r--src/vim9compile.c619
4 files changed, 450 insertions, 279 deletions
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
index 7f4a7ddfc..724087d70 100644
--- 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 !='],
-
- ['111 == 222', 'COMPARENR =='],
- ['111 != 222', 'COMPARENR !='],
- ['111 > 222', 'COMPARENR >'],
- ['111 < 222', 'COMPARENR <'],
- ['111 >= 222', 'COMPARENR >='],
- ['111 <= 222', 'COMPARENR <='],
- ['111 =~ 222', 'COMPARENR =\~'],
- ['111 !~ 222', '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'],
-
- ['0z11 == 0z22', 'COMPAREBLOB =='],
- ['0z11 != 0z22', 'COMPAREBLOB !='],
- ['0z11 is 0z22', 'COMPAREBLOB is'],
- ['0z11 isnot 0z22', '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'],
-
- ['#{a:1} == #{x:2}', 'COMPAREDICT =='],
- ['#{a:1} != #{x:2}', 'COMPAREDICT !='],
- ['#{a:1} is #{x:2}', 'COMPAREDICT is'],
- ['#{a:1} isnot #{x:2}', 'COMPAREDICT isnot'],
+ ['true == isFalse', 'COMPAREBOOL =='],
+ ['true != isFalse', 'COMPAREBOOL !='],
+ ['v:none == isNull', 'COMPARESPECIAL =='],
+ ['v:none != isNull', 'COMPARESPECIAL !='],
+
+ ['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" != 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 == aBlob', 'COMPAREBLOB =='],
+ ['0z11 != aBlob', 'COMPAREBLOB !='],
+ ['0z11 is aBlob', 'COMPAREBLOB is'],
+ ['0z11 isnot aBlob', 'COMPAREBLOB isnot'],
+
+ ['[1, 2] == aList', 'COMPARELIST =='],
+ ['[1, 2] != aList', 'COMPARELIST !='],
+ ['[1, 2] is aList', 'COMPARELIST is'],
+ ['[1, 2] isnot aList', 'COMPARELIST 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
index 3ce8b1ea8..86d171573 100644
--- 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
index 5c54b428e..5903a00e2 100644
--- 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
index 079d16008..944efa197 100644
--- 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.
+ * Get the instruction to use for comparing "type1" with "type2"
+ * Return ISN_DROP when failed.
*/
- static int
-generate_COMPARE(cctx_T *cctx, exptype_T exptype, int ic)
+ 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 exptype, int ic)
{
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 exptype, int ic)
{
semsg(_("E1072: Cannot compare %s with %s"),
vartype_name(type1), vartype_name(type2));
- return FAIL;
+ 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 **arg, cctx_T *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;
+}
+
/*
* 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_arg, cctx_T *cctx, int error)
}
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 *cctx, int *argcount)
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, cctx_T *cctx, int argcount_init)
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, int literal)
{
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, int literal)
*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_T *cctx, typval_T *tv)
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 *cctx, char *op)
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 *cctx, char *op)
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 *cctx, char *op)
* 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 *cctx)
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 *cctx)
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];
+ if (!has_const_expr)
+ {
+ generate_ppconst(cctx, ppconst);
- end_idx = instr->ga_len;
- generate_JUMP(cctx, JUMP_ALWAYS, 0);
+ // remember the type and drop it
+ --stack->ga_len;
+ type1 = ((type_T **)stack->ga_data)[stack->ga_len];
- // jump here from JUMP_IF_FALSE
- isn = ((isn_T *)instr->ga_data) + alt_idx;
- isn->isn_arg.jump.jump_where = instr->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,25 +4684,52 @@ compile_expr1(char_u **arg, cctx_T *cctx)
}
// 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);
+ 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;
+ // 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;
+}
+
+/*
* compile "return [expr]"
*/
static char_u *
@@ -4591,7 +4742,7 @@ compile_return(char_u *arg, int set_return_type, cctx_T *cctx)
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 *eap, cmdidx_T cmdidx, cctx_T *cctx)
--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;
+ ppconst_T ppconst;
- // 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;
+ 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
- cctx->ctx_skip = MAYBE;
- clear_tv(&tv);
- if (cctx->ctx_skip == MAYBE)
{
- p = arg;
- if (compile_expr1(&p, cctx) == FAIL)
+ // Not a constant, generate instructions for the expression.
+ cctx->ctx_skip = MAYBE;
+ 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 UNUSED)
{
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 cmdidx, cctx_T *cctx)
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, cctx_T *cctx)
++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 set_return_type, cctx_T *outer_cctx)
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 set_return_type, cctx_T *outer_cctx)
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