shithub: rgbds

Download patch

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