shithub: rgbds

Download patch

ref: ce58f6d6be1ea540930211a4d80de157a6e1f943
parent: 5aabb915ecdb8dff5fc38489b9d4d84c615d73d6
author: Rangi <remy.oukaour+rangi42@gmail.com>
date: Fri Dec 11 09:18:58 EST 2020

Allow {symbol} interpolation outside of strings

Fixes #629

Closes #631

--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -332,6 +332,7 @@
 	size_t captureCapacity; /* Size of the buffer above */
 
 	bool disableMacroArgs;
+	bool disableInterpolation;
 	size_t macroArgScanDistance; /* Max distance already scanned for macro args */
 	bool expandStrings;
 	struct Expansion *expansions;
@@ -351,6 +352,7 @@
 	state->captureBuf = NULL;
 
 	state->disableMacroArgs = false;
+	state->disableInterpolation = false;
 	state->macroArgScanDistance = 0;
 	state->expandStrings = true;
 	state->expansions = NULL;
@@ -767,16 +769,23 @@
 	return (unsigned char)lexerState->buf[(lexerState->index + distance) % LEXER_BUF_SIZE];
 }
 
+/* forward declarations for peek */
+static void shiftChars(uint8_t distance);
+static char const *readInterpolation(void);
+
 static int peek(uint8_t distance)
 {
-	int c = peekInternal(distance);
+	int c;
 
+restart:
+	c = peekInternal(distance);
+
 	if (distance >= lexerState->macroArgScanDistance) {
 		lexerState->macroArgScanDistance = distance + 1; /* Do not consider again */
-		/* If enabled and character is a backslash, check for a macro arg */
-		if (!lexerState->disableMacroArgs && c == '\\') {
-			distance++;
+		if (c == '\\' && !lexerState->disableMacroArgs) {
+			/* If character is a backslash, check for a macro arg */
 			lexerState->macroArgScanDistance++;
+			distance++;
 			c = peekInternal(distance);
 			if (c == '@' || (c >= '0' && c <= '9')) {
 				/* Expand the argument and return its first character */
@@ -794,8 +803,19 @@
 			} else {
 				c = '\\';
 			}
+		} else if (c == '{' && !lexerState->disableInterpolation) {
+			/* If character is an open brace, do symbol interpolation */
+			lexerState->macroArgScanDistance++;
+			shiftChars(1);
+			char const *ptr = readInterpolation();
+
+			if (ptr) {
+				beginExpansion(distance, 0, ptr, strlen(ptr), ptr);
+				goto restart;
+			}
 		}
 	}
+
 	return c;
 }
 
@@ -927,6 +947,7 @@
 {
 	dbgPrint("Discarding block comment\n");
 	lexerState->disableMacroArgs = true;
+	lexerState->disableInterpolation = true;
 	for (;;) {
 		switch (nextChar()) {
 		case EOF:
@@ -950,6 +971,7 @@
 	}
 finish:
 	lexerState->disableMacroArgs = false;
+	lexerState->disableInterpolation = false;
 }
 
 /* Function to discard all of a line's comments */
@@ -958,6 +980,7 @@
 {
 	dbgPrint("Discarding comment\n");
 	lexerState->disableMacroArgs = true;
+	lexerState->disableInterpolation = true;
 	for (;;) {
 		int c = peek(0);
 
@@ -966,6 +989,7 @@
 		shiftChars(1);
 	}
 	lexerState->disableMacroArgs = false;
+	lexerState->disableInterpolation = false;
 }
 
 /* Function to read a line continuation */
@@ -1267,14 +1291,11 @@
 
 		if (c == '{') { /* Nested interpolation */
 			shiftChars(1);
-			char const *inner = readInterpolation();
+			char const *ptr = readInterpolation();
 
-			if (inner) {
-				while (*inner) {
-					if (i == sizeof(symName))
-						break;
-					symName[i++] = *inner++;
-				}
+			if (ptr) {
+				beginExpansion(0, 0, ptr, strlen(ptr), ptr);
+				continue; /* Restart, reading from the new buffer */
 			}
 		} else if (c == EOF || c == '\r' || c == '\n' || c == '"') {
 			error("Missing }\n");
@@ -1342,6 +1363,7 @@
 	size_t i = 0;
 
 	dbgPrint("Reading string\n");
+	lexerState->disableInterpolation = true;
 	for (;;) {
 		int c = peek(0);
 
@@ -1354,7 +1376,7 @@
 			}
 			yylval.tzString[i] = '\0';
 			dbgPrint("Read string \"%s\"\n", yylval.tzString);
-			return;
+			goto finish;
 		case '\r':
 		case '\n': /* Do not shift these! */
 		case EOF:
@@ -1365,7 +1387,7 @@
 			yylval.tzString[i] = '\0';
 			error("Unterminated string\n");
 			dbgPrint("Read string \"%s\"\n", yylval.tzString);
-			return;
+			goto finish;
 
 		case '\\': /* Character escape */
 			c = peek(1);
@@ -1426,6 +1448,9 @@
 			yylval.tzString[i++] = c;
 		shiftChars(1);
 	}
+
+finish:
+	lexerState->disableInterpolation = false;
 }
 
 /* Function to report one character's worth of garbage bytes */
@@ -1812,19 +1837,6 @@
 			}
 			break;
 
-		case '{': /* Symbol interpolation */
-			shiftChars(1);
-			char const *ptr = readInterpolation();
-
-			if (ptr) {
-				while (*ptr) {
-					if (i == sizeof(yylval.tzString))
-						break;
-					yylval.tzString[i++] = *ptr++;
-				}
-			}
-			continue; /* Do not copy an additional character */
-
 		/* Regular characters will just get copied */
 		}
 		if (i < sizeof(yylval.tzString)) /* Copy one extra to flag overflow */
