shithub: rgbds

Download patch

ref: 6dc4ce65999d9f26703c4728ed983fbec90635c1
parent: 3a44cc7722315a305561cb89b63ecba674a6cdaa
author: ISSOtm <[email protected]>
date: Thu Jul 23 08:12:58 EDT 2020

Implement infrastructure around new lexer

The lexer itself is very much incomplete, but this is intended to be a
safe point to revert to should further implementation go south.

--- a/Makefile
+++ b/Makefile
@@ -56,7 +56,6 @@
 	src/asm/asmy.o \
 	src/asm/charmap.o \
 	src/asm/fstack.o \
-	src/asm/globlex.o \
 	src/asm/lexer.o \
 	src/asm/macro.o \
 	src/asm/main.o \
@@ -73,7 +72,7 @@
 	src/hashmap.o \
 	src/linkdefs.o
 
-src/asm/globlex.o src/asm/lexer.o src/asm/constexpr.o: src/asm/asmy.h
+src/asm/lexer.o: src/asm/asmy.h
 
 rgblink_obj := \
 	src/link/assign.o \
--- a/include/asm/asm.h
+++ b/include/asm/asm.h
@@ -24,11 +24,8 @@
 #define MAXMACROARGS	99999
 #define MAXINCPATHS	128
 
-extern int32_t nLineNo;
 extern uint32_t nTotalLines;
 extern uint32_t nIFDepth;
-extern bool skipElif;
-extern char tzCurrentFileName[_MAX_PATH + 1];
 extern struct Section *pCurrentSection;
 extern bool oDontExpandStrings;
 
--- a/include/asm/fstack.h
+++ b/include/asm/fstack.h
@@ -24,7 +24,7 @@
 struct MacroArgs;
 
 struct sContext {
-	YY_BUFFER_STATE FlexHandle;
+	struct LexerState *lexerState;
 	struct Symbol const *pMacro;
 	struct sContext *next;
 	char tzFileName[_MAX_PATH + 1];
@@ -32,7 +32,6 @@
 	uint32_t uniqueID;
 	int32_t nLine;
 	uint32_t nStatus;
-	FILE *pFile;
 	char *pREPTBlock;
 	uint32_t nREPTBlockCount;
 	uint32_t nREPTBlockSize;
@@ -46,11 +45,17 @@
 void fstk_Init(char *s);
 void fstk_Dump(void);
 void fstk_DumpToStr(char *buf, size_t len);
-void fstk_DumpStringExpansions(void);
 void fstk_AddIncludePath(char *s);
 void fstk_RunMacro(char *s, struct MacroArgs *args);
 void fstk_RunRept(uint32_t count, int32_t nReptLineNo);
-FILE *fstk_FindFile(char const *fname, char **incPathUsed);
+/**
+ * @param path The user-provided file name
+ * @param fullPath The address of a pointer, which will be made to point at the full path
+ *                 The pointer's value must be a valid argument to `realloc`, including NULL
+ * @param size Current size of the buffer, or 0 if the pointer is NULL
+ * @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);
 
 #endif /* RGBDS_ASM_FSTACK_H */
--- a/include/asm/lexer.h
+++ b/include/asm/lexer.h
@@ -9,78 +9,44 @@
 #ifndef RGBDS_ASM_LEXER_H
 #define RGBDS_ASM_LEXER_H
 
-#include <stdint.h>
-#include <stdio.h>
-
-#define LEXHASHSIZE	(1 << 11)
 #define MAXSTRLEN	255
 
-struct sLexInitString {
-	char *tzName;
-	uint32_t nToken;
-};
+struct LexerState;
+extern struct LexerState *lexerState;
+extern struct LexerState *lexerStateEOL;
 
-struct sLexFloat {
-	uint32_t (*Callback)(char *s, uint32_t size);
-	uint32_t nToken;
-};
+static inline struct LexerState *lexer_GetState(void)
+{
+	return lexerState;
+}
 
-struct yy_buffer_state {
-	/* Actual starting address */
-	char *pBufferRealStart;
-	/* Address where the data is initially written after a safety margin */
-	char *pBufferStart;
-	char *pBuffer;
-	size_t nBufferSize;
-	uint32_t oAtLineStart;
-};
+static inline void lexer_SetState(struct LexerState *state)
+{
+	lexerState = state;
+}
 
-enum eLexerState {
-	LEX_STATE_NORMAL,
-	LEX_STATE_MACROARGS
-};
+static inline void lexer_SetStateAtEOL(struct LexerState *state)
+{
+	lexerStateEOL = state;
+}
 
-struct sStringExpansionPos {
-	char *tzName;
-	char *pBuffer;
-	char *pBufferPos;
-	struct sStringExpansionPos *pParent;
+struct LexerState *lexer_OpenFile(char const *path);
+struct LexerState *lexer_OpenFileView(void);
+void lexer_DeleteState(struct LexerState *state);
+
+enum LexerMode {
+	LEXER_NORMAL,
+	LEXER_RAW
 };
 
-#define INITIAL		0
-#define macroarg	3
+void lexer_SetMode(enum LexerMode mode);
+void lexer_ToggleStringExpansion(bool enable);
 
-typedef struct yy_buffer_state *YY_BUFFER_STATE;
-
-void setup_lexer(void);
-
-void yy_set_state(enum eLexerState i);
-YY_BUFFER_STATE yy_create_buffer(FILE *f);
-YY_BUFFER_STATE yy_scan_bytes(char const *mem, uint32_t size);
-void yy_delete_buffer(YY_BUFFER_STATE buf);
-void yy_switch_to_buffer(YY_BUFFER_STATE buf);
-uint32_t lex_FloatAlloc(const struct sLexFloat *tok);
-void lex_FloatAddRange(uint32_t id, uint16_t start, uint16_t end);
-void lex_FloatDeleteRange(uint32_t id, uint16_t start, uint16_t end);
-void lex_FloatAddFirstRange(uint32_t id, uint16_t start, uint16_t end);
-void lex_FloatDeleteFirstRange(uint32_t id, uint16_t start, uint16_t end);
-void lex_FloatAddSecondRange(uint32_t id, uint16_t start, uint16_t end);
-void lex_FloatDeleteSecondRange(uint32_t id, uint16_t start, uint16_t end);
-void lex_Init(void);
-void lex_AddStrings(const struct sLexInitString *lex);
-void lex_SetBuffer(char *buffer, uint32_t len);
-void lex_BeginStringExpansion(const char *tzName);
-int yywrap(void);
+char const *lexer_GetFileName(void);
+unsigned int lexer_GetLineNo(void);
+void lexer_DumpStringExpansions(void);
 int yylex(void);
-void yyunput(char c);
-void yyunputstr(const char *s);
-void yyskipbytes(uint32_t count);
-void yyunputbytes(uint32_t count);
-
-extern YY_BUFFER_STATE pCurrentBuffer;
-extern struct sStringExpansionPos *pCurrentStringExpansion;
-
-void upperstring(char *s);
-void lowerstring(char *s);
+void lexer_SkipToBlockEnd(int blockStartToken, int blockEndToken, int endToken,
+			  char **capture, size_t *size, char const *name);
 
 #endif /* RGBDS_ASM_LEXER_H */
--- a/include/asm/main.h
+++ b/include/asm/main.h
@@ -43,6 +43,10 @@
 void opt_Pop(void);
 void opt_Parse(char *s);
 
+void upperstring(char *s);
+void lowerstring(char *s);
+
+/* TODO: are these really needed? */
 #define YY_FATAL_ERROR fatalerror
 
 #ifdef YYLMAX
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -31,7 +31,6 @@
     "${BISON_ASMy_OUTPUT_SOURCE}"
     "asm/charmap.c"
     "asm/fstack.c"
-    "asm/globlex.c"
     "asm/lexer.c"
     "asm/macro.c"
     "asm/main.c"
--- a/src/asm/asmy.y
+++ b/src/asm/asmy.y
@@ -39,6 +39,7 @@
 char *tzNewMacro;
 uint32_t ulNewMacroSize;
 int32_t nPCOffset;
+bool skipElifs; /* If this is set, ELIFs cannot be executed anymore */
 
 size_t symvaluetostring(char *dest, size_t maxLength, char *symName,
 			const char *mode)
@@ -111,278 +112,6 @@
 	return r;
 }
 
-static uint32_t isWhiteSpace(char s)
-{
-	return (s == ' ') || (s == '\t') || (s == '\0') || (s == '\n');
-}
-
-static uint32_t isRept(char *s)
-{
-	return (strncasecmp(s, "REPT", 4) == 0)
-		&& isWhiteSpace(*(s - 1)) && isWhiteSpace(s[4]);
-}
-
-static uint32_t isEndr(char *s)
-{
-	return (strncasecmp(s, "ENDR", 4) == 0)
-		&& isWhiteSpace(*(s - 1)) && isWhiteSpace(s[4]);
-}
-
-static void copyrept(void)
-{
-	int32_t level = 1, len, instring = 0;
-	char *src = pCurrentBuffer->pBuffer;
-	char *bufferEnd = pCurrentBuffer->pBufferStart
-			+ pCurrentBuffer->nBufferSize;
-
-	while (src < bufferEnd && level) {
-		if (instring == 0) {
-			if (isRept(src)) {
-				level++;
-				src += 4;
-			} else if (isEndr(src)) {
-				level--;
-				src += 4;
-			} else {
-				if (*src == '\"')
-					instring = 1;
-				src++;
-			}
-		} else {
-			if (*src == '\\') {
-				src += 2;
-			} else if (*src == '\"') {
-				src++;
-				instring = 0;
-			} else {
-				src++;
-			}
-		}
-	}
-
-	if (level != 0)
-		fatalerror("Unterminated REPT block\n");
-
-	len = src - pCurrentBuffer->pBuffer - 4;
-
-	src = pCurrentBuffer->pBuffer;
-	ulNewMacroSize = len;
-
-	tzNewMacro = malloc(ulNewMacroSize + 1);
-
-	if (tzNewMacro == NULL)
-		fatalerror("Not enough memory for REPT block.\n");
-
-	uint32_t i;
-
-	tzNewMacro[ulNewMacroSize] = 0;
-	for (i = 0; i < ulNewMacroSize; i++) {
-		tzNewMacro[i] = src[i];
-		if (src[i] == '\n')
-			nLineNo++;
-	}
-
-	yyskipbytes(ulNewMacroSize + 4);
-
-}
-
-static uint32_t isMacro(char *s)
-{
-	return (strncasecmp(s, "MACRO", 4) == 0)
-		&& isWhiteSpace(*(s - 1)) && isWhiteSpace(s[5]);
-}
-
-static uint32_t isEndm(char *s)
-{
-	return (strncasecmp(s, "ENDM", 4) == 0)
-		&& isWhiteSpace(*(s - 1)) && isWhiteSpace(s[4]);
-}
-
-static void copymacro(void)
-{
-	int32_t level = 1, len, instring = 0;
-	char *src = pCurrentBuffer->pBuffer;
-	char *bufferEnd = pCurrentBuffer->pBufferStart
-			+ pCurrentBuffer->nBufferSize;
-
-	while (src < bufferEnd && level) {
-		if (instring == 0) {
-			if (isMacro(src)) {
-				level++;
-				src += 4;
-			} else if (isEndm(src)) {
-				level--;
-				src += 4;
-			} else {
-				if(*src == '\"')
-					instring = 1;
-				src++;
-			}
-		} else {
-			if (*src == '\\') {
-				src += 2;
-			} else if (*src == '\"') {
-				src++;
-				instring = 0;
-			} else {
-				src++;
-			}
-		}
-	}
-
-	if (level != 0)
-		fatalerror("Unterminated MACRO definition.\n");
-
-	len = src - pCurrentBuffer->pBuffer - 4;
-
-	src = pCurrentBuffer->pBuffer;
-	ulNewMacroSize = len;
-
-	tzNewMacro = (char *)malloc(ulNewMacroSize + 1);
-	if (tzNewMacro == NULL)
-		fatalerror("Not enough memory for MACRO definition.\n");
-
-	uint32_t i;
-
-	tzNewMacro[ulNewMacroSize] = 0;
-	for (i = 0; i < ulNewMacroSize; i++) {
-		tzNewMacro[i] = src[i];
-		if (src[i] == '\n')
-			nLineNo++;
-	}
-
-	yyskipbytes(ulNewMacroSize + 4);
-}
-
-static bool endsIf(char c)
-{
-	return isWhiteSpace(c) || c == '(' || c == '{';
-}
-
-static uint32_t isIf(char *s)
-{
-	return (strncasecmp(s, "IF", 2) == 0)
-		&& isWhiteSpace(s[-1]) && endsIf(s[2]);
-}
-
-static uint32_t isElif(char *s)
-{
-	return (strncasecmp(s, "ELIF", 4) == 0)
-		&& isWhiteSpace(s[-1]) && endsIf(s[4]);
-}
-
-static uint32_t isElse(char *s)
-{
-	return (strncasecmp(s, "ELSE", 4) == 0)
-		&& isWhiteSpace(s[-1]) && isWhiteSpace(s[4]);
-}
-
-static uint32_t isEndc(char *s)
-{
-	return (strncasecmp(s, "ENDC", 4) == 0)
-		&& isWhiteSpace(s[-1]) && isWhiteSpace(s[4]);
-}
-
-static void if_skip_to_else(void)
-{
-	int32_t level = 1;
-	bool inString = false;
-	char *src = pCurrentBuffer->pBuffer;
-
-	while (*src && level) {
-		if (*src == '\n')
-			nLineNo++;
-
-		if (!inString) {
-			if (isIf(src)) {
-				level++;
-				src += 2;
-
-			} else if (level == 1 && isElif(src)) {
-				level--;
-				skipElif = false;
-
-			} else if (level == 1 && isElse(src)) {
-				level--;
-				src += 4;
-
-			} else if (isEndc(src)) {
-				level--;
-				if (level != 0)
-					src += 4;
-
-			} else {
-				if (*src == '\"')
-					inString = true;
-				src++;
-			}
-		} else {
-			if (*src == '\"') {
-				inString = false;
-			} else if (*src == '\\') {
-				/* Escaped quotes don't end the string */
-				if (*++src != '\"')
-					src--;
-			}
-			src++;
-		}
-	}
-
-	if (level != 0)
-		fatalerror("Unterminated IF construct\n");
-
-	int32_t len = src - pCurrentBuffer->pBuffer;
-
-	yyskipbytes(len);
-	yyunput('\n');
-	nLineNo--;
-}
-
-static void if_skip_to_endc(void)
-{
-	int32_t level = 1;
-	bool inString = false;
-	char *src = pCurrentBuffer->pBuffer;
-
-	while (*src && level) {
-		if (*src == '\n')
-			nLineNo++;
-
-		if (!inString) {
-			if (isIf(src)) {
-				level++;
-				src += 2;
-			} else if (isEndc(src)) {
-				level--;
-				if (level != 0)
-					src += 4;
-			} else {
-				if (*src == '\"')
-					inString = true;
-				src++;
-			}
-		} else {
-			if (*src == '\"') {
-				inString = false;
-			} else if (*src == '\\') {
-				/* Escaped quotes don't end the string */
-				if (*++src != '\"')
-					src--;
-			}
-			src++;
-		}
-	}
-
-	if (level != 0)
-		fatalerror("Unterminated IF construct\n");
-
-	int32_t len = src - pCurrentBuffer->pBuffer;
-
-	yyskipbytes(len);
-	yyunput('\n');
-	nLineNo--;
-}
-
 static size_t strlenUTF8(const char *s)
 {
 	size_t len = 0;
@@ -660,7 +389,6 @@
 			nListCountEmpty = 0;
 			nPCOffset = 0;
 		} line '\n' {
-			nLineNo++;
 			nTotalLines++;
 		}
 ;
@@ -699,9 +427,9 @@
 ;
 
 macro		: T_ID {
-			yy_set_state(LEX_STATE_MACROARGS);
+			lexer_SetMode(LEXER_RAW);
 		} macroargs {
-			yy_set_state(LEX_STATE_NORMAL);
+			lexer_SetMode(LEXER_NORMAL);
 			fstk_RunMacro($1, $3);
 		}
 ;
@@ -786,9 +514,9 @@
 ;
 
 opt		: T_POP_OPT {
-			yy_set_state(LEX_STATE_MACROARGS);
+			lexer_SetMode(LEXER_RAW);
 		} opt_list {
-			yy_set_state(LEX_STATE_NORMAL);
+			lexer_SetMode(LEXER_NORMAL);
 		}
 ;
 
@@ -875,15 +603,21 @@
 ;
 
 rept		: T_POP_REPT uconst {
-			uint32_t nDefinitionLineNo = nLineNo;
-			copyrept();
+			uint32_t nDefinitionLineNo = lexer_GetLineNo();
+			char *body;
+			size_t size;
+			lexer_SkipToBlockEnd(T_POP_REPT, T_POP_ENDR, T_POP_ENDR,
+					     &body, &size, "REPT block");
 			fstk_RunRept($2, nDefinitionLineNo);
 		}
 ;
 
 macrodef	: T_LABEL ':' T_POP_MACRO {
-			int32_t nDefinitionLineNo = nLineNo;
-			copymacro();
+			int32_t nDefinitionLineNo = lexer_GetLineNo();
+			char *body;
+			size_t size;
+			lexer_SkipToBlockEnd(T_POP_MACRO, T_POP_ENDM, T_POP_ENDM,
+					     &body, &size, "macro definition");
 			sym_AddMacro($1, nDefinitionLineNo);
 		}
 ;
@@ -956,9 +690,9 @@
 ;
 
 purge		: T_POP_PURGE {
-			oDontExpandStrings = true;
+			lexer_ToggleStringExpansion(false);
 		} purge_list {
-			oDontExpandStrings = false;
+			lexer_ToggleStringExpansion(true);
 		}
 ;
 
@@ -1054,8 +788,14 @@
 
 if		: T_POP_IF const {
 			nIFDepth++;
-			if (!$2)
-				if_skip_to_else();
+			if (!$2) {
+				/* The function is hardcoded to also stop on T_POP_ELSE and ENDC */
+				lexer_SkipToBlockEnd(T_POP_IF, T_POP_ENDC, T_POP_ELIF,
+						     NULL, NULL, "if block");
+				skipElifs = false;
+			} else {
+				skipElifs = true;
+			}
 		}
 ;
 
@@ -1063,7 +803,7 @@
 			if (nIFDepth <= 0)
 				fatalerror("Found ELIF outside an IF construct\n");
 
-			if (skipElif) {
+			if (skipElifs) {
 				/*
 				 * Executed when ELIF is reached at the end of
 				 * an IF or ELIF block for which the condition
@@ -1071,7 +811,8 @@
 				 *
 				 * Continue parsing at ENDC keyword
 				 */
-				if_skip_to_endc();
+				lexer_SkipToBlockEnd(T_POP_IF, T_POP_ENDC, T_POP_ENDC,
+						     NULL, NULL, "elif block");
 			} else {
 				/*
 				 * Executed when ELIF is skipped to because the
@@ -1078,7 +819,6 @@
 				 * condition of the previous IF or ELIF block
 				 * was false.
 				 */
-				skipElif = true;
 
 				if (!$2) {
 					/*
@@ -1085,7 +825,10 @@
 					 * Continue parsing after ELSE, or at
 					 * ELIF or ENDC keyword.
 					 */
-					if_skip_to_else();
+					lexer_SkipToBlockEnd(T_POP_IF, T_POP_ENDC, T_POP_ELIF,
+							     NULL, NULL, "elif block");
+				} else {
+					skipElifs = true;
 				}
 			}
 		}
@@ -1096,7 +839,8 @@
 				fatalerror("Found ELSE outside an IF construct\n");
 
 			/* Continue parsing at ENDC keyword */
-			if_skip_to_endc();
+			lexer_SkipToBlockEnd(T_POP_IF, T_POP_ENDC, T_POP_ENDC,
+					     NULL, NULL, "else block");
 		}
 ;
 
@@ -1267,13 +1011,13 @@
 		}
 		| T_OP_BANK '(' string ')'	{ rpn_BankSection(&$$, $3); }
 		| T_OP_DEF {
-			oDontExpandStrings = true;
+			lexer_ToggleStringExpansion(false);
 		} '(' scoped_id ')' {
 			struct Symbol const *sym = sym_FindSymbol($4);
 
 			rpn_Number(&$$, !!sym);
 
-			oDontExpandStrings = false;
+			lexer_ToggleStringExpansion(true);
 		}
 		| T_OP_ROUND '(' const ')' {
 			rpn_Number(&$$, math_Round($3));
--- a/src/asm/fstack.c
+++ b/src/asm/fstack.c
@@ -36,10 +36,7 @@
 static unsigned int nFileStackDepth;
 unsigned int nMaxRecursionDepth;
 static struct Symbol const *pCurrentMacro;
-static YY_BUFFER_STATE CurrentFlexHandle;
-static FILE *pCurrentFile;
 static uint32_t nCurrentStatus;
-char tzCurrentFileName[_MAX_PATH + 1];
 static char IncludePaths[MAXINCPATHS][_MAX_PATH + 1];
 static int32_t NextIncPath;
 static uint32_t nMacroCount;
@@ -81,10 +78,8 @@
 	if (*ppFileStack == NULL)
 		fatalerror("No memory for context\n");
 
-	(*ppFileStack)->FlexHandle = CurrentFlexHandle;
 	(*ppFileStack)->next = NULL;
-	strcpy((char *)(*ppFileStack)->tzFileName, (char *)tzCurrentFileName);
-	(*ppFileStack)->nLine = nLineNo;
+	(*ppFileStack)->nLine = lexer_GetLineNo();
 
 	switch ((*ppFileStack)->nStatus = nCurrentStatus) {
 	case STAT_isMacroArg:
@@ -93,7 +88,6 @@
 		(*ppFileStack)->pMacro = pCurrentMacro;
 		break;
 	case STAT_isInclude:
-		(*ppFileStack)->pFile = pCurrentFile;
 		break;
 	case STAT_isREPTBlock:
 		(*ppFileStack)->macroArgs = macro_GetCurrentArgs();
@@ -107,8 +101,6 @@
 		fatalerror("%s: Internal error.\n", __func__);
 	}
 	(*ppFileStack)->uniqueID = macro_GetUniqueID();
-
-	nLineNo = 0;
 }
 
 static int32_t popcontext(void)
@@ -122,20 +114,15 @@
 			int nNbCharsWritten;
 			int nNbCharsLeft;
 
-			yy_delete_buffer(CurrentFlexHandle);
-			CurrentFlexHandle =
-				yy_scan_bytes(pCurrentREPTBlock,
-					      nCurrentREPTBlockSize);
-			yy_switch_to_buffer(CurrentFlexHandle);
 			macro_SetUniqueID(nMacroCount++);
 
 			/* Increment REPT count in file path */
 			pREPTIterationWritePtr =
-				strrchr(tzCurrentFileName, '~') + 1;
+				strrchr(lexer_GetFileName(), '~') + 1;
 			nREPTIterationNo =
 				strtoul(pREPTIterationWritePtr, NULL, 10);
-			nNbCharsLeft = sizeof(tzCurrentFileName)
-				- (pREPTIterationWritePtr - tzCurrentFileName);
+			nNbCharsLeft = sizeof(lexer_GetFileName())
+				- (pREPTIterationWritePtr - lexer_GetFileName());
 			nNbCharsWritten = snprintf(pREPTIterationWritePtr,
 						   nNbCharsLeft, "%lu",
 						   nREPTIterationNo + 1);
@@ -150,7 +137,6 @@
 				fatalerror("Cannot write REPT count to file path\n");
 			}
 
-			nLineNo = nCurrentREPTBodyFirstLine;
 			return 0;
 		}
 	}
@@ -165,20 +151,9 @@
 		pLastFile = *ppLastFile;
 	}
 
-	yy_delete_buffer(CurrentFlexHandle);
-	nLineNo = nCurrentStatus == STAT_isREPTBlock ? nCurrentREPTBodyLastLine
-						     : pLastFile->nLine;
+	lexer_DeleteState(lexer_GetState());
+	lexer_SetState(pLastFile->lexerState);
 
-	if (nCurrentStatus == STAT_isInclude)
-		fclose(pCurrentFile);
-
-	if (nCurrentStatus == STAT_isMacro
-	 || nCurrentStatus == STAT_isREPTBlock)
-		nLineNo++;
-
-	CurrentFlexHandle = pLastFile->FlexHandle;
-	strcpy((char *)tzCurrentFileName, (char *)pLastFile->tzFileName);
-
 	switch (pLastFile->nStatus) {
 		struct MacroArgs *args;
 
@@ -193,7 +168,6 @@
 		pCurrentMacro = pLastFile->pMacro;
 		break;
 	case STAT_isInclude:
-		pCurrentFile = pLastFile->pFile;
 		break;
 	case STAT_isREPTBlock:
 		args = macro_GetCurrentArgs();
@@ -218,7 +192,6 @@
 
 	free(*ppLastFile);
 	*ppLastFile = NULL;
-	yy_switch_to_buffer(CurrentFlexHandle);
 	return 0;
 }
 
@@ -229,11 +202,11 @@
 	switch (nCurrentStatus) {
 	case STAT_isInclude:
 		/* This is the normal mode, also used when including a file. */
-		return nLineNo;
+		return lexer_GetLineNo();
 	case STAT_isMacro:
 		break; /* Peek top file of the stack */
 	case STAT_isMacroArg:
-		return nLineNo; /* ??? */
+		return lexer_GetLineNo(); /* ??? */
 	case STAT_isREPTBlock:
 		break; /* Peek top file of the stack */
 	default:
@@ -277,7 +250,7 @@
 		pLastFile = pLastFile->next;
 	}
 
-	fprintf(stderr, "%s(%" PRId32 ")", tzCurrentFileName, nLineNo);
+	fprintf(stderr, "%s(%" PRId32 ")", lexer_GetFileName(), lexer_GetLineNo());
 }
 
 void fstk_DumpToStr(char *buf, size_t buflen)
@@ -299,7 +272,7 @@
 	}
 
 	retcode = snprintf(&buf[buflen - len], len, "%s(%" PRId32 ")",
-			   tzCurrentFileName, nLineNo);
+			   lexer_GetFileName(), lexer_GetLineNo());
 	if (retcode < 0)
 		fatalerror("Failed to dump file stack to string: %s\n", strerror(errno));
 	else if (retcode >= len)
@@ -312,20 +285,6 @@
 }
 
 /*
- * Dump the string expansion stack to stderr
- */
-void fstk_DumpStringExpansions(void)
-{
-	const struct sStringExpansionPos *pExpansion = pCurrentStringExpansion;
-
-	while (pExpansion) {
-		fprintf(stderr, "while expanding symbol \"%s\"\n",
-			pExpansion->tzName);
-		pExpansion = pExpansion->pParent;
-	}
-}
-
-/*
  * Extra includepath stuff
  */
 void fstk_AddIncludePath(char *s)
@@ -351,63 +310,58 @@
 	}
 }
 
-static FILE *getFile(char const *pathname)
+static bool isPathValid(char const *pathname)
 {
 	struct stat statbuf;
 
 	if (stat(pathname, &statbuf) != 0)
-		return NULL;
+		return false;
 
 	/* Reject directories */
-	if (S_ISDIR(statbuf.st_mode))
-		return NULL;
-
-	return fopen(pathname, "rb");
+	return !S_ISDIR(statbuf.st_mode);
 }
 
-FILE *fstk_FindFile(char const *fname, char **incPathUsed)
+bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
 {
-	if (fname == NULL)
-		return NULL;
-
-	char path[_MAX_PATH];
-	FILE *f = getFile(fname);
-
-	if (f) {
-		printdep(fname);
-		return f;
+	if (!*size) {
+		*size = 64; /* This is arbitrary, really */
+		*fullPath = realloc(*fullPath, *size);
+		if (!*fullPath)
+			error("realloc error during include path search: %s\n",
+			      strerror(errno));
 	}
 
-	for (size_t i = 0; i < NextIncPath; ++i) {
-		/*
-		 * The function snprintf() does not write more than `size` bytes
-		 * (including the terminating null byte ('\0')).  If the output
-		 * was truncated due to this limit, the return value is the
-		 * number of characters (excluding the terminating null byte)
-		 * which would have been written to the final string if enough
-		 * space had been available. Thus, a return value of `size` or
-		 * more means that the output was truncated.
-		 */
-		int fullpathlen = snprintf(path, sizeof(path), "%s%s",
-					   IncludePaths[i], fname);
+	if (*fullPath) {
+		for (size_t i = 0; i <= NextIncPath; ++i) {
+			char *incPath = i ? IncludePaths[i - 1] : "";
+			int len = snprintf(*fullPath, *size, "%s%s", incPath, path);
 
-		if (fullpathlen >= (int)sizeof(path))
-			continue;
+			/* Oh how I wish `asnprintf` was standard... */
+			if (len >= *size) { /* `len` doesn't include the terminator, `size` does */
+				*size = len + 1;
+				*fullPath = realloc(*fullPath, *size);
+				if (!*fullPath) {
+					error("realloc error during include path search: %s\n",
+					      strerror(errno));
+					break;
+				}
+				len = sprintf(*fullPath, "%s%s", incPath, path);
+			}
 
-		f = getFile(path);
-		if (f) {
-			printdep(path);
-
-			if (incPathUsed)
-				*incPathUsed = IncludePaths[i];
-			return f;
+			if (len < 0) {
+				error("snprintf error during include path search: %s\n",
+				      strerror(errno));
+			} else if (isPathValid(*fullPath)) {
+				printdep(*fullPath);
+				return true;
+			}
 		}
 	}
 
 	errno = ENOENT;
 	if (oGeneratedMissingIncludes)
-		printdep(fname);
-	return NULL;
+		printdep(path);
+	return false;
 }
 
 /*
@@ -415,33 +369,31 @@
  */
 void fstk_RunInclude(char *tzFileName)
 {
-	char *incPathUsed = "";
-	FILE *f = fstk_FindFile(tzFileName, &incPathUsed);
+	char *fullPath = NULL;
+	size_t size = 0;
 
-	if (f == NULL) {
-		if (oGeneratedMissingIncludes) {
+	if (!fstk_FindFile(tzFileName, &fullPath, &size)) {
+		if (oGeneratedMissingIncludes)
 			oFailedOnMissingInclude = true;
-			return;
-		}
-		error("Unable to open included file '%s': %s\n", tzFileName, strerror(errno));
+		else
+			error("Unable to open included file '%s': %s\n",
+			      tzFileName, strerror(errno));
+		free(fullPath);
 		return;
 	}
 
 	pushcontext();
-	nLineNo = 1;
 	nCurrentStatus = STAT_isInclude;
-	snprintf(tzCurrentFileName, sizeof(tzCurrentFileName), "%s%s",
-		 incPathUsed, tzFileName);
 	if (verbose)
-		printf("Assembling %s\n", tzCurrentFileName);
-	pCurrentFile = f;
-	CurrentFlexHandle = yy_create_buffer(pCurrentFile);
-	yy_switch_to_buffer(CurrentFlexHandle);
+		printf("Assembling %s\n", fullPath);
 
-	/* Dirty hack to give the INCLUDE directive a linefeed */
+	struct LexerState *state = lexer_OpenFile(fullPath);
 
-	yyunput('\n');
-	nLineNo--;
+	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);
 }
 
 /*
@@ -450,7 +402,6 @@
 void fstk_RunMacro(char *s, struct MacroArgs *args)
 {
 	struct Symbol const *sym = sym_FindSymbol(s);
-	int nPrintedChars;
 
 	if (sym == NULL) {
 		error("Macro \"%s\" not defined\n", s);
@@ -464,21 +415,10 @@
 	pushcontext();
 	macro_SetUniqueID(nMacroCount++);
 	/* Minus 1 because there is a newline at the beginning of the buffer */
-	nLineNo = sym->fileLine - 1;
 	macro_UseNewArgs(args);
 	nCurrentStatus = STAT_isMacro;
-	nPrintedChars = snprintf(tzCurrentFileName, _MAX_PATH + 1,
-				 "%s::%s", sym->fileName, s);
-	if (nPrintedChars > _MAX_PATH) {
-		popcontext();
-		fatalerror("File name + macro name is too large to fit into buffer\n");
-	}
 
 	pCurrentMacro = sym;
-	/* TODO: why is `strlen` being used when there's a macro size field? */
-	CurrentFlexHandle = yy_scan_bytes(pCurrentMacro->macro,
-					  strlen(pCurrentMacro->macro));
-	yy_switch_to_buffer(CurrentFlexHandle);
 }
 
 /*
@@ -487,11 +427,6 @@
 void fstk_RunRept(uint32_t count, int32_t nReptLineNo)
 {
 	if (count) {
-		static const char *tzReptStr = "::REPT~1";
-
-		/* For error printing to make sense, fake nLineNo */
-		nCurrentREPTBodyLastLine = nLineNo;
-		nLineNo = nReptLineNo;
 		pushcontext();
 		macro_SetUniqueID(nMacroCount++);
 		nCurrentREPTBlockCount = count;
@@ -499,15 +434,6 @@
 		nCurrentREPTBlockSize = ulNewMacroSize;
 		pCurrentREPTBlock = tzNewMacro;
 		nCurrentREPTBodyFirstLine = nReptLineNo + 1;
-		nLineNo = nReptLineNo;
-
-		if (strlen(tzCurrentFileName) + strlen(tzReptStr) > _MAX_PATH)
-			fatalerror("Cannot append \"%s\" to file path\n", tzReptStr);
-		strcat(tzCurrentFileName, tzReptStr);
-
-		CurrentFlexHandle =
-			yy_scan_bytes(pCurrentREPTBlock, nCurrentREPTBlockSize);
-		yy_switch_to_buffer(CurrentFlexHandle);
 	}
 }
 
