patch 8.2.0719: Vim9: more expressions can be evaluated at compile time
Problem: Vim9: more expressions can be evaluated at compile time
Solution: Recognize has('name').
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 079d160..944efa1 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -136,9 +136,7 @@
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 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 @@
{
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 @@
{
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 @@
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 @@
}
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);
+ // "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);
- }
+ // 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 @@
return OK;
}
- if (compile_expr1(&p, cctx) == FAIL)
+ if (compile_expr0(&p, cctx) == FAIL)
return FAIL;
++*argcount;
@@ -2621,7 +2712,12 @@
* 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 @@
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 @@
p += STRLEN(p);
break;
}
- if (compile_expr1(&p, cctx) == FAIL)
+ if (compile_expr0(&p, cctx) == FAIL)
break;
++count;
if (*p == ',')
@@ -2929,7 +3055,7 @@
{
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 @@
*arg = skipwhite(*arg);
}
- if (compile_expr1(arg, cctx) == FAIL)
+ if (compile_expr0(arg, cctx) == FAIL)
return FAIL;
++count;
@@ -3626,90 +3752,7 @@
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 @@
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 @@
// 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 @@
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 @@
* 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 @@
}
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 @@
// "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 @@
// 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 @@
* % 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 @@
* .. 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 @@
{
char_u buf[3];
- clear_ppconst(&ppconst);
vim_strncpy(buf, op, oplen);
semsg(_(e_white_both), buf);
return FAIL;
@@ -4229,27 +4305,21 @@
*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 @@
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 @@
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 @@
}
}
- // TODO: move to caller
- generate_ppconst(cctx, &ppconst);
-
return OK;
}
@@ -4318,15 +4382,16 @@
* 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 @@
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 || 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 @@
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 @@
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 @@
* 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 @@
* 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 @@
* 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 @@
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 @@
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 @@
}
// 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);
- // jump here from JUMP_ALWAYS
- isn = ((isn_T *)instr->ga_data) + end_idx;
- isn->isn_arg.jump.jump_where = instr->ga_len;
+ // 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;
+}
+
+/*
* compile "return [expr]"
*/
static char_u *
@@ -4591,7 +4742,7 @@
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 @@
--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 @@
{
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;
- else
- cctx->ctx_skip = MAYBE;
- clear_tv(&tv);
- if (cctx->ctx_skip == MAYBE)
+ CLEAR_FIELD(ppconst);
+ if (compile_expr1(&p, cctx, &ppconst) == FAIL)
{
- p = arg;
- if (compile_expr1(&p, cctx) == 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;
+ if (generate_ppconst(cctx, &ppconst) == FAIL)
return NULL;
}
@@ -5595,7 +5754,7 @@
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 "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 @@
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 @@
{
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 @@
for (;;)
{
- if (compile_expr1(&p, cctx) == FAIL)
+ if (compile_expr0(&p, cctx) == FAIL)
return NULL;
++count;
p = skipwhite(p);
@@ -6305,7 +6464,7 @@
++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 @@
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 @@
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