shithub: rgbds

Download patch

ref: eb4952c188fddf486adbdb98a82ea9e6123eaff1
parent: 57b734a7dfc3767cedc1940ed55cce3262a955ef
author: Rangi <remy.oukaour+rangi42@gmail.com>
date: Sun Jan 10 09:48:48 EST 2021

Use more verbose syntax error messages

Fixes #385

--- a/Makefile
+++ b/Makefile
@@ -126,11 +126,15 @@
 	$QDEFS=; \
 	add_flag(){ \
 		if src/check_bison_ver.sh $$1 $$2; then \
-			DEFS="$$DEFS -D$$3"; \
+			DEFS="-D$$3 $$DEFS"; \
 		fi \
 	}; \
 	add_flag 3 5 api.token.raw=true; \
-	${BISON} -d $$DEFS ${YFLAGS} -o $@ $<
+	add_flag 3 6 parse.error=detailed; \
+	add_flag 3 0 parse.error=verbose; \
+	add_flag 3 0 parse.lac=full; \
+	echo "DEFS=$$DEFS"; \
+	${BISON} $$DEFS -d ${YFLAGS} -o $@ $<
 
 .c.o:
 	$Q${CC} ${REALCFLAGS} ${PNGCFLAGS} -c -o $@ $<
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -26,6 +26,14 @@
 if(BISON_VERSION VERSION_GREATER_EQUAL "3.5")
   set(BISON_FLAGS "${BISON_FLAGS} -Dapi.token.raw=true")
 endif()
+if(BISON_VERSION VERSION_GREATER_EQUAL "3.6")
+  set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=detailed")
+elseif(BISON_VERSION VERSION_GREATER_EQUAL "3.0")
+  set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=verbose")
+endif()
+if(BISON_VERSION VERSION_GREATER_EQUAL "3.0")
+  set(BISON_FLAGS "${BISON_FLAGS} -Dparse.lac=full")
+endif()
 BISON_TARGET(PARSER "asm/parser.y"
              "${PROJECT_SOURCE_DIR}/src/asm/parser.c"
              COMPILE_FLAGS "${BISON_FLAGS}"
--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -124,8 +124,8 @@
 	{"LD", T_Z80_LD},
 	{"LDI", T_Z80_LDI},
 	{"LDD", T_Z80_LDD},
-	{"LDIO", T_Z80_LDIO},
-	{"LDH", T_Z80_LDIO},
+	{"LDIO", T_Z80_LDH},
+	{"LDH", T_Z80_LDH},
 	{"NOP", T_Z80_NOP},
 	{"OR", T_Z80_OR},
 	{"POP", T_Z80_POP},
@@ -156,7 +156,7 @@
 	{"NZ", T_CC_NZ},
 	{"Z", T_CC_Z},
 	{"NC", T_CC_NC},
-	/* Handled in list of registers */
+	/* Handled after as T_TOKEN_C */
 	/* { "C", T_CC_C }, */
 
 	{"AF", T_MODE_AF},
@@ -276,11 +276,13 @@
 
 	{"RB", T_POP_RB},
 	{"RW", T_POP_RW},
+	/* Handled before as T_Z80_RL */
+	/* {"RL", T_POP_RL}, */
 	{"EQU", T_POP_EQU},
 	{"EQUS", T_POP_EQUS},
 	{"REDEF", T_POP_REDEF},
 
-	/*  Handled before in list of CPU instructions */
+	/* Handled before as T_Z80_SET */
 	/* {"SET", T_POP_SET}, */
 
 	{"PUSHS", T_POP_PUSHS},
--- a/src/asm/parser.y
+++ b/src/asm/parser.y
@@ -334,8 +334,47 @@
 	}
 }
 