@@ -1848,8 +1860,9 @@
 	int token;
 	bool atLineStart = lexerState->atLineStart;
 
-	/* Prevent expanding macro args in this state */
+	/* Prevent expanding macro args and symbol interpolation in this state */
 	lexerState->disableMacroArgs = true;
+	lexerState->disableInterpolation = true;
 
 	for (;;) {
 		if (atLineStart) {
@@ -1910,6 +1923,7 @@
 finish:
 
 	lexerState->disableMacroArgs = false;
+	lexerState->disableInterpolation = false;
 	lexerState->atLineStart = false;
 
 	return token;
@@ -1978,6 +1992,7 @@
 	lexerState->capturing = true;
 	lexerState->captureSize = 0;
 	lexerState->disableMacroArgs = true;
+	lexerState->disableInterpolation = true;
 
 	if (lexerState->isMmapped && !lexerState->expansions) {
 		return &lexerState->ptr[lexerState->offset];
@@ -2051,6 +2066,7 @@
 	*size = lexerState->captureSize - strlen("ENDR");
 	lexerState->captureBuf = NULL;
 	lexerState->disableMacroArgs = false;
+	lexerState->disableInterpolation = false;
 }
 
 void lexer_CaptureMacroBody(char **capture, size_t *size)
@@ -2116,4 +2132,5 @@
 	*size = lexerState->captureSize - strlen("ENDM");
 	lexerState->captureBuf = NULL;
 	lexerState->disableMacroArgs = false;
+	lexerState->disableInterpolation = false;
 }
--- a/src/asm/rgbasm.5
+++ b/src/asm/rgbasm.5
@@ -251,9 +251,9 @@
 .Sq $
 prepended.
 .Bd -literal -offset indent
-TOPIC equs "life, the universe, and everything"
+TOPIC equs "life, the universe, and \[rs]"everything\[rs]""
 ANSWER = 42
-;\ Prints "The answer to life, the universe, and everything is $2A"
+;\ Prints "The answer to life, the universe, and "everything" is $2A"
 PRINTT "The answer to {TOPIC} is {ANSWER}\[rs]n"
 .Ed
 .Pp
@@ -276,6 +276,20 @@
 .Ic {symbol}
 construct can also be used outside strings.
 The symbol's value is again inserted directly.
+.Bd -literal -offset indent
+NAME equs "ITEM"
+FMT equs "d"
+ZERO_NUM equ 0
+ZERO_STR equs "0"
+;\ Defines INDEX as 100
+INDEX = 1{ZERO_STR}{{FMT}:ZERO_NUM}
+;\ Defines ITEM_100 as "\[rs]"hundredth\[rs]""
+{NAME}_{d:INDEX} equs "\[rs]"hundredth\[rs]""
+;\ Prints "ITEM_100 is hundredth"
+PRINTT STRCAT("{NAME}_{d:INDEX} is ", {NAME}_{d:INDEX})
+;\ Purges ITEM_100
+PURGE {NAME}_{d:INDEX}
+.Ed
 .Pp
 The following functions operate on string expressions.
 Most of them return a string, however some of these functions actually return an integer and can be used as part of an integer expression!
--- /dev/null
+++ b/test/asm/interpolation.asm
@@ -1,0 +1,15 @@
+SECTION "Test", ROM0
+
+NAME equs "ITEM"
+FMT equs "d"
+ZERO_NUM equ 0
+ZERO_STR equs "0"
+; Defines INDEX as 100
+INDEX = 1{ZERO_STR}{{FMT}:ZERO_NUM}
+; Defines ITEM_100 as "\"hundredth\""
+{NAME}_{d:INDEX} equs "\"hundredth\""
+; Prints "ITEM_100 is hundredth"
+PRINTT STRCAT("{NAME}_{d:INDEX} is ", {NAME}_{d:INDEX}, "\n")
+; Purges ITEM_100
+PURGE {NAME}_{d:INDEX}
+ASSERT !DEF({NAME}_{d:INDEX})
--- /dev/null
+++ b/test/asm/interpolation.out
@@ -1,0 +1,1 @@
+ITEM_100 is hundredth