shithub: rgbds

Download patch

ref: 637bbbdf4312ca6efb6eb4ad6ae4ad7f06d9357e
parent: 8230e8165ca159736906e3bb33321800b360b061
author: Rangi <[email protected]>
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