0
0
mirror of https://github.com/vim/vim.git synced 2025-09-25 03:54:15 -04:00

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').
This commit is contained in:
Bram Moolenaar
2020-05-09 15:44:01 +02:00
parent 7d3664df90
commit a5565e4189
4 changed files with 445 additions and 274 deletions

View File

@@ -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)

View File

@@ -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')

View File

@@ -746,6 +746,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
719,
/**/
718,
/**/

View File

@@ -136,9 +136,7 @@ struct cctx_S {
static char e_var_notfound[] = N_("E1001: variable not found: %s");
static char e_syntax_at[] = N_("E1002: Syntax error at %s");
static int compile_expr1(char_u **arg, cctx_T *cctx);
static int compile_expr2(char_u **arg, cctx_T *cctx);
static int compile_expr3(char_u **arg, cctx_T *cctx);
static int compile_expr0(char_u **arg, cctx_T *cctx);
static void delete_def_function_contents(dfunc_T *dfunc);
static void arg_type_mismatch(type_T *expected, type_T *actual, int argidx);
static int check_type(type_T *expected, type_T *actual, int give_msg);
@@ -744,24 +742,14 @@ generate_two_op(cctx_T *cctx, char_u *op)
}
/*
* Generate an ISN_COMPARE* instruction with a boolean result.
* Get the instruction to use for comparing "type1" with "type2"
* Return ISN_DROP when failed.
*/
static int
generate_COMPARE(cctx_T *cctx, exptype_T exptype, int ic)
static isntype_T
get_compare_isn(exptype_T exptype, vartype_T type1, vartype_T type2)
{
isntype_T isntype = ISN_DROP;
isn_T *isn;
garray_T *stack = &cctx->ctx_type_stack;
vartype_T type1;
vartype_T type2;
RETURN_OK_IF_SKIP(cctx);
// Get the known type of the two items on the stack. If they are matching
// use a type-specific instruction. Otherwise fall back to runtime type
// checking.
type1 = ((type_T **)stack->ga_data)[stack->ga_len - 2]->tt_type;
type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]->tt_type;
if (type1 == VAR_UNKNOWN)
type1 = VAR_ANY;
if (type2 == VAR_UNKNOWN)
@@ -796,7 +784,7 @@ generate_COMPARE(cctx_T *cctx, exptype_T exptype, int ic)
{
semsg(_("E1037: Cannot use \"%s\" with %s"),
exptype == EXPR_IS ? "is" : "isnot" , vartype_name(type1));
return FAIL;
return ISN_DROP;
}
if (isntype == ISN_DROP
|| ((exptype != EXPR_EQUAL && exptype != EXPR_NEQUAL
@@ -809,8 +797,33 @@ generate_COMPARE(cctx_T *cctx, exptype_T exptype, int ic)
{
semsg(_("E1072: Cannot compare %s with %s"),
vartype_name(type1), vartype_name(type2));
return FAIL;
return ISN_DROP;
}
return isntype;
}
/*
* Generate an ISN_COMPARE* instruction with a boolean result.
*/
static int
generate_COMPARE(cctx_T *cctx, exptype_T exptype, int ic)
{
isntype_T isntype;
isn_T *isn;
garray_T *stack = &cctx->ctx_type_stack;
vartype_T type1;
vartype_T type2;
RETURN_OK_IF_SKIP(cctx);
// Get the known type of the two items on the stack. If they are matching
// use a type-specific instruction. Otherwise fall back to runtime type
// checking.
type1 = ((type_T **)stack->ga_data)[stack->ga_len - 2]->tt_type;
type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]->tt_type;
isntype = get_compare_isn(exptype, type1, type2);
if (isntype == ISN_DROP)
return FAIL;
if ((isn = generate_instr(cctx, isntype)) == NULL)
return FAIL;
@@ -2340,6 +2353,91 @@ may_get_next_line(char_u *whitep, char_u **arg, cctx_T *cctx)
return OK;
}
// Structure passed between the compile_expr* functions to keep track of
// constants that have been parsed but for which no code was produced yet. If
// possible expressions on these constants are applied at compile time. If
// that is not possible, the code to push the constants needs to be generated
// before other instructions.
typedef struct {
typval_T pp_tv[10]; // stack of ppconst constants
int pp_used; // active entries in pp_tv[]
} ppconst_T;
/*
* Generate a PUSH instruction for "tv".
* "tv" will be consumed or cleared.
* Nothing happens if "tv" is NULL or of type VAR_UNKNOWN;
*/
static int
generate_tv_PUSH(cctx_T *cctx, typval_T *tv)
{
if (tv != NULL)
{
switch (tv->v_type)
{
case VAR_UNKNOWN:
break;
case VAR_BOOL:
generate_PUSHBOOL(cctx, tv->vval.v_number);
break;
case VAR_SPECIAL:
generate_PUSHSPEC(cctx, tv->vval.v_number);
break;
case VAR_NUMBER:
generate_PUSHNR(cctx, tv->vval.v_number);
break;
#ifdef FEAT_FLOAT
case VAR_FLOAT:
generate_PUSHF(cctx, tv->vval.v_float);
break;
#endif
case VAR_BLOB:
generate_PUSHBLOB(cctx, tv->vval.v_blob);
tv->vval.v_blob = NULL;
break;
case VAR_STRING:
generate_PUSHS(cctx, tv->vval.v_string);
tv->vval.v_string = NULL;
break;
default:
iemsg("constant type not supported");
clear_tv(tv);
return FAIL;
}
tv->v_type = VAR_UNKNOWN;
}
return OK;
}
/*
* Generate code for any ppconst entries.
*/
static int
generate_ppconst(cctx_T *cctx, ppconst_T *ppconst)
{
int i;
int ret = OK;
for (i = 0; i < ppconst->pp_used; ++i)
if (generate_tv_PUSH(cctx, &ppconst->pp_tv[i]) == FAIL)
ret = FAIL;
ppconst->pp_used = 0;
return ret;
}
/*
* Clear ppconst constants. Used when failing.
*/
static void
clear_ppconst(ppconst_T *ppconst)
{
int i;
for (i = 0; i < ppconst->pp_used; ++i)
clear_tv(&ppconst->pp_tv[i]);
ppconst->pp_used = 0;
}
/*
* Generate an instruction to load script-local variable "name", without the
* leading "s:".
@@ -2525,12 +2623,6 @@ compile_load(char_u **arg, char_u *end_arg, cctx_T *cctx, int error)
gen_load = TRUE;
}
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.
@@ -2546,7 +2638,6 @@ compile_load(char_u **arg, char_u *end_arg, cctx_T *cctx, int error)
res = generate_funcref(cctx, name);
}
}
}
if (gen_load)
res = generate_LOAD(cctx, ISN_LOAD, idx, NULL, type);
if (gen_load_outer)
@@ -2588,7 +2679,7 @@ compile_arguments(char_u **arg, cctx_T *cctx, int *argcount)
return OK;
}
if (compile_expr1(&p, cctx) == FAIL)
if (compile_expr0(&p, cctx) == FAIL)
return FAIL;
++*argcount;
@@ -2621,7 +2712,12 @@ failret:
* BCALL / DCALL / UCALL
*/
static int
compile_call(char_u **arg, size_t varlen, cctx_T *cctx, int argcount_init)
compile_call(
char_u **arg,
size_t varlen,
cctx_T *cctx,
ppconst_T *ppconst,
int argcount_init)
{
char_u *name = *arg;
char_u *p;
@@ -2633,6 +2729,36 @@ compile_call(char_u **arg, size_t varlen, cctx_T *cctx, int argcount_init)
ufunc_T *ufunc;
int res = FAIL;
// we can evaluate "has('name')" at compile time
if (varlen == 3 && STRNCMP(*arg, "has", 3) == 0)
{
char_u *s = skipwhite(*arg + varlen + 1);
typval_T argvars[2];
argvars[0].v_type = VAR_UNKNOWN;
if (*s == '"')
(void)get_string_tv(&s, &argvars[0], TRUE);
else if (*s == '\'')
(void)get_lit_string_tv(&s, &argvars[0], TRUE);
s = skipwhite(s);
if (*s == ')' && argvars[0].v_type == VAR_STRING)
{
typval_T *tv = &ppconst->pp_tv[ppconst->pp_used];
*arg = s + 1;
argvars[1].v_type = VAR_UNKNOWN;
tv->v_type = VAR_NUMBER;
tv->vval.v_number = 0;
f_has(argvars, tv);
clear_tv(&argvars[0]);
++ppconst->pp_used;
return OK;
}
}
if (generate_ppconst(cctx, ppconst) == FAIL)
return FAIL;
if (varlen >= sizeof(namebuf))
{
semsg(_("E1011: name too long: %s"), name);
@@ -2791,7 +2917,7 @@ compile_list(char_u **arg, cctx_T *cctx)
p += STRLEN(p);
break;
}
if (compile_expr1(&p, cctx) == FAIL)
if (compile_expr0(&p, cctx) == FAIL)
break;
++count;
if (*p == ',')
@@ -2929,7 +3055,7 @@ compile_dict(char_u **arg, cctx_T *cctx, int literal)
{
isn_T *isn;
if (compile_expr1(arg, cctx) == FAIL)
if (compile_expr0(arg, cctx) == FAIL)
return FAIL;
// TODO: check type is string
isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1;
@@ -2974,7 +3100,7 @@ compile_dict(char_u **arg, cctx_T *cctx, int literal)
*arg = skipwhite(*arg);
}
if (compile_expr1(arg, cctx) == FAIL)
if (compile_expr0(arg, cctx) == FAIL)
return FAIL;
++count;
@@ -3626,90 +3752,7 @@ evaluate_const_expr1(char_u **arg, cctx_T *cctx, typval_T *tv)
return OK;
}
// Structure passed between the compile_expr* functions to keep track of
// constants that have been parsed but for which no code was produced yet. If
// possible expressions on these constants are applied at compile time. If
// that is not possible, the code to push the constants needs to be generated
// before other instructions.
typedef struct {
typval_T pp_tv[10]; // stack of ppconst constants
int pp_used; // active entries in pp_tv[]
} ppconst_T;
/*
* Generate a PUSH instruction for "tv".
* "tv" will be consumed or cleared.
* Nothing happens if "tv" is NULL or of type VAR_UNKNOWN;
*/
static int
generate_tv_PUSH(cctx_T *cctx, typval_T *tv)
{
if (tv != NULL)
{
switch (tv->v_type)
{
case VAR_UNKNOWN:
break;
case VAR_BOOL:
generate_PUSHBOOL(cctx, tv->vval.v_number);
break;
case VAR_SPECIAL:
generate_PUSHSPEC(cctx, tv->vval.v_number);
break;
case VAR_NUMBER:
generate_PUSHNR(cctx, tv->vval.v_number);
break;
#ifdef FEAT_FLOAT
case VAR_FLOAT:
generate_PUSHF(cctx, tv->vval.v_float);
break;
#endif
case VAR_BLOB:
generate_PUSHBLOB(cctx, tv->vval.v_blob);
tv->vval.v_blob = NULL;
break;
case VAR_STRING:
generate_PUSHS(cctx, tv->vval.v_string);
tv->vval.v_string = NULL;
break;
default:
iemsg("constant type not supported");
clear_tv(tv);
return FAIL;
}
tv->v_type = VAR_UNKNOWN;
}
return OK;
}
/*
* Generate code for any ppconst entries.
*/
static int
generate_ppconst(cctx_T *cctx, ppconst_T *ppconst)
{
int i;
int ret = OK;
for (i = 0; i < ppconst->pp_used; ++i)
if (generate_tv_PUSH(cctx, &ppconst->pp_tv[i]) == FAIL)
ret = FAIL;
ppconst->pp_used = 0;
return ret;
}
/*
* Clear ppconst constants. Used when failing.
*/
static void
clear_ppconst(ppconst_T *ppconst)
{
int i;
for (i = 0; i < ppconst->pp_used; ++i)
clear_tv(&ppconst->pp_tv[i]);
ppconst->pp_used = 0;
}
static int compile_expr3(char_u **arg, cctx_T *cctx, ppconst_T *ppconst);
/*
* Compile code to apply '-', '+' and '!'.
@@ -3825,7 +3868,7 @@ compile_subscript(
return FAIL;
}
// TODO: base value may not be the first argument
if (compile_call(arg, p - *arg, cctx, 1) == FAIL)
if (compile_call(arg, p - *arg, cctx, ppconst, 1) == FAIL)
return FAIL;
}
}
@@ -3841,7 +3884,7 @@ compile_subscript(
// TODO: more arguments
// TODO: dict member dict['name']
*arg = skipwhite(*arg + 1);
if (compile_expr1(arg, cctx) == FAIL)
if (compile_expr0(arg, cctx) == FAIL)
return FAIL;
if (**arg != ']')
@@ -3990,6 +4033,34 @@ compile_expr7(
ret = NOTDONE;
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]
*/
@@ -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,8 +4145,11 @@ 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.
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)
@@ -4083,9 +4157,6 @@ compile_expr7(
char_u *p;
int r;
if (generate_ppconst(cctx, ppconst) == FAIL)
return FAIL;
if (!eval_isnamec1(**arg))
{
semsg(_("E1015: Name expected: %s"), *arg);
@@ -4095,9 +4166,15 @@ compile_expr7(
// "name" or "name()"
p = to_name_end(*arg, TRUE);
if (*p == '(')
r = compile_call(arg, p - *arg, cctx, 0);
{
r = compile_call(arg, p - *arg, cctx, ppconst, 0);
}
else
{
if (generate_ppconst(cctx, ppconst) == FAIL)
return FAIL;
r = compile_load(arg, p, cctx, TRUE);
}
if (r == FAIL)
return FAIL;
}
@@ -4105,8 +4182,17 @@ compile_expr7(
// Handle following "[]", ".member", etc.
// Then deal with prefixed '-', '+' and '!', if not done already.
if (compile_subscript(arg, cctx, &start_leader, end_leader,
ppconst) == FAIL
|| compile_leader(cctx, start_leader, end_leader) == FAIL)
ppconst) == FAIL)
return FAIL;
if (ppconst->pp_used > 0)
{
// apply the '!', '-' and '+' before the constant
rettv = &ppconst->pp_tv[ppconst->pp_used - 1];
if (apply_leader(rettv, start_leader, end_leader) == FAIL)
return FAIL;
return OK;
}
if (compile_leader(cctx, start_leader, end_leader) == FAIL)
return FAIL;
return OK;
}
@@ -4117,10 +4203,7 @@ compile_expr7(
* % number modulo
*/
static int
compile_expr6(
char_u **arg,
cctx_T *cctx,
ppconst_T *ppconst)
compile_expr6(char_u **arg, cctx_T *cctx, ppconst_T *ppconst)
{
char_u *op;
int ppconst_used = ppconst->pp_used;
@@ -4191,21 +4274,15 @@ compile_expr6(
* .. string concatenation
*/
static int
compile_expr5(char_u **arg, cctx_T *cctx)
compile_expr5(char_u **arg, cctx_T *cctx, ppconst_T *ppconst)
{
char_u *op;
int oplen;
ppconst_T ppconst;
int ppconst_used = 0;
CLEAR_FIELD(ppconst);
int ppconst_used = ppconst->pp_used;
// get the first variable
if (compile_expr6(arg, cctx, &ppconst) == FAIL)
{
clear_ppconst(&ppconst);
if (compile_expr6(arg, cctx, ppconst) == FAIL)
return FAIL;
}
/*
* Repeat computing, until no "+", "-" or ".." is following.
@@ -4221,7 +4298,6 @@ compile_expr5(char_u **arg, cctx_T *cctx)
{
char_u buf[3];
clear_ppconst(&ppconst);
vim_strncpy(buf, op, oplen);
semsg(_(e_white_both), buf);
return FAIL;
@@ -4229,27 +4305,21 @@ compile_expr5(char_u **arg, cctx_T *cctx)
*arg = skipwhite(op + oplen);
if (may_get_next_line(op + oplen, arg, cctx) == FAIL)
{
clear_ppconst(&ppconst);
return FAIL;
}
// get the second expression
if (compile_expr6(arg, cctx, &ppconst) == FAIL)
{
clear_ppconst(&ppconst);
if (compile_expr6(arg, cctx, ppconst) == FAIL)
return FAIL;
}
if (ppconst.pp_used == ppconst_used + 2
if (ppconst->pp_used == ppconst_used + 2
&& (*op == '.'
? (ppconst.pp_tv[ppconst_used].v_type == VAR_STRING
&& ppconst.pp_tv[ppconst_used + 1].v_type == VAR_STRING)
: (ppconst.pp_tv[ppconst_used].v_type == VAR_NUMBER
&& ppconst.pp_tv[ppconst_used + 1].v_type == VAR_NUMBER)))
? (ppconst->pp_tv[ppconst_used].v_type == VAR_STRING
&& ppconst->pp_tv[ppconst_used + 1].v_type == VAR_STRING)
: (ppconst->pp_tv[ppconst_used].v_type == VAR_NUMBER
&& ppconst->pp_tv[ppconst_used + 1].v_type == VAR_NUMBER)))
{
typval_T *tv1 = &ppconst.pp_tv[ppconst_used];
typval_T *tv2 = &ppconst.pp_tv[ppconst_used + 1];
typval_T *tv1 = &ppconst->pp_tv[ppconst_used];
typval_T *tv2 = &ppconst->pp_tv[ppconst_used + 1];
// concat/subtract/add constant numbers
if (*op == '+')
@@ -4266,7 +4336,7 @@ compile_expr5(char_u **arg, cctx_T *cctx)
tv1->vval.v_string = alloc((int)(len1 + STRLEN(s2) + 1));
if (tv1->vval.v_string == NULL)
{
clear_ppconst(&ppconst);
clear_ppconst(ppconst);
return FAIL;
}
mch_memmove(tv1->vval.v_string, s1, len1);
@@ -4274,19 +4344,16 @@ compile_expr5(char_u **arg, cctx_T *cctx)
vim_free(s1);
vim_free(s2);
}
--ppconst.pp_used;
--ppconst->pp_used;
}
else
{
generate_ppconst(cctx, &ppconst);
generate_ppconst(cctx, ppconst);
if (*op == '.')
{
if (may_generate_2STRING(-2, cctx) == FAIL
|| may_generate_2STRING(-1, cctx) == FAIL)
{
clear_ppconst(&ppconst);
return FAIL;
}
generate_instr_drop(cctx, ISN_CONCAT, 1);
}
else
@@ -4294,9 +4361,6 @@ compile_expr5(char_u **arg, cctx_T *cctx)
}
}
// TODO: move to caller
generate_ppconst(cctx, &ppconst);
return OK;
}
@@ -4318,15 +4382,16 @@ compile_expr5(char_u **arg, cctx_T *cctx)
* COMPARE one of the compare instructions
*/
static int
compile_expr4(char_u **arg, cctx_T *cctx)
compile_expr4(char_u **arg, cctx_T *cctx, ppconst_T *ppconst)
{
exptype_T type = EXPR_UNKNOWN;
char_u *p;
int len = 2;
int type_is = FALSE;
int ppconst_used = ppconst->pp_used;
// get the first variable
if (compile_expr5(arg, cctx) == FAIL)
if (compile_expr5(arg, cctx, ppconst) == FAIL)
return FAIL;
p = skipwhite(*arg);
@@ -4369,10 +4434,34 @@ compile_expr4(char_u **arg, cctx_T *cctx)
if (may_get_next_line(p + len, arg, cctx) == FAIL)
return FAIL;
if (compile_expr5(arg, cctx) == FAIL)
if (compile_expr5(arg, cctx, ppconst) == FAIL)
return FAIL;
generate_COMPARE(cctx, type, ic);
if (ppconst->pp_used == ppconst_used + 2)
{
typval_T * tv1 = &ppconst->pp_tv[ppconst->pp_used - 2];
typval_T *tv2 = &ppconst->pp_tv[ppconst->pp_used - 1];
int ret;
// Both sides are a constant, compute the result now.
// First check for a valid combination of types, this is more
// strict than typval_compare().
if (get_compare_isn(type, tv1->v_type, tv2->v_type) == ISN_DROP)
ret = FAIL;
else
{
ret = typval_compare(tv1, tv2, type, ic);
tv1->v_type = VAR_BOOL;
tv1->vval.v_number = tv1->vval.v_number
? VVAL_TRUE : VVAL_FALSE;
clear_tv(tv2);
--ppconst->pp_used;
}
return ret;
}
generate_ppconst(cctx, ppconst);
return generate_COMPARE(cctx, type, ic);
}
return OK;
@@ -4382,7 +4471,12 @@ compile_expr4(char_u **arg, cctx_T *cctx)
* Compile || or &&.
*/
static int
compile_and_or(char_u **arg, cctx_T *cctx, char *op)
compile_and_or(
char_u **arg,
cctx_T *cctx,
char *op,
ppconst_T *ppconst,
int ppconst_used UNUSED)
{
char_u *p = skipwhite(*arg);
int opchar = *op;
@@ -4404,6 +4498,9 @@ compile_and_or(char_u **arg, cctx_T *cctx, char *op)
return FAIL;
}
// TODO: use ppconst if the value is a constant
generate_ppconst(cctx, ppconst);
if (ga_grow(&end_ga, 1) == FAIL)
{
ga_clear(&end_ga);
@@ -4419,14 +4516,15 @@ compile_and_or(char_u **arg, cctx_T *cctx, char *op)
if (may_get_next_line(p + 2, arg, cctx) == FAIL)
return FAIL;
if ((opchar == '|' ? compile_expr3(arg, cctx)
: compile_expr4(arg, cctx)) == FAIL)
if ((opchar == '|' ? compile_expr3(arg, cctx, ppconst)
: compile_expr4(arg, cctx, ppconst)) == FAIL)
{
ga_clear(&end_ga);
return FAIL;
}
p = skipwhite(*arg);
}
generate_ppconst(cctx, ppconst);
// Fill in the end label in all jumps.
while (end_ga.ga_len > 0)
@@ -4456,14 +4554,16 @@ compile_and_or(char_u **arg, cctx_T *cctx, char *op)
* end:
*/
static int
compile_expr3(char_u **arg, cctx_T *cctx)
compile_expr3(char_u **arg, cctx_T *cctx, ppconst_T *ppconst)
{
int ppconst_used = ppconst->pp_used;
// get the first variable
if (compile_expr4(arg, cctx) == FAIL)
if (compile_expr4(arg, cctx, ppconst) == FAIL)
return FAIL;
// || and && work almost the same
return compile_and_or(arg, cctx, "&&");
return compile_and_or(arg, cctx, "&&", ppconst, ppconst_used);
}
/*
@@ -4478,14 +4578,16 @@ compile_expr3(char_u **arg, cctx_T *cctx)
* end:
*/
static int
compile_expr2(char_u **arg, cctx_T *cctx)
compile_expr2(char_u **arg, cctx_T *cctx, ppconst_T *ppconst)
{
int ppconst_used = ppconst->pp_used;
// eval the first expression
if (compile_expr3(arg, cctx) == FAIL)
if (compile_expr3(arg, cctx, ppconst) == FAIL)
return FAIL;
// || and && work almost the same
return compile_and_or(arg, cctx, "||");
return compile_and_or(arg, cctx, "||", ppconst, ppconst_used);
}
/*
@@ -4500,12 +4602,13 @@ compile_expr2(char_u **arg, cctx_T *cctx)
* end:
*/
static int
compile_expr1(char_u **arg, cctx_T *cctx)
compile_expr1(char_u **arg, cctx_T *cctx, ppconst_T *ppconst)
{
char_u *p;
int ppconst_used = ppconst->pp_used;
// Evaluate the first expression.
if (compile_expr2(arg, cctx) == FAIL)
if (compile_expr2(arg, cctx, ppconst) == FAIL)
return FAIL;
p = skipwhite(*arg);
@@ -4518,6 +4621,9 @@ compile_expr1(char_u **arg, cctx_T *cctx)
isn_T *isn;
type_T *type1;
type_T *type2;
int has_const_expr = FALSE;
int const_value = FALSE;
int save_skip = cctx->ctx_skip;
if (!IS_WHITE_OR_NUL(**arg) || !IS_WHITE_OR_NUL(p[1]))
{
@@ -4525,16 +4631,33 @@ compile_expr1(char_u **arg, cctx_T *cctx)
return FAIL;
}
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;
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];
@@ -4545,6 +4668,7 @@ compile_expr1(char_u **arg, cctx_T *cctx)
// 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,13 +4684,18 @@ compile_expr1(char_u **arg, cctx_T *cctx)
}
// evaluate the third expression
if (has_const_expr)
cctx->ctx_skip = save_skip == TRUE || const_value;
*arg = skipwhite(p + 1);
if (may_get_next_line(p + 1, arg, cctx) == FAIL)
return FAIL;
if (compile_expr1(arg, cctx) == FAIL)
if (compile_expr1(arg, cctx, ppconst) == FAIL)
return FAIL;
if (!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);
@@ -4575,6 +4704,28 @@ compile_expr1(char_u **arg, cctx_T *cctx)
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_return_type, cctx_T *cctx)
if (*p != NUL && *p != '|' && *p != '\n')
{
// compile return argument into instructions
if (compile_expr1(&p, cctx) == FAIL)
if (compile_expr0(&p, cctx) == FAIL)
return NULL;
stack_type = ((type_T **)stack->ga_data)[stack->ga_len - 1];
@@ -5075,7 +5226,7 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
--cctx->ctx_locals.ga_len;
instr_count = instr->ga_len;
p = skipwhite(p + oplen);
r = compile_expr1(&p, cctx);
r = compile_expr0(&p, cctx);
if (new_local)
++cctx->ctx_locals.ga_len;
if (r == FAIL)
@@ -5526,20 +5677,28 @@ compile_if(char_u *arg, cctx_T *cctx)
{
char_u *p = arg;
garray_T *instr = &cctx->ctx_instr;
int instr_count = instr->ga_len;
scope_T *scope;
typval_T tv;
ppconst_T ppconst;
// compile "expr"; if we know it evaluates to FALSE skip the block
tv.v_type = VAR_UNKNOWN;
if (evaluate_const_expr1(&p, cctx, &tv) == OK)
cctx->ctx_skip = tv2bool(&tv) ? FALSE : TRUE;
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 @@ compile_elseif(char_u *arg, cctx_T *cctx)
if (cctx->ctx_skip == MAYBE)
{
p = arg;
if (compile_expr1(&p, cctx) == FAIL)
if (compile_expr0(&p, cctx) == FAIL)
return NULL;
// "where" is set when ":elseif", "else" or ":endif" is found
@@ -5754,7 +5913,7 @@ compile_for(char_u *arg, cctx_T *cctx)
// compile "expr", it remains on the stack until "endfor"
arg = p;
if (compile_expr1(&arg, cctx) == FAIL)
if (compile_expr0(&arg, cctx) == FAIL)
{
drop_scope(cctx);
return NULL;
@@ -5844,7 +6003,7 @@ compile_while(char_u *arg, cctx_T *cctx)
scope->se_u.se_while.ws_top_label = instr->ga_len;
// compile "expr"
if (compile_expr1(&p, cctx) == FAIL)
if (compile_expr0(&p, cctx) == FAIL)
return NULL;
// "while_end" is set when ":endwhile" is found
@@ -6219,7 +6378,7 @@ compile_throw(char_u *arg, cctx_T *cctx UNUSED)
{
char_u *p = skipwhite(arg);
if (compile_expr1(&p, cctx) == FAIL)
if (compile_expr0(&p, cctx) == FAIL)
return NULL;
if (may_generate_2STRING(-1, cctx) == FAIL)
return NULL;
@@ -6243,7 +6402,7 @@ compile_mult_expr(char_u *arg, int cmdidx, cctx_T *cctx)
for (;;)
{
if (compile_expr1(&p, cctx) == FAIL)
if (compile_expr0(&p, cctx) == FAIL)
return NULL;
++count;
p = skipwhite(p);
@@ -6305,7 +6464,7 @@ compile_exec(char_u *line, exarg_T *eap, cctx_T *cctx)
++count;
}
p += 2;
if (compile_expr1(&p, cctx) == FAIL)
if (compile_expr0(&p, cctx) == FAIL)
return NULL;
may_generate_2STRING(-1, cctx);
++count;
@@ -6423,7 +6582,7 @@ compile_def_function(ufunc_T *ufunc, int set_return_type, cctx_T *outer_cctx)
ufunc->uf_def_arg_idx[i] = instr->ga_len;
arg = ((char_u **)(ufunc->uf_def_args.ga_data))[i];
if (compile_expr1(&arg, &cctx) == FAIL)
if (compile_expr0(&arg, &cctx) == FAIL)
goto erret;
// If no type specified use the type of the default value.
@@ -6609,7 +6768,7 @@ compile_def_function(ufunc_T *ufunc, int set_return_type, cctx_T *outer_cctx)
if (ea.cmdidx == CMD_eval)
{
p = ea.cmd;
if (compile_expr1(&p, &cctx) == FAIL)
if (compile_expr0(&p, &cctx) == FAIL)
goto erret;
// drop the return value