diff options
author | Bram Moolenaar <Bram@vim.org> | 2020-03-03 21:53:32 +0100 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2020-03-03 21:53:32 +0100 |
commit | 080457c02d51f87e7d61ebd3e3aeef4468be939c (patch) | |
tree | 36adfda3a4226263e7b8f3b88622790d87880410 | |
parent | 6d69bf602b4ebdb195f02953a0b33c91ec08e599 (diff) | |
download | vim-git-080457c02d51f87e7d61ebd3e3aeef4468be939c.tar.gz |
patch 8.2.0350: Vim9: expression tests don't use recognized constantsv8.2.0350
Problem: Vim9: expression tests don't use recognized constants.
Solution: Recognize "true" and "false" as constants. Make skipping work for
assignment and expression evaluation.
-rw-r--r-- | src/version.c | 2 | ||||
-rw-r--r-- | src/vim9compile.c | 366 |
2 files changed, 232 insertions, 136 deletions
diff --git a/src/version.c b/src/version.c index 9c5fca8b2..c0e1b9146 100644 --- a/src/version.c +++ b/src/version.c @@ -739,6 +739,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 350, +/**/ 349, /**/ 348, diff --git a/src/vim9compile.c b/src/vim9compile.c index 1f61566ab..c9f72f52b 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -260,6 +260,9 @@ get_dict_type(type_T *member_type, garray_T *type_list) ///////////////////////////////////////////////////////////////////// // Following generate_ functions expect the caller to call ga_grow(). +#define RETURN_NULL_IF_SKIP(cctx) if (cctx->ctx_skip == TRUE) return NULL +#define RETURN_OK_IF_SKIP(cctx) if (cctx->ctx_skip == TRUE) return OK + /* * Generate an instruction without arguments. * Returns a pointer to the new instruction, NULL if failed. @@ -270,6 +273,7 @@ generate_instr(cctx_T *cctx, isntype_T isn_type) garray_T *instr = &cctx->ctx_instr; isn_T *isn; + RETURN_NULL_IF_SKIP(cctx); if (ga_grow(instr, 1) == FAIL) return NULL; isn = ((isn_T *)instr->ga_data) + instr->ga_len; @@ -290,6 +294,7 @@ generate_instr_drop(cctx_T *cctx, isntype_T isn_type, int drop) { garray_T *stack = &cctx->ctx_type_stack; + RETURN_NULL_IF_SKIP(cctx); stack->ga_len -= drop; return generate_instr(cctx, isn_type); } @@ -364,6 +369,8 @@ generate_two_op(cctx_T *cctx, char_u *op) vartype_T vartype; isn_T *isn; + 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. @@ -461,6 +468,8 @@ generate_COMPARE(cctx_T *cctx, exptype_T exptype, int ic) 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. @@ -536,6 +545,7 @@ generate_2BOOL(cctx_T *cctx, int invert) isn_T *isn; garray_T *stack = &cctx->ctx_type_stack; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr(cctx, ISN_2BOOL)) == NULL) return FAIL; isn->isn_arg.number = invert; @@ -552,6 +562,7 @@ generate_TYPECHECK(cctx_T *cctx, type_T *vartype, int offset) isn_T *isn; garray_T *stack = &cctx->ctx_type_stack; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr(cctx, ISN_CHECKTYPE)) == NULL) return FAIL; isn->isn_arg.type.ct_type = vartype->tt_type; // TODO: whole type @@ -571,6 +582,7 @@ generate_PUSHNR(cctx_T *cctx, varnumber_T number) { isn_T *isn; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr_type(cctx, ISN_PUSHNR, &t_number)) == NULL) return FAIL; isn->isn_arg.number = number; @@ -586,6 +598,7 @@ generate_PUSHBOOL(cctx_T *cctx, varnumber_T number) { isn_T *isn; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr_type(cctx, ISN_PUSHBOOL, &t_bool)) == NULL) return FAIL; isn->isn_arg.number = number; @@ -601,6 +614,7 @@ generate_PUSHSPEC(cctx_T *cctx, varnumber_T number) { isn_T *isn; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr_type(cctx, ISN_PUSHSPEC, &t_special)) == NULL) return FAIL; isn->isn_arg.number = number; @@ -617,6 +631,7 @@ generate_PUSHF(cctx_T *cctx, float_T fnumber) { isn_T *isn; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr_type(cctx, ISN_PUSHF, &t_float)) == NULL) return FAIL; isn->isn_arg.fnumber = fnumber; @@ -634,6 +649,7 @@ generate_PUSHS(cctx_T *cctx, char_u *str) { isn_T *isn; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr_type(cctx, ISN_PUSHS, &t_string)) == NULL) return FAIL; isn->isn_arg.string = str; @@ -650,6 +666,7 @@ generate_PUSHCHANNEL(cctx_T *cctx, channel_T *channel) { isn_T *isn; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr_type(cctx, ISN_PUSHCHANNEL, &t_channel)) == NULL) return FAIL; isn->isn_arg.channel = channel; @@ -666,6 +683,7 @@ generate_PUSHJOB(cctx_T *cctx, job_T *job) { isn_T *isn; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr_type(cctx, ISN_PUSHJOB, &t_channel)) == NULL) return FAIL; isn->isn_arg.job = job; @@ -682,6 +700,7 @@ generate_PUSHBLOB(cctx_T *cctx, blob_T *blob) { isn_T *isn; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr_type(cctx, ISN_PUSHBLOB, &t_blob)) == NULL) return FAIL; isn->isn_arg.blob = blob; @@ -698,6 +717,7 @@ generate_PUSHFUNC(cctx_T *cctx, char_u *name) { isn_T *isn; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr_type(cctx, ISN_PUSHFUNC, &t_func_void)) == NULL) return FAIL; isn->isn_arg.string = name; @@ -714,6 +734,7 @@ generate_PUSHPARTIAL(cctx_T *cctx, partial_T *part) { isn_T *isn; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr_type(cctx, ISN_PUSHPARTIAL, &t_partial_any)) == NULL) return FAIL; @@ -730,6 +751,7 @@ generate_STORE(cctx_T *cctx, isntype_T isn_type, int idx, char_u *name) { isn_T *isn; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr_drop(cctx, isn_type, 1)) == NULL) return FAIL; if (name != NULL) @@ -748,6 +770,7 @@ generate_STORENR(cctx_T *cctx, int idx, varnumber_T value) { isn_T *isn; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr(cctx, ISN_STORENR)) == NULL) return FAIL; isn->isn_arg.storenr.str_idx = idx; @@ -764,6 +787,7 @@ generate_STOREOPT(cctx_T *cctx, char_u *name, int opt_flags) { isn_T *isn; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr(cctx, ISN_STOREOPT)) == NULL) return FAIL; isn->isn_arg.storeopt.so_name = vim_strsave(name); @@ -785,6 +809,7 @@ generate_LOAD( { isn_T *isn; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr_type(cctx, isn_type, type)) == NULL) return FAIL; if (name != NULL) @@ -807,6 +832,7 @@ generate_LOADV( // load v:var int vidx = find_vim_var(name); + RETURN_OK_IF_SKIP(cctx); if (vidx < 0) { if (error) @@ -831,6 +857,7 @@ generate_OLDSCRIPT( { isn_T *isn; + RETURN_OK_IF_SKIP(cctx); if (isn_type == ISN_LOADS) isn = generate_instr_type(cctx, isn_type, type); else @@ -856,6 +883,7 @@ generate_VIM9SCRIPT( { isn_T *isn; + RETURN_OK_IF_SKIP(cctx); if (isn_type == ISN_LOADSCRIPT) isn = generate_instr_type(cctx, isn_type, type); else @@ -879,6 +907,7 @@ generate_NEWLIST(cctx_T *cctx, int count) type_T *type; type_T *member; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr(cctx, ISN_NEWLIST)) == NULL) return FAIL; isn->isn_arg.number = count; @@ -915,6 +944,7 @@ generate_NEWDICT(cctx_T *cctx, int count) type_T *type; type_T *member; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr(cctx, ISN_NEWDICT)) == NULL) return FAIL; isn->isn_arg.number = count; @@ -948,6 +978,7 @@ generate_FUNCREF(cctx_T *cctx, int dfunc_idx) isn_T *isn; garray_T *stack = &cctx->ctx_type_stack; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr(cctx, ISN_FUNCREF)) == NULL) return FAIL; isn->isn_arg.number = dfunc_idx; @@ -970,6 +1001,7 @@ generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where) isn_T *isn; garray_T *stack = &cctx->ctx_type_stack; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr(cctx, ISN_JUMP)) == NULL) return FAIL; isn->isn_arg.jump.jump_when = when; @@ -987,6 +1019,7 @@ generate_FOR(cctx_T *cctx, int loop_idx) isn_T *isn; garray_T *stack = &cctx->ctx_type_stack; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr(cctx, ISN_FOR)) == NULL) return FAIL; isn->isn_arg.forloop.for_idx = loop_idx; @@ -1012,6 +1045,7 @@ generate_BCALL(cctx_T *cctx, int func_idx, int argcount) type_T *argtypes[MAX_FUNC_ARGS]; int i; + RETURN_OK_IF_SKIP(cctx); if (check_internal_func(func_idx, argcount) == FAIL) return FAIL; @@ -1045,6 +1079,7 @@ generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount) int regular_args = ufunc->uf_args.ga_len; int argcount = pushed_argcount; + RETURN_OK_IF_SKIP(cctx); if (argcount > regular_args && !has_varargs(ufunc)) { semsg(_(e_toomanyarg), ufunc->uf_name); @@ -1105,6 +1140,7 @@ generate_UCALL(cctx_T *cctx, char_u *name, int argcount) isn_T *isn; garray_T *stack = &cctx->ctx_type_stack; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr(cctx, ISN_UCALL)) == NULL) return FAIL; isn->isn_arg.ufunc.cuf_name = vim_strsave(name); @@ -1129,6 +1165,7 @@ generate_PCALL(cctx_T *cctx, int argcount, int at_top) isn_T *isn; garray_T *stack = &cctx->ctx_type_stack; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr(cctx, ISN_PCALL)) == NULL) return FAIL; isn->isn_arg.pfunc.cpf_top = at_top; @@ -1152,6 +1189,7 @@ generate_MEMBER(cctx_T *cctx, char_u *name, size_t len) garray_T *stack = &cctx->ctx_type_stack; type_T *type; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr(cctx, ISN_MEMBER)) == NULL) return FAIL; isn->isn_arg.string = vim_strnsave(name, (int)len); @@ -1178,6 +1216,7 @@ generate_ECHO(cctx_T *cctx, int with_white, int count) { isn_T *isn; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr_drop(cctx, ISN_ECHO, count)) == NULL) return FAIL; isn->isn_arg.echo.echo_with_white = with_white; @@ -1206,6 +1245,7 @@ generate_EXEC(cctx_T *cctx, char_u *line) { isn_T *isn; + RETURN_OK_IF_SKIP(cctx); if ((isn = generate_instr(cctx, ISN_EXEC)) == NULL) return FAIL; isn->isn_arg.string = vim_strsave(line); @@ -2878,37 +2918,12 @@ compile_expr5(char_u **arg, cctx_T *cctx) return OK; } -/* - * expr5a == expr5b - * expr5a =~ expr5b - * expr5a != expr5b - * expr5a !~ expr5b - * expr5a > expr5b - * expr5a >= expr5b - * expr5a < expr5b - * expr5a <= expr5b - * expr5a is expr5b - * expr5a isnot expr5b - * - * Produces instructions: - * EVAL expr5a Push result of "expr5a" - * EVAL expr5b Push result of "expr5b" - * COMPARE one of the compare instructions - */ - static int -compile_expr4(char_u **arg, cctx_T *cctx) + static exptype_T +get_compare_type(char_u *p, int *len, int *type_is) { exptype_T type = EXPR_UNKNOWN; - char_u *p; - int len = 2; int i; - int type_is = FALSE; - - // get the first variable - if (compile_expr5(arg, cctx) == FAIL) - return FAIL; - p = skipwhite(*arg); switch (p[0]) { case '=': if (p[1] == '=') @@ -2924,7 +2939,7 @@ compile_expr4(char_u **arg, cctx_T *cctx) case '>': if (p[1] != '=') { type = EXPR_GREATER; - len = 1; + *len = 1; } else type = EXPR_GEQUAL; @@ -2932,7 +2947,7 @@ compile_expr4(char_u **arg, cctx_T *cctx) case '<': if (p[1] != '=') { type = EXPR_SMALLER; - len = 1; + *len = 1; } else type = EXPR_SEQUAL; @@ -2941,16 +2956,50 @@ compile_expr4(char_u **arg, cctx_T *cctx) { // "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]; + *len = 5; + i = p[*len]; if (!isalnum(i) && i != '_') { - type = len == 2 ? EXPR_IS : EXPR_ISNOT; - type_is = TRUE; + type = *len == 2 ? EXPR_IS : EXPR_ISNOT; + *type_is = TRUE; } } break; } + return type; +} + +/* + * expr5a == expr5b + * expr5a =~ expr5b + * expr5a != expr5b + * expr5a !~ expr5b + * expr5a > expr5b + * expr5a >= expr5b + * expr5a < expr5b + * expr5a <= expr5b + * expr5a is expr5b + * expr5a isnot expr5b + * + * Produces instructions: + * EVAL expr5a Push result of "expr5a" + * EVAL expr5b Push result of "expr5b" + * COMPARE one of the compare instructions + */ + static int +compile_expr4(char_u **arg, cctx_T *cctx) +{ + exptype_T type = EXPR_UNKNOWN; + char_u *p; + int len = 2; + int type_is = FALSE; + + // get the first variable + if (compile_expr5(arg, cctx) == FAIL) + return FAIL; + + p = skipwhite(*arg); + type = get_compare_type(p, &len, &type_is); /* * If there is a comparative operator, use it. @@ -3324,128 +3373,131 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) if (name == NULL) return NULL; - if (*arg == '&') - { - int cc; - long numval; - char_u *stringval = NULL; - - dest = dest_option; - if (cmdidx == CMD_const) - { - emsg(_(e_const_option)); - return NULL; - } - if (is_decl) - { - semsg(_("E1052: Cannot declare an option: %s"), arg); - goto theend; - } - p = arg; - p = find_option_end(&p, &opt_flags); - if (p == NULL) - { - emsg(_(e_letunexp)); - return NULL; - } - cc = *p; - *p = NUL; - opt_type = get_option_value(arg + 1, &numval, &stringval, opt_flags); - *p = cc; - if (opt_type == -3) - { - semsg(_(e_unknown_option), *arg); - return NULL; - } - if (opt_type == -2 || opt_type == 0) - type = &t_string; - else - type = &t_number; // both number and boolean option - } - else if (*arg == '$') + if (cctx->ctx_skip != TRUE) { - dest = dest_env; - if (is_decl) + if (*arg == '&') { - semsg(_("E1065: Cannot declare an environment variable: %s"), name); - goto theend; - } - } - else if (*arg == '@') - { - if (!valid_yank_reg(arg[1], TRUE)) - { - emsg_invreg(arg[1]); - return FAIL; + int cc; + long numval; + char_u *stringval = NULL; + + dest = dest_option; + if (cmdidx == CMD_const) + { + emsg(_(e_const_option)); + return NULL; + } + if (is_decl) + { + semsg(_("E1052: Cannot declare an option: %s"), arg); + goto theend; + } + p = arg; + p = find_option_end(&p, &opt_flags); + if (p == NULL) + { + emsg(_(e_letunexp)); + return NULL; + } + cc = *p; + *p = NUL; + opt_type = get_option_value(arg + 1, &numval, &stringval, opt_flags); + *p = cc; + if (opt_type == -3) + { + semsg(_(e_unknown_option), *arg); + return NULL; + } + if (opt_type == -2 || opt_type == 0) + type = &t_string; + else + type = &t_number; // both number and boolean option } - dest = dest_reg; - if (is_decl) + else if (*arg == '$') { - semsg(_("E1066: Cannot declare a register: %s"), name); - goto theend; + dest = dest_env; + if (is_decl) + { + semsg(_("E1065: Cannot declare an environment variable: %s"), name); + goto theend; + } } - } - else if (STRNCMP(arg, "g:", 2) == 0) - { - dest = dest_global; - if (is_decl) + else if (*arg == '@') { - semsg(_("E1016: Cannot declare a global variable: %s"), name); - goto theend; + if (!valid_yank_reg(arg[1], TRUE)) + { + emsg_invreg(arg[1]); + return FAIL; + } + dest = dest_reg; + if (is_decl) + { + semsg(_("E1066: Cannot declare a register: %s"), name); + goto theend; + } } - } - else if (STRNCMP(arg, "v:", 2) == 0) - { - vimvaridx = find_vim_var(name + 2); - if (vimvaridx < 0) + else if (STRNCMP(arg, "g:", 2) == 0) { - semsg(_(e_var_notfound), arg); - goto theend; + dest = dest_global; + if (is_decl) + { + semsg(_("E1016: Cannot declare a global variable: %s"), name); + goto theend; + } } - dest = dest_vimvar; - if (is_decl) + else if (STRNCMP(arg, "v:", 2) == 0) { - semsg(_("E1064: Cannot declare a v: variable: %s"), name); - goto theend; - } - } - else - { - for (idx = 0; reserved[idx] != NULL; ++idx) - if (STRCMP(reserved[idx], name) == 0) + vimvaridx = find_vim_var(name + 2); + if (vimvaridx < 0) { - semsg(_("E1034: Cannot use reserved name %s"), name); + semsg(_(e_var_notfound), arg); goto theend; } - - idx = lookup_local(arg, varlen, cctx); - if (idx >= 0) - { + dest = dest_vimvar; if (is_decl) { - semsg(_("E1017: Variable already declared: %s"), name); + semsg(_("E1064: Cannot declare a v: variable: %s"), name); goto theend; } - else + } + else + { + for (idx = 0; reserved[idx] != NULL; ++idx) + if (STRCMP(reserved[idx], name) == 0) + { + semsg(_("E1034: Cannot use reserved name %s"), name); + goto theend; + } + + idx = lookup_local(arg, varlen, cctx); + if (idx >= 0) { - lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx; - if (lvar->lv_const) + if (is_decl) { - semsg(_("E1018: Cannot assign to a constant: %s"), name); + semsg(_("E1017: Variable already declared: %s"), name); goto theend; } + else + { + lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx; + if (lvar->lv_const) + { + semsg(_("E1018: Cannot assign to a constant: %s"), name); + goto theend; + } + } } - } - else if (STRNCMP(arg, "s:", 2) == 0 - || lookup_script(arg, varlen) == OK - || find_imported(arg, varlen, cctx) != NULL) - { - dest = dest_script; - if (is_decl) + else if (STRNCMP(arg, "s:", 2) == 0 + || lookup_script(arg, varlen) == OK + || find_imported(arg, varlen, cctx) != NULL) { - semsg(_("E1054: Variable already declared in the script: %s"), - name); - goto theend; + dest = dest_script; + if (is_decl) + { + semsg(_("E1054: Variable already declared in the script: %s"), + name); + goto theend; + } } } } @@ -3493,7 +3545,7 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) } // +=, /=, etc. require an existing variable - if (idx < 0 && dest == dest_local) + if (idx < 0 && dest == dest_local && cctx->ctx_skip != TRUE) { if (oplen > 1 && !heredoc) { @@ -3840,8 +3892,23 @@ evaluate_const_expr7(char_u **arg, cctx_T *cctx UNUSED, typval_T *tv) end_leader = *arg; /* - * Recognize only has() for now. + * 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) return FAIL; *arg = skipwhite(*arg + 4); @@ -3881,6 +3948,33 @@ evaluate_const_expr7(char_u **arg, cctx_T *cctx UNUSED, typval_T *tv) 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) + { + // TODO + return FAIL; + } + + return OK; +} + static int evaluate_const_expr3(char_u **arg, cctx_T *cctx, typval_T *tv); /* @@ -3911,7 +4005,7 @@ evaluate_const_and_or(char_u **arg, cctx_T *cctx, char *op, typval_T *tv) tv2.v_type = VAR_UNKNOWN; tv2.v_lock = 0; if ((opchar == '|' ? evaluate_const_expr3(arg, cctx, &tv2) - : evaluate_const_expr7(arg, cctx, &tv2)) == FAIL) + : evaluate_const_expr4(arg, cctx, &tv2)) == FAIL) { clear_tv(&tv2); return FAIL; @@ -3940,7 +4034,7 @@ evaluate_const_and_or(char_u **arg, cctx_T *cctx, char *op, typval_T *tv) evaluate_const_expr3(char_u **arg, cctx_T *cctx, typval_T *tv) { // evaluate the first expression - if (evaluate_const_expr7(arg, cctx, tv) == FAIL) + if (evaluate_const_expr4(arg, cctx, tv) == FAIL) return FAIL; // || and && work almost the same |