-#define yyerror(str) error(str "\n")
+void yyerror(char const *str)
+{
+	size_t len = strlen(str);
+	char *buf = malloc(len + 2);
 
+	memcpy(buf, str, len);
+	buf[len] = '\n';
+	buf[len + 1] = '\0';
+	error(buf);
+	free(buf);
+}
+
+/*
+ * The default 'yytnamerr' implementation strips the added double quotes
+ * from token names only if they do not contain quotes themselves.
+ * This strips them in all cases.
+ */
+static size_t rgbasm_yytnamerr(char *yyres, const char *yystr)
+{
+	size_t n = strlen(yystr);
+
+	if (!yyres)
+		return n;
+
+	if (yystr[0] == '"' && yystr[n-1] == '"') {
+		memcpy(yyres, yystr + 1, n - 2);
+		yyres[n-1] = '\0';
+		return n - 2;
+	}
+
+	strcpy(yyres, yystr);
+	return n;
+}
+
+/* Avoid a "'yystpcpy' defined but not used" error from gcc */
+#ifndef _MSC_VER
+# define yystpcpy stpcpy
+#endif
+
+#define yytnamerr rgbasm_yytnamerr
+
 %}
 
 %union
@@ -377,19 +416,29 @@
 %type	<nConstValue>	sectorg
 %type	<sectSpec>	sectattrs
 
-%token	<nConstValue>	T_NUMBER
-%token	<tzString>	T_STRING
+%token	<nConstValue>	T_NUMBER "number"
+%token	<tzString>	T_STRING "string"
 
-%left	T_COMMA
-%left	T_COLON
-%left	T_LBRACK
-%left	T_RBRACK
-%left	T_LPAREN
-%left	T_RPAREN
-%left	T_NEWLINE
+%token	T_COMMA "','"
+%token	T_COLON "':'"
+%token	T_LBRACK "'['" T_RBRACK "']'"
+%token	T_LPAREN "'('" T_RPAREN "')'"
+%token	T_NEWLINE "newline"
+%left	T_COMMA T_COLON T_LBRACK T_RBRACK T_LPAREN T_RPAREN T_NEWLINE
 
+%token	T_OP_LOGICNOT "'!'"
+%token	T_OP_LOGICAND "'&&'" T_OP_LOGICOR "'||'"
+%token	T_OP_LOGICGT "'>'" T_OP_LOGICLT "'<'"
+%token	T_OP_LOGICGE "'>='" T_OP_LOGICLE "'<='"
+%token	T_OP_LOGICNE "'!='" T_OP_LOGICEQU "'=='"
+%token	T_OP_ADD "'+'" T_OP_SUB "'-'"
+%token	T_OP_OR "'|'" T_OP_XOR "'^'" T_OP_AND "'&'"
+%token	T_OP_SHL "'<<'" T_OP_SHR "'>>'"
+%token	T_OP_MUL "'*'" T_OP_DIV "'/'" T_OP_MOD "'%'"
+%token	T_OP_NOT "'~'"
 %left	T_OP_LOGICNOT
-%left	T_OP_LOGICOR T_OP_LOGICAND
+%left	T_OP_LOGICOR
+%left	T_OP_LOGICAND
 %left	T_OP_LOGICGT T_OP_LOGICLT T_OP_LOGICGE T_OP_LOGICLE T_OP_LOGICNE T_OP_LOGICEQU
 %left	T_OP_ADD T_OP_SUB
 %left	T_OP_OR T_OP_XOR T_OP_AND
@@ -399,85 +448,86 @@
 
 %left	NEG /* negation -- unary minus */
 
+%token	T_OP_EXP "'**'"
 %left	T_OP_EXP
 
-%left	T_OP_DEF
-%left	T_OP_BANK T_OP_ALIGN
-%left	T_OP_SIN
-%left	T_OP_COS
-%left	T_OP_TAN
-%left	T_OP_ASIN
-%left	T_OP_ACOS
-%left	T_OP_ATAN
-%left	T_OP_ATAN2
-%left	T_OP_FDIV
-%left	T_OP_FMUL
-%left	T_OP_POW
-%left	T_OP_LOG
-%left	T_OP_ROUND
-%left	T_OP_CEIL
-%left	T_OP_FLOOR
+%token	T_OP_DEF "DEF"
+%token	T_OP_BANK "BANK"
+%token	T_OP_ALIGN "ALIGN"
+%token	T_OP_SIN "SIN" T_OP_COS "COS" T_OP_TAN "TAN"
+%token	T_OP_ASIN "ASIN" T_OP_ACOS "ACOS" T_OP_ATAN "ATAN" T_OP_ATAN2 "ATAN2"
+%token	T_OP_FDIV "FDIV"
+%token	T_OP_FMUL "FMUL"
+%token	T_OP_POW "POW"
+%token	T_OP_LOG "LOG"
+%token	T_OP_ROUND "ROUND"
+%token	T_OP_CEIL "CEIL" T_OP_FLOOR "FLOOR"
+%left	T_OP_DEF T_OP_BANK T_OP_ALIGN
+%left	T_OP_SIN T_OP_COS T_OP_TAN T_OP_ASIN T_OP_ACOS T_OP_ATAN T_OP_ATAN2
+%left	T_OP_FDIV T_OP_FMUL T_OP_POW T_OP_LOG
+%left	T_OP_ROUND T_OP_CEIL T_OP_FLOOR
 
-%token	T_OP_HIGH T_OP_LOW
-%token	T_OP_ISCONST
+%token	T_OP_HIGH "HIGH" T_OP_LOW "LOW"
+%token	T_OP_ISCONST "ISCONST"
 
-%left	T_OP_STRCMP
-%left	T_OP_STRIN
-%left	T_OP_STRRIN
-%left	T_OP_STRSUB
-%left	T_OP_STRLEN
-%left	T_OP_STRCAT
-%left	T_OP_STRUPR
-%left	T_OP_STRLWR
-%left	T_OP_STRRPL
-%left	T_OP_STRFMT
+%token	T_OP_STRCMP "STRCMP"
+%token	T_OP_STRIN "STRIN" T_OP_STRRIN "STRRIN"
+%token	T_OP_STRSUB "STRSUB"
+%token	T_OP_STRLEN "STRLEN"
+%token	T_OP_STRCAT "STRCAT"
+%token	T_OP_STRUPR "STRUPR" T_OP_STRLWR "STRLWR"
+%token	T_OP_STRRPL "STRRPL"
+%token	T_OP_STRFMT "STRFMT"
+%left	T_OP_STRCMP T_OP_STRIN T_OP_STRRIN T_OP_STRSUB T_OP_STRLEN T_OP_STRCAT
+%left	T_OP_STRUPR T_OP_STRLWR T_OP_STRRPL T_OP_STRFMT
 
-%token	<tzSym> T_LABEL
-%token	<tzSym> T_ID
-%token	<tzSym> T_LOCAL_ID
-%token	<tzSym> T_ANON
+%token	<tzSym> T_LABEL "label"
+%token	<tzSym> T_ID "identifier"
+%token	<tzSym> T_LOCAL_ID "local identifier"
+%token	<tzSym> T_ANON "anonymous label"
 %type	<tzSym> scoped_id
 %type	<tzSym> scoped_anon_id
-%token	T_POP_EQU
-%token	T_POP_SET
-%token	T_POP_EQUAL
-%token	T_POP_EQUS
+%token	T_POP_EQU "EQU"
+%token	T_POP_SET "SET"
+%token	T_POP_EQUAL "'='"
+%token	T_POP_EQUS "EQUS"
 
-%token	T_POP_INCLUDE T_POP_PRINT T_POP_PRINTLN T_POP_PRINTF T_POP_PRINTT T_POP_PRINTV T_POP_PRINTI
-%token	T_POP_IF T_POP_ELIF T_POP_ELSE T_POP_ENDC
-%token	T_POP_EXPORT T_POP_GLOBAL T_POP_XDEF
-%token	T_POP_DB T_POP_DS T_POP_DW T_POP_DL
-%token	T_POP_SECTION T_POP_FRAGMENT
-%token	T_POP_RB
-%token	T_POP_RW
-// There is no T_POP_RL, only T_Z80_RL
-%token	T_POP_MACRO
-%token	T_POP_ENDM
-%token	T_POP_RSRESET T_POP_RSSET
-%token	T_POP_UNION T_POP_NEXTU T_POP_ENDU
-%token	T_POP_INCBIN T_POP_REPT T_POP_FOR
-%token	T_POP_CHARMAP
-%token	T_POP_NEWCHARMAP
-%token	T_POP_SETCHARMAP
-%token	T_POP_PUSHC
-%token	T_POP_POPC
-%token	T_POP_SHIFT
-%token	T_POP_ENDR
-%token	T_POP_BREAK
-%token	T_POP_LOAD T_POP_ENDL
-%token	T_POP_FAIL
-%token	T_POP_WARN
-%token	T_POP_FATAL
-%token	T_POP_ASSERT T_POP_STATIC_ASSERT
-%token	T_POP_PURGE
-%token	T_POP_REDEF
-%token	T_POP_POPS
-%token	T_POP_PUSHS
-%token	T_POP_POPO
-%token	T_POP_PUSHO
-%token	T_POP_OPT
-%token	T_SECT_WRAM0 T_SECT_VRAM T_SECT_ROMX T_SECT_ROM0 T_SECT_HRAM
-%token	T_SECT_WRAMX T_SECT_SRAM T_SECT_OAM
+%token	T_POP_INCLUDE "INCLUDE"
+%token	T_POP_PRINT "PRINT" T_POP_PRINTLN "PRINTLN"
+%token	T_POP_PRINTF "PRINTF" T_POP_PRINTT "PRINTT" T_POP_PRINTV "PRINTV" T_POP_PRINTI "PRINTI"
+%token	T_POP_IF "IF" T_POP_ELIF "ELIF" T_POP_ELSE "ELSE" T_POP_ENDC "ENDC"
+%token	T_POP_EXPORT "EXPORT" T_POP_GLOBAL "GLOBAL" T_POP_XDEF "XDEF"
+%token	T_POP_DB "DB" T_POP_DS "DS" T_POP_DW "DW" T_POP_DL "DL"
+%token	T_POP_SECTION "SECTION" T_POP_FRAGMENT "FRAGMENT"
+%token	T_POP_RB "RB" T_POP_RW "RW" // There is no T_POP_RL, only T_Z80_RL
+%token	T_POP_MACRO "MACRO"
+%token	T_POP_ENDM "ENDM"
+%token	T_POP_RSRESET "RSRESET" T_POP_RSSET "RSSET"
+%token	T_POP_UNION "UNION" T_POP_NEXTU "NEXTU" T_POP_ENDU "ENDU"
+%token	T_POP_INCBIN "INCBIN" T_POP_REPT "REPT" T_POP_FOR "FOR"
+%token	T_POP_CHARMAP "CHARMAP"
+%token	T_POP_NEWCHARMAP "NEWCHARMAP"
+%token	T_POP_SETCHARMAP "SETCHARMAP"
+%token	T_POP_PUSHC "PUSHC"
+%token	T_POP_POPC "POPC"
+%token	T_POP_SHIFT "SHIFT"
+%token	T_POP_ENDR "ENDR"
+%token	T_POP_BREAK "BREAK"
+%token	T_POP_LOAD "LOAD" T_POP_ENDL "ENDL"
+%token	T_POP_FAIL "FAIL"
+%token	T_POP_WARN "WARN"
+%token	T_POP_FATAL "FATAL"
+%token	T_POP_ASSERT "ASSERT" T_POP_STATIC_ASSERT "STATIC_ASSERT"
+%token	T_POP_PURGE "PURGE"
+%token	T_POP_REDEF "REDEF"
+%token	T_POP_POPS "POPS"
+%token	T_POP_PUSHS "PUSHS"
+%token	T_POP_POPO "POPO"
+%token	T_POP_PUSHO "PUSHO"
+%token	T_POP_OPT "OPT"
+%token	T_SECT_ROM0 "ROM0" T_SECT_ROMX "ROMX"
+%token	T_SECT_WRAM0 "WRAM0" T_SECT_WRAMX "WRAMX" T_SECT_HRAM "HRAM"
+%token	T_SECT_VRAM "VRAM" T_SECT_SRAM "SRAM" T_SECT_OAM "OAM"
 
 %type	<sectMod> sectmod
 %type	<macroArg> macroargs
