summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2020-05-07 16:58:17 +0200
committerBram Moolenaar <Bram@vim.org>2020-05-07 16:58:17 +0200
commit61a89816996a0cad0d711c91e6e7cea9a9101211 (patch)
treec1256b63ced7f3efb60548685764189fdef61889
parentf391586f3f6a304d3bb0160ab75bdd9d758bd2da (diff)
downloadvim-git-61a89816996a0cad0d711c91e6e7cea9a9101211.tar.gz
patch 8.2.0708: Vim9: constant expressions are not simplifiedv8.2.0708
Problem: Vim9: constant expressions are not simplified. Solution: Simplify string concatenation.
-rw-r--r--src/testdir/test_vim9_disassemble.vim16
-rw-r--r--src/testdir/test_vim9_expr.vim6
-rw-r--r--src/version.c2
-rw-r--r--src/vim9compile.c859
4 files changed, 516 insertions, 367 deletions
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
index 5c4ecd290..5cb7383e9 100644
--- 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
diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim
index 68eb40d2e..df6860c77 100644
--- 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)
diff --git a/src/version.c b/src/version.c
index c004e33e5..14504da4f 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 */
/**/
+ 708,
+/**/
707,
/**/
706,
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 5e29cc015..5f6ce7492 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -1041,6 +1041,43 @@ generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type)
}
/*
+ * 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 *rettv)
}
/*
+ * 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;
+ typval_T tv;
- // TODO: Try parsing as a constant. If that works just one PUSH
+ // 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, cctx_T *cctx)
|| 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: