shithub: rgbds

Download patch

ref: 1d9cc01ae1f1c0493f53fb9fb018b7a924455dd7
parent: f31deb5010b73b337b2611e2555a871ddac3cd28
author: Rangi <remy.oukaour+rangi42@gmail.com>
date: Mon Dec 14 04:57:45 EST 2020

Macro arguments within a string literal are read into the string, not expanded

Fixes #643

--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -692,7 +692,7 @@
 	free(expansion);
 }
 
-static char const *expandMacroArg(char name, size_t distance)
+static char const *readMacroArg(char name)
 {
 	char const *str;
 
@@ -707,11 +707,6 @@
 	if (!str)
 		fatalerror("Macro argument '\\%c' not defined\n", name);
 
-	/* Cannot expand an empty string */
-	if (!str[0])
-		return NULL;
-
-	beginExpansion(distance, 2, str, strlen(str), name == '#', NULL);
 	return str;
 }
 
@@ -801,18 +796,19 @@
 			lexerState->macroArgScanDistance++;
 			c = peekInternal(distance + 1);
 			if (c == '@' || c == '#' || (c >= '0' && c <= '9')) {
-				/* Expand the argument and return its first character */
-				char const *str = expandMacroArg(c, distance);
+				char const *str = readMacroArg(c);
 
 				/*
-				 * If the argument is an empty string, nothing was
+				 * If the argument is an empty string, it cannot be
 				 * expanded, so skip it and keep peeking.
 				 */
-				if (!str) {
+				if (!str[0]) {
 					shiftChars(2);
 					goto restart;
 				}
 
+				beginExpansion(distance, 2, str, strlen(str), c == '#', NULL);
+
 				/*
 				 * Assuming macro args can't be recursive (I'll be damned if a way
 				 * is found...), then we mark the entire macro arg as scanned;
@@ -820,10 +816,7 @@
 				 * so they shouldn't be counted in the scan distance!
 				 */
 				lexerState->macroArgScanDistance += strlen(str) - 2;
-				/*
-				 * This assumes macro args can't be empty, since expandMacroArg
-				 * returns NULL instead of an empty string.
-				 */
+
 				c = str[0];
 			} else {
 				c = '\\';
@@ -1398,11 +1391,62 @@
 	return NULL;
 }
 
+static int appendMacroArg(char const *str, int i)
+{
+	while (*str && i < sizeof(yylval.tzString)) {
+		int c = *str++;
+
+		if (c != '\\') {
+			yylval.tzString[i++] = c;
+			continue;
+		}
+
+		c = *str++;
+
+		switch (c) {
+		case '\\': /* Return that character unchanged */
+		case '"':
+		case '{':
+		case '}':
+			break;
+		case 'n':
+			c = '\n';
+			break;
+		case 'r':
+			c = '\r';
+			break;
+		case 't':
+			c = '\t';
+			break;
+
+		case '\0': /* Can't really print that one */
+			error("Illegal character escape at end of macro arg\n");
+			yylval.tzString[i++] = '\\';
+			break;
+
+		/*
+		 * Line continuations and macro args were already
+		 * handled while reading the macro args, so '\@',
+		 * '\#', and '\0'-'\9' should not occur here.
+		 */
+
+		default:
+			error("Illegal character escape '%s'\n", print(c));
+			c = '\\';
+			break;
+		}
+		yylval.tzString[i++] = c;
+	}
+
+	return i;
+}
+
 static void readString(void)
 {
 	size_t i = 0;
 
 	dbgPrint("Reading string\n");
+	lexerState->disableMacroArgs = true;
 	lexerState->disableInterpolation = true;
 	for (;;) {
 		int c = peek(0);
@@ -1417,6 +1461,7 @@
 			yylval.tzString[i] = '\0';
 			dbgPrint("Read string \"%s\"\n", yylval.tzString);
 			goto finish;
+
 		case '\r':
 		case '\n': /* Do not shift these! */
 		case EOF:
@@ -1429,7 +1474,7 @@
 			dbgPrint("Read string \"%s\"\n", yylval.tzString);
 			goto finish;
 
-		case '\\': /* Character escape */
+		case '\\': /* Character escape or macro arg */
 			c = peek(1);
 			switch (c) {
 			case '\\': /* Return that character unchanged */
@@ -1451,6 +1496,7 @@
 				shiftChars(1);
 				break;
 
+			/* Line continuation */
 			case ' ':
 			case '\r':
 			case '\n':
@@ -1458,6 +1504,25 @@
 				readLineContinuation();
 				continue;
 
+			/* Macro arg */
+			case '@':
+			case '#':
+			case '0':
+			case '1':
+			case '2':
+			case '3':
+			case '4':
+			case '5':
+			case '6':
+			case '7':
+			case '8':
+			case '9':
+				shiftChars(2);
+				char const *str = readMacroArg(c);
+
+				i = appendMacroArg(str, i);
+				continue; /* Do not copy an additional character */
+
 			case EOF: /* Can't really print that one */
 				error("Illegal character escape at end of input\n");
 				c = '\\';
@@ -1471,15 +1536,13 @@
 
 		case '{': /* Symbol interpolation */
 			shiftChars(1);
+			lexerState->disableMacroArgs = false;
 			char const *ptr = readInterpolation();
 
-			if (ptr) {
-				while (*ptr) {
-					if (i == sizeof(yylval.tzString))
-						break;
+			if (ptr)
+				while (*ptr && i < sizeof(yylval.tzString))
 					yylval.tzString[i++] = *ptr++;
-				}
-			}
+			lexerState->disableMacroArgs = true;
 			continue; /* Do not copy an additional character */
 
 		/* Regular characters will just get copied */
@@ -1490,6 +1553,7 @@
 	}
 
 finish:
+	lexerState->disableMacroArgs = false;
 	lexerState->disableInterpolation = false;
 }
 
--- /dev/null
+++ b/test/asm/macro-arg-in-string.asm
@@ -1,0 +1,19 @@
+print: MACRO
+	PRINTT "\1"
+	PRINTT "\n"
+ENDM
+
+	print John "Danger" Smith
+	print \\A\nB
+	print C\
+D
+	print E\!F ; illegal character escape
+
+
+iprint: MACRO
+	PRINTT "{\1}"
+	PRINTT "\n"
+ENDM
+
+s EQUS "hello"
+	iprint s
--- /dev/null
+++ b/test/asm/macro-arg-in-string.err
@@ -1,0 +1,3 @@
+ERROR: macro-arg-in-string.asm(10) -> macro-arg-in-string.asm::print(2):
+    Illegal character escape '!'
+error: Assembly aborted (1 errors)!
--- /dev/null
+++ b/test/asm/macro-arg-in-string.out
@@ -1,0 +1,6 @@
+John "Danger" Smith
+\A
+B
+CD
+E\F
+hello