@@ -484,36 +534,37 @@
 
 %type	<forArgs> for_args
 
-%token	T_Z80_ADC T_Z80_ADD T_Z80_AND
-%token	T_Z80_BIT
-%token	T_Z80_CALL T_Z80_CCF T_Z80_CP T_Z80_CPL
-%token	T_Z80_DAA T_Z80_DEC T_Z80_DI
-%token	T_Z80_EI
-%token	T_Z80_HALT
-%token	T_Z80_INC
-%token	T_Z80_JP T_Z80_JR
-%token	T_Z80_LD
-%token	T_Z80_LDI
-%token	T_Z80_LDD
-%token	T_Z80_LDIO
-%token	T_Z80_NOP
-%token	T_Z80_OR
-%token	T_Z80_POP T_Z80_PUSH
-%token	T_Z80_RES T_Z80_RET T_Z80_RETI T_Z80_RST
-%token	T_Z80_RL T_Z80_RLA T_Z80_RLC T_Z80_RLCA
-%token	T_Z80_RR T_Z80_RRA T_Z80_RRC T_Z80_RRCA
-%token	T_Z80_SBC T_Z80_SCF T_Z80_STOP
-%token	T_Z80_SLA T_Z80_SRA T_Z80_SRL T_Z80_SUB T_Z80_SWAP
-%token	T_Z80_XOR
+%token	T_Z80_ADC "'adc'" T_Z80_ADD "'add'" T_Z80_AND "'and'"
+%token	T_Z80_BIT "'bit'"
+%token	T_Z80_CALL "'call'" T_Z80_CCF "'ccf'" T_Z80_CP "'cp'" T_Z80_CPL "'cpl'"
+%token	T_Z80_DAA "'daa'" T_Z80_DEC "'dec'" T_Z80_DI "'di'"
+%token	T_Z80_EI "'ei'"
+%token	T_Z80_HALT "'halt'"
+%token	T_Z80_INC "'inc'"
+%token	T_Z80_JP "'jp'" T_Z80_JR "'jr'"
+%token	T_Z80_LD "'ld'"
+%token	T_Z80_LDI "'ldi'"
+%token	T_Z80_LDD "'ldd'"
+%token	T_Z80_LDH "'ldh'"
+%token	T_Z80_NOP "'nop'"
+%token	T_Z80_OR "'or'"
+%token	T_Z80_POP "'pop'" T_Z80_PUSH "'push'"
+%token	T_Z80_RES "'res'" T_Z80_RET "'ret'" T_Z80_RETI "'reti'" T_Z80_RST "'rst'"
+%token	T_Z80_RL "'rl'" T_Z80_RLA "'rla'" T_Z80_RLC "'rlc'" T_Z80_RLCA "'rlca'"
+%token	T_Z80_RR "'rr'" T_Z80_RRA "'rra'" T_Z80_RRC "'rrc'" T_Z80_RRCA "'rrca'"
+%token	T_Z80_SBC "'sbc'" T_Z80_SCF "'scf'" T_Z80_STOP "'stop'"
+%token	T_Z80_SLA "'sla'" T_Z80_SRA "'sra'" T_Z80_SRL "'srl'" T_Z80_SUB "'sub'"
+%token	T_Z80_SWAP "'swap'"
+%token	T_Z80_XOR "'xor'"
 
