Skip to content

Commit

Permalink
Logical operators || and && are short-circuiting.asm
Browse files Browse the repository at this point in the history
`true || X` short-circuits to 1 without evaluating X
`false && X` short-circuits to 0 without evaluating X

Fixes gbdev#619
  • Loading branch information
Rangi42 committed Jan 9, 2021
1 parent cab9cb0 commit 5491d74
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 29 deletions.
3 changes: 3 additions & 0 deletions include/asm/rpn.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions src/asm/lexer.c
Original file line number Diff line number Diff line change
Expand Up @@ -1333,6 +1333,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--;
Expand Down
99 changes: 70 additions & 29 deletions src/asm/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -1160,11 +1160,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);
Expand Down Expand Up @@ -1330,32 +1346,52 @@ const_no_str : relocexpr_no_str {

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';
}
| T_OP_STRCAT T_LPAREN strcat_args T_RPAREN {
strcpy($$, $3);
if (rpn_IsShortCircuited())
$$[0] = '\0';
else
strcpy($$, $3);
}
| T_OP_STRUPR T_LPAREN string T_RPAREN {
upperstring($$, $3);
if (rpn_IsShortCircuited())
$$[0] = '\0';
else
upperstring($$, $3);
}
| T_OP_STRLWR T_LPAREN string T_RPAREN {
lowerstring($$, $3);
if (rpn_IsShortCircuited())
$$[0] = '\0';
else
lowerstring($$, $3);
}
| T_OP_STRRPL T_LPAREN string T_COMMA string T_COMMA string T_RPAREN {
strrpl($$, sizeof($$), $3, $5, $7);
if (rpn_IsShortCircuited())
$$[0] = '\0';
else
strrpl($$, sizeof($$), $3, $5, $7);
}
| T_OP_STRFMT T_LPAREN strfmt_args T_RPAREN {
strfmt($$, sizeof($$), $3.format, $3.nbArgs, $3.args);
if (rpn_IsShortCircuited())
$$[0] = '\0';
else
strfmt($$, sizeof($$), $3.format, $3.nbArgs, $3.args);
freeStrFmtArgList(&$3);
}
;

strcat_args : string
| strcat_args T_COMMA string {
if (snprintf($$, sizeof($$), "%s%s", $1, $3) > MAXSTRLEN)
if (rpn_IsShortCircuited())
$$[0] = '\0';
else if (snprintf($$, sizeof($$), "%s%s", $1, $3) > MAXSTRLEN)
warning(WARNING_LONG_STR, "STRCAT: String too long '%s%s'\n",
$1, $3);
}
Expand All @@ -1370,31 +1406,36 @@ strfmt_args : string strfmt_va_args {
;

strfmt_va_args : /* empty */ {
initStrFmtArgList(&$$);
if (!rpn_IsShortCircuited())
initStrFmtArgList(&$$);
}
| strfmt_va_args T_COMMA relocexpr_no_str {
int32_t value;
if (!rpn_IsShortCircuited()) {
int32_t value;

if (!rpn_isKnown(&$3)) {
error("Expected constant expression: %s\n",
$3.reason);
value = 0;
} else {
value = $3.nVal;
}
if (!rpn_isKnown(&$3)) {
error("Expected constant expression: %s\n",
$3.reason);
value = 0;
} else {
value = $3.nVal;
}

size_t i = nextStrFmtArgListIndex(&$1);
size_t i = nextStrFmtArgListIndex(&$1);

$1.args[i].number = value;
$1.args[i].isNumeric = true;
$$ = $1;
$1.args[i].number = value;
$1.args[i].isNumeric = true;
$$ = $1;
}
}
| strfmt_va_args T_COMMA string {
size_t i = nextStrFmtArgListIndex(&$1);
if (!rpn_IsShortCircuited()) {
size_t i = nextStrFmtArgListIndex(&$1);

$1.args[i].string = strdup($3);
$1.args[i].isNumeric = false;
$$ = $1;
$1.args[i].string = strdup($3);
$1.args[i].isNumeric = false;
$$ = $1;
}
}
;

Expand Down
42 changes: 42 additions & 0 deletions src/asm/rpn.c
Original file line number Diff line number Diff line change
Expand Up @@ -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_; \
Expand Down Expand Up @@ -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
*/
Expand All @@ -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()) {
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -335,6 +372,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 */
Expand Down
8 changes: 8 additions & 0 deletions test/asm/short-circuiting.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

println 1 == 1 || undef_n == 42

println 1 == 2 && 0 < STRLEN("{undef_s}")

println ((1 == 1 || (undef_a || undef_b)) || undef_c)

println 1 == 2 && 0 < STRLEN(STRFMT("%d%s%v", "{undef_s}"))
Empty file added test/asm/short-circuiting.err
Empty file.
4 changes: 4 additions & 0 deletions test/asm/short-circuiting.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
$1
$0
$1
$0

0 comments on commit 5491d74

Please sign in to comment.