shithub: rgbds

Download patch

ref: b3c0db218d564bbc9a801d8635591fd969c36e82
parent: 76446e6d00eb86ed8a55e5bcbf1eb40ce47bb59c
author: ISSOtm <eldredhabert0@gmail.com>
date: Thu Feb 11 07:48:37 EST 2021

Remove "EOF-newline" lexer hack

In preparation for an upcoming change
Makes for nicer error messages, complaining about EOF instead of newlines
The hack had to be kept for the lexer raw mode to avoid a bug;
see the relevant code comment for more info.

--- a/include/asm/output.h
+++ b/include/asm/output.h
@@ -22,8 +22,7 @@
 void out_RegisterNode(struct FileStackNode *node);
 void out_ReplaceNode(struct FileStackNode *node);
 void out_SetFileName(char *s);
-void out_CreatePatch(uint32_t type, struct Expression const *expr,
-		     uint32_t ofs);
+void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs, bool isOperand);
 bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
 		      char const *message, uint32_t ofs);
 void out_WriteObject(void);
--- a/include/asm/section.h
+++ b/include/asm/section.h
@@ -62,11 +62,11 @@
 void out_AbsLongGroup(uint8_t const *s, int32_t length);
 void out_Skip(int32_t skip, bool ds);
 void out_String(char const *s);
-void out_RelByte(struct Expression *expr);
+void out_RelByte(struct Expression *expr, bool isOperand);
 void out_RelBytes(struct Expression *expr, uint32_t n);
-void out_RelWord(struct Expression *expr);
-void out_RelLong(struct Expression *expr);
-void out_PCRelByte(struct Expression *expr);
+void out_RelWord(struct Expression *expr, bool isOperand);
+void out_RelLong(struct Expression *expr, bool isOperand);
+void out_PCRelByte(struct Expression *expr, bool isOperand);
 void out_BinaryFile(char const *s, int32_t startPos);
 void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length);
 
--- a/include/link/section.h
+++ b/include/link/section.h
@@ -34,6 +34,7 @@
 	uint32_t pcSectionID;
 	uint32_t pcOffset;
 	enum PatchType type;
+	bool isOperand;
 	int32_t rpnSize;
 	uint8_t *rpnExpression;
 
--- a/include/linkdefs.h
+++ b/include/linkdefs.h
@@ -99,6 +99,8 @@
 	SYMTYPE_EXPORT
 };
 