@@ -526,7 +452,6 @@
 	// 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++] = '\\';
 		}
@@ -541,19 +466,8 @@
 	sym_AddString("__FILE__", tzSymFileName);
 
 	pFileStack = NULL;
-	if (strcmp(pFileName, "-") == 0) {
-		pCurrentFile = stdin;
-	} else {
-		pCurrentFile = fopen(pFileName, "rb");
-		if (pCurrentFile == NULL)
-			fatalerror("Unable to open file '%s': %s\n", pFileName, strerror(errno));
-	}
 	nFileStackDepth = 0;
 
 	nMacroCount = 0;
 	nCurrentStatus = STAT_isInclude;
-	snprintf(tzCurrentFileName, _MAX_PATH + 1, "%s", pFileName);
-	CurrentFlexHandle = yy_create_buffer(pCurrentFile);
-	yy_switch_to_buffer(CurrentFlexHandle);
-	nLineNo = 1;
 }
--- a/src/asm/globlex.c
+++ b/src/asm/globlex.c
@@ -287,10 +287,10 @@
 			/* Feed the symbol's contents into the buffer */
 			yyunputstr(s = sym_GetStringValue(sym));
 
-			/* Lines inserted this way shall not increase nLineNo */
+			/* Lines inserted this way shall not increase lexer_GetLineNo() */
 			while (*s) {
 				if (*s++ == '\n')
-					nLineNo--;
+					lexer_GetLineNo()--;
 			}
 			return 0;
 		}
--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -1,1054 +1,364 @@
 /*
  * This file is part of RGBDS.
  *
- * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
+ * Copyright (c) 2020, Eldred Habert and RGBDS contributors.
  *
  * SPDX-License-Identifier: MIT
  */
 
+#include <sys/mman.h>
+#include <sys/stat.h>
 #include <assert.h>
-#include <ctype.h>
-#include <inttypes.h>
-#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdbool.h>
 #include <stdint.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 
-#include "asm/asm.h"
-#include "asm/fstack.h"
 #include "asm/lexer.h"
-#include "asm/macro.h"
-#include "asm/main.h"
 #include "asm/rpn.h"
-#include "asm/section.h"
+#include "asm/symbol.h" /* For MAXSYMLEN in asmy.h */
 #include "asm/warning.h"
+/* Include this last so it gets all type & constant definitions */
+#include "asmy.h" /* For token definitions, generated from asmy.y */
 
-#include "extern/err.h"
+#define LEXER_BUF_SIZE 42 /* TODO: determine a sane value for this */
+/* This caps the size of buffer reads, and according to POSIX, passing more than SSIZE_MAX is UB */
+static_assert(LEXER_BUF_SIZE <= SSIZE_MAX);
 
