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 "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