changeset 20305:9a5e2370df0e v8.2.0708

patch 8.2.0708: Vim9: constant expressions are not simplified Commit: https://github.com/vim/vim/commit/61a89816996a0cad0d711c91e6e7cea9a9101211 Author: Bram Moolenaar <Bram@vim.org> Date: Thu May 7 16:58:17 2020 +0200 patch 8.2.0708: Vim9: constant expressions are not simplified Problem: Vim9: constant expressions are not simplified. Solution: Simplify string concatenation.
author Bram Moolenaar <Bram@vim.org>
date Thu, 07 May 2020 17:00:03 +0200
parents dd0b3edad4cf
children 9ca1046b0e91
files src/testdir/test_vim9_disassemble.vim src/testdir/test_vim9_expr.vim src/version.c src/vim9compile.c
diffstat 4 files changed, 517 insertions(+), 368 deletions(-) [+]
line wrap: on
line diff
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -996,9 +996,7 @@ def Test_disassemble_echomsg()
         '\d PUSHS "message".*' ..
         '\d ECHOMSG 2.*' ..
         "echoerr 'went' .. 'wrong'.*" ..
-        '\d PUSHS "went".*' ..
-        '\d PUSHS "wrong".*' ..
-        '\d CONCAT.*' ..
+        '\d PUSHS "wentwrong".*' ..
         '\d ECHOERR 1.*' ..
         '\d PUSHNR 0.*' ..
         '\d RETURN',
@@ -1037,4 +1035,16 @@ def Test_display_func()
         res3)
 enddef
 
+def s:ConcatStrings(): string
+  return 'one' .. 'two' .. 'three'
+enddef
+
+def Test_simplify_const_expr()
+  let res = execute('disass s:ConcatStrings')
+  assert_match('\<SNR>\d*_ConcatStrings.*' ..
+        '\d PUSHS "onetwothree".*' ..
+        '\d RETURN',
+        res)
+enddef
+
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
--- a/src/testdir/test_vim9_expr.vim
+++ b/src/testdir/test_vim9_expr.vim
@@ -485,8 +485,10 @@ def Test_expr5()
   assert_equal(-6, g:alsoint - g:anint)
 
   assert_equal('hello', 'hel' .. 'lo')
-  assert_equal('hello 123', 'hello ' ..
-					123)
+  " TODO: a line break here doesn't work
+"  assert_equal('hello 123', 'hello ' ..
+"					123)
+  assert_equal('hello 123', 'hello ' ..  123)
   assert_equal('123 hello', 123 .. ' hello')
   assert_equal('123456', 123 .. 456)
 
--- 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 */
 /**/
+    708,
+/**/
     707,
 /**/
     706,
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -1041,6 +1041,43 @@ generate_PUSHFUNC(cctx_T *cctx, char_u *
 }
 
 /*
+ * Generate a PUSH instruction for "tv".
+ */
+    static int
+generate_tv_PUSH(cctx_T *cctx, typval_T *tv)
+{
+    switch (tv->v_type)
+    {
+	case VAR_BOOL:
+	    generate_PUSHBOOL(cctx, tv->vval.v_number);
+	    break;
+	case VAR_SPECIAL:
+	    generate_PUSHSPEC(cctx, tv->vval.v_number);
+	    break;
+	case VAR_NUMBER:
+	    generate_PUSHNR(cctx, tv->vval.v_number);
+	    break;
+#ifdef FEAT_FLOAT
+	case VAR_FLOAT:
+	    generate_PUSHF(cctx, tv->vval.v_float);
+	    break;
+#endif
+	case VAR_BLOB:
+	    generate_PUSHBLOB(cctx, tv->vval.v_blob);
+	    tv->vval.v_blob = NULL;
+	    break;
+	case VAR_STRING:
+	    generate_PUSHS(cctx, tv->vval.v_string);
+	    tv->vval.v_string = NULL;
+	    break;
+	default:
+	    iemsg("constant type not supported");
+	    return FAIL;
+    }
+    return OK;
+}
+
+/*
  * Generate an ISN_STORE instruction.
  */
     static int
