From ca6978c1cbe36cdf6dcb76f5d6a095cf7599c997 Mon Sep 17 00:00:00 2001 From: Rangi Date: Fri, 25 Dec 2020 20:53:27 -0500 Subject: [PATCH] Logical operators `||` and `&&` are short-circuiting.asm `true || X` short-circuits to 1 without evaluating X `false && X` short-circuits to 0 without evaluating X Fixes #619 --- include/asm/rpn.h | 3 +++ src/asm/lexer.c | 3 +++ src/asm/parser.y | 35 +++++++++++++++++++++++------ src/asm/rpn.c | 42 +++++++++++++++++++++++++++++++++++ test/asm/short-circuiting.asm | 9 ++++++++ test/asm/short-circuiting.err | 0 test/asm/short-circuiting.out | 3 +++ 7 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 test/asm/short-circuiting.asm create mode 100644 test/asm/short-circuiting.err create mode 100644 test/asm/short-circuiting.out diff --git a/include/asm/rpn.h b/include/asm/rpn.h index c971bf988b..e460c4ba9c 100644 --- a/include/asm/rpn.h +++ b/include/asm/rpn.h @@ -46,6 +46,9 @@ static inline bool rpn_isSymbol(const struct Expression *expr) return expr->isSymbol; } +void rpn_EnterShortCircuitOp(bool startShortCircuit); +void rpn_LeaveShortCircuitOp(void); +bool rpn_IsShortCircuited(void); void rpn_Symbol(struct Expression *expr, char const *tzSym); void rpn_Number(struct Expression *expr, uint32_t i); void rpn_LOGNOT(struct Expression *expr, const struct Expression *src); diff --git a/src/asm/lexer.c b/src/asm/lexer.c index 50bec43926..6e866ddd79 100644 --- a/src/asm/lexer.c +++ b/src/asm/lexer.c @@ -1319,6 +1319,9 @@ static char const *readInterpolation(void) } } + if (rpn_IsShortCircuited()) + return NULL; + if (i == sizeof(symName)) { warning(WARNING_LONG_STR, "Symbol name too long\n"); i--; diff --git a/src/asm/parser.y b/src/asm/parser.y index ab39178a72..41a4bc919a 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -1077,11 +1077,27 @@ relocexpr_no_str : scoped_anon_id { rpn_Symbol(&$$, $1); } | T_OP_LOGICNOT relocexpr %prec NEG { rpn_LOGNOT(&$$, &$2); } - | relocexpr T_OP_LOGICOR relocexpr { - rpn_BinaryOp(RPN_LOGOR, &$$, &$1, &$3); - } - | relocexpr T_OP_LOGICAND relocexpr { - rpn_BinaryOp(RPN_LOGAND, &$$, &$1, &$3); + | relocexpr T_OP_LOGICOR { + // If the first operand is nonzero... + rpn_EnterShortCircuitOp(rpn_isKnown(&$1) && $1.nVal); + } relocexpr { + // ...then short-circuit to 1 without evaluating the second operand. + if (rpn_IsShortCircuited()) + rpn_Number(&$$, 1); + else + rpn_BinaryOp(RPN_LOGOR, &$$, &$1, &$4); + rpn_LeaveShortCircuitOp(); + } + | relocexpr T_OP_LOGICAND { + // If the first operand is zero... + rpn_EnterShortCircuitOp(rpn_isKnown(&$1) && !$1.nVal); + } relocexpr { + // ...then short-circuit to 0 without evaluating the second operand. + if (rpn_IsShortCircuited()) + rpn_Number(&$$, 0); + else + rpn_BinaryOp(RPN_LOGAND, &$$, &$1, &$4); + rpn_LeaveShortCircuitOp(); } | relocexpr T_OP_LOGICEQU relocexpr { rpn_BinaryOp(RPN_LOGEQ, &$$, &$1, &$3); @@ -1227,7 +1243,10 @@ const : relocexpr { string : T_STRING | T_OP_STRSUB T_LPAREN string T_COMMA uconst T_COMMA uconst T_RPAREN { - strsubUTF8($$, $3, $5, $7); + if (rpn_IsShortCircuited()) + $$[0] = '\0'; + else + strsubUTF8($$, $3, $5, $7); } | T_OP_STRCAT T_LPAREN T_RPAREN { $$[0] = '\0'; @@ -1249,7 +1268,9 @@ string : T_STRING strcat_args : string | strcat_args T_COMMA string { - if (snprintf($$, MAXSTRLEN + 1, "%s%s", $1, $3) > MAXSTRLEN) + if (rpn_IsShortCircuited()) + $$[0] = '\0'; + else if (snprintf($$, MAXSTRLEN + 1, "%s%s", $1, $3) > MAXSTRLEN) warning(WARNING_LONG_STR, "STRCAT: String too long '%s%s'\n", $1, $3); } diff --git a/src/asm/rpn.c b/src/asm/rpn.c index 112015d481..cf993098fb 100644 --- a/src/asm/rpn.c +++ b/src/asm/rpn.c @@ -24,6 +24,9 @@ #include "asm/symbol.h" #include "asm/warning.h" +uint32_t nestedShortCircuitOps; // How many nested short-circuitable operators there currently are +uint32_t shortCircuitAtDepth; // The nested depth at which short-circuiting occurred (0 if N/A) + /* Makes an expression "not known", also setting its error message */ #define makeUnknown(expr_, ...) do { \ struct Expression *_expr = expr_; \ @@ -95,6 +98,25 @@ void rpn_Free(struct Expression *expr) rpn_Init(expr); } +void rpn_EnterShortCircuitOp(bool startShortCircuit) +{ + nestedShortCircuitOps++; + if (startShortCircuit && shortCircuitAtDepth == 0) + shortCircuitAtDepth = nestedShortCircuitOps; +} + +void rpn_LeaveShortCircuitOp(void) +{ + nestedShortCircuitOps--; + if (shortCircuitAtDepth > nestedShortCircuitOps) + shortCircuitAtDepth = 0; +} + +bool rpn_IsShortCircuited(void) +{ + return shortCircuitAtDepth > 0 && shortCircuitAtDepth >= nestedShortCircuitOps; +} + /* * Add symbols, constants and operators to expression */ @@ -106,6 +128,11 @@ void rpn_Number(struct Expression *expr, uint32_t i) void rpn_Symbol(struct Expression *expr, char const *tzSym) { + if (rpn_IsShortCircuited()) { + rpn_Number(expr, 0); + return; + } + struct Symbol *sym = sym_FindScopedSymbol(tzSym); if (sym_IsPC(sym) && !sect_GetSymbolSection()) { @@ -157,6 +184,11 @@ void rpn_BankSelf(struct Expression *expr) void rpn_BankSymbol(struct Expression *expr, char const *tzSym) { + if (rpn_IsShortCircuited()) { + rpn_Number(expr, 0); + return; + } + struct Symbol const *sym = sym_FindScopedSymbol(tzSym); /* The @ symbol is treated differently. */ @@ -189,6 +221,11 @@ void rpn_BankSymbol(struct Expression *expr, char const *tzSym) void rpn_BankSection(struct Expression *expr, char const *tzSectionName) { + if (rpn_IsShortCircuited()) { + rpn_Number(expr, 0); + return; + } + rpn_Init(expr); struct Section *pSection = out_FindSectionByName(tzSectionName); @@ -321,6 +358,11 @@ static bool isDiffConstant(struct Expression const *src1, void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr, const struct Expression *src1, const struct Expression *src2) { + if (rpn_IsShortCircuited()) { + rpn_Number(expr, 0); + return; + } + expr->isSymbol = false; /* First, check if the expression is known */ diff --git a/test/asm/short-circuiting.asm b/test/asm/short-circuiting.asm new file mode 100644 index 0000000000..b29f58e984 --- /dev/null +++ b/test/asm/short-circuiting.asm @@ -0,0 +1,9 @@ + + printv 1 == 1 || undef_n == 42 + printt "\n" + + printv 1 == 2 && 0 < STRLEN("{undef_s}") + printt "\n" + + printv (((1 == 1 || undef_a) || undef_b) || undef_c) + printt "\n" diff --git a/test/asm/short-circuiting.err b/test/asm/short-circuiting.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/asm/short-circuiting.out b/test/asm/short-circuiting.out new file mode 100644 index 0000000000..beccd71538 --- /dev/null +++ b/test/asm/short-circuiting.out @@ -0,0 +1,3 @@ +$1 +$0 +$1