shithub: rgbds

Download patch

ref: 0e405437574b9c37f23c1ded0efc0d9b3d690931
parent: ce58f6d6be1ea540930211a4d80de157a6e1f943
author: Rangi <remy.oukaour+rangi42@gmail.com>
date: Sat Dec 12 13:23:26 EST 2020

Implement `\#` to expand to all unshifted macro arguments

Fixes #596

--- a/include/asm/macro.h
+++ b/include/asm/macro.h
@@ -24,6 +24,7 @@
 void macro_UseNewArgs(struct MacroArgs *args);
 void macro_FreeArgs(struct MacroArgs *args);
 char const *macro_GetArg(uint32_t i);
+char *macro_GetAllArgs(void);
 
 uint32_t macro_GetUniqueID(void);
 char const *macro_GetUniqueIDStr(void);
--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -290,11 +290,15 @@
 	struct Expansion *firstChild;
 	struct Expansion *next;
 	char *name;
-	char const *contents;
+	union {
+		char const *unowned;
+		char *owned;
+	} contents;
 	size_t len;
 	size_t totalLen;
 	size_t distance; /* Distance between the beginning of this expansion and of its parent */
 	uint8_t skip; /* How many extra characters to skip after the expansion is over */
+	bool owned; /* Whether or not to free contents when this expansion is freed */
 };
 
 struct LexerState {
@@ -632,7 +636,8 @@
 }
 
 static void beginExpansion(size_t distance, uint8_t skip,
-			   char const *str, size_t size, char const *name)
+			   char const *str, size_t size, bool owned,
+			   char const *name)
 {
 	distance += lexerState->expansionOfs; /* Distance argument is relative to read offset! */
 	/* Increase the total length of all parents, and return the topmost one */
@@ -659,11 +664,12 @@
 	(*insertPoint)->firstChild = NULL;
 	(*insertPoint)->next = NULL; /* Expansions are always performed left to right */
 	(*insertPoint)->name = name ? strdup(name) : NULL;
-	(*insertPoint)->contents = str;
+	(*insertPoint)->contents.unowned = str;
 	(*insertPoint)->len = size;
 	(*insertPoint)->totalLen = size;
 	(*insertPoint)->distance = distance;
 	(*insertPoint)->skip = skip;
+	(*insertPoint)->owned = owned;
 
 	/* If expansion is the new closest one, update offset */
 	if (insertPoint == &lexerState->expansions)
@@ -681,6 +687,8 @@
 		child = next;
 	}
 	free(expansion->name);
+	if (expansion->owned)
+		free(expansion->contents.owned);
 	free(expansion);
 }
 
@@ -690,6 +698,8 @@
 
 	if (name == '@')
 		str = macro_GetUniqueIDStr();
+	else if (name == '#')
+		str = macro_GetAllArgs();
 	else if (name == '0')
 		fatalerror("Invalid macro argument '\\0'\n");
 	else
@@ -697,7 +707,11 @@
 	if (!str)
 		fatalerror("Macro argument '\\%c' not defined\n", name);
 
-	beginExpansion(distance, 2, str, strlen(str), NULL);
+	/* Cannot expand an empty string */
+	if (!str[0])
+		return NULL;
+
+	beginExpansion(distance, 2, str, strlen(str), name == '#', NULL);
 	return str;
 }
 
@@ -713,7 +727,7 @@
 
 	if (expansion) {
 		assert(ofs < expansion->len);
-		return expansion->contents[ofs];
+		return expansion->contents.unowned[ofs];
 	}
 
 	distance = ofs;
@@ -785,13 +799,21 @@
 		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')) {
+			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 - 1);
+				char const *str = expandMacroArg(c, distance);
 
 				/*
+				 * If the argument is an empty string, nothing was
+				 * expanded, so skip it and keep peeking.
+				 */
+				if (!str) {
+					shiftChars(2);
+					goto restart;
+				}
+
+				/*
 				 * 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;
 				 * however, the two macro arg characters (\1) will be ignored,
@@ -798,7 +820,10 @@
 				 * so they shouldn't be counted in the scan distance!
 				 */
 				lexerState->macroArgScanDistance += strlen(str) - 2;
-				/* WARNING: this assumes macro args can't be empty!! */
+				/*
+				 * This assumes macro args can't be empty, since expandMacroArg
+				 * returns NULL instead of an empty string.
+				 */
 				c = str[0];
 			} else {
 				c = '\\';
@@ -810,7 +835,7 @@
 			char const *ptr = readInterpolation();
 
 			if (ptr) {
-				beginExpansion(distance, 0, ptr, strlen(ptr), ptr);
+				beginExpansion(distance, 0, ptr, strlen(ptr), false, ptr);
 				goto restart;
 			}
 		}
@@ -1294,7 +1319,7 @@
 			char const *ptr = readInterpolation();
 
 			if (ptr) {
-				beginExpansion(0, 0, ptr, strlen(ptr), ptr);
+				beginExpansion(0, 0, ptr, strlen(ptr), false, ptr);
 				continue; /* Restart, reading from the new buffer */
 			}
 		} else if (c == EOF || c == '\r' || c == '\n' || c == '"') {
@@ -1739,7 +1764,8 @@
 					if (sym && sym->type == SYM_EQUS) {
 						char const *s = sym_GetStringValue(sym);
 
-						beginExpansion(0, 0, s, strlen(s), sym->name);
+						beginExpansion(0, 0, s, strlen(s), false,
+							       sym->name);
 						continue; /* Restart, reading from the new buffer */
 					}
 				}
--- a/src/asm/macro.c
+++ b/src/asm/macro.c
@@ -98,6 +98,41 @@
 					      : macroArgs->args[realIndex];
 }
 
+char *macro_GetAllArgs(void)
+{
+	if (!macroArgs)
+		return NULL;
+
+	if (macroArgs->shift >= macroArgs->nbArgs)
+		return "";
+
+	size_t len = strlen(macroArgs->args[macroArgs->shift]);
+
+	for (uint32_t i = macroArgs->shift + 1; i < macroArgs->nbArgs; i++)
+		len += 1 + strlen(macroArgs->args[i]);
+
+	char *str = malloc(len + 1);
+
+	if (!str)
+		fatalerror("Failed to allocate memory for expanding '\\#': %s\n", strerror(errno));
+
+	char *ptr = str;
+
+	size_t n = strlen(macroArgs->args[macroArgs->shift]);
+
+	memcpy(ptr, macroArgs->args[macroArgs->shift], n);
+	ptr += n;
+	for (uint32_t i = macroArgs->shift + 1; i < macroArgs->nbArgs; i++) {
+		*ptr++ = ','; /* no space after comma */
+		n = strlen(macroArgs->args[i]);
+		memcpy(ptr, macroArgs->args[i], n);
+		ptr += n;
+	}
+	*ptr = '\0';
+
+	return str;
+}
+
 uint32_t macro_GetUniqueID(void)
 {
 	return uniqueID;
--- /dev/null
+++ b/test/asm/macro-#.asm
@@ -1,0 +1,46 @@
+SECTION "Test", ROM0
+
+list: MACRO
+	db _NARG
+if _NARG > 0
+	db \#
+endc
+	db -1
+ENDM
+
+	list
+	list 42
+	list $aa, $bb, $cc, $dd, $ee
+
+person: MACRO
+	db \1, \2, \3, \4, \5
+ENDM
+
+object: MACRO
+x = \1
+y = \2
+	shift 2
+	person y, x, \#
+ENDM
+
+	person  5, 10, $33, $44, $55
+	object 12,  6, $66, $77, $88
+
+echo: MACRO
+	printt "\#\n"
+ENDM
+
+R EQUS "S"
+
+	echo P
+	echo Q,R, {R},  T
+	echo 42,$2a
+
+print: MACRO
+	printt STRCAT(\#)
+	printt "\n"
+ENDM
+
+	print
+	print "A"
+	print "B", "C",  "D"
--- /dev/null
+++ b/test/asm/macro-#.out
@@ -1,0 +1,6 @@
+P
+Q,R,S,T
+42,$2a
+
+A
+BCD
binary files /dev/null b/test/asm/macro-#.out.bin differ