@@ -3181,6 +3218,452 @@ get_vim_constant(char_u **arg, typval_T 
 }
 
 /*
+ * Evaluate an expression that is a constant:
+ *  has(arg)
+ *
+ * Also handle:
+ *  ! in front		logical NOT
+ *
+ * Return FAIL if the expression is not a constant.
+ */
+    static int
+evaluate_const_expr7(char_u **arg, cctx_T *cctx UNUSED, typval_T *tv)
+{
+    typval_T	argvars[2];
+    char_u	*start_leader, *end_leader;
+    int		has_call = FALSE;
+
+    /*
+     * Skip '!' characters.  They are handled later.
+     * TODO: '-' and '+' characters
+     */
+    start_leader = *arg;
+    while (**arg == '!')
+	*arg = skipwhite(*arg + 1);
+    end_leader = *arg;
+
+    /*
+     * Recognize only a few types of constants for now.
+     */
+    if (STRNCMP("true", *arg, 4) == 0 && !ASCII_ISALNUM((*arg)[4]))
+    {
+	tv->v_type = VAR_BOOL;
+	tv->vval.v_number = VVAL_TRUE;
+	*arg += 4;
+	return OK;
+    }
+    if (STRNCMP("false", *arg, 5) == 0 && !ASCII_ISALNUM((*arg)[5]))
+    {
+	tv->v_type = VAR_BOOL;
+	tv->vval.v_number = VVAL_FALSE;
+	*arg += 5;
+	return OK;
+    }
+
+    if (STRNCMP("has(", *arg, 4) == 0)
+    {
+	has_call = TRUE;
+	*arg = skipwhite(*arg + 4);
+    }
+
+    if (**arg == '"')
+    {
+	if (get_string_tv(arg, tv, TRUE) == FAIL)
+	    return FAIL;
+    }
+    else if (**arg == '\'')
+    {
+	if (get_lit_string_tv(arg, tv, TRUE) == FAIL)
+	    return FAIL;
+    }
+    else
+	return FAIL;
+
+    if (has_call)
+    {
+	*arg = skipwhite(*arg);
+	if (**arg != ')')
+	    return FAIL;
+	*arg = *arg + 1;
+
+	argvars[0] = *tv;
+	argvars[1].v_type = VAR_UNKNOWN;
+	tv->v_type = VAR_NUMBER;
+	tv->vval.v_number = 0;
+	f_has(argvars, tv);
+	clear_tv(&argvars[0]);
+
+	while (start_leader < end_leader)
+	{
+	    if (*start_leader == '!')
+		tv->vval.v_number = !tv->vval.v_number;
+	    ++start_leader;
+	}
+    }
+    else if (end_leader > start_leader)
+    {
+	clear_tv(tv);
+	return FAIL;
+    }
+
+    return OK;
+}
+
+/*
+ *	*	number multiplication
+ *	/	number division
+ *	%	number modulo
+ */
+    static int
+evaluate_const_expr6(char_u **arg, cctx_T *cctx, typval_T *tv)
+{
+    char_u	*op;
+
+    // get the first variable
+    if (evaluate_const_expr7(arg, cctx, tv) == FAIL)
+	return FAIL;
+
+    /*
+     * Repeat computing, until no "*", "/" or "%" is following.
+     */
+    for (;;)
+    {
+	op = skipwhite(*arg);
+	if (*op != '*' && *op != '/' && *op != '%')
+	    break;
+	// TODO: not implemented yet.
+	clear_tv(tv);
+	return FAIL;
+    }
+    return OK;
+}
+
+/*
+ *      +	number addition
+ *      -	number subtraction
+ *      ..	string concatenation
+ */
+    static int
+evaluate_const_expr5(char_u **arg, cctx_T *cctx, typval_T *tv)
+{
+    char_u	*op;
+    int		oplen;
+
+    // get the first variable
+    if (evaluate_const_expr6(arg, cctx, tv) == FAIL)
+	return FAIL;
+
+    /*
+     * Repeat computing, until no "+", "-" or ".." is following.
+     */
+    for (;;)
+    {
+	op = skipwhite(*arg);
+	if (*op != '+' && *op != '-' && !(*op == '.' && (*(*arg + 1) == '.')))
+	    break;
+	oplen = (*op == '.' ? 2 : 1);
+
+	if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(op[oplen]))
+	{
+	    clear_tv(tv);
+	    return FAIL;
+	}
+
+	if (*op == '.' && tv->v_type == VAR_STRING)
+	{
+	    typval_T	tv2;
+	    size_t	len1;
+	    char_u	*s1, *s2;
+
+	    tv2.v_type = VAR_UNKNOWN;
+	    *arg = skipwhite(op + oplen);
+
+	    // TODO: what if we fail???
+	    if (may_get_next_line(op + oplen, arg, cctx) == FAIL)
+		return FAIL;
+
+	    // get the second variable
+	    if (evaluate_const_expr6(arg, cctx, &tv2) == FAIL)
+	    {
+		clear_tv(tv);
+		return FAIL;
+	    }
+	    if (tv2.v_type != VAR_STRING)
+	    {
+		clear_tv(tv);
+		clear_tv(&tv2);
+		return FAIL;
+	    }
+	    s1 = tv->vval.v_string;
+	    len1 = STRLEN(s1);
+	    s2 = tv2.vval.v_string;
+	    tv->vval.v_string = alloc((int)(len1 + STRLEN(s2) + 1));
+	    if (tv->vval.v_string == NULL)
+	    {
+		vim_free(s1);
+		vim_free(s2);
+		return FAIL;
+	    }
+	    mch_memmove(tv->vval.v_string, s1, len1);
+	    STRCPY(tv->vval.v_string + len1, s2);
+	    continue;
+	}
+
+	// TODO: Not implemented yet.
+	clear_tv(tv);
+	return FAIL;
+    }
+    return OK;
+}
+
+    static exptype_T
+get_compare_type(char_u *p, int *len, int *type_is)
+{
+    exptype_T	type = EXPR_UNKNOWN;
+    int		i;
+
+    switch (p[0])
+    {
+	case '=':   if (p[1] == '=')
+			type = EXPR_EQUAL;
+		    else if (p[1] == '~')
+			type = EXPR_MATCH;
+		    break;
+	case '!':   if (p[1] == '=')
+			type = EXPR_NEQUAL;
+		    else if (p[1] == '~')
+			type = EXPR_NOMATCH;
+		    break;
+	case '>':   if (p[1] != '=')
+		    {
+			type = EXPR_GREATER;
+			*len = 1;
+		    }
+		    else
+			type = EXPR_GEQUAL;
+		    break;
+	case '<':   if (p[1] != '=')
+		    {
+			type = EXPR_SMALLER;
+			*len = 1;
+		    }
+		    else
+			type = EXPR_SEQUAL;
+		    break;
+	case 'i':   if (p[1] == 's')
+		    {
+			// "is" and "isnot"; but not a prefix of a name
+			if (p[2] == 'n' && p[3] == 'o' && p[4] == 't')
+			    *len = 5;
+			i = p[*len];
+			if (!isalnum(i) && i != '_')
+			{
+			    type = *len == 2 ? EXPR_IS : EXPR_ISNOT;
+			    *type_is = TRUE;
+			}
+		    }
+		    break;
+    }
+    return type;
+}
+
+/*
+ * Only comparing strings is supported right now.
+ * expr5a == expr5b
+ */
+    static int
+evaluate_const_expr4(char_u **arg, cctx_T *cctx UNUSED, typval_T *tv)
+{
+    exptype_T	type = EXPR_UNKNOWN;
+    char_u	*p;
+    int		len = 2;
+    int		type_is = FALSE;
+
+    // get the first variable
+    if (evaluate_const_expr5(arg, cctx, tv) == FAIL)
+	return FAIL;
+
+    p = skipwhite(*arg);
+    type = get_compare_type(p, &len, &type_is);
+
+    /*
+     * If there is a comparative operator, use it.
+     */
+    if (type != EXPR_UNKNOWN)
+    {
+	typval_T    tv2;
+	char_u	    *s1, *s2;
+	char_u	    buf1[NUMBUFLEN], buf2[NUMBUFLEN];
+	int	    n;
+
+	// TODO:  Only string == string is supported now
+	if (tv->v_type != VAR_STRING)
+	    return FAIL;
+	if (type != EXPR_EQUAL)
+	    return FAIL;
+
+	// get the second variable
+	init_tv(&tv2);
+	*arg = skipwhite(p + len);
+	if (evaluate_const_expr5(arg, cctx, &tv2) == FAIL
+						   || tv2.v_type != VAR_STRING)
+	{
+	    clear_tv(&tv2);
+	    return FAIL;
+	}
+	s1 = tv_get_string_buf(tv, buf1);
+	s2 = tv_get_string_buf(&tv2, buf2);
+	n = STRCMP(s1, s2);
+	clear_tv(tv);
+	clear_tv(&tv2);
+	tv->v_type = VAR_BOOL;
+	tv->vval.v_number = n == 0 ? VVAL_TRUE : VVAL_FALSE;
+    }
+
+    return OK;
+}
+
+static int evaluate_const_expr3(char_u **arg, cctx_T *cctx, typval_T *tv);
+
+/*
+ * Compile constant || or &&.
+ */
+    static int
+evaluate_const_and_or(char_u **arg, cctx_T *cctx, char *op, typval_T *tv)
+{
+    char_u	*p = skipwhite(*arg);
+    int		opchar = *op;
+
+    if (p[0] == opchar && p[1] == opchar)
+    {
+	int	val = tv2bool(tv);
+
+	/*
+	 * Repeat until there is no following "||" or "&&"
+	 */
+	while (p[0] == opchar && p[1] == opchar)
+	{
+	    typval_T	tv2;
+
+	    if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[2]))
+		return FAIL;
+
+	    // eval the next expression
+	    *arg = skipwhite(p + 2);
+	    tv2.v_type = VAR_UNKNOWN;
+	    tv2.v_lock = 0;
+	    if ((opchar == '|' ? evaluate_const_expr3(arg, cctx, &tv2)
+			       : evaluate_const_expr4(arg, cctx, &tv2)) == FAIL)
+	    {
+		clear_tv(&tv2);
+		return FAIL;
+	    }
+	    if ((opchar == '&') == val)
+	    {
+		// false || tv2  or true && tv2: use tv2
+		clear_tv(tv);
+		*tv = tv2;
+		val = tv2bool(tv);
+	    }
+	    else
+		clear_tv(&tv2);
+	    p = skipwhite(*arg);
+	}
+    }
+
+    return OK;
+}
+
+/*
+ * Evaluate an expression that is a constant: expr4 && expr4 && expr4
+ * Return FAIL if the expression is not a constant.
+ */
+    static int
+evaluate_const_expr3(char_u **arg, cctx_T *cctx, typval_T *tv)
+{
+    // evaluate the first expression
+    if (evaluate_const_expr4(arg, cctx, tv) == FAIL)
+	return FAIL;
+
+    // || and && work almost the same
+    return evaluate_const_and_or(arg, cctx, "&&", tv);
+}
+
+/*
+ * Evaluate an expression that is a constant: expr3 || expr3 || expr3
+ * Return FAIL if the expression is not a constant.
+ */
+    static int
+evaluate_const_expr2(char_u **arg, cctx_T *cctx, typval_T *tv)
+{
+    // evaluate the first expression
+    if (evaluate_const_expr3(arg, cctx, tv) == FAIL)
+	return FAIL;
+
+    // || and && work almost the same
+    return evaluate_const_and_or(arg, cctx, "||", tv);
+}
+
+/*
+ * Evaluate an expression that is a constant: expr2 ? expr1 : expr1
+ * E.g. for "has('feature')".
+ * This does not produce error messages.  "tv" should be cleared afterwards.
+ * Return FAIL if the expression is not a constant.
+ */
+    static int
+evaluate_const_expr1(char_u **arg, cctx_T *cctx, typval_T *tv)
+{
+    char_u	*p;
+
+    // evaluate the first expression
+    if (evaluate_const_expr2(arg, cctx, tv) == FAIL)
+	return FAIL;
+
+    p = skipwhite(*arg);
+    if (*p == '?')
+    {
+	int		val = tv2bool(tv);
+	typval_T	tv2;
+
+	// require space before and after the ?
+	if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1]))
+	    return FAIL;
+
+	// evaluate the second expression; any type is accepted
+	clear_tv(tv);
+	*arg = skipwhite(p + 1);
+	if (evaluate_const_expr1(arg, cctx, tv) == FAIL)
+	    return FAIL;
+
+	// Check for the ":".
+	p = skipwhite(*arg);
+	if (*p != ':' || !VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1]))
+	    return FAIL;
+
+	// evaluate the third expression
+	*arg = skipwhite(p + 1);
+	tv2.v_type = VAR_UNKNOWN;
+	if (evaluate_const_expr1(arg, cctx, &tv2) == FAIL)
+	{
+	    clear_tv(&tv2);
+	    return FAIL;
+	}
+	if (val)
+	{
+	    // use the expr after "?"
+	    clear_tv(&tv2);
+	}
+	else
+	{
+	    // use the expr after ":"
+	    clear_tv(tv);
+	    *tv = tv2;
+	}
+    }
+    return OK;
+}
+
+/*
  * Compile code to apply '-', '+' and '!'.
  */
     static int
