shithub: rgbds

Download patch

ref: 1dafc1c762b4ea50030272775e0ca217fe93c0c0
parent: 63d15ac8c901cab95c3d37abad2a97c7020a61d7
author: Rangi <remy.oukaour+rangi42@gmail.com>
date: Wed Feb 17 13:54:02 EST 2021

Allow empty macro arguments, with a warning

Fixes #739

--- a/include/asm/lexer.h
+++ b/include/asm/lexer.h
@@ -67,6 +67,7 @@
 };
 
 void lexer_SetMode(enum LexerMode mode);
+bool lexer_IsRawMode(void);
 void lexer_ToggleStringExpansion(bool enable);
 
 uint32_t lexer_GetIFDepth(void);
--- a/include/asm/macro.h
+++ b/include/asm/macro.h
@@ -10,6 +10,7 @@
 #define RGBDS_MACRO_H
 
 #include <stdint.h>
+#include <stdbool.h>
 #include <stdlib.h>
 
 #include "asm/warning.h"
@@ -20,7 +21,7 @@
 
 struct MacroArgs *macro_GetCurrentArgs(void);
 struct MacroArgs *macro_NewArgs(void);
-void macro_AppendArg(struct MacroArgs **args, char *s);
+void macro_AppendArg(struct MacroArgs **args, char *s, bool isLastArg);
 void macro_UseNewArgs(struct MacroArgs *args);
 void macro_FreeArgs(struct MacroArgs *args);
 char const *macro_GetArg(uint32_t i);
--- a/include/asm/warning.h
+++ b/include/asm/warning.h
@@ -20,6 +20,7 @@
 	WARNING_DIV,		      /* Division undefined behavior */
 	WARNING_EMPTY_DATA_DIRECTIVE, /* `db`, `dw` or `dl` directive without data in ROM */
 	WARNING_EMPTY_ENTRY,	      /* Empty entry in `db`, `dw` or `dl` */
+	WARNING_EMPTY_MACRO_ARG,      /* Empty macro argument */
 	WARNING_EMPTY_STRRPL,	      /* Empty second argument in `STRRPL` */
 	WARNING_LARGE_CONSTANT,	      /* Constants too large */
 	WARNING_LONG_STR,	      /* String too long for internal buffers */
--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -360,6 +360,7 @@
 	bool disableMacroArgs;
 	bool disableInterpolation;
 	size_t macroArgScanDistance; /* Max distance already scanned for macro args */
+	bool injectNewline; /* Whether to inject a newline at EOF */
 	bool expandStrings;
 	struct Expansion *expansions;
 	size_t expansionOfs; /* Offset into the current top-level expansion (negative = before) */
@@ -381,6 +382,7 @@
 	state->disableMacroArgs = false;
 	state->disableInterpolation = false;
 	state->macroArgScanDistance = 0;
+	state->injectNewline = false;
 	state->expandStrings = true;
 	state->expansions = NULL;
 	state->expansionOfs = 0;
@@ -638,6 +640,11 @@
 	lexerState->mode = mode;
 }
 
+bool lexer_IsRawMode(void)
+{
+	return lexerState->mode == LEXER_RAW;
+}
+
 void lexer_ToggleStringExpansion(bool enable)
 {
 	lexerState->expandStrings = enable;
@@ -2047,6 +2054,10 @@
 			return T_NEWLINE;
 
 		case EOF:
+			if (lexerState->injectNewline) {
+				lexerState->injectNewline = false;
+				return T_NEWLINE;
+			}
 			return T_EOF;
 
 		/* Handle escapes */
@@ -2140,6 +2151,19 @@
 		case '\r':
 		case '\n':
 		case EOF:
+			// Returning T_COMMAs to the parser would mean that two consecutive commas
+			// (i.e. an empty argument) need to return two different tokens (T_STRING
+			// then T_COMMA) without advancing the read. To avoid this, commas in raw
+			// mode end the current macro argument but are not tokenized themselves.
+			if (c == ',')
+				shiftChars(1);
+			else
+				lexer_SetMode(LEXER_NORMAL);
+			// If a macro is invoked on the last line of a file, with no blank
+			// line afterwards, returning EOF afterwards will cause Bison to
+			// stop parsing, despite the lexer being ready to output more.
+			if (c == EOF)
+				lexerState->injectNewline = true;
 			/* Trim right whitespace */
 			while (i && isWhitespace(yylval.tzString[i - 1]))
 				i--;
@@ -2146,20 +2170,6 @@
 			if (i == sizeof(yylval.tzString)) {
 				i--;
 				warning(WARNING_LONG_STR, "Macro argument too long\n");
-			}
-			/* Empty macro args break their expansion, so prevent that */
-			if (i == 0) {
-				// If at EOF, don't shift a non-existent char.
-				// However, don't return EOF, as this might cause a bug...
-				// If a macro is invoked on the last line of a file, with no blank
-				// line afterwards, returning EOF here will cause Bison to stop
-				// parsing, despite the lexer being ready to output more.
-				if (c == EOF)
-					return T_NEWLINE;
-				shiftChars(1);
-				if (c == '\r' && peek(0) == '\n')
-					shiftChars(1);
-				return c == ',' ? T_COMMA : T_NEWLINE;
 			}
 			yylval.tzString[i] = '\0';
 			dbgPrint("Read raw string \"%s\"\n", yylval.tzString);
--- a/src/asm/macro.c
+++ b/src/asm/macro.c
@@ -2,6 +2,7 @@
 #include <assert.h>
 #include <errno.h>
 #include <inttypes.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -58,9 +59,15 @@
 	return args;
 }
 