-#include "asmy.h"
-#include "platform.h" // strncasecmp, strdup
+struct LexerState {
+	char const *path;
 
-struct sLexString {
-	char *tzName;
-	uint32_t nToken;
-	uint32_t nNameLength;
-	struct sLexString *next;
+	/* mmap()-dependent IO state */
+	bool isMmapped;
+	union {
+		struct { /* If mmap()ed */
+			char *ptr;
+			off_t size;
+			off_t offset;
+		};
+		struct { /* Otherwise */
+			int fd;
+			size_t index; /* Read index into the buffer */
+			size_t nbChars; /* Number of chars in front of the buffer */
+			char buf[LEXER_BUF_SIZE]; /* Circular buffer */
+		};
+	};
+
+	/* Common state */
+	enum LexerMode mode;
+	bool atLineStart;
+	unsigned int lineNo;
+	bool capturing; /* Whether the text being lexed should be captured */
+	size_t captureSize; /* Amount of text captured */
+	char *captureBuf; /* Buffer to send the captured text to if non-NULL */
+	size_t captureCapacity; /* Size of the buffer above */
+	bool expandStrings;
 };
 
-#define pLexBufferRealStart	(pCurrentBuffer->pBufferRealStart)
-#define pLexBuffer		(pCurrentBuffer->pBuffer)
-#define AtLineStart		(pCurrentBuffer->oAtLineStart)
+struct LexerState *lexerState = NULL;
+struct LexerState *lexerStateEOL = NULL;
 
-#define SAFETYMARGIN		1024
-
-#define BOM_SIZE 3
-
-struct sLexFloat tLexFloat[32];
-struct sLexString *tLexHash[LEXHASHSIZE];
-YY_BUFFER_STATE pCurrentBuffer;
-uint32_t nLexMaxLength; // max length of all keywords and operators
-
-uint32_t tFloatingSecondChar[256];
-uint32_t tFloatingFirstChar[256];
-uint32_t tFloatingChars[256];
-uint32_t nFloating;
-enum eLexerState lexerstate = LEX_STATE_NORMAL;
-
-struct sStringExpansionPos *pCurrentStringExpansion;
-static unsigned int nNbStringExpansions;
-
-/* UTF-8 byte order mark */
-static const unsigned char bom[BOM_SIZE] = { 0xEF, 0xBB, 0xBF };
-
-void upperstring(char *s)
+struct LexerState *lexer_OpenFile(char const *path)
 {
-	while (*s) {
-		*s = toupper(*s);
-		s++;
-	}
-}
+	bool isStdin = !strcmp(path, "-");
+	struct LexerState *state = malloc(sizeof(*state));
 
-void lowerstring(char *s)
-{
-	while (*s) {
-		*s = tolower(*s);
-		s++;
+	/* Give stdin a nicer file name */
+	if (isStdin)
+		path = "<stdin>";
+	if (!state) {
+		error("Failed to open file \"%s\": %s\n", path, strerror(errno));
+		return NULL;
 	}
-}
+	state->path = path;
 
-void yyskipbytes(uint32_t count)
-{
-	pLexBuffer += count;
-}
+	state->fd = isStdin ? STDIN_FILENO : open(path, O_RDONLY);
+	state->isMmapped = false; /* By default, assume it won't be mmap()ed */
+	off_t size = lseek(state->fd, 0, SEEK_END);
 
-void yyunputbytes(uint32_t count)
-{
-	pLexBuffer -= count;
-}
+	if (size != 1) {
+		/* The file is a regular file, so use `mmap` for better performance */
 
-void yyunput(char c)
-{
-	if (pLexBuffer <= pLexBufferRealStart)
-		fatalerror("Buffer safety margin exceeded\n");
+		/*
+		 * Important: do NOT assign to `state->ptr` directly, to avoid a cast that may
+		 * alter an eventual `MAP_FAILED` value. It would also invalidate `state->fd`,
+		 * being on the other side of the union.
+		 */
+		void *pa = mmap(NULL, size, PROT_READ, MAP_PRIVATE, state->fd, 0);
 
-	*(--pLexBuffer) = c;
-}
+		if (pa == MAP_FAILED && errno == ENOTSUP)
+			/*
+			 * The implementation may not support MAP_PRIVATE; try again with MAP_SHARED
+			 * instead, offering, I believe, weaker guarantees about external
+			 * modifications to the file while reading it. That's still better than not
+			 * opening it at all, though.
+			 */
+			pa = mmap(NULL, size, PROT_READ, MAP_SHARED, state->fd, 0);
 
-void yyunputstr(const char *s)
-{
-	int32_t len;
+		if (pa == MAP_FAILED) {
+			/* If mmap()ing failed, try again using another method (below) */
+			state->isMmapped = false;
+		} else {
+			/* IMPORTANT: the `union` mandates this is accessed before other members! */
+			close(state->fd);
 
-	len = strlen(s);
+			state->isMmapped = true;
+			state->ptr = pa;
+			state->size = size;
+		}
+	}
+	if (!state->isMmapped) {
+		/* Sometimes mmap() fails or isn't available, so have a fallback */
+		lseek(state->fd, 0, SEEK_SET);
+		state->index = 0;
+		state->nbChars = 0;
+	}
 
-	/*
-	 * It would be undefined behavior to subtract `len` from pLexBuffer and
-	 * potentially have it point outside of pLexBufferRealStart's buffer,
-	 * this is why the check is done this way.
-	 * Refer to https://github.com/rednex/rgbds/pull/411#discussion_r319779797
-	 */
-	if (pLexBuffer - pLexBufferRealStart < len)
-		fatalerror("Buffer safety margin exceeded\n");
-
-	pLexBuffer -= len;
-
-	memcpy(pLexBuffer, s, len);
+	state->mode = LEXER_NORMAL;
+	state->atLineStart = true;
+	state->lineNo = 0;
+	state->capturing = false;
+	state->captureBuf = NULL;
+	return state;
 }
 
-/*
- * Marks that a new string expansion with name `tzName` ends here
- * Enforces recursion depth
- */
-void lex_BeginStringExpansion(const char *tzName)
+struct LexerState *lexer_OpenFileView(void)
 {
-	if (++nNbStringExpansions > nMaxRecursionDepth)
-		fatalerror("Recursion limit (%u) exceeded\n", nMaxRecursionDepth);
-
-	struct sStringExpansionPos *pNewStringExpansion =
-		malloc(sizeof(*pNewStringExpansion));
-	char *tzNewExpansionName = strdup(tzName);
-
-	if (!pNewStringExpansion || !tzNewExpansionName)
-		fatalerror("Could not allocate memory to expand '%s'\n", tzName);
-
-	pNewStringExpansion->tzName = tzNewExpansionName;
-	pNewStringExpansion->pBuffer = pLexBufferRealStart;
-	pNewStringExpansion->pBufferPos = pLexBuffer;
-	pNewStringExpansion->pParent = pCurrentStringExpansion;
-
-	pCurrentStringExpansion = pNewStringExpansion;
+	return NULL;
 }
 
-void yy_switch_to_buffer(YY_BUFFER_STATE buf)
+void lexer_DeleteState(struct LexerState *state)
 {
-	pCurrentBuffer = buf;
+	if (state->isMmapped)
+		munmap(state->ptr, state->size);
+	else
+		close(state->fd);
+	free(state);
 }
 
-void yy_set_state(enum eLexerState i)
+void lexer_SetMode(enum LexerMode mode)
 {
-	lexerstate = i;
+	lexerState->mode = mode;
 }
 
-void yy_delete_buffer(YY_BUFFER_STATE buf)
+void lexer_ToggleStringExpansion(bool enable)
 {
-	free(buf->pBufferStart - SAFETYMARGIN);
-	free(buf);
+	lexerState->expandStrings = enable;
 }
 
-/*
- * Maintains the following invariants:
- * 1. nBufferSize < capacity
- * 2. The buffer is terminated with 0
- * 3. nBufferSize is the size without the terminator
- */
-static void yy_buffer_append(YY_BUFFER_STATE buf, size_t capacity, char c)
-{
-	assert(buf->pBufferStart[buf->nBufferSize] == 0);
-	assert(buf->nBufferSize + 1 < capacity);
+/* Functions for the actual lexer to obtain characters */
 
-	buf->pBufferStart[buf->nBufferSize++] = c;
-	buf->pBufferStart[buf->nBufferSize] = 0;
-}
-
-static void yy_buffer_append_newlines(YY_BUFFER_STATE buf, size_t capacity)
+static void reallocCaptureBuf(void)
 {
-	/* Add newline if file doesn't end with one */
-	if (buf->nBufferSize == 0
-	 || buf->pBufferStart[buf->nBufferSize - 1] != '\n')
-		yy_buffer_append(buf, capacity, '\n');
-
-	/* Add newline if \ will eat the last newline */
-	if (buf->nBufferSize >= 2) {
-		size_t pos = buf->nBufferSize - 2;
-
-		/* Skip spaces and tabs */
-		while (pos > 0 && (buf->pBufferStart[pos] == ' '
-				|| buf->pBufferStart[pos] == '\t'))
-			pos--;
-
-		if (buf->pBufferStart[pos] == '\\')
-			yy_buffer_append(buf, capacity, '\n');
-	}
+	lexerState->captureCapacity *= 2;
+	lexerState->captureBuf = realloc(lexerState->captureBuf, lexerState->captureCapacity);
+	if (!lexerState->captureBuf)
+		fatalerror("realloc error while resizing capture buffer: %s\n", strerror(errno));
 }
 
-YY_BUFFER_STATE yy_scan_bytes(char const *mem, uint32_t size)
+/* If at any point we need more than 255 characters of lookahead, something went VERY wrong. */
+static int peek(uint8_t distance)
 {
-	YY_BUFFER_STATE pBuffer = malloc(sizeof(struct yy_buffer_state));
-
-	if (pBuffer == NULL)
-		fatalerror("%s: Out of memory!\n", __func__);
-
-	size_t capacity = size + 3; /* space for 2 newlines and terminator */
-
-	pBuffer->pBufferRealStart = malloc(capacity + SAFETYMARGIN);
-
-	if (pBuffer->pBufferRealStart == NULL)
-		fatalerror("%s: Out of memory for buffer!\n", __func__);
-
-	pBuffer->pBufferStart = pBuffer->pBufferRealStart + SAFETYMARGIN;
-	pBuffer->pBuffer = pBuffer->pBufferRealStart + SAFETYMARGIN;
-	memcpy(pBuffer->pBuffer, mem, size);
-	pBuffer->pBuffer[size] = 0;
-	pBuffer->nBufferSize = size;
-	yy_buffer_append_newlines(pBuffer, capacity);
-	pBuffer->oAtLineStart = 1;
-
-	return pBuffer;
-}
-
-YY_BUFFER_STATE yy_create_buffer(FILE *f)
-{
-	YY_BUFFER_STATE pBuffer = malloc(sizeof(struct yy_buffer_state));
-
-	if (pBuffer == NULL)
-		fatalerror("%s: Out of memory!\n", __func__);
-
-	size_t size = 0, capacity = -1;
-	char *buf = NULL;
-
-	/*
-	 * Check if we can get the file size without implementation-defined
-	 * behavior:
-	 *
-	 * From ftell(3p):
-	 * [On error], ftell() and ftello() shall return −1, and set errno to
-	 * indicate the error.
-	 *
-	 * The ftell() and ftello() functions shall fail if: [...]
-	 * ESPIPE The file descriptor underlying stream is associated with a
-	 * pipe, FIFO, or socket.
-	 *
-	 * From fseek(3p):
-	 * The behavior of fseek() on devices which are incapable of seeking
-	 * is implementation-defined.
-	 */
-	if (ftell(f) != -1) {
-		fseek(f, 0, SEEK_END);
-		capacity = ftell(f);
-		rewind(f);
+	if (lexerState->isMmapped) {
+		if (lexerState->offset + distance >= lexerState->size)
+			return EOF;
+		return lexerState->ptr[lexerState->offset + distance];
 	}
 
-	// If ftell errored or the block above wasn't executed
-	if (capacity == -1)
-		capacity = 4096;
-	// Handle 0-byte files gracefully
-	else if (capacity == 0)
-		capacity = 1;
+	if (lexerState->nbChars <= distance) {
+		/* Buffer isn't full enough, read some chars in */
 
-	do {
-		if (buf == NULL || size >= capacity) {
-			if (buf)
-				capacity *= 2;
-			/* Give extra room for 2 newlines and terminator */
-			buf = realloc(buf, capacity + SAFETYMARGIN + 3);
+		/* Compute the index we'll start writing to */
+		size_t writeIndex = (lexerState->index + lexerState->nbChars) % LEXER_BUF_SIZE;
+		size_t target = LEXER_BUF_SIZE - lexerState->nbChars; /* Aim: making the buf full */
+		ssize_t nbCharsRead = 0;
 
-			if (buf == NULL)
-				fatalerror("%s: Out of memory for buffer!\n",
-					   __func__);
-		}
+#define readChars(size) do { \
+	nbCharsRead = read(lexerState->fd, &lexerState->buf[writeIndex], (size)); \
+	if (nbCharsRead == -1) \
+		fatalerror("Error while reading \"%s\": %s\n", lexerState->path, errno); \
+	writeIndex += nbCharsRead; \
+	if (writeIndex == LEXER_BUF_SIZE) \
+		writeIndex = 0; \
+	lexerState->nbChars += nbCharsRead; /* Count all those chars in */ \
+	target -= nbCharsRead; \
+} while (0)
 
-		char *bufpos = buf + SAFETYMARGIN + size;
-		size_t read_count = fread(bufpos, 1, capacity - size, f);
-
-		if (read_count == 0 && !feof(f))
-			fatalerror("%s: fread error\n", __func__);
-
-		size += read_count;
-	} while (!feof(f));
-
-	pBuffer->pBufferRealStart = buf;
-	pBuffer->pBufferStart = buf + SAFETYMARGIN;
-	pBuffer->pBuffer = buf + SAFETYMARGIN;
-	pBuffer->pBuffer[size] = 0;
-	pBuffer->nBufferSize = size;
-
-	/* This is added here to make the buffer scaling above easy to express,
-	 * while taking the newline space into account
-	 * for the yy_buffer_append_newlines() call below.
-	 */
-	capacity += 3;
-
-	/* Skip UTF-8 byte order mark. */
-	if (pBuffer->nBufferSize >= BOM_SIZE
-	 && !memcmp(pBuffer->pBuffer, bom, BOM_SIZE))
-		pBuffer->pBuffer += BOM_SIZE;
-
-	/* Convert all line endings to LF and spaces */
-
-	char *mem = pBuffer->pBuffer;
-	int32_t lineCount = 0;
-
-	while (*mem) {
-		if ((mem[0] == '\\') && (mem[1] == '\"' || mem[1] == '\\')) {
-			mem += 2;
-		} else {
-			/* LF CR and CR LF */
-			if (((mem[0] == '\n') && (mem[1] == '\r'))
-			 || ((mem[0] == '\r') && (mem[1] == '\n'))) {
-				*mem++ = ' ';
-				*mem++ = '\n';
-				lineCount++;
-			/* LF and CR */
-			} else if ((mem[0] == '\n') || (mem[0] == '\r')) {
-				*mem++ = '\n';
-				lineCount++;
-			} else {
-				mem++;
-			}
+		/* If the range to fill passes over the buffer wrapping point, we need two reads */
+		if (writeIndex + target > LEXER_BUF_SIZE) {
+			readChars(LEXER_BUF_SIZE - writeIndex);
+			/* If the read was incomplete, don't perform a second read */
+			if (nbCharsRead < LEXER_BUF_SIZE - writeIndex)
+				target = 0;
 		}
-	}
+		if (target != 0)
+			readChars(target);
 
-	if (mem != pBuffer->pBuffer + size) {
-		nLineNo = lineCount + 1;
-		fatalerror("Found null character\n");
-	}
+#undef readChars
 
-	/* Remove comments */
-
-	mem = pBuffer->pBuffer;
-	bool instring = false;
-
-	while (*mem) {
-		if (*mem == '\"')
-			instring = !instring;
-
-		if ((mem[0] == '\\') && (mem[1] == '\"' || mem[1] == '\\')) {
-			mem += 2;
-		} else if (instring) {
-			mem++;
-		} else {
-			/* Comments that start with ; anywhere in a line */
-			if (*mem == ';') {
-				while (!((*mem == '\n') || (*mem == '\0')))
-					*mem++ = ' ';
-			/* Comments that start with * at the start of a line */
-			} else if ((mem[0] == '\n') && (mem[1] == '*')) {
-				warning(WARNING_OBSOLETE,
-					"'*' is deprecated for comments, please use ';' instead\n");
-				mem++;
-				while (!((*mem == '\n') || (*mem == '\0')))
-					*mem++ = ' ';
-			} else {
-				mem++;
-			}
-		}
+		/* If there aren't enough chars even after refilling, give up */
+		if (lexerState->nbChars <= distance)
+			return EOF;
 	}
-
-	yy_buffer_append_newlines(pBuffer, capacity);
-	pBuffer->oAtLineStart = 1;
-	return pBuffer;
+	return lexerState->buf[(lexerState->index + distance) % LEXER_BUF_SIZE];
 }
 
-uint32_t lex_FloatAlloc(const struct sLexFloat *token)
+static void shiftChars(uint8_t distance)
 {
-	tLexFloat[nFloating] = *token;
-
-	return (1 << (nFloating++));
-}
-
-/*
- * Make sure that only non-zero ASCII characters are used. Also, check if the
- * start is greater than the end of the range.
- */
-bool lex_CheckCharacterRange(uint16_t start, uint16_t end)
-{
-	if (start > end || start < 1 || end > 127) {
-		error("Invalid character range (start: %" PRIu16 ", end: %" PRIu16 ")\n",
-			start, end);
-		return false;
-	}
-	return true;
-}
-
-void lex_FloatDeleteRange(uint32_t id, uint16_t start, uint16_t end)
-{
-	if (lex_CheckCharacterRange(start, end)) {
-		while (start <= end) {
-			tFloatingChars[start] &= ~id;
-			start++;
+	if (lexerState->capturing) {
+		if (lexerState->captureBuf) {
+			if (lexerState->captureSize + distance >= lexerState->captureCapacity)
+				reallocCaptureBuf();
+			/* TODO: improve this? */
+			for (uint8_t i = 0; i < distance; i++)
+				lexerState->captureBuf[lexerState->captureSize++] = peek(i);
+		} else {
+			lexerState->captureSize += distance;
 		}
 	}
-}
 
-void lex_FloatAddRange(uint32_t id, uint16_t start, uint16_t end)
-{
-	if (lex_CheckCharacterRange(start, end)) {
-		while (start <= end) {
-			tFloatingChars[start] |= id;
-			start++;
-		}
+	if (lexerState->isMmapped) {
+		lexerState->offset += distance;
+	} else {
+		lexerState->nbChars -= distance;
+		lexerState->index += distance;
+		/* Wrap around if necessary */
+		if (lexerState->index >= LEXER_BUF_SIZE)
+			lexerState->index %= LEXER_BUF_SIZE;
 	}
 }
 
-void lex_FloatDeleteFirstRange(uint32_t id, uint16_t start, uint16_t end)
+static int nextChar(void)
 {
-	if (lex_CheckCharacterRange(start, end)) {
-		while (start <= end) {
-			tFloatingFirstChar[start] &= ~id;
-			start++;
-		}
-	}
-}
+	int c = peek(0);
 
-void lex_FloatAddFirstRange(uint32_t id, uint16_t start, uint16_t end)
-{
-	if (lex_CheckCharacterRange(start, end)) {
-		while (start <= end) {
-			tFloatingFirstChar[start] |= id;
-			start++;
-		}
-	}
+	/* If not at EOF, advance read position */
+	if (c != EOF)
+		shiftChars(1);
+	return c;
 }
 
-void lex_FloatDeleteSecondRange(uint32_t id, uint16_t start, uint16_t end)
-{
-	if (lex_CheckCharacterRange(start, end)) {
-		while (start <= end) {
-			tFloatingSecondChar[start] &= ~id;
-			start++;
-		}
-	}
-}
+/* "Services" provided by the lexer to the rest of the program */
 
-void lex_FloatAddSecondRange(uint32_t id, uint16_t start, uint16_t end)
+char const *lexer_GetFileName(void)
 {
-	if (lex_CheckCharacterRange(start, end)) {
-		while (start <= end) {
-			tFloatingSecondChar[start] |= id;
-			start++;
-		}
-	}
+	return lexerState->path;
 }
 
-static struct sLexFloat *lexgetfloat(uint32_t nFloatMask)
+unsigned int lexer_GetLineNo(void)
 {
-	if (nFloatMask == 0)
-		fatalerror("Internal error in %s\n", __func__);
-
-	int32_t i = 0;
-
-	while ((nFloatMask & 1) == 0) {
-		nFloatMask >>= 1;
-		i++;
-	}
-
-	return &tLexFloat[i];
+	return lexerState->lineNo;
 }
 
-static uint32_t lexcalchash(char *s)
+void lexer_DumpStringExpansions(void)
 {
-	uint32_t hash = 0;
-
-	while (*s)
-		hash = (hash * 283) ^ toupper(*s++);
-
-	return hash % LEXHASHSIZE;
+	/* TODO */
 }
 
-void lex_Init(void)
+static int yylex_NORMAL(void)
 {
-	uint32_t i;
+	for (;;) {
+		int c = nextChar();
 
-	for (i = 0; i < LEXHASHSIZE; i++)
-		tLexHash[i] = NULL;
+		switch (c) {
+		case '\n':
+			if (lexerStateEOL) {
+				lexer_SetState(lexerStateEOL);
+				lexerStateEOL = NULL;
+			}
+			return '\n';
 
-	for (i = 0; i < 256; i++) {
-		tFloatingFirstChar[i] = 0;
-		tFloatingSecondChar[i] = 0;
-		tFloatingChars[i] = 0;
-	}
+		/* Ignore whitespace */
+		case ' ':
+		case '\t':
+			break;
 
-	nLexMaxLength = 0;
-	nFloating = 0;
+		case EOF:
+			/* Captures end at their buffer's boundary no matter what */
+			if (!lexerState->capturing) {
+				/* TODO: use `yywrap()` */
+			}
+			return 0;
 
-	pCurrentStringExpansion = NULL;
-	nNbStringExpansions = 0;
-}
-
-void lex_AddStrings(const struct sLexInitString *lex)
-{
-	while (lex->tzName) {
-		struct sLexString **ppHash;
-		uint32_t hash = lexcalchash(lex->tzName);
-
-		ppHash = &tLexHash[hash];
-		while (*ppHash)
-			ppHash = &((*ppHash)->next);
-
-		*ppHash = malloc(sizeof(struct sLexString));
-		if (*ppHash == NULL)
-			fatalerror("Out of memory!\n");
-
-		(*ppHash)->tzName = (char *)strdup(lex->tzName);
-		if ((*ppHash)->tzName == NULL)
-			fatalerror("Out of memory!\n");
-
-		(*ppHash)->nNameLength = strlen(lex->tzName);
-		(*ppHash)->nToken = lex->nToken;
-		(*ppHash)->next = NULL;
-
-		upperstring((*ppHash)->tzName);
-
-		if ((*ppHash)->nNameLength > nLexMaxLength)
-			nLexMaxLength = (*ppHash)->nNameLength;
-
-		lex++;
-	}
-}
-
-/*
- * Gets the "float" mask and "float" length.
- * "Float" refers to the token type of a token that is not a keyword.
- * The character classes floatingFirstChar, floatingSecondChar, and
- * floatingChars are defined separately for each token type.
- * It uses bit masks to match against a set of simple regular expressions
- * of the form /[floatingFirstChar]([floatingSecondChar][floatingChars]*)?/.
- * The token types with the longest match from the current position in the
- * buffer will have their bits set in the float mask.
- */
-void yylex_GetFloatMaskAndFloatLen(uint32_t *pnFloatMask, uint32_t *pnFloatLen)
-{
-	/*
-	 * Note that '\0' should always have a bit mask of 0 in the "floating"
-	 * tables, so it doesn't need to be checked for separately.
-	 */
-
-	char *s = pLexBuffer;
-	uint32_t nOldFloatMask = 0;
-	uint32_t nFloatMask = tFloatingFirstChar[(uint8_t)*s];
-
-	if (nFloatMask != 0) {
-		s++;
-		nOldFloatMask = nFloatMask;
-		nFloatMask &= tFloatingSecondChar[(uint8_t)*s];
-
-		while (nFloatMask != 0) {
-			s++;
-			nOldFloatMask = nFloatMask;
-			nFloatMask &= tFloatingChars[(uint8_t)*s];
+		default:
+			error("Unknown character '%c'\n");
 		}
 	}
-
-	*pnFloatMask = nOldFloatMask;
-	*pnFloatLen = (uint32_t)(s - pLexBuffer);
 }
 
-/*
- * Gets the longest keyword/operator from the current position in the buffer.
- */
-struct sLexString *yylex_GetLongestFixed(void)
+static int yylex_RAW(void)
 {
-	struct sLexString *pLongestFixed = NULL;
-	char *s = pLexBuffer;
-	uint32_t hash = 0;
-	uint32_t length = 0;
-
-	while (length < nLexMaxLength && *s) {
-		hash = (hash * 283) ^ toupper(*s);
-		s++;
-		length++;
-
-		struct sLexString *lex = tLexHash[hash % LEXHASHSIZE];
-
-		while (lex) {
-			if (lex->nNameLength == length
-			 && strncasecmp(pLexBuffer, lex->tzName, length) == 0) {
-				pLongestFixed = lex;
-				break;
-			}
-			lex = lex->next;
-		}
-	}
-
-	return pLongestFixed;
+	fatalerror("LEXER_RAW not yet implemented\n");
 }
 
-size_t CopyMacroArg(char *dest, size_t maxLength, char c)
+int yylex(void)
 {
-	size_t i;
-	char const *s;
+	if (lexerState->atLineStart)
+		lexerState->lineNo++;
 
-	if (c == '@')
-		s = macro_GetUniqueIDStr();
-	else if (c >= '1' && c <= '9')
-		s = macro_GetArg(c - '0');
-	else
-		return 0;
+	static int (* const lexerModeFuncs[])(void) = {
+		[LEXER_NORMAL] = yylex_NORMAL,
+		[LEXER_RAW]    = yylex_RAW,
+	};
+	int token = lexerModeFuncs[lexerState->mode]();
 
-	if (s == NULL)
-		fatalerror("Macro argument '\\%c' not defined\n", c);
+	if (token == '\n')
+		lexerState->atLineStart = true;
+	else if (lexerState->atLineStart)
+		lexerState->atLineStart = false;
 
-	// TODO: `strncpy`, nay?
-	for (i = 0; s[i] != 0; i++) {
-		if (i >= maxLength)
-			fatalerror("Macro argument too long to fit buffer\n");
-
-		dest[i] = s[i];
-	}
-
-	return i;
+	return token;
 }
 
-static inline void yylex_StringWriteChar(char *s, size_t index, char c)
+void lexer_SkipToBlockEnd(int blockStartToken, int blockEndToken, int endToken,
+			  char **capture, size_t *size, char const *name)
 {
-	if (index >= MAXSTRLEN)
-		fatalerror("String too long\n");
+	lexerState->capturing = true;
+	lexerState->captureSize = 0;
+	unsigned int level = 0;
+	char *captureStart;
 
-	s[index] = c;
-}
-
-static inline void yylex_SymbolWriteChar(char *s, size_t index, char c)
-{
-	if (index >= MAXSYMLEN)
-		fatalerror("Symbol too long\n");
-
-	s[index] = c;
-}
-
-/*
- * Trims white space at the end of a string.
- * The index parameter is the index of the 0 at the end of the string.
- */
-void yylex_TrimEnd(char *s, size_t index)
-{
-	int32_t i = (int32_t)index - 1;
-
-	while ((i >= 0) && (s[i] == ' ' || s[i] == '\t')) {
-		s[i] = 0;
-		i--;
-	}
-}
-
-size_t yylex_ReadBracketedSymbol(char *dest, size_t index)
-{
-	char sym[MAXSYMLEN + 1];
-	char ch;
-	size_t i = 0;
-	size_t length, maxLength;
-	const char *mode = NULL;
-
-	for (ch = *pLexBuffer;
-	     ch != '}' && ch != '"' && ch != '\n';
-		 ch = *(++pLexBuffer)) {
-		if (ch == '\\') {
-			ch = *(++pLexBuffer);
-			maxLength = MAXSYMLEN - i;
-			length = CopyMacroArg(&sym[i], maxLength, ch);
-
-			if (length != 0)
-				i += length;
-			else
-				fatalerror("Illegal character escape '%c'\n", ch);
-		} else if (ch == '{') {
-			/* Handle nested symbols */
-			++pLexBuffer;
-			i += yylex_ReadBracketedSymbol(sym, i);
-			--pLexBuffer;
-		} else if (ch == ':' && !mode) { /* Only grab 1st colon */
-			/* Use a whitelist of modes, which does prevent the
-			 * use of some features such as precision,
-			 * but also avoids a security flaw
-			 */
-			const char *acceptedModes = "bxXd";
-			/* Binary isn't natively supported,
-			 * so it's handled differently
-			 */
-			static const char * const formatSpecifiers[] = {
-				"", "%" PRIx32, "%" PRIX32, "%" PRId32
-			};
-			/* Prevent reading out of bounds! */
-			const char *designatedMode;
-
-			if (i != 1)
-				fatalerror("Print types are exactly 1 character long\n");
-
-			designatedMode = strchr(acceptedModes, sym[i - 1]);
-			if (!designatedMode)
-				fatalerror("Illegal print type '%c'\n", sym[i - 1]);
-			mode = formatSpecifiers[designatedMode - acceptedModes];
-			/* Begin writing the symbol again */
-			i = 0;
+	if (capture) {
+		if (lexerState->isMmapped) {
+			captureStart = lexerState->ptr;
 		} else {
-			yylex_SymbolWriteChar(sym, i++, ch);
+			lexerState->captureCapacity = 128; /* The initial size will be twice that */
+			reallocCaptureBuf();
+			captureStart = lexerState->captureBuf;
 		}
 	}
 
-	/* Properly terminate the string */
-	yylex_SymbolWriteChar(sym, i, 0);
+	for (;;) {
+		int token = yylex();
 
-	/* It's assumed we're writing to a T_STRING */
-	maxLength = MAXSTRLEN - index;
-	length = symvaluetostring(&dest[index], maxLength, sym, mode);
-
-	if (*pLexBuffer == '}')
-		pLexBuffer++;
-	else
-		fatalerror("Missing }\n");
-
-	return length;
-}
-
-static void yylex_ReadQuotedString(void)
-{
-	size_t index = 0;
-	size_t length, maxLength;
-
-	while (*pLexBuffer != '"' && *pLexBuffer != '\n') {
-		char ch = *pLexBuffer++;
-
-		if (ch == '\\') {
-			ch = *pLexBuffer++;
-
-			switch (ch) {
-			case 'n':
-				ch = '\n';
+		if (level == 0) {
+			if (token == endToken)
 				break;
-			case 'r':
-				ch = '\r';
+			/*
+			 * Hack: skipping after a `if` requires stopping on three different tokens,
+			 * which there is no simple way to make this function support. Instead,
+			 * if ELIF is the end token, ELSE and ENDC are also checked for here.
+			 */
+			if (endToken == T_POP_ELIF && (token == T_POP_ELSE || token == T_POP_ENDC))
 				break;
-			case 't':
-				ch = '\t';
-				break;
-			case '\\':
-				ch = '\\';
-				break;
-			case '"':
-				ch = '"';
-				break;
-			case ',':
-				ch = ',';
-				break;
-			case '{':
-				ch = '{';
-				break;
-			case '}':
-				ch = '}';
-				break;
-			default:
-				maxLength = MAXSTRLEN - index;
-				length = CopyMacroArg(&yylval.tzString[index],
-						      maxLength, ch);
-
-				if (length != 0)
-					index += length;
-				else
-					fatalerror("Illegal character escape '%c'\n", ch);
-
-				ch = 0;
-				break;
-			}
-		} else if (ch == '{') {
-			// Get bracketed symbol within string.
-			index += yylex_ReadBracketedSymbol(yylval.tzString,
-							   index);
-			ch = 0;
 		}
 
-		if (ch)
-			yylex_StringWriteChar(yylval.tzString, index++, ch);
+		if (token == EOF)
+			error("Unterminated %s\n", name);
+		else if (token == blockStartToken)
+			level++;
+		else if (token == blockEndToken)
+			level--;
 	}
 
-	yylex_StringWriteChar(yylval.tzString, index, 0);
-
-	if (*pLexBuffer == '"')
-		pLexBuffer++;
-	else
-		fatalerror("Unterminated string\n");
-}
-
-static uint32_t yylex_NORMAL(void)
-{
-	struct sLexString *pLongestFixed = NULL;
-	uint32_t nFloatMask, nFloatLen;
-	uint32_t linestart = AtLineStart;
-
-	AtLineStart = 0;
-
-scanagain:
-	while (*pLexBuffer == ' ' || *pLexBuffer == '\t') {
-		linestart = 0;
-		pLexBuffer++;
+	if (capture) {
+		*capture = captureStart;
+		*size = lexerState->captureSize;
 	}
-
-	if (*pLexBuffer == 0) {
-		// Reached the end of a file, macro, or rept.
-		if (yywrap() == 0) {
-			linestart = AtLineStart;
-			AtLineStart = 0;
-			goto scanagain;
-		}
-	}
-
-	/* Check for line continuation character */
-	if (*pLexBuffer == '\\') {
-		/*
-		 * Look for line continuation character after a series of
-		 * spaces. This is also useful for files that use Windows line
-		 * endings: "\r\n" is replaced by " \n" before the lexer has the
-		 * opportunity to see it.
-		 */
-		if (pLexBuffer[1] == ' ' || pLexBuffer[1] == '\t') {
-			pLexBuffer += 2;
-			while (1) {
-				if (*pLexBuffer == ' ' || *pLexBuffer == '\t') {
-					pLexBuffer++;
-				} else if (*pLexBuffer == '\n') {
-					pLexBuffer++;
-					nLineNo++;
-					goto scanagain;
-				} else {
-					error("Expected a new line after the continuation character.\n");
-					pLexBuffer++;
-				}
-			}
-		}
-
-		/* Line continuation character */
-		if (pLexBuffer[1] == '\n') {
-			pLexBuffer += 2;
-			nLineNo++;
-			goto scanagain;
-		}
-
-		/*
-		 * If there isn't a newline character or a space, ignore the
-		 * character '\'. It will eventually be handled by other
-		 * functions like PutMacroArg().
-		 */
-	}
-
-	/*
-	 * Try to match an identifier, macro argument (e.g. \1),
-	 * or numeric literal.
-	 */
-	yylex_GetFloatMaskAndFloatLen(&nFloatMask, &nFloatLen);
-
-	/* Try to match a keyword or operator. */
-	pLongestFixed = yylex_GetLongestFixed();
-
-	if (nFloatLen == 0 && pLongestFixed == NULL) {
-		/*
-		 * No keyword, identifier, operator, or numerical literal
-		 * matches.
-		 */
-
-		if (*pLexBuffer == '"') {
-			pLexBuffer++;
-			yylex_ReadQuotedString();
-			return T_STRING;
-		} else if (*pLexBuffer == '{') {
-			pLexBuffer++;
-			size_t len = yylex_ReadBracketedSymbol(yylval.tzString,
-							       0);
-			yylval.tzString[len] = 0;
-			return T_STRING;
-		}
-
-		/*
-		 * It's not a keyword, operator, identifier, macro argument,
-		 * numeric literal, string, or bracketed symbol, so just return
-		 * the ASCII character.
-		 */
-		unsigned char ch = *pLexBuffer++;
-
-		if (ch == '\n')
-			AtLineStart = 1;
-
-		/*
-		 * Check for invalid unprintable characters.
-		 * They may not be readily apparent in a text editor,
-		 * so this is useful for identifying encoding problems.
-		 */
-		if (ch != 0
-		 && ch != '\n'
-		 && !(ch >= 0x20 && ch <= 0x7E))
-			fatalerror("Found garbage character: 0x%02X\n", ch);
-
-		return ch;
-	}
-
-	if (pLongestFixed == NULL || nFloatLen > pLongestFixed->nNameLength) {
-		/*
-		 * Longest match was an identifier, macro argument, or numeric
-		 * literal.
-		 */
-		struct sLexFloat *token = lexgetfloat(nFloatMask);
-
-		if (token->Callback) {
-			int32_t done = token->Callback(pLexBuffer, nFloatLen);
-
-			if (!done)
-				goto scanagain;
-		}
-
-		uint32_t type = token->nToken;
-
-		if (type == T_ID && strchr(yylval.tzSym, '.'))
-			type = T_LOCAL_ID;
-
-		if (linestart && type == T_ID)
-			return T_LABEL;
-		return type;
-	}
-
-	/* Longest match was a keyword or operator. */
-	pLexBuffer += pLongestFixed->nNameLength;
-	yylval.nConstValue = pLongestFixed->nToken;
-	return pLongestFixed->nToken;
-}
-
-static uint32_t yylex_MACROARGS(void)
-{
-	size_t index = 0;
-	size_t length, maxLength;
-
-	while ((*pLexBuffer == ' ') || (*pLexBuffer == '\t'))
-		pLexBuffer++;
-
-	while ((*pLexBuffer != ',') && (*pLexBuffer != '\n')) {
-		char ch = *pLexBuffer++;
-
-		if (ch == '\\') {
-			ch = *pLexBuffer++;
-
-			switch (ch) {
-			case 'n':
-				ch = '\n';
-				break;
-			case 't':
-				ch = '\t';
-				break;
-			case '\\':
-				ch = '\\';
-				break;
-			case '"':
-				ch = '\"';
-				break;
-			case ',':
-				ch = ',';
-				break;
-			case '{':
-				ch = '{';
-				break;
-			case '}':
-				ch = '}';
-				break;
-			case ' ':
-			case '\t':
-				/*
-				 * Look for line continuation character after a
-				 * series of spaces. This is also useful for
-				 * files that use Windows line endings: "\r\n"
-				 * is replaced by " \n" before the lexer has the
-				 * opportunity to see it.
-				 */
-				while (1) {
-					if (*pLexBuffer == ' '
-					 || *pLexBuffer == '\t') {
-						pLexBuffer++;
-					} else if (*pLexBuffer == '\n') {
-						pLexBuffer++;
-						nLineNo++;
-						ch = 0;
-						break;
-					} else {
-						error("Expected a new line after the continuation character.\n");
-					}
-				}
-				break;
-			case '\n':
-				/* Line continuation character */
-				nLineNo++;
-				ch = 0;
-				break;
-			default:
-				maxLength = MAXSTRLEN - index;
-				length = CopyMacroArg(&yylval.tzString[index],
-						      maxLength, ch);
-
-				if (length != 0)
-					index += length;
-				else
-					fatalerror("Illegal character escape '%c'\n", ch);
-
-				ch = 0;
-				break;
-			}
-		} else if (ch == '{') {
-			index += yylex_ReadBracketedSymbol(yylval.tzString,
-							   index);
-			ch = 0;
-		}
-		if (ch)
-			yylex_StringWriteChar(yylval.tzString, index++, ch);
-	}
-
-	if (index) {
-		yylex_StringWriteChar(yylval.tzString, index, 0);
-
-		/* trim trailing white space at the end of the line */
-		if (*pLexBuffer == '\n')
-			yylex_TrimEnd(yylval.tzString, index);
-
-		return T_STRING;
-	} else if (*pLexBuffer == '\n') {
-		pLexBuffer++;
-		AtLineStart = 1;
-		return '\n';
-	} else if (*pLexBuffer == ',') {
-		pLexBuffer++;
-		return ',';
-	}
-
-	fatalerror("Internal error in %s\n", __func__);
-}
-
-int yylex(void)
-{
-	int returnedChar;
-
-	switch (lexerstate) {
-	case LEX_STATE_NORMAL:
-		returnedChar = yylex_NORMAL();
-		break;
-	case LEX_STATE_MACROARGS:
-		returnedChar = yylex_MACROARGS();
-		break;
-	default:
-		fatalerror("%s: Internal error.\n", __func__);
-	}
-
-	/* Check if string expansions were fully read */
-	while (pCurrentStringExpansion
-	    && pCurrentStringExpansion->pBuffer == pLexBufferRealStart
-	    && pCurrentStringExpansion->pBufferPos <= pLexBuffer) {
-		struct sStringExpansionPos *pParent =
-			pCurrentStringExpansion->pParent;
-		free(pCurrentStringExpansion->tzName);
-		free(pCurrentStringExpansion);
-
-		pCurrentStringExpansion = pParent;
-		nNbStringExpansions--;
-	}
-
-	return returnedChar;
+	lexerState->captureBuf = NULL;
 }
--- a/src/asm/macro.c
+++ b/src/asm/macro.c
@@ -61,7 +61,7 @@
 #define macArgs (*argPtr)
 	if (macArgs->nbArgs == MAXMACROARGS)
 		error("A maximum of " EXPAND_AND_STR(MAXMACROARGS)
-			" arguments is allowed\n");
+		      " arguments is allowed\n");
 	if (macArgs->nbArgs >= macArgs->capacity) {
 		macArgs->capacity *= 2;
 		/* Check that overflow didn't roll us back */
--- a/src/asm/main.c
+++ b/src/asm/main.c
@@ -6,6 +6,7 @@
  * SPDX-License-Identifier: MIT
  */
 
+#include <ctype.h>
 #include <errno.h>
 #include <float.h>
 #include <inttypes.h>
@@ -41,11 +42,7 @@
 
 clock_t nStartClock, nEndClock;
 uint32_t nTotalLines, nIFDepth;
-bool skipElif;
-uint32_t unionStart[128], unionSize[128];
 
-int32_t nLineNo;
-
 #if defined(YYDEBUG) && YYDEBUG
 extern int yydebug;
 #endif
@@ -76,64 +73,8 @@
 
 void opt_SetCurrentOptions(struct sOptions *pOpt)
 {
-	if (nGBGfxID != -1) {
-		lex_FloatDeleteRange(nGBGfxID, CurrentOptions.gbgfx[0],
-				     CurrentOptions.gbgfx[0]);
-		lex_FloatDeleteRange(nGBGfxID, CurrentOptions.gbgfx[1],
-				     CurrentOptions.gbgfx[1]);
-		lex_FloatDeleteRange(nGBGfxID, CurrentOptions.gbgfx[2],
-				     CurrentOptions.gbgfx[2]);
-		lex_FloatDeleteRange(nGBGfxID, CurrentOptions.gbgfx[3],
-				     CurrentOptions.gbgfx[3]);
-		lex_FloatDeleteSecondRange(nGBGfxID, CurrentOptions.gbgfx[0],
-					   CurrentOptions.gbgfx[0]);
-		lex_FloatDeleteSecondRange(nGBGfxID, CurrentOptions.gbgfx[1],
-					   CurrentOptions.gbgfx[1]);
-		lex_FloatDeleteSecondRange(nGBGfxID, CurrentOptions.gbgfx[2],
-					   CurrentOptions.gbgfx[2]);
-		lex_FloatDeleteSecondRange(nGBGfxID, CurrentOptions.gbgfx[3],
-					   CurrentOptions.gbgfx[3]);
-	}
-	if (nBinaryID != -1) {
-		lex_FloatDeleteRange(nBinaryID, CurrentOptions.binary[0],
-				     CurrentOptions.binary[0]);
-		lex_FloatDeleteRange(nBinaryID, CurrentOptions.binary[1],
-				     CurrentOptions.binary[1]);
-		lex_FloatDeleteSecondRange(nBinaryID, CurrentOptions.binary[0],
-					   CurrentOptions.binary[0]);
-		lex_FloatDeleteSecondRange(nBinaryID, CurrentOptions.binary[1],
-					   CurrentOptions.binary[1]);
-	}
-	CurrentOptions = *pOpt;
-
-	if (nGBGfxID != -1) {
-		lex_FloatAddRange(nGBGfxID, CurrentOptions.gbgfx[0],
-				  CurrentOptions.gbgfx[0]);
-		lex_FloatAddRange(nGBGfxID, CurrentOptions.gbgfx[1],
-				  CurrentOptions.gbgfx[1]);
-		lex_FloatAddRange(nGBGfxID, CurrentOptions.gbgfx[2],
-				  CurrentOptions.gbgfx[2]);
-		lex_FloatAddRange(nGBGfxID, CurrentOptions.gbgfx[3],
-				  CurrentOptions.gbgfx[3]);
-		lex_FloatAddSecondRange(nGBGfxID, CurrentOptions.gbgfx[0],
-					CurrentOptions.gbgfx[0]);
-		lex_FloatAddSecondRange(nGBGfxID, CurrentOptions.gbgfx[1],
-					CurrentOptions.gbgfx[1]);
-		lex_FloatAddSecondRange(nGBGfxID, CurrentOptions.gbgfx[2],
-					CurrentOptions.gbgfx[2]);
-		lex_FloatAddSecondRange(nGBGfxID, CurrentOptions.gbgfx[3],
-					CurrentOptions.gbgfx[3]);
-	}
-	if (nBinaryID != -1) {
-		lex_FloatAddRange(nBinaryID, CurrentOptions.binary[0],
-				  CurrentOptions.binary[0]);
-		lex_FloatAddRange(nBinaryID, CurrentOptions.binary[1],
-				  CurrentOptions.binary[1]);
-		lex_FloatAddSecondRange(nBinaryID, CurrentOptions.binary[0],
-					CurrentOptions.binary[0]);
-		lex_FloatAddSecondRange(nBinaryID, CurrentOptions.binary[1],
-					CurrentOptions.binary[1]);
-	}
+	/* TODO */
+	(void)pOpt;
 }
 
 void opt_Parse(char *s)
@@ -251,6 +192,22 @@
 		sym_AddString(cldefines[i], cldefines[i + 1]);
 }
 
+void upperstring(char *s)
+{
+	while (*s) {
+		*s = toupper(*s);
+		s++;
+	}
+}
+
+void lowerstring(char *s)
+{
+	while (*s) {
+		*s = tolower(*s);
+		s++;
+	}
+}
+
 /* Escapes Make-special chars from a string */
 static char *make_escape(const char *str)
 {
@@ -516,8 +473,6 @@
 
 	tzMainfile = argv[argc - 1];
 
-	setup_lexer();
-
 	if (verbose)
 		printf("Assembling %s\n", tzMainfile);
 
@@ -530,17 +485,20 @@
 
 	nStartClock = clock();
 
-	nLineNo = 1;
 	nTotalLines = 0;
 	nIFDepth = 0;
-	skipElif = true;
 	sym_Init();
 	sym_SetExportAll(exportall);
 	fstk_Init(tzMainfile);
+	struct LexerState *state = lexer_OpenFile(tzMainfile);
+
+	if (!state)
+		fatalerror("Failed to open main file!");
+	lexer_SetState(state);
+
 	opt_ParseDefines();
 	charmap_New("main", NULL);
 
-	yy_set_state(LEX_STATE_NORMAL);
 	opt_SetCurrentOptions(&DefaultOptions);
 
 	if (yyparse() != 0 || nbErrors != 0)
--- a/src/asm/section.c
+++ b/src/asm/section.c
@@ -656,9 +656,15 @@
 		startPos = 0;
 	}
 
-	FILE *f = fstk_FindFile(s, NULL);
+	char *fullPath = NULL;
+	size_t size = 0;
+	FILE *f = NULL;
 
+	if (fstk_FindFile(s, &fullPath, &size))
+		f = fopen(fullPath, "rb");
+
 	if (!f) {
+		free(fullPath);
 		if (oGeneratedMissingIncludes) {
 			oFailedOnMissingInclude = true;
 			return;
@@ -699,6 +705,7 @@
 		error("Error reading INCBIN file '%s': %s\n", s, strerror(errno));
 
 	fclose(f);
+	free(fullPath);
 }
 
 void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
@@ -715,9 +722,15 @@
 	if (length == 0) /* Don't even bother with 0-byte slices */
 		return;
 
-	FILE *f = fstk_FindFile(s, NULL);
+	char *fullPath = NULL;
+	size_t size = 0;
+	FILE *f = NULL;
 
+	if (fstk_FindFile(s, &fullPath, &size))
+		f = fopen(fullPath, "rb");
+
 	if (!f) {
+		free(fullPath);
 		if (oGeneratedMissingIncludes) {
 			oFailedOnMissingInclude = true;
 			return;
@@ -767,6 +780,7 @@
 	}
 
 	fclose(f);
+	free(fullPath);
 }
 
 /*
--- a/src/asm/symbol.c
+++ b/src/asm/symbol.c
@@ -82,7 +82,7 @@
 
 static int32_t Callback__LINE__(void)
 {
-	return nLineNo;
+	return lexer_GetLineNo();
 }
 
 static int32_t CallbackPC(void)
@@ -113,8 +113,9 @@
 static void updateSymbolFilename(struct Symbol *sym)
 {
 	if (snprintf(sym->fileName, _MAX_PATH + 1, "%s",
-		     tzCurrentFileName) > _MAX_PATH)
-		fatalerror("%s: File name is too long: '%s'\n", __func__, tzCurrentFileName);
+		     lexer_GetFileName()) > _MAX_PATH)
+		fatalerror("%s: File name is too long: '%s'\n", __func__,
+			   lexer_GetFileName());
 	sym->fileLine = fstk_GetLine();
 }
 
--- a/src/asm/warning.c
+++ b/src/asm/warning.c
@@ -204,7 +204,7 @@
 	fstk_Dump();
 	fprintf(stderr, flag ? ": [-Werror=%s]\n    " : ":\n    ", flag);
 	vfprintf(stderr, fmt, args);
-	fstk_DumpStringExpansions();
+	lexer_DumpStringExpansions();
 	nbErrors++;
 }
 
@@ -256,7 +256,7 @@
 	fstk_Dump();
 	fprintf(stderr, ": [-W%s]\n    ", flag);
 	vfprintf(stderr, fmt, args);
-	fstk_DumpStringExpansions();
+	lexer_DumpStringExpansions();
 
 	va_end(args);
 }