changeset 20328:445c2b2ea44b v8.2.0719

patch 8.2.0719: Vim9: more expressions can be evaluated at compile time Commit: https://github.com/vim/vim/commit/a5565e4189b7c4d3f03d1f5405fc64d5dc00f717 Author: Bram Moolenaar <Bram@vim.org> 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').
author Bram Moolenaar <Bram@vim.org>
date Sat, 09 May 2020 15:45:03 +0200
parents fdd4ca33af69
children 5658f8442037
files src/testdir/test_vim9_disassemble.vim src/testdir/test_vim9_expr.vim src/version.c src/vim9compile.c
diffstat 4 files changed, 455 insertions(+), 284 deletions(-) [+]
line wrap: on
line diff
--- 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)
--- 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')
--- 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,
--- 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