@@ -3525,34 +4008,8 @@ compile_expr7(char_u **arg, cctx_T *cctx
 	start_leader = end_leader;   // don't apply again below
 
 	// push constant
-	switch (rettv.v_type)
-	{
-	    case VAR_BOOL:
-		generate_PUSHBOOL(cctx, rettv.vval.v_number);
-		break;
-	    case VAR_SPECIAL:
-		generate_PUSHSPEC(cctx, rettv.vval.v_number);
-		break;
-	    case VAR_NUMBER:
-		generate_PUSHNR(cctx, rettv.vval.v_number);
-		break;
-#ifdef FEAT_FLOAT
-	    case VAR_FLOAT:
-		generate_PUSHF(cctx, rettv.vval.v_float);
-		break;
-#endif
-	    case VAR_BLOB:
-		generate_PUSHBLOB(cctx, rettv.vval.v_blob);
-		rettv.vval.v_blob = NULL;
-		break;
-	    case VAR_STRING:
-		generate_PUSHS(cctx, rettv.vval.v_string);
-		rettv.vval.v_string = NULL;
-		break;
-	    default:
-		iemsg("constant type missing");
-		return FAIL;
-	}
+	if (generate_tv_PUSH(cctx, &rettv) == FAIL)
+	    return FAIL;
     }
     else if (ret == NOTDONE)
     {
@@ -3682,57 +4139,6 @@ compile_expr5(char_u **arg, cctx_T *cctx
     return OK;
 }
 
-    static exptype_T
-get_compare_type(char_u *p, int *len, int *type_is)
-{
-    exptype_T	type = EXPR_UNKNOWN;
-    int		i;
-
-    switch (p[0])
-    {
-	case '=':   if (p[1] == '=')
-			type = EXPR_EQUAL;
-		    else if (p[1] == '~')
-			type = EXPR_MATCH;
-		    break;
-	case '!':   if (p[1] == '=')
-			type = EXPR_NEQUAL;
-		    else if (p[1] == '~')
-			type = EXPR_NOMATCH;
-		    break;
-	case '>':   if (p[1] != '=')
-		    {
-			type = EXPR_GREATER;
-			*len = 1;
-		    }
-		    else
-			type = EXPR_GEQUAL;
-		    break;
-	case '<':   if (p[1] != '=')
-		    {
-			type = EXPR_SMALLER;
-			*len = 1;
-		    }
-		    else
-			type = EXPR_SEQUAL;
-		    break;
-	case 'i':   if (p[1] == 's')
-		    {
-			// "is" and "isnot"; but not a prefix of a name
-			if (p[2] == 'n' && p[3] == 'o' && p[4] == 't')
-			    *len = 5;
-			i = p[*len];
-			if (!isalnum(i) && i != '_')
-			{
-			    type = *len == 2 ? EXPR_IS : EXPR_ISNOT;
-			    *type_is = TRUE;
-			}
-		    }
-		    break;
-    }
-    return type;
-}
-
 /*
  * expr5a == expr5b
  * expr5a =~ expr5b
@@ -3936,12 +4342,19 @@ compile_expr2(char_u **arg, cctx_T *cctx
 compile_expr1(char_u **arg,  cctx_T *cctx)
 {
     char_u	*p;
-
-    // TODO: Try parsing as a constant.  If that works just one PUSH
+    typval_T	tv;
+
+    // Evaluate the first expression.
+    // First try parsing as a constant.  If that works just one PUSH
     // instruction needs to be generated.
-
-    // evaluate the first expression
-    if (compile_expr2(arg, cctx) == FAIL)
+    tv.v_type = VAR_UNKNOWN;
+    p = *arg;
+    if (evaluate_const_expr2(&p, cctx, &tv) == OK)
+    {
+	*arg = p;
+	generate_tv_PUSH(cctx, &tv);
+    }
+    else if (compile_expr2(arg, cctx) == FAIL)
 	return FAIL;
 
     p = skipwhite(*arg);
@@ -4113,7 +4526,7 @@ compile_nested_function(exarg_T *eap, cc
 	    || generate_STORE(cctx, ISN_STORE, lvar->lv_idx, NULL) == FAIL)
 	return NULL;
 
-    // TODO: warning for trailing?
+    // TODO: warning for trailing text?
     return (char_u *)"";
 }
 
@@ -4926,284 +5339,6 @@ drop_scope(cctx_T *cctx)
 }
 
 /*
- * Evaluate an expression that is a constant:
- *  has(arg)
- *
- * Also handle:
- *  ! in front		logical NOT
- *
- * Return FAIL if the expression is not a constant.
- */
-    static int
-evaluate_const_expr7(char_u **arg, cctx_T *cctx UNUSED, typval_T *tv)
-{
-    typval_T	argvars[2];
-    char_u	*start_leader, *end_leader;
-    int		has_call = FALSE;
-
-    /*
-     * Skip '!' characters.  They are handled later.
-     */
-    start_leader = *arg;
-    while (**arg == '!')
-	*arg = skipwhite(*arg + 1);
-    end_leader = *arg;
-
-    /*
-     * Recognize only a few types of constants for now.
-     */
-    if (STRNCMP("true", *arg, 4) == 0 && !ASCII_ISALNUM((*arg)[4]))
-    {
-	tv->v_type = VAR_SPECIAL;
-	tv->vval.v_number = VVAL_TRUE;
-	*arg += 4;
-	return OK;
-    }
-    if (STRNCMP("false", *arg, 5) == 0 && !ASCII_ISALNUM((*arg)[5]))
-    {
-	tv->v_type = VAR_SPECIAL;
-	tv->vval.v_number = VVAL_FALSE;
-	*arg += 5;
-	return OK;
-    }
-
-    if (STRNCMP("has(", *arg, 4) == 0)
-    {
-	has_call = TRUE;
-	*arg = skipwhite(*arg + 4);
-    }
-
-    if (**arg == '"')
-    {
-	if (get_string_tv(arg, tv, TRUE) == FAIL)
-	    return FAIL;
-    }
-    else if (**arg == '\'')
-    {
-	if (get_lit_string_tv(arg, tv, TRUE) == FAIL)
-	    return FAIL;
-    }
-    else
-	return FAIL;
-
-    if (has_call)
-    {
-	*arg = skipwhite(*arg);
-	if (**arg != ')')
-	    return FAIL;
-	*arg = *arg + 1;
-
-	argvars[0] = *tv;
-	argvars[1].v_type = VAR_UNKNOWN;
-	tv->v_type = VAR_NUMBER;
-	tv->vval.v_number = 0;
-	f_has(argvars, tv);
-	clear_tv(&argvars[0]);
-
-	while (start_leader < end_leader)
-	{
-	    if (*start_leader == '!')
-		tv->vval.v_number = !tv->vval.v_number;
-	    ++start_leader;
-	}
-    }
-
-    return OK;
-}
-
-    static int
-evaluate_const_expr4(char_u **arg, cctx_T *cctx UNUSED, typval_T *tv)
-{
-    exptype_T	type = EXPR_UNKNOWN;
-    char_u	*p;
-    int		len = 2;
-    int		type_is = FALSE;
-
-    // get the first variable
-    if (evaluate_const_expr7(arg, cctx, tv) == FAIL)
-	return FAIL;
-
-    p = skipwhite(*arg);
-    type = get_compare_type(p, &len, &type_is);
-
-    /*
-     * If there is a comparative operator, use it.
-     */
-    if (type != EXPR_UNKNOWN)
-    {
-	typval_T    tv2;
-	char_u	    *s1, *s2;
-	char_u	    buf1[NUMBUFLEN], buf2[NUMBUFLEN];
-	int	    n;
-
-	// TODO:  Only string == string is supported now
-	if (tv->v_type != VAR_STRING)
-	    return FAIL;
-	if (type != EXPR_EQUAL)
-	    return FAIL;
-
-	// get the second variable
-	init_tv(&tv2);
-	*arg = skipwhite(p + len);
-	if (evaluate_const_expr7(arg, cctx, &tv2) == FAIL
-						   || tv2.v_type != VAR_STRING)
-	{
-	    clear_tv(&tv2);
-	    return FAIL;
-	}
-	s1 = tv_get_string_buf(tv, buf1);
-	s2 = tv_get_string_buf(&tv2, buf2);
-	n = STRCMP(s1, s2);
-	clear_tv(tv);
-	clear_tv(&tv2);
-	tv->v_type = VAR_BOOL;
-	tv->vval.v_number = n == 0 ? VVAL_TRUE : VVAL_FALSE;
-    }
-
-    return OK;
-}
-
-static int evaluate_const_expr3(char_u **arg, cctx_T *cctx, typval_T *tv);
-
-/*
- * Compile constant || or &&.
- */
-    static int
-evaluate_const_and_or(char_u **arg, cctx_T *cctx, char *op, typval_T *tv)
-{
-    char_u	*p = skipwhite(*arg);
-    int		opchar = *op;
-
-    if (p[0] == opchar && p[1] == opchar)
-    {
-	int	val = tv2bool(tv);
-
-	/*
-	 * Repeat until there is no following "||" or "&&"
-	 */
-	while (p[0] == opchar && p[1] == opchar)
-	{
-	    typval_T	tv2;
-
-	    if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[2]))
-		return FAIL;
-
-	    // eval the next expression
-	    *arg = skipwhite(p + 2);
-	    tv2.v_type = VAR_UNKNOWN;
-	    tv2.v_lock = 0;
-	    if ((opchar == '|' ? evaluate_const_expr3(arg, cctx, &tv2)
-			       : evaluate_const_expr4(arg, cctx, &tv2)) == FAIL)
-	    {
-		clear_tv(&tv2);
-		return FAIL;
-	    }
-	    if ((opchar == '&') == val)
-	    {
-		// false || tv2  or true && tv2: use tv2
-		clear_tv(tv);
-		*tv = tv2;
-		val = tv2bool(tv);
-	    }
-	    else
-		clear_tv(&tv2);
-	    p = skipwhite(*arg);
-	}
-    }
-
-    return OK;
-}
-
-/*
- * Evaluate an expression that is a constant: expr4 && expr4 && expr4
- * Return FAIL if the expression is not a constant.
- */
-    static int
-evaluate_const_expr3(char_u **arg, cctx_T *cctx, typval_T *tv)
-{
-    // evaluate the first expression
-    if (evaluate_const_expr4(arg, cctx, tv) == FAIL)
-	return FAIL;
-
-    // || and && work almost the same
-    return evaluate_const_and_or(arg, cctx, "&&", tv);
-}
-
-/*
- * Evaluate an expression that is a constant: expr3 || expr3 || expr3
- * Return FAIL if the expression is not a constant.
- */
-    static int
-evaluate_const_expr2(char_u **arg, cctx_T *cctx, typval_T *tv)
-{
-    // evaluate the first expression
-    if (evaluate_const_expr3(arg, cctx, tv) == FAIL)
-	return FAIL;
-
-    // || and && work almost the same
-    return evaluate_const_and_or(arg, cctx, "||", tv);
-}
-
-/*
- * Evaluate an expression that is a constant: expr2 ? expr1 : expr1
- * E.g. for "has('feature')".
- * This does not produce error messages.  "tv" should be cleared afterwards.
- * Return FAIL if the expression is not a constant.
- */
-    static int
-evaluate_const_expr1(char_u **arg, cctx_T *cctx, typval_T *tv)
-{
-    char_u	*p;
-
-    // evaluate the first expression
-    if (evaluate_const_expr2(arg, cctx, tv) == FAIL)
-	return FAIL;
-
-    p = skipwhite(*arg);
-    if (*p == '?')
-    {
-	int		val = tv2bool(tv);
-	typval_T	tv2;
-
-	// require space before and after the ?
-	if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1]))
-	    return FAIL;
-
-	// evaluate the second expression; any type is accepted
-	clear_tv(tv);
-	*arg = skipwhite(p + 1);
-	if (evaluate_const_expr1(arg, cctx, tv) == FAIL)
-	    return FAIL;
-
-	// Check for the ":".
-	p = skipwhite(*arg);
-	if (*p != ':' || !VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1]))
-	    return FAIL;
-
-	// evaluate the third expression
-	*arg = skipwhite(p + 1);
-	tv2.v_type = VAR_UNKNOWN;
-	if (evaluate_const_expr1(arg, cctx, &tv2) == FAIL)
-	{
-	    clear_tv(&tv2);
-	    return FAIL;
-	}
-	if (val)
-	{
-	    // use the expr after "?"
-	    clear_tv(&tv2);
-	}
-	else
-	{
-	    // use the expr after ":"
-	    clear_tv(tv);
-	    *tv = tv2;
-	}
-    }
-    return OK;
-}
-
-/*
  * compile "if expr"
  *
  * "if expr" Produces instructions: