shithub: rgbds

Download patch

ref: 401fd8b56bddd1f31977968d91aecb00e27d46c1
parent: 58556f91f7cf0d822ca0b4c84046bd5b00e2e78f
parent: 191ee4ba1fb090422b1ac2919d0e8dff11c178ba
author: Eldred Habert <[email protected]>
date: Mon Nov 18 15:58:21 EST 2019

Merge pull request #452 from ISSOtm/warn

Add support for toggleable warnings

--- a/Makefile
+++ b/Makefile
@@ -66,6 +66,7 @@
 	src/asm/rpn.o \
 	src/asm/symbol.o \
 	src/asm/util.o \
+	src/asm/warning.o \
 	src/extern/err.o \
 	src/extern/getopt.o \
 	src/extern/utf8decoder.o \
--- a/include/asm/main.h
+++ b/include/asm/main.h
@@ -40,29 +40,6 @@
 void opt_Pop(void);
 void opt_Parse(char *s);
 
-/*
- * Used for errors that compromise the whole assembly process by affecting the
- * folliwing code, potencially making the assembler generate errors caused by
- * the first one and unrelated to the code that the assembler complains about.
- * It is also used when the assembler goes into an invalid state (for example,
- * when it fails to allocate memory).
- */
-noreturn_ void fatalerror(const char *fmt, ...);
-
-/*
- * Used for errors that make it impossible to assemble correctly, but don't
- * affect the following code. The code will fail to assemble but the user will
- * get a list of all errors at the end, making it easier to fix all of them at
- * once.
- */
-void yyerror(const char *fmt, ...);
-
-/*
- * Used to warn the user about problems that don't prevent the generation of
- * valid code.
- */
-void warning(const char *fmt, ...);
-
 #define YY_FATAL_ERROR fatalerror
 
 #ifdef YYLMAX
--- /dev/null
+++ b/include/asm/warning.h
@@ -1,0 +1,52 @@
+#ifndef WARNING_H
+#define WARNING_H
+
+extern unsigned int nbErrors;
+
+enum WarningID {
+	WARNING_USER,
+	WARNING_OBSOLETE,
+	WARNING_BUILTIN_ARG,
+	WARNING_LARGE_CONSTANT,
+	WARNING_SHIFT,
+	WARNING_DIV,
+	WARNING_EMPTY_ENTRY,
+	WARNING_LONG_STR,
+
+	NB_WARNINGS,
+
+	/* Warnings past this point are "meta" warnings */
+	WARNING_ALL = NB_WARNINGS,
+	WARNING_EXTRA,
+	WARNING_EVERYTHING,
+
+	NB_WARNINGS_ALL
+#define NB_META_WARNINGS (NB_WARNINGS_ALL - NB_WARNINGS)
+};
+
+void processWarningFlag(char const *flag);
+
+/*
+ * Used to warn the user about problems that don't prevent the generation of
+ * valid code.
+ */
+void warning(enum WarningID id, const char *fmt, ...);
+
+/*
+ * Used for errors that compromise the whole assembly process by affecting the
+ * following code, potencially making the assembler generate errors caused by
+ * the first one and unrelated to the code that the assembler complains about.
+ * It is also used when the assembler goes into an invalid state (for example,
+ * when it fails to allocate memory).
+ */
+noreturn_ void fatalerror(const char *fmt, ...);
+
+/*
+ * Used for errors that make it impossible to assemble correctly, but don't
+ * affect the following code. The code will fail to assemble but the user will
+ * get a list of all errors at the end, making it easier to fix all of them at
+ * once.
+ */
+void yyerror(const char *fmt, ...);
+
+#endif
--- a/src/asm/asmy.y
+++ b/src/asm/asmy.y
@@ -27,6 +27,7 @@
 #include "asm/rpn.h"
 #include "asm/symbol.h"
 #include "asm/util.h"
+#include "asm/warning.h"
 
 #include "extern/utf8decoder.h"
 
@@ -482,7 +483,7 @@
 	uint32_t curLen = 0;
 
 	if (pos < 1) {
-		warning("STRSUB: Position starts at 1");
+		warning(WARNING_BUILTIN_ARG, "STRSUB: Position starts at 1");
 		pos = 1;
 	}
 
@@ -500,7 +501,7 @@
 	}
 
 	if (!src[srcIndex])
-		warning("STRSUB: Position %lu is past the end of the string",
+		warning(WARNING_BUILTIN_ARG, "STRSUB: Position %lu is past the end of the string",
 			(unsigned long)pos);
 
 	/* Copy from source to destination. */
@@ -517,7 +518,7 @@
 	}
 
 	if (curLen < len)
-		warning("STRSUB: Length too big: %lu", (unsigned long)len);
+		warning(WARNING_BUILTIN_ARG, "STRSUB: Length too big: %lu", (unsigned long)len);
 
 	/* Check for partial code point. */
 	if (state != 0)
@@ -807,7 +808,7 @@
 fail		: T_POP_FAIL string	{ fatalerror("%s", $2); }
 ;
 
-warn		: T_POP_WARN string	{ warning("%s", $2); }
+warn		: T_POP_WARN string	{ warning(WARNING_USER, "%s", $2); }
 ;
 
 shift		: T_POP_SHIFT		{ sym_ShiftCurrentMacroArgs(); }
@@ -905,7 +906,7 @@
 
 db		: T_POP_DB constlist_8bit_entry comma constlist_8bit {
 			if (nListCountEmpty > 0) {
-				warning("Empty entry in list of 8-bit elements (treated as 0).");
+				warning(WARNING_EMPTY_ENTRY, "Empty entry in list of 8-bit elements (treated as 0).");
 			}
 		}
 		| T_POP_DB constlist_8bit_entry
@@ -913,7 +914,7 @@
 
 dw		: T_POP_DW constlist_16bit_entry comma constlist_16bit {
 			if (nListCountEmpty > 0) {
-				warning("Empty entry in list of 16-bit elements (treated as 0).");
+				warning(WARNING_EMPTY_ENTRY, "Empty entry in list of 16-bit elements (treated as 0).");
 			}
 		}
 		| T_POP_DW constlist_16bit_entry
@@ -921,7 +922,7 @@
 
 dl		: T_POP_DL constlist_32bit_entry comma constlist_32bit {
 			if (nListCountEmpty > 0) {
-				warning("Empty entry in list of 32-bit elements (treated as 0).");
+				warning(WARNING_EMPTY_ENTRY, "Empty entry in list of 32-bit elements (treated as 0).");
 			}
 		}
 		| T_POP_DL constlist_32bit_entry
@@ -957,7 +958,7 @@
 			 * This is done automatically if the label isn't found
 			 * in the list of defined symbols.
 			 */
-			warning("IMPORT is a deprecated keyword with no effect: %s", $1);
+			warning(WARNING_OBSOLETE, "IMPORT is a deprecated keyword with no effect: %s", $1);
 		}
 ;
 
