ref: 637bbbdf4312ca6efb6eb4ad6ae4ad7f06d9357e
parent: 8230e8165ca159736906e3bb33321800b360b061
author: Rangi <remy.oukaour+rangi42@gmail.com>
date: Sun Apr 18 19:26:03 EDT 2021
Support multi-digit macro arguments in parentheses This allows access to arguments past \9 without using 'shift'
--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -717,21 +717,65 @@
static bool isMacroChar(char c)
{
- return c == '@' || c == '#' || (c >= '0' && c <= '9');
+ return c == '@' || c == '#' || c == '(' || (c >= '0' && c <= '9');
}
+/* forward declarations for readParentheticMacroArgNum */
+static int peek(void);
+static void shiftChar(void);
+static uint32_t readNumber(int radix, uint32_t baseValue);
+
+static uint32_t readParentheticMacroArgNum(void)
+{
+ dbgPrint("Reading parenthetic macro arg\n");
+ bool disableMacroArgs = lexerState->disableMacroArgs;
+ bool disableInterpolation = lexerState->disableInterpolation;
+
+ lexerState->disableMacroArgs = false;
+ lexerState->disableInterpolation = false;
+
+ uint32_t num = 0;
+ int c = peek();
+ bool hasDigit = c >= '0' && c <= '9';
+
+ if (hasDigit)
+ num = readNumber(10, 0);
+
+ c = peek();
+ if (c != ')')
+ fatalerror("Invalid character in parenthetic macro argument %s\n", printChar(c));
+ else if (!hasDigit)
+ fatalerror("Empty parenthetic macro argument\n");
+ else if (num == 0)
+ fatalerror("Invalid parenthetic macro argument '\\(0)'\n");
+
+ shiftChar();
+
+ lexerState->disableMacroArgs = disableMacroArgs;
+ lexerState->disableInterpolation = disableInterpolation;
+ return num;
+}
+
static char const *readMacroArg(char name)
{
char const *str;
- if (name == '@')
+ if (name == '@') {
str = macro_GetUniqueIDStr();
- else if (name == '#')
+ } else if (name == '#') {
str = macro_GetAllArgs();
- else if (name == '0')
+ } else if (name == '(') {
+ uint32_t num = readParentheticMacroArgNum();
+
+ str = macro_GetArg(num);
+ if (!str)
+ fatalerror("Macro argument '\\(%" PRIu32 ")' not defined\n", num);
+ } else if (name == '0') {
fatalerror("Invalid macro argument '\\0'\n");
- else
+ } else {
str = macro_GetArg(name - '0');
+ }
+
if (!str)
fatalerror("Macro argument '\\%c' not defined\n", name);
@@ -1061,7 +1105,7 @@
/* Functions to lex numbers of various radixes */
-static void readNumber(int radix, int32_t baseValue)
+static uint32_t readNumber(int radix, uint32_t baseValue)
{
uint32_t value = baseValue;
@@ -1077,10 +1121,10 @@
value = value * radix + (c - '0');
}
- yylval.constValue = value;
+ return value;
}
-static void readFractionalPart(void)
+static uint32_t readFractionalPart(int32_t integer)
{
uint32_t value = 0, divisor = 1;
@@ -1105,20 +1149,20 @@
divisor *= 10;
}
- if (yylval.constValue > INT16_MAX || yylval.constValue < INT16_MIN)
+ if (integer > INT16_MAX || integer < INT16_MIN)
warning(WARNING_LARGE_CONSTANT, "Magnitude of fixed-point constant is too large\n");
/* Cast to unsigned avoids UB if shifting discards bits */
- yylval.constValue = (uint32_t)yylval.constValue << 16;
+ integer = (uint32_t)integer << 16;
/* Cast to unsigned avoids undefined overflow behavior */
uint16_t fractional = (uint16_t)round(value * 65536.0 / divisor);
- yylval.constValue |= fractional * (yylval.constValue >= 0 ? 1 : -1);
+ return (uint32_t)integer | (fractional * (integer >= 0 ? 1 : -1));
}
char binDigits[2];
-static void readBinaryNumber(void)
+static uint32_t readBinaryNumber(void)
{
uint32_t value = 0;
@@ -1140,10 +1184,10 @@
value = value * 2 + bit;
}
- yylval.constValue = value;
+ return value;
}
-static void readHexNumber(void)
+static uint32_t readHexNumber(void)
{
uint32_t value = 0;
bool empty = true;
@@ -1173,12 +1217,12 @@
if (empty)
error("Invalid integer constant, no digits after '$'\n");
- yylval.constValue = value;
+ return value;
}
char gfxDigits[4];
-static void readGfxConstant(void)
+static uint32_t readGfxConstant(void)
{
uint32_t bp0 = 0, bp1 = 0;
uint8_t width = 0;
@@ -1215,7 +1259,7 @@
warning(WARNING_LARGE_CONSTANT,
"Graphics constant is too long, only 8 first pixels considered\n");
- yylval.constValue = bp1 << 8 | bp0;
+ return bp1 << 8 | bp0;
}
/* Functions to read identifiers & keywords */
@@ -1496,6 +1540,7 @@
case '7':
case '8':
case '9':
+ case '(':
shiftChar();
char const *str = readMacroArg(c);
@@ -1641,6 +1686,7 @@
case '7':
case '8':
case '9':
+ case '(':
shiftChar();
char const *str = readMacroArg(c);
@@ -1828,8 +1874,7 @@
/* Handle numbers */
case '$':
- yylval.constValue = 0;
- readHexNumber();
+ yylval.constValue = readHexNumber();
/* Attempt to match `$ff00+c` */
if (yylval.constValue == 0xff00) {
/* Whitespace is ignored anyways */
@@ -1859,10 +1904,10 @@
case '7':
case '8':
case '9':
- readNumber(10, c - '0');
+ yylval.constValue = readNumber(10, c - '0');
if (peek() == '.') {
shiftChar();
- readFractionalPart();
+ yylval.constValue = readFractionalPart(yylval.constValue);
}
return T_NUMBER;
@@ -1872,7 +1917,7 @@
shiftChar();
return T_OP_LOGICAND;
} else if (secondChar >= '0' && secondChar <= '7') {
- readNumber(8, 0);
+ yylval.constValue = readNumber(8, 0);
return T_NUMBER;
}
return T_OP_AND;
@@ -1882,12 +1927,11 @@
if (secondChar != binDigits[0] && secondChar != binDigits[1])
return T_OP_MOD;
- yylval.constValue = 0;
- readBinaryNumber();
+ yylval.constValue = readBinaryNumber();
return T_NUMBER;
case '`': /* Gfx constant */
- readGfxConstant();
+ yylval.constValue = readGfxConstant();
return T_NUMBER;
/* Handle strings */
--- a/src/asm/rgbasm.5
+++ b/src/asm/rgbasm.5
@@ -1566,16 +1566,30 @@
.Ql \[rs]n
does not need to be escaped because string literals also work as usual inside macro arguments.
.Pp
-In reality, up to 256 arguments can be passed to a macro, but you can only use the first 9 like this.
-If you want to use the rest, you need to use the
-.Ic SHIFT
-command.
+Since there are only nine digits, you can only access the first nine macro arguments like this.
+To use the rest, you need to put the multi-digit argument number in parentheses, like
+.Ql \[rs](10) .
+This parenthetic syntax only supports decimal numbers.
.Pp
+Other macro arguments and symbol interpolations will be expanded inside the parentheses.
+For example, if
+.Ql \[rs]1
+is
+.Ql 13 ,
+then
+.Ql \[rs](\[rs]1)
+will expand to
+.Ql \[rs](13) .
+And if
+.Ql x = 42 ,
+then
+.Ql \[rs]({d:x})
+will expand to
+.Ql \[rs](42) .
+.Pp
+Another way to access more than nine macro arguments is the
.Ic SHIFT
-is a special command only available in macros.
-Very useful in
-.Ic REPT
-blocks.
+command, a special command only available in macros.
It will shift the arguments by one to the left, and decrease
.Dv _NARG
by 1.
@@ -1586,11 +1600,14 @@
.Ic \[rs]3 ,
and so forth.
.Pp
-This is the only way of accessing the value of arguments from 10 to 256.
-.Pp
.Ic SHIFT
can optionally be given an integer parameter, and will apply the above shifting that number of times.
A negative parameter will shift the arguments in reverse.
+.Pp
+.Ic SHIFT
+is useful in
+.Ic REPT
+blocks to repeat the same commands with multiple arguments.
.Ss Printing things during assembly
The
.Ic PRINT
--- /dev/null
+++ b/test/asm/invalid-empty-macro-arg.asm
@@ -1,0 +1,1 @@
+\()
--- /dev/null
+++ b/test/asm/invalid-empty-macro-arg.err
@@ -1,0 +1,2 @@
+FATAL: invalid-empty-macro-arg.asm(1):
+ Empty parenthetic macro argument
--- /dev/null
+++ b/test/asm/invalid-macro-arg-character.asm
@@ -1,0 +1,1 @@
+\(10!)
--- /dev/null
+++ b/test/asm/invalid-macro-arg-character.err
@@ -1,0 +1,2 @@
+FATAL: invalid-macro-arg-character.asm(1):
+ Invalid character in parenthetic macro argument '!'
--- /dev/null
+++ b/test/asm/parenthetic-macro-args.asm
@@ -1,0 +1,17 @@
+MACRO printargs
+ PRINTLN "first = \(1)"
+ FOR I, 2, _NARG
+ PRINTLN "next = \({d:I})"
+ ENDR
+ PRINTLN "last = \({d:_NARG})"
+ENDM
+
+ printargs A, B, C, D
+
+MACRO mac
+ println \(2__) + \(1_2) + \(\1)
+x = 2
+ println \({d:x}) + \(1_{d:x}) + \(\(\(13)))
+ENDM
+
+ mac 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 1
--- /dev/null
+++ b/test/asm/parenthetic-macro-args.out
@@ -1,0 +1,6 @@
+first = A
+next = B
+next = C
+last = D
+$F0
+$F0