+// Bit 7 is special, not part of the actual patch type
+#define PATCH_ISOPERAND 0x80
 enum PatchType {
 	PATCHTYPE_BYTE,
 	PATCHTYPE_WORD,
--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -341,7 +341,6 @@
 	bool atLineStart;
 	uint32_t lineNo;
 	uint32_t colNo;
-	int lastToken;
 
 	bool capturing; /* Whether the text being lexed should be captured */
 	size_t captureSize; /* Amount of text captured */
@@ -363,7 +362,6 @@
 {
 	state->mode = LEXER_NORMAL;
 	state->atLineStart = true; /* yylex() will init colNo due to this */
-	state->lastToken = 0;
 
 	state->capturing = false;
 	state->captureBuf = NULL;
@@ -1921,9 +1919,13 @@
 				i--;
 			/* Empty macro args break their expansion, so prevent that */
 			if (i == 0) {
-				/* Return the EOF token, and don't shift a non-existent char! */
+				// If at EOF, don't shift a non-existent chat
+				// 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 0;
+					return T_NEWLINE;
 				shiftChars(1);
 				if (c == '\r' && peek(0) == '\n')
 					shiftChars(1);
@@ -2163,21 +2165,16 @@
 
 	/* Make sure to terminate files with a line feed */
 	if (token == 0) {
-		if (lexerState->lastToken != T_NEWLINE) {
-			dbgPrint("Forcing EOL at EOF\n");
-			token = T_NEWLINE;
-		} else { /* Try to switch to new buffer; if it succeeds, scan again */
-			dbgPrint("Reached EOF!\n");
-			/* Captures end at their buffer's boundary no matter what */
-			if (!lexerState->capturing) {
-				if (!yywrap())
-					goto restart;
-				dbgPrint("Reached end of input.");
-				return 0;
-			}
+		/* Try to switch to new buffer; if it succeeds, scan again */
+		dbgPrint("Reached EOF!\n");
+		/* Captures end at their buffer's boundary no matter what */
+		if (!lexerState->capturing) {
+			if (!yywrap())
+				goto restart;
+			dbgPrint("Reached end of input.");
+			return 0;
 		}
 	}
-	lexerState->lastToken = token;
 
 	lexerState->atLineStart = false;
 	if (token == T_NEWLINE)
@@ -2238,7 +2235,6 @@
 					 * We know we have read exactly "ENDR", not e.g. an EQUS
 					 */
 					lexerState->captureSize -= strlen("ENDR");
-					lexerState->lastToken = T_POP_ENDR; // Force EOL at EOF
 					goto finish;
 				}
 				level--;
@@ -2301,7 +2297,6 @@
 				 * We know we have read exactly "ENDM", not e.g. an EQUS
 				 */
 				lexerState->captureSize -= strlen("ENDM");
-				lexerState->lastToken = T_POP_ENDM; // Force EOL at EOF
 				goto finish;
 			}
 		}
--- a/src/asm/output.c
+++ b/src/asm/output.c
@@ -39,6 +39,7 @@
 	struct Section *pcSection;
 	uint32_t pcOffset;
 	uint8_t type;
+	bool isOperand; // If set, PC is not at the patch's address, but at the byte before
 	uint32_t nRPNSize;
 	uint8_t *pRPN;
 	struct Patch *next;
@@ -203,13 +204,17 @@
 static void writepatch(struct Patch const *patch, FILE *f)
 {
 	assert(patch->src->ID != -1);
+	uint8_t type = patch->type;
 
+	if (patch->isOperand)
+		type |= PATCH_ISOPERAND;
+
 	putlong(patch->src->ID, f);
 	putlong(patch->lineNo, f);
 	putlong(patch->nOffset, f);
 	putlong(getSectIDIfAny(patch->pcSection), f);
 	putlong(patch->pcOffset, f);
-	putc(patch->type, f);
+	putc(type, f);
 	putlong(patch->nRPNSize, f);
 	fwrite(patch->pRPN, 1, patch->nRPNSize, f);
 }
@@ -382,6 +387,7 @@
 		fatalerror("No memory for patch's RPN expression: %s\n", strerror(errno));
 
 	patch->type = type;
+	patch->isOperand = false;
 	patch->src = node;
 	out_RegisterNode(node);
 	patch->lineNo = lexer_GetLineNo();
@@ -410,10 +416,11 @@
 /*
  * Create a new patch (includes the rpn expr)
  */
-void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs)
+void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs, bool isOperand)
 {
 	struct Patch *patch = allocpatch(type, expr, ofs);
 
+	patch->isOperand = isOperand;
 	patch->next = pCurrentSection->patches;
 	pCurrentSection->patches = patch;
 }
--- a/src/asm/parser.y
+++ b/src/asm/parser.y
@@ -36,9 +36,6 @@
 #include "linkdefs.h"
 #include "platform.h" // strncasecmp, strdup
 
-int32_t nPCOffset; /* Read by rpn_Symbol */
-
-static uint32_t nListCountEmpty;
 static bool executeElseBlock; /* If this is set, ELIFs cannot be executed anymore */
 static struct CaptureBody captureBody; /* Captures a REPT/FOR or MACRO */
 
@@ -406,6 +403,7 @@
 		int32_t step;
 	} forArgs;
 	struct StrFmtArgList strfmtArgs;
+	bool hasEmpty; // Whether `db`, `dw`, `dl` argument lists contain any empty entries
 }
 
 %type	<sVal>		relocexpr
@@ -420,6 +418,9 @@
 %type	<sVal>		reloc_16bit
 %type	<sVal>		reloc_16bit_no_str
 %type	<nConstValue>	sectiontype