-%token	T_TOKEN_A T_TOKEN_B T_TOKEN_C T_TOKEN_D T_TOKEN_E T_TOKEN_H T_TOKEN_L
-%token	T_MODE_AF
-%token	T_MODE_BC
-%token	T_MODE_DE
-%token	T_MODE_SP
-%token	T_MODE_HW_C
-%token	T_MODE_HL T_MODE_HL_DEC T_MODE_HL_INC
-%token	T_CC_NZ T_CC_Z T_CC_NC
+%token	T_TOKEN_A "'a'"
+%token	T_TOKEN_B "'b'" T_TOKEN_C "'c'"
+%token	T_TOKEN_D "'d'" T_TOKEN_E "'e'"
+%token	T_TOKEN_H "'h'" T_TOKEN_L "'l'"
+%token	T_MODE_AF "'af'" T_MODE_BC "'bc'" T_MODE_DE "'de'" T_MODE_SP "'sp'"
+%token	T_MODE_HW_C "'$ff00+c'"
+%token	T_MODE_HL "'hl'" T_MODE_HL_DEC "'hld'/'hl-'" T_MODE_HL_INC "'hli'/'hl+'"
+%token	T_CC_NZ "'nz'" T_CC_Z "'z'" T_CC_NC "'nc'"
 
 %type	<nConstValue>	reg_r
 %type	<nConstValue>	reg_ss
@@ -1626,22 +1677,22 @@
 		}
 ;
 