-void macro_AppendArg(struct MacroArgs **argPtr, char *s)
+void macro_AppendArg(struct MacroArgs **argPtr, char *s, bool isLastArg)
 {
 #define macArgs (*argPtr)
+	if (s[0] == '\0') {
+		/* Zero arguments are parsed as a spurious empty argument; do not append it */
+		if (isLastArg && !macArgs->nbArgs)
+			return;
+		warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument\n");
+	}
 	if (macArgs->nbArgs == MAXMACROARGS)
 		error("A maximum of " EXPAND_AND_STR(MAXMACROARGS) " arguments is allowed\n");
 	if (macArgs->nbArgs >= macArgs->capacity) {
--- a/src/asm/parser.y
+++ b/src/asm/parser.y
@@ -730,7 +730,6 @@
 macro		: T_ID {
 			lexer_SetMode(LEXER_RAW);
 		} macroargs {
-			lexer_SetMode(LEXER_NORMAL);
 			fstk_RunMacro($1, $3);
 		}
 ;
@@ -738,12 +737,8 @@
 macroargs	: %empty {
 			$$ = macro_NewArgs();
 		}
-		| T_STRING {
-			$$ = macro_NewArgs();
-			macro_AppendArg(&($$), strdup($1));
-		}
-		| macroargs T_COMMA T_STRING {
-			macro_AppendArg(&($$), strdup($3));
+		| macroargs T_STRING {
+			macro_AppendArg(&($$), strdup($2), !lexer_IsRawMode());
 		}
 ;
 
--- a/src/asm/rgbasm.1
+++ b/src/asm/rgbasm.1
@@ -215,6 +215,10 @@
 list.
 This warning is enabled by
 .Fl Wextra .
+.It Fl Wempty-macro-arg
+Warn when a macro argument is empty.
+This warning is enabled by
+.Fl Wextra .
 .It Fl Wempty-strrpl
 Warn when
 .Fn STRRPL
--- a/src/asm/warning.c
+++ b/src/asm/warning.c
@@ -35,6 +35,7 @@
 	[WARNING_DIV]			= WARNING_DISABLED,
 	[WARNING_EMPTY_DATA_DIRECTIVE]	= WARNING_DISABLED,
 	[WARNING_EMPTY_ENTRY]		= WARNING_DISABLED,
+	[WARNING_EMPTY_MACRO_ARG]	= WARNING_DISABLED,
 	[WARNING_EMPTY_STRRPL]		= WARNING_DISABLED,
 	[WARNING_LARGE_CONSTANT]	= WARNING_DISABLED,
 	[WARNING_LONG_STR]		= WARNING_DISABLED,
@@ -77,6 +78,7 @@
 	"div",
 	"empty-data-directive",
 	"empty-entry",
+	"empty-macro-arg",
 	"empty-strrpl",
 	"large-constant",
 	"long-string",
@@ -112,6 +114,7 @@
 /* Warnings that are less likely to indicate an error */
 static uint8_t const _wextraCommands[] = {
 	WARNING_EMPTY_ENTRY,
+	WARNING_EMPTY_MACRO_ARG,
 	WARNING_MACRO_SHIFT,
 	WARNING_NESTED_COMMENT,
 	META_WARNING_DONE
@@ -123,6 +126,7 @@
 	WARNING_DIV,
 	WARNING_EMPTY_DATA_DIRECTIVE,
 	WARNING_EMPTY_ENTRY,
+	WARNING_EMPTY_MACRO_ARG,
 	WARNING_EMPTY_STRRPL,
 	WARNING_LARGE_CONSTANT,
 	WARNING_LONG_STR,
--- a/test/asm/macro-arguments.asm
+++ b/test/asm/macro-arguments.asm
@@ -14,3 +14,10 @@
 	c, d
 	mac 1, 2 + /* another ;
 		; comment */ 2, 3
+
+	mac
+	mac a,,
+	mac ,,z
+	mac a,,z
+	mac ,a,b,c,
+	mac ,,x,,
--- a/test/asm/macro-arguments.err
+++ b/test/asm/macro-arguments.err
@@ -1,0 +1,22 @@
+warning: macro-arguments.asm(19): [-Wempty-macro-arg]
+    Empty macro argument
+warning: macro-arguments.asm(19): [-Wempty-macro-arg]
+    Empty macro argument
+warning: macro-arguments.asm(20): [-Wempty-macro-arg]
+    Empty macro argument
+warning: macro-arguments.asm(20): [-Wempty-macro-arg]
+    Empty macro argument
+warning: macro-arguments.asm(21): [-Wempty-macro-arg]
+    Empty macro argument
+warning: macro-arguments.asm(22): [-Wempty-macro-arg]
+    Empty macro argument
+warning: macro-arguments.asm(22): [-Wempty-macro-arg]
+    Empty macro argument
+warning: macro-arguments.asm(23): [-Wempty-macro-arg]
+    Empty macro argument
+warning: macro-arguments.asm(23): [-Wempty-macro-arg]
+    Empty macro argument
+warning: macro-arguments.asm(23): [-Wempty-macro-arg]
+    Empty macro argument
+warning: macro-arguments.asm(23): [-Wempty-macro-arg]
+    Empty macro argument
--- a/test/asm/macro-arguments.out
+++ b/test/asm/macro-arguments.out
@@ -13,3 +13,34 @@
 \2: <2 +  2>
 \3: <3>
 
+'mac ':
+
+'mac a,,':
+\1: <a>
+\2: <>
+\3: <>
+
+'mac ,,z':
+\1: <>
+\2: <>
+\3: <z>
+
+'mac a,,z':
+\1: <a>
+\2: <>
+\3: <z>
+
+'mac ,a,b,c,':
+\1: <>
+\2: <a>
+\3: <b>
+\4: <c>
+\5: <>
+
+'mac ,,x,,':
+\1: <>
+\2: <>
+\3: <x>
+\4: <>
+\5: <>
+