ref: 8e2a164a32b488ef3955250889f1873f77fcf115
parent: b76819792d8a940cdfed3d07536bcde00c0b296c
author: Rangi <[email protected]>
date: Thu Nov 18 14:40:23 EST 2021
Implement compound assignment operators for mutable constants Fixes #943
--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -284,8 +284,6 @@
{"EQU", T_POP_EQU},
{"EQUS", T_POP_EQUS},
{"REDEF", T_POP_REDEF},
- /* Handled before as T_Z80_SET */
- /* {"SET", T_POP_SET}, */
{"PUSHS", T_POP_PUSHS},
{"POPS", T_POP_POPS},
@@ -1805,12 +1803,6 @@
/* Handle unambiguous single-char tokens */
- case '^':
- return T_OP_XOR;
- case '+':
- return T_OP_ADD;
- case '-':
- return T_OP_SUB;
case '~':
return T_OP_NOT;
@@ -1832,36 +1824,82 @@
/* Handle ambiguous 1- or 2-char tokens */
- case '*': /* Either MUL or EXP */
- if (peek() == '*') {
+ case '+': /* Either += or ADD */
+ if (peek() == '=') {
shiftChar();
+ return T_POP_ADDEQ;
+ }
+ return T_OP_ADD;
+
+ case '-': /* Either -= or SUB */
+ if (peek() == '=') {
+ shiftChar();
+ return T_POP_SUBEQ;
+ }
+ return T_OP_SUB;
+
+ case '*': /* Either *=, MUL, or EXP */
+ switch (peek()) {
+ case '=':
+ shiftChar();
+ return T_POP_MULEQ;
+ case '*':
+ shiftChar();
return T_OP_EXP;
+ default:
+ return T_OP_MUL;
}
- return T_OP_MUL;
- case '/': /* Either division or a block comment */
- if (peek() == '*') {
+ case '/': /* Either /=, DIV, or a block comment */
+ switch (peek()) {
+ case '=':
shiftChar();
+ return T_POP_DIVEQ;
+ case '*':
+ shiftChar();
discardBlockComment();
break;
+ default:
+ return T_OP_DIV;
}
- return T_OP_DIV;
+ break;
- case '|': /* Either binary or logical OR */
- if (peek() == '|') {
+ case '|': /* Either |=, binary OR, or logical OR */
+ switch (peek()) {
+ case '=':
shiftChar();
+ return T_POP_OREQ;
+ case '|':
+ shiftChar();
return T_OP_LOGICOR;
+ default:
+ return T_OP_OR;
}
- return T_OP_OR;
- case '=': /* Either SET alias, or EQ */
+ case '^': /* Either ^= or XOR */
if (peek() == '=') {
shiftChar();
+ return T_POP_XOREQ;
+ }
+ return T_OP_XOR;
+
+ case '=': /* Either assignment or EQ */
+ if (peek() == '=') {
+ shiftChar();
return T_OP_LOGICEQU;
}
return T_POP_EQUAL;
- case '<': /* Either a LT, LTE, or left shift */
+ case '!': /* Either a NEQ or negation */
+ if (peek() == '=') {
+ shiftChar();
+ return T_OP_LOGICNE;
+ }
+ return T_OP_LOGICNOT;
+
+ /* Handle ambiguous 1-, 2-, or 3-char tokens */
+
+ case '<': /* Either <<=, LT, LTE, or left shift */
switch (peek()) {
case '=':
shiftChar();
@@ -1868,12 +1906,16 @@
return T_OP_LOGICLE;
case '<':
shiftChar();
+ if (peek() == '=') {
+ shiftChar();
+ return T_POP_SHLEQ;
+ }
return T_OP_SHL;
default:
return T_OP_LOGICLT;
}
- case '>': /* Either a GT, GTE, or right shift */
+ case '>': /* Either >>=, GT, GTE, or right shift */
switch (peek()) {
case '=':
shiftChar();
@@ -1880,18 +1922,15 @@
return T_OP_LOGICGE;
case '>':
shiftChar();
+ if (peek() == '=') {
+ shiftChar();
+ return T_POP_SHREQ;
+ }
return T_OP_SHR;
default:
return T_OP_LOGICGT;
}
- case '!': /* Either a NEQ, or negation */
- if (peek() == '=') {
- shiftChar();
- return T_OP_LOGICNE;
- }
- return T_OP_LOGICNOT;
-
/* Handle colon, which may begin an anonymous label ref */
case ':':
@@ -1904,11 +1943,7 @@
/* Handle numbers */
- case '$':
- yylval.constValue = readHexNumber();
- return T_NUMBER;
-
- case '0': /* Decimal number */
+ case '0': /* Decimal or fixed-point number */
case '1':
case '2':
case '3':
@@ -1925,10 +1960,13 @@
}
return T_NUMBER;
- case '&':
+ case '&': /* Either &=, binary AND, logical AND, or an octal constant */
secondChar = peek();
- if (secondChar == '&') {
+ if (secondChar == '=') {
shiftChar();
+ return T_POP_ANDEQ;
+ } else if (secondChar == '&') {
+ shiftChar();
return T_OP_LOGICAND;
} else if (secondChar >= '0' && secondChar <= '7') {
yylval.constValue = readNumber(8, 0);
@@ -1936,12 +1974,19 @@
}
return T_OP_AND;
- case '%': /* Either a modulo, or a binary constant */
+ case '%': /* Either %=, MOD, or a binary constant */
secondChar = peek();
- if (secondChar != binDigits[0] && secondChar != binDigits[1])
- return T_OP_MOD;
+ if (secondChar == '=') {
+ shiftChar();
+ return T_POP_MODEQ;
+ } else if (secondChar == binDigits[0] || secondChar == binDigits[1]) {
+ yylval.constValue = readBinaryNumber();
+ return T_NUMBER;
+ }
+ return T_OP_MOD;
- yylval.constValue = readBinaryNumber();
+ case '$': /* Hex constant */
+ yylval.constValue = readHexNumber();
return T_NUMBER;
case '`': /* Gfx constant */
--- a/src/asm/parser.y
+++ b/src/asm/parser.y
@@ -364,6 +364,17 @@
dest[i] = '\0';
}
+static void compoundAssignment(const char *symName, enum RPNCommand op, int32_t constValue) {
+ struct Expression oldExpr, constExpr, newExpr;
+ int32_t newValue;
+
+ rpn_Symbol(&oldExpr, symName);
+ rpn_Number(&constExpr, constValue);
+ rpn_BinaryOp(op, &newExpr, &oldExpr, &constExpr);
+ newValue = rpn_GetConstVal(&newExpr);
+ sym_AddSet(symName, newValue);
+}
+
static void initDsArgList(struct DsArgList *args)
{
args->nbArgs = 0;
@@ -468,6 +479,7 @@
char string[MAXSTRLEN + 1];
struct Expression expr;
int32_t constValue;
+ enum RPNCommand compoundEqual;
enum SectionModifier sectMod;
struct SectionSpec sectSpec;
struct MacroArgs *macroArg;
@@ -579,6 +591,12 @@
%token T_POP_EQUAL "="
%token T_POP_EQUS "EQUS"
+%token T_POP_ADDEQ "+=" T_POP_SUBEQ "-="
+%token T_POP_MULEQ "*=" T_POP_DIVEQ "/=" T_POP_MODEQ "%="
+%token T_POP_OREQ "|=" T_POP_XOREQ "^=" T_POP_ANDEQ "&="
+%token T_POP_SHLEQ "<<=" T_POP_SHREQ ">>="
+%type <compoundEqual> compoundeq
+
%token T_POP_INCLUDE "INCLUDE"
%token T_POP_PRINT "PRINT" T_POP_PRINTLN "PRINTLN"
%token T_POP_PRINTF "PRINTF" T_POP_PRINTT "PRINTT" T_POP_PRINTV "PRINTV" T_POP_PRINTI "PRINTI"
@@ -894,10 +912,23 @@
trailing_comma : %empty | T_COMMA
;
+compoundeq : T_POP_ADDEQ { $$ = RPN_ADD; }
+ | T_POP_SUBEQ { $$ = RPN_SUB; }
+ | T_POP_MULEQ { $$ = RPN_MUL; }
+ | T_POP_DIVEQ { $$ = RPN_DIV; }
+ | T_POP_MODEQ { $$ = RPN_MOD; }
+ | T_POP_XOREQ { $$ = RPN_XOR; }
+ | T_POP_OREQ { $$ = RPN_OR; }
+ | T_POP_ANDEQ { $$ = RPN_AND; }
+ | T_POP_SHLEQ { $$ = RPN_SHL; }
+ | T_POP_SHREQ { $$ = RPN_SHR; }
+;
+
equ : T_LABEL T_POP_EQU const { sym_AddEqu($1, $3); }
;
set : T_LABEL T_POP_EQUAL const { sym_AddSet($1, $3); }
+ | T_LABEL compoundeq const { compoundAssignment($1, $2, $3); }
| T_LABEL T_POP_SET const {
warning(WARNING_OBSOLETE, "`SET` is deprecated; use `=`\n");
sym_AddSet($1, $3);
@@ -1145,6 +1176,8 @@
def_set : def_id T_POP_EQUAL const { sym_AddSet($1, $3); }
| redef_id T_POP_EQUAL const { sym_AddSet($1, $3); }
+ | def_id compoundeq const { compoundAssignment($1, $2, $3); }
+ | redef_id compoundeq const { compoundAssignment($1, $2, $3); }
| def_id T_POP_SET const {
warning(WARNING_OBSOLETE, "`SET` is deprecated; use `=`\n");
sym_AddSet($1, $3);
--- a/src/asm/rgbasm.5
+++ b/src/asm/rgbasm.5
@@ -1036,6 +1036,25 @@
Note that colons
.Ql \&:
following the name are not allowed.
+.Pp
+Mutable constants can be conveniently redefined by compound assignment operators like in C:
+.Bl -column -offset indent "*= /= %="
+.It Sy Operator Ta Sy Meaning
+.It Li += -= Ta Compound plus/minus
+.It Li *= /= %= Ta Compound multiply/divide/modulo
+.It Li <<= >>= Ta Compound shift left/right
+.It Li &= \&|= ^= Ta Compound and/or/xor
+.El
+.Pp
+Examples:
+.Bd -literal -offset indent
+DEF x = 10
+DEF x += 1 ; x == 11
+DEF y = x - 1 ; y == 10
+DEF y *= 2 ; y == 20
+DEF y >>= 1 ; y == 10
+DEF x ^= y ; x == 1
+.Ed
.Ss Offset constants
The RS group of commands is a handy way of defining structure offsets:
.Bd -literal -offset indent
--- a/src/asm/symbol.c
+++ b/src/asm/symbol.c
@@ -476,7 +476,7 @@
}
/*
- * Alter a SET symbol's value
+ * Alter a mutable symbol's value
*/
struct Symbol *sym_AddSet(char const *symName, int32_t value)
{
--- /dev/null
+++ b/test/asm/compound-assignment.asm
@@ -1,0 +1,40 @@
+macro try
+println \1, "\2:"
+def prefix equs \1
+{prefix}\2 = 10
+println \2 ; 10
+{prefix}\2 += 5
+println \2 ; 15
+{prefix}\2 -= 1
+println \2 ; 14
+{prefix}\2 *= 2
+println \2 ; 28
+{prefix}\2 /= 4
+println \2 ; 7
+{prefix}\2 %= 3
+println \2 ; 1
+{prefix}\2 |= 11
+println \2 ; 11
+{prefix}\2 ^= 12
+println \2 ; 7
+{prefix}\2 &= 21
+println \2 ; 5
+{prefix}\2 <<= 2
+println \2 ; 20
+{prefix}\2 >>= 1
+println \2 ; 10
+purge prefix
+endm
+
+ try "", p
+ try "def ", q
+ try "redef ", r
+
+_RS += 100
+println _RS
+
+__LINE__ *= 200
+println __LINE__
+
+UnDeFiNeD ^= 300
+println UnDeFiNeD
--- /dev/null
+++ b/test/asm/compound-assignment.err
@@ -1,0 +1,5 @@
+ERROR: compound-assignment.asm(36):
+ '__LINE__' already defined as constant at <builtin>
+ERROR: compound-assignment.asm(39):
+ Expected constant expression: 'UnDeFiNeD' is not constant at assembly time
+error: Assembly aborted (2 errors)!
--- /dev/null
+++ b/test/asm/compound-assignment.out
@@ -1,0 +1,39 @@
+p:
+$A
+$F
+$E
+$1C
+$7
+$1
+$B
+$7
+$5
+$14
+$A
+def q:
+$A
+$F
+$E
+$1C
+$7
+$1
+$B
+$7
+$5
+$14
+$A
+redef r:
+$A
+$F
+$E
+$1C
+$7
+$1
+$B
+$7
+$5
+$14
+$A
+$64
+$25
+$0