From 8e2a164a32b488ef3955250889f1873f77fcf115 Mon Sep 17 00:00:00 2001 From: Rangi Date: Thu, 18 Nov 2021 19:40:23 -0500 Subject: [PATCH] Implement compound assignment operators for mutable constants Fixes #943 --- src/asm/lexer.c | 121 +++++++++++++++++++++---------- src/asm/parser.y | 33 +++++++++ src/asm/rgbasm.5 | 19 +++++ src/asm/symbol.c | 2 +- test/asm/compound-assignment.asm | 40 ++++++++++ test/asm/compound-assignment.err | 5 ++ test/asm/compound-assignment.out | 39 ++++++++++ 7 files changed, 220 insertions(+), 39 deletions(-) create mode 100644 test/asm/compound-assignment.asm create mode 100644 test/asm/compound-assignment.err create mode 100644 test/asm/compound-assignment.out diff --git a/src/asm/lexer.c b/src/asm/lexer.c index 9475d7e3..8b47a059 100644 --- a/src/asm/lexer.c +++ b/src/asm/lexer.c @@ -284,8 +284,6 @@ static struct KeywordMapping { {"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 @@ static int yylex_NORMAL(void) /* 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,66 +1824,113 @@ static int yylex_NORMAL(void) /* 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(); 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(); 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 @@ static int yylex_NORMAL(void) /* 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,9 +1960,12 @@ static int yylex_NORMAL(void) } 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') { @@ -1936,12 +1974,19 @@ static int yylex_NORMAL(void) } 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 */ diff --git a/src/asm/parser.y b/src/asm/parser.y index 8a25ebff..38358f55 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -364,6 +364,17 @@ static void strfmt(char *dest, size_t destLen, char const *fmt, size_t nbArgs, s 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 @@ enum { 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 @@ enum { %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 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 @@ directive : endc 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 @@ redef_equ : redef_id T_POP_EQU const { sym_RedefEqu($1, $3); } 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); diff --git a/src/asm/rgbasm.5 b/src/asm/rgbasm.5 index 785ea9da..f5a75e56 100644 --- a/src/asm/rgbasm.5 +++ b/src/asm/rgbasm.5 @@ -1036,6 +1036,25 @@ COUNT = COUNT*2 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 diff --git a/src/asm/symbol.c b/src/asm/symbol.c index e13b37b4..52d44143 100644 --- a/src/asm/symbol.c +++ b/src/asm/symbol.c @@ -476,7 +476,7 @@ struct Symbol *sym_RedefString(char const *symName, char const *value) } /* - * Alter a SET symbol's value + * Alter a mutable symbol's value */ struct Symbol *sym_AddSet(char const *symName, int32_t value) { diff --git a/test/asm/compound-assignment.asm b/test/asm/compound-assignment.asm new file mode 100644 index 00000000..4654c26d --- /dev/null +++ b/test/asm/compound-assignment.asm @@ -0,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 diff --git a/test/asm/compound-assignment.err b/test/asm/compound-assignment.err new file mode 100644 index 00000000..cb707f95 --- /dev/null +++ b/test/asm/compound-assignment.err @@ -0,0 +1,5 @@ +ERROR: compound-assignment.asm(36): + '__LINE__' already defined as constant at +ERROR: compound-assignment.asm(39): + Expected constant expression: 'UnDeFiNeD' is not constant at assembly time +error: Assembly aborted (2 errors)! diff --git a/test/asm/compound-assignment.out b/test/asm/compound-assignment.out new file mode 100644 index 00000000..805d2988 --- /dev/null +++ b/test/asm/compound-assignment.out @@ -0,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