@@ -1461,7 +1462,7 @@
 string		: T_STRING
 		{
 			if (snprintf($$, MAXSTRLEN + 1, "%s", $1) > MAXSTRLEN)
-				warning("String is too long '%s'", $1);
+				warning(WARNING_LONG_STR, "String is too long '%s'", $1);
 		}
 		| T_OP_STRSUB '(' string comma uconst comma uconst ')'
 		{
@@ -1470,12 +1471,12 @@
 		| T_OP_STRCAT '(' string comma string ')'
 		{
 			if (snprintf($$, MAXSTRLEN + 1, "%s%s", $3, $5) > MAXSTRLEN)
-				warning("STRCAT: String too long '%s%s'", $3, $5);
+				warning(WARNING_LONG_STR, "STRCAT: String too long '%s%s'", $3, $5);
 		}
 		| T_OP_STRUPR '(' string ')'
 		{
 			if (snprintf($$, MAXSTRLEN + 1, "%s", $3) > MAXSTRLEN)
-				warning("STRUPR: String too long '%s'", $3);
+				warning(WARNING_LONG_STR, "STRUPR: String too long '%s'", $3);
 
 			upperstring($$);
 		}
@@ -1482,7 +1483,7 @@
 		| T_OP_STRLWR '(' string ')'
 		{
 			if (snprintf($$, MAXSTRLEN + 1, "%s", $3) > MAXSTRLEN)
-				warning("STRUPR: String too long '%s'", $3);
+				warning(WARNING_LONG_STR, "STRUPR: String too long '%s'", $3);
 
 			lowerstring($$);
 		}
@@ -1533,22 +1534,22 @@
 		| T_SECT_OAM	{ $$ = SECTTYPE_OAM; }
 		| T_SECT_HOME
 		{
-			warning("HOME section name is deprecated, use ROM0 instead.");
+			warning(WARNING_OBSOLETE, "HOME section name is deprecated, use ROM0 instead.");
 			$$ = SECTTYPE_ROM0;
 		}
 		| T_SECT_DATA
 		{
-			warning("DATA section name is deprecated, use ROMX instead.");
+			warning(WARNING_OBSOLETE, "DATA section name is deprecated, use ROMX instead.");
 			$$ = SECTTYPE_ROMX;
 		}
 		| T_SECT_CODE
 		{
-			warning("CODE section name is deprecated, use ROMX instead.");
+			warning(WARNING_OBSOLETE, "CODE section name is deprecated, use ROMX instead.");
 			$$ = SECTTYPE_ROMX;
 		}
 		| T_SECT_BSS
 		{
-			warning("BSS section name is deprecated, use WRAM0 instead.");
+			warning(WARNING_OBSOLETE, "BSS section name is deprecated, use WRAM0 instead.");
 			$$ = SECTTYPE_WRAM0;
 		}
 ;
@@ -1746,7 +1747,7 @@
 		| T_Z80_JP T_MODE_HL_IND
 		{
 			out_AbsByte(0xE9);
-			warning("'JP [HL]' is obsolete, use 'JP HL' instead.");
+			warning(WARNING_OBSOLETE, "'JP [HL]' is obsolete, use 'JP HL' instead.");
 		}
 		| T_Z80_JP T_MODE_HL
 		{
@@ -1773,7 +1774,7 @@
 		| T_Z80_LDI T_MODE_A comma T_MODE_HL
 		{
 			out_AbsByte(0x0A | (2 << 4));
-			warning("'LDI A,HL' is obsolete, use 'LDI A,[HL]' or 'LD A,[HL+] instead.");
+			warning(WARNING_OBSOLETE, "'LDI A,HL' is obsolete, use 'LDI A,[HL]' or 'LD A,[HL+] instead.");
 		}
 		| T_Z80_LDI T_MODE_A comma T_MODE_HL_IND
 		{
@@ -1788,7 +1789,7 @@
 		| T_Z80_LDD T_MODE_A comma T_MODE_HL
 		{
 			out_AbsByte(0x0A | (3 << 4));
-			warning("'LDD A,HL' is obsolete, use 'LDD A,[HL]' or 'LD A,[HL-] instead.");
+			warning(WARNING_OBSOLETE, "'LDD A,HL' is obsolete, use 'LDD A,[HL]' or 'LD A,[HL-] instead.");
 		}
 		| T_Z80_LDD T_MODE_A comma T_MODE_HL_IND
 		{
@@ -1842,7 +1843,7 @@
 		{
 			out_AbsByte(0xF8);
 			out_RelByte(&$6);
-			warning("'LD HL,[SP+e8]' is obsolete, use 'LD HL,SP+e8' instead.");
+			warning(WARNING_OBSOLETE, "'LD HL,[SP+e8]' is obsolete, use 'LD HL,SP+e8' instead.");
 		}
 		| T_Z80_LD T_MODE_HL comma T_MODE_SP const_8bit
 		{
--- a/src/asm/charmap.c
+++ b/src/asm/charmap.c
@@ -17,6 +17,7 @@
 #include "asm/main.h"
 #include "asm/output.h"
 #include "asm/util.h"
+#include "asm/warning.h"
 
 #define CHARMAP_HASH_SIZE (1 << 9)
 
@@ -39,7 +40,7 @@
 	if (warned)
 		return;
 
-	warning("Using 'charmap' within a section when the current charmap is 'main' is deprecated");
+	warning(WARNING_OBSOLETE, "Using 'charmap' within a section when the current charmap is 'main' is deprecated");
 	warned = true;
 }
 
--- a/src/asm/constexpr.c
+++ b/src/asm/constexpr.c
@@ -17,6 +17,7 @@
 #include "asm/mymath.h"
 #include "asm/rpn.h"
 #include "asm/symbol.h"
+#include "asm/warning.h"
 
 #include "asmy.h"
 
@@ -171,7 +172,7 @@
 			break;
 		case T_OP_SHL:
 			if (value1 < 0)
-				warning("Left shift of negative value: %d",
+				warning(WARNING_SHIFT, "Left shift of negative value: %d",
 					value1);
 
 			if (value2 < 0)
@@ -200,7 +201,7 @@
 			if (value2 == 0)
 				fatalerror("Division by zero");
 			if (value1 == INT32_MIN && value2 == -1) {
-				warning("Division of min value by -1");
+				warning(WARNING_DIV, "Division of min value by -1");
 				result = INT32_MIN;
 			} else {
 				result = value1 / value2;
--- a/src/asm/fstack.c
+++ b/src/asm/fstack.c
@@ -22,6 +22,7 @@
 #include "asm/main.h"
 #include "asm/output.h"
 #include "asm/symbol.h"
+#include "asm/warning.h"
 
 #include "extern/err.h"
 
@@ -291,7 +292,7 @@
 		len -= retcode;
 
 	if (!len)
-		warning("File stack dump too long, got truncated");
+		warning(WARNING_LONG_STR, "File stack dump too long, got truncated");
 }
 
 /*
--- a/src/asm/globlex.c
+++ b/src/asm/globlex.c
@@ -20,6 +20,7 @@
 #include "asm/rpn.h"
 #include "asm/symbol.h"
 #include "asm/symbol.h"
+#include "asm/warning.h"
 
 #include "helpers.h"
 
@@ -126,7 +127,7 @@
 		 * the Game Boy tile width, produces a nonsensical result.
 		 */
 		if (size > 8) {
-			warning("Graphics constant '%s' is too long",
+			warning(WARNING_LARGE_CONSTANT, "Graphics constant '%s' is too long",
 				start);
 		}
 	} else {
@@ -143,7 +144,7 @@
 		}
 
 		if (overflow)
-			warning("Integer constant '%s' is too large",
+			warning(WARNING_LARGE_CONSTANT, "Integer constant '%s' is too large",
 				start);
 	}
 
--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -20,6 +20,7 @@
 #include "asm/lexer.h"
 #include "asm/main.h"
 #include "asm/rpn.h"
+#include "asm/warning.h"
 
 #include "extern/err.h"
 
--- a/src/asm/main.c
+++ b/src/asm/main.c
@@ -21,6 +21,7 @@
 #include "asm/output.h"
 #include "asm/main.h"
 #include "asm/charmap.h"
+#include "asm/warning.h"
 
 #include "extern/err.h"
 #include "extern/getopt.h"
@@ -38,7 +39,7 @@
 
 clock_t nStartClock, nEndClock;
 int32_t nLineNo;
-uint32_t nTotalLines, nPC, nIFDepth, nUnionDepth, nErrors;
+uint32_t nTotalLines, nPC, nIFDepth, nUnionDepth;
 bool skipElif;
 uint32_t unionStart[128], unionSize[128];
 
@@ -234,61 +235,8 @@
 		sym_AddString(cldefines[i], cldefines[i + 1]);
 }
 
-/*
- * Error handling
- */
-void verror(const char *fmt, va_list args)
-{
-	fputs("ERROR: ", stderr);
-	fstk_Dump();
-	fputs(":\n    ", stderr);
-	vfprintf(stderr, fmt, args);
-	fputc('\n', stderr);
-	fstk_DumpStringExpansions();
-	nErrors++;
-}
-
-void yyerror(const char *fmt, ...)
-{
-	va_list args;
-
-	va_start(args, fmt);
-	verror(fmt, args);
-	va_end(args);
-}
-
-noreturn_ void fatalerror(const char *fmt, ...)
-{
-	va_list args;
-
-	va_start(args, fmt);
-	verror(fmt, args);
-	va_end(args);
-
-	exit(5);
-}
-
-void warning(const char *fmt, ...)
-{
-	if (!CurrentOptions.warnings)
-		return;
-
-	va_list args;
-
-	va_start(args, fmt);
-
-	fputs("warning: ", stderr);
-	fstk_Dump();
-	fputs(":\n    ", stderr);
-	vfprintf(stderr, fmt, args);
-	fputc('\n', stderr);
-	fstk_DumpStringExpansions();
-
-	va_end(args);
-}
-
 /* Short options */
-static char const *optstring = "b:D:Eg:hi:LM:o:p:r:Vvw";
+static char const *optstring = "b:D:Eg:hi:LM:o:p:r:VvW:w";
 
 /*
  * Equivalent long options
@@ -314,7 +262,7 @@
 	{ "recursion-depth",  required_argument, NULL, 'r' },
 	{ "version",          no_argument,       NULL, 'V' },
 	{ "verbose",          no_argument,       NULL, 'v' },
-	{ "warning",          no_argument,       NULL, 'w' },
+	{ "warning",          required_argument, NULL, 'W' },
 	{ NULL,               no_argument,       NULL, 0   }
 };
 
@@ -323,7 +271,7 @@
 	printf(
 "usage: rgbasm [-EhLVvw] [-b chars] [-Dname[=value]] [-g chars] [-i path]\n"
 "              [-M dependfile] [-o outfile] [-p pad_value]\n"
-"              [-r recursion_depth] file.asm\n");
+"              [-r recursion_depth] [-W warning] [-w] file.asm\n");
 	exit(1);
 }
 
@@ -436,6 +384,9 @@
 		case 'v':
 			newopt.verbose = true;
 			break;
+		case 'W':
+			processWarningFlag(optarg);
+			break;
 		case 'w':
 			newopt.warnings = false;
 			break;
@@ -476,7 +427,6 @@
 	skipElif = true;
 	nUnionDepth = 0;
 	nPC = 0;
-	nErrors = 0;
 	sym_Init();
 	sym_SetExportAll(CurrentOptions.exportall);
 	fstk_Init(tzMainfile);
@@ -486,8 +436,8 @@
 	yy_set_state(LEX_STATE_NORMAL);
 	opt_SetCurrentOptions(&DefaultOptions);
 
-	if (yyparse() != 0 || nErrors != 0)
-		errx(1, "Assembly aborted (%ld errors)!", nErrors);
+	if (yyparse() != 0 || nbErrors != 0)
+		errx(1, "Assembly aborted (%ld errors)!", nbErrors);
 
 	if (nIFDepth != 0)
 		errx(1, "Unterminated IF construct (%ld levels)!", nIFDepth);
--- a/src/asm/output.c
+++ b/src/asm/output.c
@@ -24,6 +24,7 @@
 #include "asm/output.h"
 #include "asm/rpn.h"
 #include "asm/symbol.h"
+#include "asm/warning.h"
 
 #include "extern/err.h"
 
--- a/src/asm/rpn.c
+++ b/src/asm/rpn.c
@@ -19,6 +19,7 @@
 #include "asm/main.h"
 #include "asm/rpn.h"
 #include "asm/symbol.h"
+#include "asm/warning.h"
 
 #include "linkdefs.h"
 
@@ -396,7 +397,8 @@
 
 	if (!expr->isReloc) {
 		if (src1->nVal < 0)
-			warning("Left shift of negative value: %d", src1->nVal);
+			warning(WARNING_SHIFT, "Left shift of negative value: %d",
+				src1->nVal);
 
 		if (src2->nVal < 0)
 			fatalerror("Shift by negative value: %d", src2->nVal);
@@ -447,7 +449,7 @@
 			fatalerror("Division by zero");
 
 		if (src1->nVal == INT32_MIN && src2->nVal == -1) {
-			warning("Division of min value by -1");
+			warning(WARNING_DIV, "Division of min value by -1");
 			expr->nVal = INT32_MIN;
 		} else {
 			expr->nVal = (src1->nVal / src2->nVal);
--- a/src/asm/symbol.c
+++ b/src/asm/symbol.c
@@ -23,6 +23,7 @@
 #include "asm/mymath.h"
 #include "asm/output.h"
 #include "asm/util.h"
+#include "asm/warning.h"
 
 #include "extern/err.h"
 
@@ -133,7 +134,7 @@
 	}
 
 	if (snprintf((*ppsym)->tzName, MAXSYMLEN + 1, "%s", s) > MAXSYMLEN)
-		warning("Symbol name is too long: '%s'", s);
+		warning(WARNING_LONG_STR, "Symbol name is too long: '%s'", s);
 
 	(*ppsym)->nValue = 0;
 	(*ppsym)->nType = 0;
--- a/src/asm/util.c
+++ b/src/asm/util.c
@@ -10,6 +10,7 @@
 
 #include "asm/main.h"
 #include "asm/util.h"
+#include "asm/warning.h"
 
 #include "extern/utf8decoder.h"
 
--- /dev/null
+++ b/src/asm/warning.c
@@ -1,0 +1,240 @@
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "asm/fstack.h"
+#include "asm/main.h"
+#include "asm/warning.h"
+
+#include "extern/err.h"
+
+unsigned int nbErrors = 0;
+
+enum WarningState {
+	WARNING_DEFAULT,
+	WARNING_DISABLED,
+	WARNING_ENABLED,
+	WARNING_ERROR
+};
+
+static enum WarningState const defaultWarnings[NB_WARNINGS] = {
+	WARNING_ENABLED,  /* User warnings */
+	WARNING_DISABLED, /* Obsolete things */
+	WARNING_DISABLED, /* Invalid args to builtins */
+	WARNING_DISABLED, /* Constants too large */
+	WARNING_DISABLED, /* Shifting undefined behavior */
+	WARNING_DISABLED, /* Division undefined behavior */
+	WARNING_DISABLED, /* Empty entry in `db`, `dw` or `dl` */
+	WARNING_DISABLED, /* String too long for internal buffers */
+};
+
+static enum WarningState warningStates[NB_WARNINGS];
+
+static bool warningsAreErrors; /* Set if `-Werror` was specified */
+
+static enum WarningState warningState(enum WarningID id)
+{
+	/* Check if warnings are globally disabled */
+	if (!CurrentOptions.warnings)
+		return WARNING_DISABLED;
+
+	/* Get the actual state */
+	enum WarningState state = warningStates[id];
+
+	if (state == WARNING_DEFAULT)
+		/* The state isn't set, grab its default state */
+		state = defaultWarnings[id];
+
+	if (warningsAreErrors && state == WARNING_ENABLED)
+		state = WARNING_ERROR;
+
+	return state;
+}
+
+static char const *warningFlags[NB_WARNINGS_ALL] = {
+	"user",
+	"obsolete",
+	"builtin-args",
+	"large-constant",
+	"shift",
+	"div",
+	"empty-entry",
+	"long-string",
+
+	/* Meta warnings */
+	"all",
+	"extra",
+	"everything" /* Especially useful for testing */
+};
+
+enum MetaWarningCommand {
+	META_WARNING_DONE = NB_WARNINGS
+};
+
+/* Warnings that probably indicate an error */
+static uint8_t const _wallCommands[] = {
+	WARNING_USER,
+	WARNING_BUILTIN_ARG,
+	WARNING_LARGE_CONSTANT,
+	WARNING_EMPTY_ENTRY,
+	WARNING_LONG_STR,
+	META_WARNING_DONE
+};
+
+/* Warnings that are less likely to indicate an error */
+static uint8_t const _wextraCommands[] = {
+	WARNING_OBSOLETE,
+	META_WARNING_DONE
+};
+
+/* Literally everything. Notably useful for testing */
+static uint8_t const _weverythingCommands[] = {
+	WARNING_USER,
+	WARNING_OBSOLETE,
+	WARNING_BUILTIN_ARG,
+	WARNING_LARGE_CONSTANT,
+	WARNING_SHIFT,
+	WARNING_DIV,
+	WARNING_EMPTY_ENTRY,
+	WARNING_LONG_STR,
+	META_WARNING_DONE
+};
+
+static uint8_t const *metaWarningCommands[NB_META_WARNINGS] = {
+	_wallCommands,
+	_wextraCommands,
+	_weverythingCommands
+};
+
+void processWarningFlag(char const *flag)
+{
+	static bool setError = false;
+
+	/* First, try to match against a "meta" warning */
+	for (enum WarningID id = NB_WARNINGS; id < NB_WARNINGS_ALL; id++) {
+		/* TODO: improve the matching performance? */
+		if (!strcmp(flag, warningFlags[id])) {
+			/* We got a match! */
+			uint8_t const *ptr =
+					metaWarningCommands[id - NB_WARNINGS];
+
+			for (;;) {
+				if (*ptr == META_WARNING_DONE)
+					return;
+
+				/* Warning flag, set without override */
+				if (warningStates[*ptr] == WARNING_DEFAULT)
+					warningStates[*ptr] = WARNING_ENABLED;
+				ptr++;
+			}
+		}
+	}
+
+	/* If it's not a meta warning, specially check against `-Werror` */
+	if (!strncmp(flag, "error", strlen("error"))) {
+		char const *errorFlag = flag + strlen("error");
+
+		switch (*errorFlag) {
+		case '\0':
+			/* `-Werror` */
+			warningsAreErrors = true;
+			return;
+
+		case '=':
+			/* `-Werror=XXX */
+			setError = true;
+			processWarningFlag(errorFlag + 1); /* Skip the `=` */
+			setError = false;
+			return;
+
+		/* Otherwise, allow parsing as another flag */
+		}
+	}
+
+	/* Well, it's either a normal warning or a mistake */
+
+	/* Check if this is a negation */
+	bool isNegation = !strncmp(flag, "no-", strlen("no-")) && !setError;
+	char const *rootFlag = isNegation ? flag + strlen("no-") : flag;
+	enum WarningState state = setError ? WARNING_ERROR :
+			isNegation ? WARNING_DISABLED : WARNING_ENABLED;
+
+	/* Try to match the flag against a "normal" flag */
+	for (enum WarningID id = 0; id < NB_WARNINGS; id++) {
+		if (!strcmp(rootFlag, warningFlags[id])) {
+			/* We got a match! */
+			warningStates[id] = state;
+			return;
+		}
+	}
+
+	warnx("Unknown warning `%s`", flag);
+}
+
+void verror(const char *fmt, va_list args, char const *flag)
+{
+	fputs("ERROR: ", stderr);
+	fstk_Dump();
+	fprintf(stderr, flag ? ": [-Werror=%s]\n    " : ":\n    ", flag);
+	vfprintf(stderr, fmt, args);
+	fputc('\n', stderr);
+	fstk_DumpStringExpansions();
+	nbErrors++;
+}
+
+void yyerror(const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	verror(fmt, args, NULL);
+	va_end(args);
+}
+
+noreturn_ void fatalerror(const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	verror(fmt, args, NULL);
+	va_end(args);
+
+	exit(5);
+}
+
+void warning(enum WarningID id, char const *fmt, ...)
+{
+	char const *flag = warningFlags[id];
+	va_list args;
+
+	va_start(args, fmt);
+
+	switch (warningState(id)) {
+	case WARNING_DISABLED:
+		return;
+
+	case WARNING_ERROR:
+		verror(fmt, args, flag);
+		va_end(args);
+		return;
+
+	case WARNING_DEFAULT:
+		abort();
+		/* Not reached */
+
+	case WARNING_ENABLED:
+		break;
+	}
+
+	fputs("warning: ", stderr);
+	fstk_Dump();
+	fprintf(stderr, ": [-W%s]\n    ", flag);
+	vfprintf(stderr, fmt, args);
+	fputc('\n', stderr);
+	fstk_DumpStringExpansions();
+
+	va_end(args);
+}
--- a/test/asm/correct-line-number.out
+++ b/test/asm/correct-line-number.out
@@ -1,4 +1,4 @@
-warning: correct-line-number.asm(5):
+warning: correct-line-number.asm(5): [-Wuser]
     Am I geting ahead of myself?
-warning: correct-line-number.asm(11):
+warning: correct-line-number.asm(11): [-Wuser]
     Hopefully not.
--- a/test/asm/multiple-charmaps.out
+++ b/test/asm/multiple-charmaps.out
@@ -1,4 +1,4 @@
-warning: multiple-charmaps.asm(75):
+warning: multiple-charmaps.asm(75): [-Wobsolete]
     Using 'charmap' within a section when the current charmap is 'main' is deprecated
 ERROR: multiple-charmaps.asm(100) -> multiple-charmaps.asm::new_(7):
     Charmap 'map1' already exists
--- a/test/asm/overflow.out
+++ b/test/asm/overflow.out
@@ -1,14 +1,14 @@
-warning: overflow.asm(24):
+warning: overflow.asm(24): [-Wdiv]
     Division of min value by -1
-warning: overflow.asm(25):
+warning: overflow.asm(25): [-Wdiv]
     Division of min value by -1
-warning: overflow.asm(34):
+warning: overflow.asm(34): [-Wshift]
     Left shift of negative value: -1
-warning: overflow.asm(35):
+warning: overflow.asm(35): [-Wshift]
     Left shift of negative value: -1
-warning: overflow.asm(39):
+warning: overflow.asm(39): [-Wlarge-constant]
     Integer constant '4294967296' is too large
-warning: overflow.asm(42):
+warning: overflow.asm(42): [-Wlarge-constant]
     Graphics constant '`333333333' is too long
 $80000000
 $7FFFFFFF
--- a/test/asm/strsub.out
+++ b/test/asm/strsub.out
@@ -1,18 +1,18 @@
-warning: strsub.asm(13) -> strsub.asm::xstrsub(4):
+warning: strsub.asm(13) -> strsub.asm::xstrsub(4): [-Wbuiltin-args]
     STRSUB: Length too big: 32
-warning: strsub.asm(14) -> strsub.asm::xstrsub(4):
+warning: strsub.asm(14) -> strsub.asm::xstrsub(4): [-Wbuiltin-args]
     STRSUB: Length too big: 300
-warning: strsub.asm(15) -> strsub.asm::xstrsub(4):
+warning: strsub.asm(15) -> strsub.asm::xstrsub(4): [-Wbuiltin-args]
     STRSUB: Position starts at 1
-warning: strsub.asm(15) -> strsub.asm::xstrsub(4):
+warning: strsub.asm(15) -> strsub.asm::xstrsub(4): [-Wbuiltin-args]
     STRSUB: Length too big: 300
-warning: strsub.asm(16) -> strsub.asm::xstrsub(4):
+warning: strsub.asm(16) -> strsub.asm::xstrsub(4): [-Wbuiltin-args]
     STRSUB: Position 4 is past the end of the string
-warning: strsub.asm(17) -> strsub.asm::xstrsub(4):
+warning: strsub.asm(17) -> strsub.asm::xstrsub(4): [-Wbuiltin-args]
     STRSUB: Position 4 is past the end of the string
-warning: strsub.asm(17) -> strsub.asm::xstrsub(4):
+warning: strsub.asm(17) -> strsub.asm::xstrsub(4): [-Wbuiltin-args]
     STRSUB: Length too big: 1
-warning: strsub.asm(20) -> strsub.asm::xstrsub(4):
+warning: strsub.asm(20) -> strsub.asm::xstrsub(4): [-Wbuiltin-args]
     STRSUB: Length too big: 10
 A
 B
--- a/test/asm/test.sh
+++ b/test/asm/test.sh
@@ -10,7 +10,7 @@
 for i in *.asm; do
 	for variant in '' '.pipe'; do
 		if [ -z "$variant" ]; then
-			../../rgbasm -o $o $i > $after 2>&1
+			../../rgbasm -Weverything -o $o $i > $after 2>&1
 			desired_output=${i%.asm}.out
 		else
 			# `include-recursion.asm` refers to its own name inside the test code.
@@ -23,7 +23,7 @@
 			# stdin redirection makes the input an unseekable pipe - a scenario
 			# that's harder to deal with and was broken when the feature was
 			# first implemented.
-			cat $i | ../../rgbasm -o $o - > $after 2>&1
+			cat $i | ../../rgbasm -Weverything -o $o - > $after 2>&1
 
 			# Escape regex metacharacters
 			desired_output=$before