+%type	<hasEmpty>	constlist_8bit  constlist_8bit_entry
+%type	<hasEmpty>	constlist_16bit constlist_16bit_entry
+%type	<hasEmpty>	constlist_32bit constlist_32bit_entry
 
 %type	<tzString>	string
 %type	<tzString>	strcat_args
@@ -584,21 +585,21 @@
 
 %%
 
-asmfile		: lines;
+asmfile		: lines last_line
+;
 
 /* Note: The lexer adds T_NEWLINE at the end of the input */
 lines		: %empty
-		| lines {
-			nListCountEmpty = 0;
-			nPCOffset = 0;
-		} line
+		| lines line
 ;
 
-line		: label T_NEWLINE
-		| label cpu_command T_NEWLINE
-		| label macro T_NEWLINE
-		| label directive T_NEWLINE
-		| assignment_directive T_NEWLINE
+last_line	: label
+		| label cpu_command
+		| label macro
+		| label directive
+		| assignment_directive
+;
+line		: last_line T_NEWLINE
 		| line_directive /* Directives that manage newlines themselves */
 		| error T_NEWLINE { /* Continue parsing the next line on a syntax error */
 			fstk_StopRept();
@@ -951,7 +952,7 @@
 
 /* Authorize empty entries if there is only one */
 db		: T_POP_DB constlist_8bit_entry T_COMMA constlist_8bit {
-			if (nListCountEmpty > 0)
+			if ($2 || $4)
 				warning(WARNING_EMPTY_ENTRY,
 					"Empty entry in list of 8-bit elements (treated as padding).\n");
 		}
@@ -959,7 +960,7 @@
 ;
 
 dw		: T_POP_DW constlist_16bit_entry T_COMMA constlist_16bit {
-			if (nListCountEmpty > 0)
+			if ($2 || $4)
 				warning(WARNING_EMPTY_ENTRY,
 					"Empty entry in list of 16-bit elements (treated as padding).\n");
 		}
@@ -967,7 +968,7 @@
 ;
 
 dl		: T_POP_DL constlist_32bit_entry T_COMMA constlist_32bit {
-			if (nListCountEmpty > 0)
+			if ($2 || $4)
 				warning(WARNING_EMPTY_ENTRY,
 					"Empty entry in list of 32-bit elements (treated as padding).\n");
 		}
@@ -1110,14 +1111,19 @@
 ;
 
 constlist_8bit	: constlist_8bit_entry
-		| constlist_8bit T_COMMA constlist_8bit_entry
+		| constlist_8bit T_COMMA constlist_8bit_entry {
+			$$ = $1 || $3;
+		}
 ;
 
 constlist_8bit_entry : %empty {
 			out_Skip(1, false);
-			nListCountEmpty++;
+			$$ = true;
 		}
-		| reloc_8bit_no_str	{ out_RelByte(&$1); }
+		| reloc_8bit_no_str	{
+			out_RelByte(&$1, false);
+			$$ = false;
+		}
 		| string {
 			uint8_t *output = malloc(strlen($1)); /* Cannot be larger than that */
 			int32_t length = charmap_Convert($1, output);
@@ -1124,18 +1130,24 @@
 
 			out_AbsByteGroup(output, length);
 			free(output);
+			$$ = false;
 		}
 ;
 
 constlist_16bit : constlist_16bit_entry
-		| constlist_16bit T_COMMA constlist_16bit_entry
+		| constlist_16bit T_COMMA constlist_16bit_entry {
+			$$ = $1 || $3;
+		}
 ;
 
 constlist_16bit_entry : %empty {
 			out_Skip(2, false);
-			nListCountEmpty++;
+			$$ = true;
 		}
-		| reloc_16bit_no_str	{ out_RelWord(&$1); }
+		| reloc_16bit_no_str	{
+			out_RelWord(&$1, false);
+			$$ = false;
+		}
 		| string {
 			uint8_t *output = malloc(strlen($1)); /* Cannot be larger than that */
 			int32_t length = charmap_Convert($1, output);
@@ -1142,18 +1154,24 @@
 
 			out_AbsWordGroup(output, length);
 			free(output);
+			$$ = false;
 		}
 ;
 
 constlist_32bit : constlist_32bit_entry
-		| constlist_32bit T_COMMA constlist_32bit_entry
+		| constlist_32bit T_COMMA constlist_32bit_entry {
+			$$ = $1 || $3;
+		}
 ;
 
 constlist_32bit_entry : %empty {
 			out_Skip(4, false);
-			nListCountEmpty++;
+			$$ = true;
 		}
-		| relocexpr_no_str	{ out_RelLong(&$1); }
+		| relocexpr_no_str	{
+			out_RelLong(&$1, false);
+			$$ = false;
+		}
 		| string {
 			uint8_t *output = malloc(strlen($1)); /* Cannot be larger than that */
 			int32_t length = charmap_Convert($1, output);
@@ -1160,6 +1178,7 @@
 
 			out_AbsLongGroup(output, length);
 			free(output);
+			$$ = false;
 		}
 ;
 
@@ -1500,31 +1519,31 @@
 ;
 
 
-cpu_command	: { nPCOffset = 1; } z80_adc
-		| { nPCOffset = 1; } z80_add
-		| { nPCOffset = 1; } z80_and
-		| { nPCOffset = 1; } z80_bit
-		| { nPCOffset = 1; } z80_call
+cpu_command	: z80_adc
+		| z80_add
+		| z80_and
+		| z80_bit
+		| z80_call
 		| z80_ccf
-		| { nPCOffset = 1; } z80_cp
+		| z80_cp
 		| z80_cpl
 		| z80_daa
-		| { nPCOffset = 1; } z80_dec
+		| z80_dec
 		| z80_di
 		| z80_ei
 		| z80_halt
 		| z80_inc
-		| { nPCOffset = 1; } z80_jp
-		| { nPCOffset = 1; } z80_jr
-		| { nPCOffset = 1; } z80_ld
+		| z80_jp
+		| z80_jr
+		| z80_ld
 		| z80_ldd
 		| z80_ldi
-		| { nPCOffset = 1; } z80_ldio
+		| z80_ldio
 		| z80_nop
-		| { nPCOffset = 1; } z80_or
+		| z80_or
 		| z80_pop
 		| z80_push
-		| { nPCOffset = 1; } z80_res
+		| z80_res
 		| z80_ret
 		| z80_reti
 		| z80_rl
@@ -1536,21 +1555,21 @@
 		| z80_rrc
 		| z80_rrca
 		| /*{ nPCOffset = 0; }*/ z80_rst
-		| { nPCOffset = 1; } z80_sbc
+		| z80_sbc
 		| z80_scf
-		| { nPCOffset = 1; } z80_set
+		| z80_set
 		| z80_sla
 		| z80_sra
 		| z80_srl
-		| { nPCOffset = 1; } z80_stop
-		| { nPCOffset = 1; } z80_sub
+		| z80_stop
+		| z80_sub
 		| z80_swap
-		| { nPCOffset = 1; } z80_xor
+		| z80_xor
 ;
 
 z80_adc		: T_Z80_ADC op_a_n {
 			out_AbsByte(0xCE);
-			out_RelByte(&$2);
+			out_RelByte(&$2, true);
 		}
 		| T_Z80_ADC op_a_r	{ out_AbsByte(0x88 | $2); }
 ;
@@ -1557,13 +1576,13 @@
 
 z80_add		: T_Z80_ADD op_a_n {
 			out_AbsByte(0xC6);
-			out_RelByte(&$2);
+			out_RelByte(&$2, true);
 		}
 		| T_Z80_ADD op_a_r	{ out_AbsByte(0x80 | $2); }
 		| T_Z80_ADD op_hl_ss	{ out_AbsByte(0x09 | ($2 << 4)); }
 		| T_Z80_ADD T_MODE_SP T_COMMA reloc_8bit {
 			out_AbsByte(0xE8);
-			out_RelByte(&$4);
+			out_RelByte(&$4, true);
 		}
 
 ;
@@ -1570,7 +1589,7 @@
 
 z80_and		: T_Z80_AND op_a_n {
 			out_AbsByte(0xE6);
-			out_RelByte(&$2);
+			out_RelByte(&$2, true);
 		}
 		| T_Z80_AND op_a_r	{ out_AbsByte(0xA0 | $2); }
 ;
@@ -1583,11 +1602,11 @@
 
 z80_call	: T_Z80_CALL reloc_16bit {
 			out_AbsByte(0xCD);
-			out_RelWord(&$2);
+			out_RelWord(&$2, true);
 		}
 		| T_Z80_CALL ccode T_COMMA reloc_16bit {
 			out_AbsByte(0xC4 | ($2 << 3));
-			out_RelWord(&$4);
+			out_RelWord(&$4, true);
 		}
 ;
 
@@ -1596,7 +1615,7 @@
 
 z80_cp		: T_Z80_CP op_a_n {
 			out_AbsByte(0xFE);
-			out_RelByte(&$2);
+			out_RelByte(&$2, true);
 		}
 		| T_Z80_CP op_a_r	{ out_AbsByte(0xB8 | $2); }
 ;
@@ -1630,11 +1649,11 @@
 
 z80_jp		: T_Z80_JP reloc_16bit {
 			out_AbsByte(0xC3);
-			out_RelWord(&$2);
+			out_RelWord(&$2, true);
 		}
 		| T_Z80_JP ccode T_COMMA reloc_16bit {
 			out_AbsByte(0xC2 | ($2 << 3));
-			out_RelWord(&$4);
+			out_RelWord(&$4, true);
 		}
 		| T_Z80_JP T_MODE_HL {
 			out_AbsByte(0xE9);
@@ -1643,11 +1662,11 @@
 
 z80_jr		: T_Z80_JR reloc_16bit {
 			out_AbsByte(0x18);
-			out_PCRelByte(&$2);
+			out_PCRelByte(&$2, true);
 		}
 		| T_Z80_JR ccode T_COMMA reloc_16bit {
 			out_AbsByte(0x20 | ($2 << 3));
-			out_PCRelByte(&$4);
+			out_PCRelByte(&$4, true);
 		}
 ;
 
@@ -1671,13 +1690,13 @@
 			rpn_CheckHRAM(&$4, &$4);
 
 			out_AbsByte(0xF0);
-			out_RelByte(&$4);
+			out_RelByte(&$4, true);
 		}
 		| T_Z80_LDH op_mem_ind T_COMMA T_MODE_A {
 			rpn_CheckHRAM(&$2, &$2);
 
 			out_AbsByte(0xE0);
-			out_RelByte(&$2);
+			out_RelByte(&$2, true);
 		}
 		| T_Z80_LDH T_MODE_A T_COMMA c_ind {
 			out_AbsByte(0xF2);
@@ -1703,11 +1722,11 @@
 
 z80_ld_hl	: T_Z80_LD T_MODE_HL T_COMMA T_MODE_SP reloc_8bit {
 			out_AbsByte(0xF8);
-			out_RelByte(&$5);
+			out_RelByte(&$5, true);
 		}
 		| T_Z80_LD T_MODE_HL T_COMMA reloc_16bit {
 			out_AbsByte(0x01 | (REG_HL << 4));
-			out_RelWord(&$4);
+			out_RelWord(&$4, true);
 		}
 ;
 
@@ -1714,13 +1733,13 @@
 z80_ld_sp	: T_Z80_LD T_MODE_SP T_COMMA T_MODE_HL	{ out_AbsByte(0xF9); }
 		| T_Z80_LD T_MODE_SP T_COMMA reloc_16bit {
 			out_AbsByte(0x01 | (REG_SP << 4));
-			out_RelWord(&$4);
+			out_RelWord(&$4, true);
 		}
 ;
 
 z80_ld_mem	: T_Z80_LD op_mem_ind T_COMMA T_MODE_SP {
 			out_AbsByte(0x08);
-			out_RelWord(&$2);
+			out_RelWord(&$2, true);
 		}
 		| T_Z80_LD op_mem_ind T_COMMA T_MODE_A {
 			if (optimizeloads && rpn_isKnown(&$2)
@@ -1730,7 +1749,7 @@
 				rpn_Free(&$2);
 			} else {
 				out_AbsByte(0xEA);
-				out_RelWord(&$2);
+				out_RelWord(&$2, true);
 			}
 		}
 ;
@@ -1747,7 +1766,7 @@
 
 z80_ld_r	: T_Z80_LD reg_r T_COMMA reloc_8bit {
 			out_AbsByte(0x06 | ($2 << 3));
-			out_RelByte(&$4);
+			out_RelByte(&$4, true);
 		}
 		| T_Z80_LD reg_r T_COMMA reg_r {
 			if (($2 == REG_HL_IND) && ($4 == REG_HL_IND))
@@ -1778,7 +1797,7 @@
 					rpn_Free(&$4);
 				} else {
 					out_AbsByte(0xFA);
-					out_RelWord(&$4);
+					out_RelWord(&$4, true);
 				}
 			} else {
 				error("Destination operand must be A\n");
@@ -1789,11 +1808,11 @@
 
 z80_ld_ss	: T_Z80_LD T_MODE_BC T_COMMA reloc_16bit {
 			out_AbsByte(0x01 | (REG_BC << 4));
-			out_RelWord(&$4);
+			out_RelWord(&$4, true);
 		}
 		| T_Z80_LD T_MODE_DE T_COMMA reloc_16bit {
 			out_AbsByte(0x01 | (REG_DE << 4));
-			out_RelWord(&$4);
+			out_RelWord(&$4, true);
 		}
 		/*
 		 * HL is taken care of in z80_ld_hl
@@ -1806,7 +1825,7 @@
 
 z80_or		: T_Z80_OR op_a_n {
 			out_AbsByte(0xF6);
-			out_RelByte(&$2);
+			out_RelByte(&$2, true);
 		}
 		| T_Z80_OR op_a_r	{ out_AbsByte(0xB0 | $2); }
 ;
@@ -1870,7 +1889,10 @@
 z80_rst		: T_Z80_RST reloc_8bit {
 			rpn_CheckRST(&$2, &$2);
 			if (!rpn_isKnown(&$2))
-				out_RelByte(&$2);
+				// This could be considered as an "operand", but the purpose of the
+				// "operand" flag is to signal to RGBLINK to correct PC,
+				// which we don't want here.
+				out_RelByte(&$2, false);
 			else
 				out_AbsByte(0xC7 | $2.nVal);
 			rpn_Free(&$2);
@@ -1879,7 +1901,7 @@
 
 z80_sbc		: T_Z80_SBC op_a_n {
 			out_AbsByte(0xDE);
-			out_RelByte(&$2);
+			out_RelByte(&$2, true);
 		}
 		| T_Z80_SBC op_a_r	{ out_AbsByte(0x98 | $2); }
 ;
@@ -1917,13 +1939,13 @@
 		}
 		| T_Z80_STOP reloc_8bit {
 			out_AbsByte(0x10);
-			out_RelByte(&$2);
+			out_RelByte(&$2, true);
 		}
 ;
 
 z80_sub		: T_Z80_SUB op_a_n {
 			out_AbsByte(0xD6);
-			out_RelByte(&$2);
+			out_RelByte(&$2, true);
 		}
 		| T_Z80_SUB op_a_r	{ out_AbsByte(0x90 | $2);
 		}
@@ -1937,7 +1959,7 @@
 
 z80_xor		: T_Z80_XOR op_a_n {
 			out_AbsByte(0xEE);
-			out_RelByte(&$2);
+			out_RelByte(&$2, true);
 		}
 		| T_Z80_XOR op_a_r	{ out_AbsByte(0xA8 | $2); }
 ;
--- a/src/asm/rpn.c
+++ b/src/asm/rpn.c
@@ -125,16 +125,6 @@
 		uint8_t *ptr = reserveSpace(expr, nameLen + 1);
 		*ptr++ = RPN_SYM;
 		memcpy(ptr, sym->name, nameLen);
-
-		/* RGBLINK assumes PC is at the byte being computed... */
-		if (sym_IsPC(sym) && nPCOffset) {
-			struct Expression pc = *expr, offset;
-
-			rpn_Number(&offset, nPCOffset);
-			rpn_BinaryOp(RPN_SUB, expr, &pc, &offset);
-			if (!rpn_isKnown(expr))
-				expr->isSymbol = true;
-		}
 	} else {
 		rpn_Number(expr, sym_GetConstantValue(tzSym));
 	}
--- a/src/asm/section.c
+++ b/src/asm/section.c
@@ -488,10 +488,9 @@
 	writebyte(b >> 24);
 }
 
