shithub: rgbds

Download patch

ref: e11f25024e619c9c1fdd6d3a2cf577f937fa13f9
parent: 7c895f8a1b02e433f9790d43f1376ae163b3dbe8
author: ISSOtm <eldredhabert0@gmail.com>
date: Fri Jul 31 14:24:44 EDT 2020

Add test for built-in file symbol

It's currently defined in fstack.c, making it more prone to accidental
dropping. Let's not repeat the 0.3.9 scenario...

--- a/Makefile
+++ b/Makefile
@@ -72,7 +72,7 @@
 	src/hashmap.o \
 	src/linkdefs.o
 
-src/asm/lexer.o: src/asm/asmy.h
+src/asm/lexer.o src/asm/main.o: src/asm/asmy.h
 
 rgblink_obj := \
 	src/link/assign.o \
--- a/include/asm/fstack.h
+++ b/include/asm/fstack.h
@@ -41,13 +41,7 @@
 
 extern unsigned int nMaxRecursionDepth;
 
-void fstk_RunInclude(char *tzFileName);
-void fstk_Init(char *s);
-void fstk_Dump(void);
-void fstk_DumpToStr(char *buf, size_t len);
-void fstk_AddIncludePath(char *s);
-void fstk_RunMacro(char *s, struct MacroArgs *args);
-void fstk_RunRept(uint32_t count, int32_t nReptLineNo, char const *body, size_t size);
+void fstk_AddIncludePath(char const *s);
 /**
  * @param path The user-provided file name
  * @param fullPath The address of a pointer, which will be made to point at the full path
@@ -56,6 +50,16 @@
  * @return True if the file was found, false if no path worked
  */
 bool fstk_FindFile(char const *path, char **fullPath, size_t *size);
-int32_t fstk_GetLine(void);
+
+bool yywrap(void);
+void fstk_RunInclude(char const *path);
+void fstk_RunMacro(char *macroName, struct MacroArgs *args);
+void fstk_RunRept(uint32_t count, int32_t nReptLineNo, char *body, size_t size);
+
+void fstk_Dump(void);
+char *fstk_DumpToStr(void);
+uint32_t fstk_GetLine(void);
+
+void fstk_Init(char *mainPath, uint32_t maxRecursionDepth);
 
 #endif /* RGBDS_ASM_FSTACK_H */
--- a/include/asm/lexer.h
+++ b/include/asm/lexer.h
@@ -31,7 +31,8 @@
 }
 
 struct LexerState *lexer_OpenFile(char const *path);
-struct LexerState *lexer_OpenFileView(void);
+struct LexerState *lexer_OpenFileView(char *buf, size_t size, uint32_t lineNo);
+void lexer_RestartRept(uint32_t lineNo);
 void lexer_DeleteState(struct LexerState *state);
 void lexer_Init(void);
 
@@ -50,7 +51,7 @@
 uint32_t lexer_GetColNo(void);
 void lexer_DumpStringExpansions(void);
 int yylex(void);
-void lexer_CaptureBlock(int blockStartToken, int blockEndToken, char const **capture, size_t *size,
+void lexer_CaptureBlock(int blockStartToken, int blockEndToken, char **capture, size_t *size,
 			char const *name);
 
 #endif /* RGBDS_ASM_LEXER_H */
--- a/include/asm/macro.h
+++ b/include/asm/macro.h
@@ -28,6 +28,7 @@
 uint32_t macro_GetUniqueID(void);
 char const *macro_GetUniqueIDStr(void);
 void macro_SetUniqueID(uint32_t id);
+uint32_t macro_UseNewUniqueID(void);
 void macro_ShiftCurrentArgs(void);
 uint32_t macro_NbArgs(void);
 
--- a/include/asm/symbol.h
+++ b/include/asm/symbol.h
@@ -45,7 +45,7 @@
 		};
 		struct { /* For SYM_MACRO */
 			size_t macroSize;
-			char const *macro;
+			char *macro;
 		};
 	};
 
@@ -117,7 +117,7 @@
 uint32_t sym_GetConstantSymValue(struct Symbol const *sym);
 uint32_t sym_GetConstantValue(char const *s);
 struct Symbol *sym_FindSymbol(char const *symName);
-struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char const *body, size_t size);
+struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size);
 struct Symbol *sym_Ref(char const *symName);
 struct Symbol *sym_AddString(char const *symName, char const *value);
 uint32_t sym_GetDefinedValue(char const *s);
--- a/src/asm/asmy.y
+++ b/src/asm/asmy.y
@@ -597,21 +597,21 @@
 
 rept		: T_POP_REPT uconst {
 			uint32_t nDefinitionLineNo = lexer_GetLineNo();
-			char const *body;
+			char *body;
 			size_t size;
 			lexer_CaptureBlock(T_POP_REPT, T_POP_ENDR, &body, &size,
 					   "REPT block");
-			fstk_RunRept($2, nDefinitionLineNo, body, size);
+			fstk_RunRept($2, nDefinitionLineNo, body, size - strlen("ENDR"));
 		}
 ;
 
 macrodef	: T_LABEL ':' T_POP_MACRO {
 			int32_t nDefinitionLineNo = lexer_GetLineNo();
-			char const *body;
+			char *body;
 			size_t size;
 			lexer_CaptureBlock(T_POP_MACRO, T_POP_ENDM, &body, &size,
 					   "macro definition");
-			sym_AddMacro($1, nDefinitionLineNo, body, size);
+			sym_AddMacro($1, nDefinitionLineNo, body, size - strlen("ENDM"));
 		}
 ;
 