-z80_ldio	: T_Z80_LDIO T_MODE_A T_COMMA op_mem_ind {
+z80_ldio	: T_Z80_LDH T_MODE_A T_COMMA op_mem_ind {
 			rpn_CheckHRAM(&$4, &$4);
 
 			out_AbsByte(0xF0);
 			out_RelByte(&$4);
 		}
-		| T_Z80_LDIO op_mem_ind T_COMMA T_MODE_A {
+		| T_Z80_LDH op_mem_ind T_COMMA T_MODE_A {
 			rpn_CheckHRAM(&$2, &$2);
 
 			out_AbsByte(0xE0);
 			out_RelByte(&$2);
 		}
-		| T_Z80_LDIO T_MODE_A T_COMMA c_ind {
+		| T_Z80_LDH T_MODE_A T_COMMA c_ind {
 			out_AbsByte(0xF2);
 		}
-		| T_Z80_LDIO c_ind T_COMMA T_MODE_A {
+		| T_Z80_LDH c_ind T_COMMA T_MODE_A {
 			out_AbsByte(0xE2);
 		}
 ;
--- a/test/asm/anon-label-bad.err
+++ b/test/asm/anon-label-bad.err
@@ -3,5 +3,5 @@
 ERROR: anon-label-bad.asm(6):
     Reference to anonymous label 2 before, when only 1 has been created so far
 ERROR: anon-label-bad.asm(18):
-    syntax error
+    syntax error, unexpected ':'
 error: Assembly aborted (3 errors)!
--- /dev/null
+++ b/test/asm/anon-label-bad.simple.err
@@ -1,0 +1,7 @@
+ERROR: anon-label-bad.asm(2):
+    Label "!0" created outside of a SECTION
+ERROR: anon-label-bad.asm(6):
+    Reference to anonymous label 2 before, when only 1 has been created so far
+ERROR: anon-label-bad.asm(18):
+    syntax error
+error: Assembly aborted (3 errors)!
--- a/test/asm/block-comment-termination-error.err
+++ b/test/asm/block-comment-termination-error.err
@@ -1,5 +1,5 @@
 ERROR: block-comment-termination-error.asm(1):
     Unterminated block comment
 ERROR: block-comment-termination-error.asm(1):
-    syntax error
+    syntax error, unexpected newline
 error: Assembly aborted (2 errors)!
--- /dev/null
+++ b/test/asm/block-comment-termination-error.simple.err
@@ -1,0 +1,5 @@
+ERROR: block-comment-termination-error.asm(1):
+    Unterminated block comment
+ERROR: block-comment-termination-error.asm(1):
+    syntax error
+error: Assembly aborted (2 errors)!
--- a/test/asm/label-macro-arg.err
+++ b/test/asm/label-macro-arg.err
@@ -1,7 +1,4 @@
 ERROR: label-macro-arg.asm(38) -> label-macro-arg.asm::test_char(25):
-    Local label 'sizeof_.something' in main scope
+    syntax error, unexpected '='
 while expanding symbol "VAR_DEF"
-ERROR: label-macro-arg.asm(38) -> label-macro-arg.asm::test_char(25):
-    syntax error
-while expanding symbol "VAR_DEF"
-error: Assembly aborted (2 errors)!
+error: Assembly aborted (1 errors)!
--- /dev/null
+++ b/test/asm/label-macro-arg.simple.err
@@ -1,0 +1,7 @@
+ERROR: label-macro-arg.asm(38) -> label-macro-arg.asm::test_char(25):
+    Local label 'sizeof_.something' in main scope
+while expanding symbol "VAR_DEF"
+ERROR: label-macro-arg.asm(38) -> label-macro-arg.asm::test_char(25):
+    syntax error
+while expanding symbol "VAR_DEF"
+error: Assembly aborted (2 errors)!
--- /dev/null
+++ b/test/asm/syntax-error.asm
@@ -1,0 +1,2 @@
+; The reported error here depends on Bison's parse.error flag.
+print a
--- /dev/null
+++ b/test/asm/syntax-error.err
@@ -1,0 +1,3 @@
+ERROR: syntax-error.asm(2):
+    syntax error, unexpected 'a'
+error: Assembly aborted (1 errors)!
--- /dev/null
+++ b/test/asm/syntax-error.simple.err
@@ -1,0 +1,3 @@
+ERROR: syntax-error.asm(2):
+    syntax error
+error: Assembly aborted (1 errors)!
--- a/test/asm/test.sh
+++ b/test/asm/test.sh
@@ -47,13 +47,26 @@
 EOF
 fi
 
+# Check whether to use '.simple.err' files if they exist
+# (rgbasm with pre-3.0 Bison just reports "syntax error")
+../../rgbasm -Weverything -o $o syntax-error.asm > $output 2> $errput
+cmp syntax-error.err $errput > /dev/null 2> /dev/null
+simple_error=$?
+if [ "$simple_error" -eq 1 ]; then
+	echo "${bold}${orange}Warning: using .simple.err files when available.${rescolors}${resbold}"
+fi
+
 for i in *.asm; do
 	for variant in '' '.pipe'; do
 		echo "${bold}${green}${i%.asm}${variant}...${rescolors}${resbold}"
+		desired_errname=${i%.asm}.err
+		if [ "$simple_error" -eq 1 ] && [ -e ${i%.asm}.simple.err ]; then
+			desired_errname=${i%.asm}.simple.err
+		fi
 		if [ -z "$variant" ]; then
 			../../rgbasm -Weverything -o $o $i > $output 2> $errput
 			desired_output=${i%.asm}.out
-			desired_errput=${i%.asm}.err
+			desired_errput=$desired_errname
 		else
 			# `include-recursion.asm` refers to its own name inside the test code.
 			# Skip testing with stdin input for that file.
@@ -74,7 +87,7 @@
 			subst="$(printf '%s\n' "$i" | sed 's:[][\/.^$*]:\\&:g')"
 			# Replace the file name with a dash to match changed output
 			sed "s/$subst/<stdin>/g" ${i%.asm}.out > $desired_output
-			sed "s/$subst/<stdin>/g" ${i%.asm}.err > $desired_errput
+			sed "s/$subst/<stdin>/g" $desired_errname > $desired_errput
 		fi
 
 		tryDiff $desired_output $output out