-static inline void createPatch(enum PatchType type,
-			       struct Expression const *expr)
+static inline void createPatch(enum PatchType type, struct Expression const *expr, bool isOperand)
 {
-	out_CreatePatch(type, expr, sect_GetOutputOffset());
+	out_CreatePatch(type, expr, sect_GetOutputOffset(), isOperand);
 }
 
 void sect_StartUnion(void)
@@ -618,13 +617,13 @@
  * Output a relocatable byte. Checking will be done to see if it
  * is an absolute value in disguise.
  */
-void out_RelByte(struct Expression *expr)
+void out_RelByte(struct Expression *expr, bool isOperand)
 {
 	checkcodesection();
 	reserveSpace(1);
 
 	if (!rpn_isKnown(expr)) {
-		createPatch(PATCHTYPE_BYTE, expr);
+		createPatch(PATCHTYPE_BYTE, expr, isOperand);
 		writebyte(0);
 	} else {
 		writebyte(expr->nVal);
@@ -643,7 +642,7 @@
 
 	while (n--) {
 		if (!rpn_isKnown(expr)) {
-			createPatch(PATCHTYPE_BYTE, expr);
+			createPatch(PATCHTYPE_BYTE, expr, false);
 			writebyte(0);
 		} else {
 			writebyte(expr->nVal);
@@ -656,13 +655,13 @@
  * Output a relocatable word. Checking will be done to see if
  * it's an absolute value in disguise.
  */
-void out_RelWord(struct Expression *expr)
+void out_RelWord(struct Expression *expr, bool isOperand)
 {
 	checkcodesection();
 	reserveSpace(2);
 
 	if (!rpn_isKnown(expr)) {
-		createPatch(PATCHTYPE_WORD, expr);
+		createPatch(PATCHTYPE_WORD, expr, isOperand);
 		writeword(0);
 	} else {
 		writeword(expr->nVal);
@@ -674,13 +673,13 @@
  * Output a relocatable longword. Checking will be done to see if
  * is an absolute value in disguise.
  */
-void out_RelLong(struct Expression *expr)
+void out_RelLong(struct Expression *expr, bool isOperand)
 {
 	checkcodesection();
 	reserveSpace(2);
 
 	if (!rpn_isKnown(expr)) {
-		createPatch(PATCHTYPE_LONG, expr);
+		createPatch(PATCHTYPE_LONG, expr, isOperand);
 		writelong(0);
 	} else {
 		writelong(expr->nVal);
@@ -692,7 +691,7 @@
  * Output a PC-relative relocatable byte. Checking will be done to see if it
  * is an absolute value in disguise.
  */
-void out_PCRelByte(struct Expression *expr)
+void out_PCRelByte(struct Expression *expr, bool isOperand)
 {
 	checkcodesection();
 	reserveSpace(1);
@@ -699,7 +698,7 @@
 	struct Symbol const *pc = sym_GetPC();
 
 	if (!rpn_IsDiffConstant(expr, pc)) {
-		createPatch(PATCHTYPE_JR, expr);
+		createPatch(PATCHTYPE_JR, expr, isOperand);
 		writebyte(0);
 	} else {
 		struct Symbol const *sym = rpn_SymbolOf(expr);
--- a/src/link/object.c
+++ b/src/link/object.c
@@ -260,11 +260,11 @@
  * @param fileName The filename to report in errors
  * @param i The number of the patch to report in errors
  */
-static void readPatch(FILE *file, struct Patch *patch, char const *fileName,
-		      char const *sectName, uint32_t i,
-		      struct Section *fileSections[], struct FileStackNode fileNodes[])
+static void readPatch(FILE *file, struct Patch *patch, char const *fileName, char const *sectName,
+		      uint32_t i, struct Section *fileSections[], struct FileStackNode fileNodes[])
 {
 	uint32_t nodeID;
+	uint8_t type;
 
 	tryReadlong(nodeID, file,
 		   "%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s node ID: %s",
@@ -279,14 +279,15 @@
 	tryReadlong(patch->pcSectionID, file,
 		    "%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
 		    fileName, sectName, i);
-	patch->pcSection = patch->pcSectionID == -1 ? NULL
-						    : fileSections[patch->pcSectionID];
+	patch->pcSection = patch->pcSectionID == -1 ? NULL : fileSections[patch->pcSectionID];
 	tryReadlong(patch->pcOffset, file,
 		    "%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
 		    fileName, sectName, i);
-	tryGetc(patch->type, file,
+	tryGetc(type, file,
 		"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s type: %s",
 		fileName, sectName, i);
+	patch->type = type & 0x7F;
+	patch->isOperand = type & PATCH_ISOPERAND;
 	tryReadlong(patch->rpnSize, file,
 		    "%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s RPN size: %s",
 		    fileName, sectName, i);
--- a/src/link/patch.c
+++ b/src/link/patch.c
@@ -421,6 +421,10 @@
 					isError = true;
 				} else {
 					value = patch->pcOffset + patch->pcSection->org;
+					// If the patch is an operand, PC is not at the patch's
+					// location, but at the (opcode) byte right before it
+					if (patch->isOperand)
+						value--;
 				}
 			} else {
 				symbol = getSymbol(fileSymbols, value);
--- 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, unexpected newline
+    syntax error, unexpected end of file
 error: Assembly aborted (2 errors)!
--- /dev/null
+++ b/test/asm/macro-eof.asm
@@ -1,0 +1,6 @@
+; Macro invocations not followed by a newline may scan an EOF;
+; If this is the case, it shouldn't cause the parse to end before the macro is expanded
+mac: macro
+	PRINTLN "Hi beautiful"
+endm
+	mac
\ No newline at end of file
--- /dev/null
+++ b/test/asm/macro-eof.out
@@ -1,0 +1,1 @@
+Hi beautiful
--- a/test/asm/macro-recursion.asm
+++ b/test/asm/macro-recursion.asm
@@ -1,4 +1,4 @@
 recurse: MACRO
 	recurse
 ENDM
-	recurse
\ No newline at end of file
+	recurse
binary files a/test/asm/null-in-macro.asm b/test/asm/null-in-macro.asm differ
--- a/test/asm/pc-operand.asm
+++ b/test/asm/pc-operand.asm
@@ -3,11 +3,13 @@
 	rst @    ; rst 0
 	ld de, @ ; ld de, 1
 	bit @, h ; bit 4, h
-	db @, @  ; db 6, 7
+	jr @     ; jr 6
 
 SECTION "floating", ROM0
 
 	rst @    ; rst 8
 	ld l, @  ; ld l, 9
-	dw @, @  ; dw 11, 13
-	dl @, @  ; dl 15, 19
+	db @, @  ; db 11, 12
+	dw @, @  ; dw 13, 15
+	dl @, @  ; dl 17, 21
+	jr @     ; jr 25
binary files a/test/asm/pc-operand.out.bin b/test/asm/pc-operand.out.bin differ