--- a/src/asm/fstack.c
+++ b/src/asm/fstack.c
@@ -6,318 +6,82 @@
  * SPDX-License-Identifier: MIT
  */
 
-/*
- * FileStack routines
- */
-
+#include <sys/stat.h>
+#include <assert.h>
 #include <errno.h>
 #include <inttypes.h>
-#include <limits.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
 
 #include "asm/fstack.h"
-#include "asm/lexer.h"
 #include "asm/macro.h"
 #include "asm/main.h"
-#include "asm/output.h"
+#include "asm/symbol.h"
 #include "asm/warning.h"
+#include "platform.h" /* S_ISDIR (stat macro) */
 
-#include "extern/err.h"
+struct Context {
+	struct Context *parent;
+	struct Context *child;
+	struct LexerState *lexerState;
+	uint32_t uniqueID;
+	char *fileName;
+	uint32_t lineNo; /* Line number at which the context was EXITED */
+	struct Symbol const *macro;
+	uint32_t nbReptIters; /* If zero, this isn't a REPT block */
+	size_t reptDepth;
+	uint32_t reptIters[];
+};
 
-#include "platform.h" // S_ISDIR (stat macro)
-#include "types.h"
-
-static struct sContext *pFileStack;
-static unsigned int nFileStackDepth;
+static struct Context *contextStack;
+static struct Context *topLevelContext;
+static unsigned int contextDepth = 0;
 unsigned int nMaxRecursionDepth;
-static struct Symbol const *pCurrentMacro;
-static uint32_t nCurrentStatus;
-static char IncludePaths[MAXINCPATHS][_MAX_PATH + 1];
-static int32_t NextIncPath;
-static uint32_t nMacroCount;
 
-static char const *pCurrentREPTBlock;
-static uint32_t nCurrentREPTBlockSize;
-static uint32_t nCurrentREPTBlockCount;
-static int32_t nCurrentREPTBodyFirstLine;
-static int32_t nCurrentREPTBodyLastLine;
+static unsigned int nbIncPaths = 0;
+static char const *includePaths[MAXINCPATHS];
 
-uint32_t ulMacroReturnValue;
-
-/*
- * defines for nCurrentStatus
- */
-#define STAT_isInclude		0 /* 'Normal' state as well */
-#define STAT_isMacro		1
-#define STAT_isMacroArg		2
-#define STAT_isREPTBlock	3
-
-/* Max context stack size */
-
-/*
- * Context push and pop
- */
-static void pushcontext(void)
+void fstk_AddIncludePath(char const *path)
 {
-	struct sContext **ppFileStack;
-
-	if (++nFileStackDepth > nMaxRecursionDepth)
-		fatalerror("Recursion limit (%u) exceeded\n", nMaxRecursionDepth);
-
-	ppFileStack = &pFileStack;
-	while (*ppFileStack)
-		ppFileStack = &((*ppFileStack)->next);
-
-	*ppFileStack = malloc(sizeof(struct sContext));
-
-	if (*ppFileStack == NULL)
-		fatalerror("No memory for context\n");
-
-	(*ppFileStack)->next = NULL;
-	(*ppFileStack)->nLine = lexer_GetLineNo();
-
-	switch ((*ppFileStack)->nStatus = nCurrentStatus) {
-	case STAT_isMacroArg:
-	case STAT_isMacro:
-		(*ppFileStack)->macroArgs = macro_GetCurrentArgs();
-		(*ppFileStack)->pMacro = pCurrentMacro;
-		break;
-	case STAT_isInclude:
-		break;
-	case STAT_isREPTBlock:
-		(*ppFileStack)->macroArgs = macro_GetCurrentArgs();
-		(*ppFileStack)->pREPTBlock = pCurrentREPTBlock;
-		(*ppFileStack)->nREPTBlockSize = nCurrentREPTBlockSize;
-		(*ppFileStack)->nREPTBlockCount = nCurrentREPTBlockCount;
-		(*ppFileStack)->nREPTBodyFirstLine = nCurrentREPTBodyFirstLine;
-		(*ppFileStack)->nREPTBodyLastLine = nCurrentREPTBodyLastLine;
-		break;
-	default:
-		fatalerror("%s: Internal error.\n", __func__);
+	if (path[0] == '\0')
+		return;
+	if (nbIncPaths >= MAXINCPATHS) {
+		error("Too many include directories passed from command line\n");
+		return;
 	}
-	(*ppFileStack)->uniqueID = macro_GetUniqueID();
-}
+	size_t len = strlen(path);
+	size_t allocSize = len + (path[len - 1] != '/') + 1;
+	char *str = malloc(allocSize);
 
-static int32_t popcontext(void)
-{
-	struct sContext *pLastFile, **ppLastFile;
-
-	if (nCurrentStatus == STAT_isREPTBlock) {
-		if (--nCurrentREPTBlockCount) {
-			char *pREPTIterationWritePtr;
-			unsigned long nREPTIterationNo;
-			int nNbCharsWritten;
-			int nNbCharsLeft;
-
-			macro_SetUniqueID(nMacroCount++);
-
-			/* Increment REPT count in file path */
-			pREPTIterationWritePtr =
-				strrchr(lexer_GetFileName(), '~') + 1;
-			nREPTIterationNo =
-				strtoul(pREPTIterationWritePtr, NULL, 10);
-			nNbCharsLeft = sizeof(lexer_GetFileName())
-				- (pREPTIterationWritePtr - lexer_GetFileName());
-			nNbCharsWritten = snprintf(pREPTIterationWritePtr,
-						   nNbCharsLeft, "%lu",
-						   nREPTIterationNo + 1);
-			if (nNbCharsWritten >= nNbCharsLeft) {
-				/*
-				 * The string is probably corrupted somehow,
-				 * revert the change to avoid a bad error
-				 * output.
-				 */
-				sprintf(pREPTIterationWritePtr, "%lu",
-					nREPTIterationNo);
-				fatalerror("Cannot write REPT count to file path\n");
-			}
-
-			return 0;
-		}
+	if (!str) {
+		/* Attempt to continue without that path */
+		error("Failed to allocate new include path: %s\n", strerror(errno));
+		return;
 	}
+	memcpy(str, path, len);
+	char *end = str + len - 1;
 
-	pLastFile = pFileStack;
-	if (pLastFile == NULL)
-		return 1;
-
-	ppLastFile = &pFileStack;
-	while (pLastFile->next) {
-		ppLastFile = &(pLastFile->next);
-		pLastFile = *ppLastFile;
-	}
-
-	lexer_DeleteState(lexer_GetState());
-	lexer_SetState(pLastFile->lexerState);
-
-	switch (pLastFile->nStatus) {
-		struct MacroArgs *args;
-
-	case STAT_isMacroArg:
-	case STAT_isMacro:
-		args = macro_GetCurrentArgs();
-		if (nCurrentStatus == STAT_isMacro) {
-			macro_FreeArgs(args);
-			free(args);
-		}
-		macro_UseNewArgs(pLastFile->macroArgs);
-		pCurrentMacro = pLastFile->pMacro;
-		break;
-	case STAT_isInclude:
-		break;
-	case STAT_isREPTBlock:
-		args = macro_GetCurrentArgs();
-		if (nCurrentStatus == STAT_isMacro) {
-			macro_FreeArgs(args);
-			free(args);
-		}
-		macro_UseNewArgs(pLastFile->macroArgs);
-		pCurrentREPTBlock = pLastFile->pREPTBlock;
-		nCurrentREPTBlockSize = pLastFile->nREPTBlockSize;
-		nCurrentREPTBlockCount = pLastFile->nREPTBlockCount;
-		nCurrentREPTBodyFirstLine = pLastFile->nREPTBodyFirstLine;
-		break;
-	default:
-		fatalerror("%s: Internal error.\n", __func__);
-	}
-	macro_SetUniqueID(pLastFile->uniqueID);
-
-	nCurrentStatus = pLastFile->nStatus;
-
-	nFileStackDepth--;
-
-	free(*ppLastFile);
-	*ppLastFile = NULL;
-	return 0;
+	if (*end++ != '/')
+		*end++ = '/';
+	*end = '\0';
+	includePaths[nbIncPaths++] = str;
 }
 
-int32_t fstk_GetLine(void)
+static void printDep(char const *path)
 {
-	struct sContext *pLastFile, **ppLastFile;
-
-	switch (nCurrentStatus) {
-	case STAT_isInclude:
-		/* This is the normal mode, also used when including a file. */
-		return lexer_GetLineNo();
-	case STAT_isMacro:
-		break; /* Peek top file of the stack */
-	case STAT_isMacroArg:
-		return lexer_GetLineNo(); /* ??? */
-	case STAT_isREPTBlock:
-		break; /* Peek top file of the stack */
-	default:
-		fatalerror("%s: Internal error.\n", __func__);
-	}
-
-	pLastFile = pFileStack;
-
-	if (pLastFile != NULL) {
-		while (pLastFile->next) {
-			ppLastFile = &(pLastFile->next);
-			pLastFile = *ppLastFile;
-		}
-		return pLastFile->nLine;
-	}
-
-	/*
-	 * This is only reached if the lexer is in REPT or MACRO mode but there
-	 * are no saved contexts with the origin of said REPT or MACRO.
-	 */
-	fatalerror("%s: Internal error.\n", __func__);
-}
-
-int yywrap(void)
-{
-	return popcontext();
-}
-
-/*
- * Dump the context stack to stderr
- */
-void fstk_Dump(void)
-{
-	const struct sContext *pLastFile;
-
-	pLastFile = pFileStack;
-
-	while (pLastFile) {
-		fprintf(stderr, "%s(%" PRId32 ") -> ", pLastFile->tzFileName,
-			pLastFile->nLine);
-		pLastFile = pLastFile->next;
-	}
-	char const *fileName = lexer_GetFileName();
-
-	if (fileName)
-		fprintf(stderr, "%s(%" PRId32 ",%" PRId32 "): ",
-			fileName, lexer_GetLineNo(), lexer_GetColNo());
-}
-
-void fstk_DumpToStr(char *buf, size_t buflen)
-{
-	const struct sContext *pLastFile = pFileStack;
-	int retcode;
-	size_t len = buflen;
-
-	while (pLastFile) {
-		retcode = snprintf(&buf[buflen - len], len, "%s(%" PRId32 ") -> ",
-				   pLastFile->tzFileName, pLastFile->nLine);
-		if (retcode < 0)
-			fatalerror("Failed to dump file stack to string: %s\n", strerror(errno));
-		else if (retcode >= len)
-			len = 0;
-		else
-			len -= retcode;
-		pLastFile = pLastFile->next;
-	}
-
-	retcode = snprintf(&buf[buflen - len], len, "%s(%" PRId32 ")",
-			   lexer_GetFileName(), lexer_GetLineNo());
-	if (retcode < 0)
-		fatalerror("Failed to dump file stack to string: %s\n", strerror(errno));
-	else if (retcode >= len)
-		len = 0;
-	else
-		len -= retcode;
-
-	if (!len)
-		warning(WARNING_LONG_STR, "File stack dump too long, got truncated\n");
-}
-
-/*
- * Extra includepath stuff
- */
-void fstk_AddIncludePath(char *s)
-{
-	if (NextIncPath == MAXINCPATHS)
-		fatalerror("Too many include directories passed from command line\n");
-
-	// Find last occurrence of slash; is it at the end of the string?
-	char const *lastSlash = strrchr(s, '/');
-	char const *pattern = lastSlash && *(lastSlash + 1) == 0 ? "%s" : "%s/";
-
-	if (snprintf(IncludePaths[NextIncPath++], _MAX_PATH, pattern,
-		     s) >= _MAX_PATH)
-		fatalerror("Include path too long '%s'\n", s);
-}
-
-static void printdep(const char *fileName)
-{
 	if (dependfile) {
-		fprintf(dependfile, "%s: %s\n", tzTargetFileName, fileName);
+		fprintf(dependfile, "%s: %s\n", tzTargetFileName, path);
 		if (oGeneratePhonyDeps)
-			fprintf(dependfile, "%s:\n", fileName);
+			fprintf(dependfile, "%s:\n", path);
 	}
 }
 
-static bool isPathValid(char const *pathname)
+static bool isPathValid(char const *path)
 {
 	struct stat statbuf;
 
-	if (stat(pathname, &statbuf) != 0)
+	if (stat(path, &statbuf) != 0)
 		return false;
 
 	/* Reject directories */
@@ -335,8 +99,8 @@
 	}
 
 	if (*fullPath) {
-		for (size_t i = 0; i <= NextIncPath; ++i) {
-			char *incPath = i ? IncludePaths[i - 1] : "";
+		for (size_t i = 0; i <= nbIncPaths; ++i) {
+			char const *incPath = i ? includePaths[i - 1] : "";
 			int len = snprintf(*fullPath, *size, "%s%s", incPath, path);
 
 			/* Oh how I wish `asnprintf` was standard... */
@@ -355,7 +119,7 @@
 				error("snprintf error during include path search: %s\n",
 				      strerror(errno));
 			} else if (isPathValid(*fullPath)) {
-				printdep(*fullPath);
+				printDep(*fullPath);
 				return true;
 			}
 		}
@@ -363,114 +127,210 @@
 
 	errno = ENOENT;
 	if (oGeneratedMissingIncludes)
-		printdep(path);
+		printDep(path);
 	return false;
 }
 
-/*
- * Set up an include file for parsing
- */
-void fstk_RunInclude(char *tzFileName)
+bool yywrap(void)
 {
+	if (contextStack->nbReptIters) { /* The context is a REPT block, which may loop */
+		contextStack->reptIters[contextStack->reptDepth - 1]++;
+		/* If this wasn't the last iteration, wrap instead of popping */
+		if (contextStack->reptIters[contextStack->reptDepth - 1]
+								<= contextStack->nbReptIters) {
+			lexer_RestartRept(contextStack->parent->lineNo);
+			contextStack->uniqueID = macro_UseNewUniqueID();
+			return false;
+		}
+	} else if (!contextStack->parent) {
+		return true;
+	}
+	contextStack = contextStack->parent;
+	contextDepth--;
+
+	lexer_DeleteState(contextStack->child->lexerState);
+	/* If at top level (= not in macro or in REPT), free the file name */
+	if (!contextStack->macro && contextStack->reptIters == 0)
+		free(contextStack->child->fileName);
+	/* Free the entry and make its parent the current entry */
+	free(contextStack->child);
+
+	contextStack->child = NULL;
+	lexer_SetState(contextStack->lexerState);
+	return false;
+}
+
+static void newContext(uint32_t reptDepth)
+{
+	if (++contextDepth >= nMaxRecursionDepth)
+		fatalerror("Recursion limit (%u) exceeded\n", nMaxRecursionDepth);
+	contextStack->child = malloc(sizeof(*contextStack->child)
+						+ reptDepth * sizeof(contextStack->reptIters[0]));
+	if (!contextStack->child)
+		fatalerror("Failed to allocate memory for new context: %s\n", strerror(errno));
+
+	contextStack->lineNo = lexer_GetLineNo();
+	/* Link new entry to its parent so it's reachable later */
+	contextStack->child->parent = contextStack;
+	contextStack = contextStack->child;
+
+	contextStack->child = NULL;
+	contextStack->reptDepth = reptDepth;
+}
+
+void fstk_RunInclude(char const *path)
+{
 	char *fullPath = NULL;
 	size_t size = 0;
 
-	if (!fstk_FindFile(tzFileName, &fullPath, &size)) {
-		if (oGeneratedMissingIncludes)
-			oFailedOnMissingInclude = true;
-		else
-			error("Unable to open included file '%s': %s\n",
-			      tzFileName, strerror(errno));
+	if (!fstk_FindFile(path, &fullPath, &size)) {
 		free(fullPath);
+		error("Unable to open included file '%s': %s\n", path, strerror(errno));
 		return;
 	}
 
-	pushcontext();
-	nCurrentStatus = STAT_isInclude;
-	if (verbose)
-		printf("Assembling %s\n", fullPath);
-
-	struct LexerState *state = lexer_OpenFile(fullPath);
-
-	if (!state)
-		/* If lexer had an error, it already reported it */
-		fatalerror("Failed to open file for INCLUDE\n"); /* TODO: make this non-fatal? */
-	lexer_SetStateAtEOL(state);
-	free(fullPath);
+	newContext(0);
+	contextStack->lexerState = lexer_OpenFile(fullPath);
+	if (!contextStack->lexerState)
+		fatalerror("Failed to set up lexer for file include\n");
+	lexer_SetStateAtEOL(contextStack->lexerState);
+	/* We're back at top-level, so most things are reset */
+	contextStack->uniqueID = 0;
+	macro_SetUniqueID(0);
+	contextStack->fileName = fullPath;
+	contextStack->macro = NULL;
+	contextStack->nbReptIters = 0;
 }
 
-/*
- * Set up a macro for parsing
- */
-void fstk_RunMacro(char *s, struct MacroArgs *args)
+void fstk_RunMacro(char *macroName, struct MacroArgs *args)
 {
-	struct Symbol const *sym = sym_FindSymbol(s);
+	struct Symbol *macro = sym_FindSymbol(macroName);
 
-	if (sym == NULL) {
-		error("Macro \"%s\" not defined\n", s);
+	if (!macro) {
+		error("Macro \"%s\" not defined\n", macroName);
 		return;
 	}
-	if (sym->type != SYM_MACRO) {
-		error("\"%s\" is not a macro\n", s);
+	if (macro->type != SYM_MACRO) {
+		error("\"%s\" is not a macro\n", macroName);
 		return;
 	}
-
-	pushcontext();
-	macro_SetUniqueID(nMacroCount++);
-	/* Minus 1 because there is a newline at the beginning of the buffer */
 	macro_UseNewArgs(args);
-	nCurrentStatus = STAT_isMacro;
 
-	pCurrentMacro = sym;
+	newContext(0);
+	contextStack->lexerState = lexer_OpenFileView(macro->macro,
+						      macro->macroSize, macro->fileLine);
+	if (!contextStack->lexerState)
+		fatalerror("Failed to set up lexer for macro invocation\n");
+	lexer_SetStateAtEOL(contextStack->lexerState);
+	contextStack->uniqueID = macro_UseNewUniqueID();
+	contextStack->fileName = macro->fileName;
+	contextStack->macro = macro;
+	contextStack->nbReptIters = 0;
 }
 
-/*
- * Set up a repeat block for parsing
- */
-void fstk_RunRept(uint32_t count, int32_t nReptLineNo, char const *body, size_t size)
+void fstk_RunRept(uint32_t count, int32_t nReptLineNo, char *body, size_t size)
 {
-	if (count) {
-		pushcontext();
-		macro_SetUniqueID(nMacroCount++);
-		nCurrentREPTBlockCount = count;
-		nCurrentStatus = STAT_isREPTBlock;
-		nCurrentREPTBlockSize = size;
-		pCurrentREPTBlock = body;
-		nCurrentREPTBodyFirstLine = nReptLineNo + 1;
-	}
+	uint32_t reptDepth = contextStack->reptDepth;
+
+	newContext(reptDepth + 1);
+	contextStack->lexerState = lexer_OpenFileView(body, size, nReptLineNo);
+	if (!contextStack->lexerState)
+		fatalerror("Failed to set up lexer for macro invocation\n");
+	lexer_SetStateAtEOL(contextStack->lexerState);
+	contextStack->uniqueID = macro_UseNewUniqueID();
+	contextStack->fileName = contextStack->parent->fileName;
+	contextStack->macro = contextStack->parent->macro; /* Inherit */
+	contextStack->nbReptIters = count;
+	/* Copy all of parent's iters, and add ours */
+	if (reptDepth)
+		memcpy(contextStack->reptIters, contextStack->parent->reptIters,
+		       sizeof(contextStack->reptIters[0]) * reptDepth);
+	contextStack->reptIters[reptDepth] = 1;
+
+	/* Correct our parent's line number, which currently points to the `ENDR` line */
+	contextStack->parent->lineNo = nReptLineNo;
 }
 
-/*
- * Initialize the filestack routines
- */
-void fstk_Init(char *pFileName)
+static void printContext(FILE *stream, struct Context const *context)
 {
-	char tzSymFileName[_MAX_PATH + 1 + 2];
+	fprintf(stream, "%s", context->fileName);
+	if (context->macro)
+		fprintf(stream, "::%s", context->macro->name);
+	for (size_t i = 0; i < context->reptDepth; i++)
+		fprintf(stream, "::REPT~%" PRIu32, context->reptIters[i]);
+	fprintf(stream, "(%" PRId32 ")", context->lineNo);
+}
 
-	char *c = pFileName;
-	int fileNameIndex = 0;
+static void dumpToStream(FILE *stream)
+{
+	struct Context *context = topLevelContext;
 
-	tzSymFileName[fileNameIndex++] = '"';
+	while (context != contextStack) {
+		printContext(stream, context);
+		fprintf(stream, " -> ");
+		context = context->child;
+	}
+	contextStack->lineNo = lexer_GetLineNo();
+	printContext(stream, contextStack);
+}
 
-	// minus 2 to account for trailing "\"\0"
-	// minus 1 to avoid a buffer overflow in extreme cases
-	while (*c && fileNameIndex < sizeof(tzSymFileName) - 2 - 1) {
-		if (*c == '"') {
-			tzSymFileName[fileNameIndex++] = '\\';
-		}
+void fstk_Dump(void)
+{
+	dumpToStream(stderr);
+}
 
-		tzSymFileName[fileNameIndex++] = *c;
-		++c;
-	}
+char *fstk_DumpToStr(void)
+{
+	char *str;
+	size_t size;
+	/* `open_memstream` is specified to always include a '\0' at the end of the buffer! */
+	FILE *stream = open_memstream(&str, &size);
 
-	tzSymFileName[fileNameIndex++] = '"';
-	tzSymFileName[fileNameIndex]   = '\0';
+	if (!stream)
+		fatalerror("Failed to dump file stack to string: %s\n", strerror(errno));
+	dumpToStream(stream);
+	fclose(stream);
+	return str;
+}
 
-	sym_AddString("__FILE__", tzSymFileName);
+uint32_t fstk_GetLine(void)
+{
+	return lexer_GetLineNo();
+}
 
-	pFileStack = NULL;
-	nFileStackDepth = 0;
+void fstk_Init(char *mainPath, uint32_t maxRecursionDepth)
+{
+	topLevelContext = malloc(sizeof(*topLevelContext));
+	if (!topLevelContext)
+		fatalerror("Failed to allocate memory for initial context: %s\n", strerror(errno));
+	topLevelContext->parent = NULL;
+	topLevelContext->child = NULL;
+	topLevelContext->lexerState = lexer_OpenFile(mainPath);
+	if (!topLevelContext->lexerState)
+		fatalerror("Failed to open main file!\n");
+	lexer_SetState(topLevelContext->lexerState);
+	topLevelContext->uniqueID = 0;
+	macro_SetUniqueID(0);
+	topLevelContext->fileName = mainPath;
+	topLevelContext->macro = NULL;
+	topLevelContext->nbReptIters = 0;
+	topLevelContext->reptDepth = 0;
 
-	nMacroCount = 0;
-	nCurrentStatus = STAT_isInclude;
+	contextStack = topLevelContext;
+
+#if 0
+	if (maxRecursionDepth
+			> (SIZE_MAX - sizeof(*contextStack)) / sizeof(contextStack->reptIters[0])) {
+#else
+	/* If this holds, then GCC raises a warning about the `if` above being dead code */
+	static_assert(UINT32_MAX
+			<= (SIZE_MAX - sizeof(*contextStack)) / sizeof(contextStack->reptIters[0]));
+	if (0) {
+#endif
+		error("Recursion depth may not be higher than %zu, defaulting to 64\n",
+			(SIZE_MAX - sizeof(*contextStack)) / sizeof(contextStack->reptIters[0]));
+		nMaxRecursionDepth = 64;
+	} else {
+		nMaxRecursionDepth = maxRecursionDepth;
+	}
 }
--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -258,6 +258,8 @@
 	};
 
 	/* Common state */
+	bool isFile;
+
 	enum LexerMode mode;
 	bool atLineStart;
 	uint32_t lineNo;
@@ -278,6 +280,21 @@
 struct LexerState *lexerState = NULL;
 struct LexerState *lexerStateEOL = NULL;
 
+static void initState(struct LexerState *state)
+{
+	state->mode = LEXER_NORMAL;
+	state->atLineStart = true; /* yylex() will init colNo due to this */
+	state->lastToken = 0;
+
+	state->capturing = false;
+	state->captureBuf = NULL;
+
+	state->nbChars = 0;
+	state->expandStrings = true;
+	state->expansions = NULL;
+	state->expansionOfs = 0;
+}
+
 struct LexerState *lexer_OpenFile(char const *path)
 {
 	bool isStdin = !strcmp(path, "-");
@@ -292,6 +309,7 @@
 	}
 	state->path = path;
 
+	state->isFile = true;
 	state->fd = isStdin ? STDIN_FILENO : open(path, O_RDONLY);
 	if (state->fd == -1) {
 		error("Failed to open file \"%s\": %s\n", path, strerror(errno));
@@ -345,32 +363,45 @@
 		state->index = 0;
 	}
 
-	state->mode = LEXER_NORMAL;
-	state->atLineStart = true; /* yylex() will init colNo due to this */
-	state->lineNo = 0;
-	state->lastToken = 0;
+	initState(state);
+	state->lineNo = 0; /* Will be incremented at first line start */
+	return state;
+}
 
-	state->capturing = false;
-	state->captureBuf = NULL;
+struct LexerState *lexer_OpenFileView(char *buf, size_t size, uint32_t lineNo)
+{
+	struct LexerState *state = malloc(sizeof(*state));
 
-	state->nbChars = 0;
-	state->expandStrings = true;
-	state->expansions = NULL;
-	state->expansionOfs = 0;
+	if (!state) {
+		error("Failed to allocate memory for lexer state: %s", strerror(errno));
+		return NULL;
+	}
+	// TODO: init `path`
+
+	state->isFile = false;
+	state->isMmapped = true; /* It's not *really* mmap()ed, but it behaves the same */
+	state->ptr = buf;
+	state->size = size;
+	state->offset = 0;
+
+	initState(state);
+	state->lineNo = lineNo; /* Will be incremented at first line start */
 	return state;
 }
 
-struct LexerState *lexer_OpenFileView(void)
+void lexer_RestartRept(uint32_t lineNo)
 {
-	return NULL;
+	lexerState->offset = 0;
+	initState(lexerState);
+	lexerState->lineNo = lineNo;
 }
 
 void lexer_DeleteState(struct LexerState *state)
 {
-	if (state->isMmapped)
-		munmap(state->ptr, state->size);
-	else
+	if (!state->isMmapped)
 		close(state->fd);
+	else if (state->isFile)
+		munmap(state->ptr, state->size);
 	free(state);
 }
 
@@ -523,7 +554,7 @@
 #define LOOKUP_PRE_NEST(exp) (exp)->totalLen += size
 #define LOOKUP_POST_NEST(exp) do { \
 	if (++depth >= nMaxRecursionDepth) \
-		fatalerror("Recursion limit (%u) exceeded", nMaxRecursionDepth); \
+		fatalerror("Recursion limit (%u) exceeded\n", nMaxRecursionDepth); \
 } while (0)
 	lookupExpansion(parent, distance);
 #undef LOOKUP_PRE_NEST
@@ -536,7 +567,7 @@
 
 	*insertPoint = malloc(sizeof(**insertPoint));
 	if (!*insertPoint)
-		fatalerror("Unable to allocate new expansion: %s", strerror(errno));
+		fatalerror("Unable to allocate new expansion: %s\n", strerror(errno));
 	(*insertPoint)->firstChild = NULL;
 	(*insertPoint)->next = NULL; /* Expansions are always performed left to right */
 	(*insertPoint)->name = strdup(name);
@@ -1417,10 +1448,6 @@
 			return '\n';
 
 		case EOF:
-			/* Captures end at their buffer's boundary no matter what */
-			if (!lexerState->capturing) {
-				/* TODO: use `yywrap()` */
-			}
 			return 0;
 
 		/* Handle identifiers... or error out */
@@ -1520,6 +1547,7 @@
 
 int yylex(void)
 {
+restart:
 	if (lexerState->atLineStart
 	/* Newlines read within an expansion should not increase the line count */
 	 && (!lexerState->expansions || lexerState->expansions->distance)) {
@@ -1536,8 +1564,17 @@
 	int token = lexerModeFuncs[lexerState->mode]();
 
 	/* Make sure to terminate files with a line feed */
-	if (token == 0 && lexerState->lastToken != '\n')
-		token = '\n';
+	if (token == 0) {
+		if (lexerState->lastToken != '\n') {
+			token = '\n';
+		} else { /* Try to switch to new buffer; if it succeeds, scan again */
+			/* Captures end at their buffer's boundary no matter what */
+			if (!lexerState->capturing) {
+				if (!yywrap())
+					goto restart;
+			}
+		}
+	}
 	lexerState->lastToken = token;
 
 	lexerState->atLineStart = false;
@@ -1547,9 +1584,11 @@
 	return token;
 }
 
-void lexer_CaptureBlock(int blockStartToken, int blockEndToken, char const **capture, size_t *size,
+void lexer_CaptureBlock(int blockStartToken, int blockEndToken, char **capture, size_t *size,
 			char const *name)
 {
+	assert(!lexerState->expansions);
+
 	lexerState->capturing = true;
 	lexerState->captureSize = 0;
 	unsigned int level = 0;
@@ -1556,7 +1595,7 @@
 	char *captureStart;
 
 	if (lexerState->isMmapped) {
-		captureStart = lexerState->ptr;
+		captureStart = &lexerState->ptr[lexerState->offset];
 	} else {
 		lexerState->captureCapacity = 128; /* The initial size will be twice that */
 		reallocCaptureBuf();
--- a/src/asm/macro.c
+++ b/src/asm/macro.c
@@ -29,7 +29,8 @@
 			    sizeof(((struct MacroArgs){0}).args[0]) * (nbArgs))
 
 static struct MacroArgs *macroArgs = NULL;
-static uint32_t uniqueID = -1;
+static uint32_t uniqueID = 0;
+static uint32_t maxUniqueID = 0;
 /*
  * The initialization is somewhat harmful, since it is never used, but it
  * guarantees the size of the buffer will be correct. I was unable to find a
@@ -107,13 +108,21 @@
 void macro_SetUniqueID(uint32_t id)
 {
 	uniqueID = id;
-	if (id == -1) {
+	if (id == 0) {
 		uniqueIDPtr = NULL;
 	} else {
+		if (uniqueID > maxUniqueID)
+			maxUniqueID = uniqueID;
 		/* The buffer is guaranteed to be the correct size */
 		sprintf(uniqueIDBuf, "_%" PRIu32, id);
 		uniqueIDPtr = uniqueIDBuf;
 	}
+}
+
+uint32_t macro_UseNewUniqueID(void)
+{
+	macro_SetUniqueID(++maxUniqueID);
+	return maxUniqueID;
 }
 
 void macro_ShiftCurrentArgs(void)
--- a/src/asm/main.c
+++ b/src/asm/main.c
@@ -23,8 +23,10 @@
 #include "asm/lexer.h"
 #include "asm/main.h"
 #include "asm/output.h"
+#include "asm/rpn.h"
 #include "asm/symbol.h"
 #include "asm/warning.h"
+#include "asmy.h"
 
 #include "extern/err.h"
 #include "extern/getopt.h"
@@ -32,8 +34,6 @@
 #include "helpers.h"
 #include "version.h"
 
-extern int yyparse(void);
-
 size_t cldefines_index;
 size_t cldefines_numindices;
 size_t cldefines_bufsize;
@@ -307,11 +307,11 @@
 	yydebug = 1;
 #endif
 
-	nMaxRecursionDepth = 64;
 	oGeneratePhonyDeps = false;
 	oGeneratedMissingIncludes = false;
 	oFailedOnMissingInclude = false;
 	tzTargetFileName = NULL;
+	uint32_t maxRecursionDepth = 64;
 	size_t nTargetFileNameLen = 0;
 
 	DefaultOptions.gbgfx[0] = '0';
@@ -390,7 +390,7 @@
 
 			break;
 		case 'r':
-			nMaxRecursionDepth = strtoul(optarg, &ep, 0);
+			maxRecursionDepth = strtoul(optarg, &ep, 0);
 
 			if (optarg[0] == '\0' || *ep != '\0')
 				errx(1, "Invalid argument for option 'r'");
@@ -483,13 +483,9 @@
 		fprintf(dependfile, "%s: %s\n", tzTargetFileName, tzMainfile);
 	}
 
-	/* Init lexer; important to do first, since that's what provides the file name, line, etc */
-	struct LexerState *state = lexer_OpenFile(tzMainfile);
-
-	if (!state)
-		fatalerror("Failed to open main file!\n");
+	/* Init file stack; important to do first, since it provides the file name, line, etc */
 	lexer_Init();
-	lexer_SetState(state);
+	fstk_Init(tzMainfile, maxRecursionDepth);
 
 	nStartClock = clock();
 
@@ -497,7 +493,6 @@
 	nIFDepth = 0;
 	sym_Init();
 	sym_SetExportAll(exportall);
-	fstk_Init(tzMainfile);
 
 	opt_ParseDefines();
 	charmap_New("main", NULL);
--- a/src/asm/output.c
+++ b/src/asm/output.c
@@ -33,7 +33,7 @@
 #include "platform.h" // strdup
 
 struct Patch {
-	char tzFilename[_MAX_PATH + 1];
+	char *tzFilename;
 	uint32_t nOffset;
 	struct Section *pcSection;
 	uint32_t pcOffset;
@@ -318,7 +318,7 @@
 		fatalerror("No memory for patch's RPN expression: %s\n", strerror(errno));
 
 	patch->type = type;
-	fstk_DumpToStr(patch->tzFilename, sizeof(patch->tzFilename));
+	patch->tzFilename = fstk_DumpToStr();
 	patch->nOffset = ofs;
 	patch->pcSection = sect_GetSymbolSection();
 	patch->pcOffset = sect_GetSymbolOffset();
--- a/src/asm/symbol.c
+++ b/src/asm/symbol.c
@@ -477,7 +477,7 @@
 /*
  * Add a macro definition
  */
-struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char const *body, size_t size)
+struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size)
 {
 	struct Symbol *sym = createNonrelocSymbol(symName);
 
--- /dev/null
+++ b/test/asm/file-sym.asm
@@ -1,0 +1,1 @@
+PRINTT "{__FILE__}\n"
--- /dev/null
+++ b/test/asm/file-sym.out
@@ -1,0 +1,1 @@
+"test/asm/file-sym.asm"