shithub: rgbds

ref: 93ee41756796733c5421f79ebe1d3264871194d7
dir: /src/asm/fstack.c/

View raw version
/*
 * This file is part of RGBDS.
 *
 * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
 *
 * SPDX-License-Identifier: MIT
 */

/*
 * FileStack routines
 */

#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "asm/fstack.h"
#include "asm/lexer.h"
#include "asm/main.h"
#include "asm/output.h"
#include "asm/symbol.h"
#include "asm/warning.h"

#include "extern/err.h"

#include "types.h"

static struct sContext *pFileStack;
static unsigned int nFileStackDepth;
unsigned int nMaxRecursionDepth;
static struct sSymbol *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;

static char *pCurrentREPTBlock;
static uint32_t nCurrentREPTBlockSize;
static uint32_t nCurrentREPTBlockCount;
static int32_t nCurrentREPTBodyFirstLine;
static int32_t nCurrentREPTBodyLastLine;

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)
{
	struct sContext **ppFileStack;

	if (++nFileStackDepth > nMaxRecursionDepth)
		fatalerror("Recursion limit (%d) exceeded", nMaxRecursionDepth);

	ppFileStack = &pFileStack;
	while (*ppFileStack)
		ppFileStack = &((*ppFileStack)->pNext);

	*ppFileStack = malloc(sizeof(struct sContext));

	if (*ppFileStack == NULL)
		fatalerror("No memory for context");

	(*ppFileStack)->FlexHandle = CurrentFlexHandle;
	(*ppFileStack)->pNext = NULL;
	strcpy((char *)(*ppFileStack)->tzFileName, (char *)tzCurrentFileName);
	(*ppFileStack)->nLine = nLineNo;

	switch ((*ppFileStack)->nStatus = nCurrentStatus) {
	case STAT_isMacroArg:
	case STAT_isMacro:
		sym_SaveCurrentMacroArgs((*ppFileStack)->tzMacroArgs);
		(*ppFileStack)->pMacro = pCurrentMacro;
		break;
	case STAT_isInclude:
		(*ppFileStack)->pFile = pCurrentFile;
		break;
	case STAT_isREPTBlock:
		sym_SaveCurrentMacroArgs((*ppFileStack)->tzMacroArgs);
		(*ppFileStack)->pREPTBlock = pCurrentREPTBlock;
		(*ppFileStack)->nREPTBlockSize = nCurrentREPTBlockSize;
		(*ppFileStack)->nREPTBlockCount = nCurrentREPTBlockCount;
		(*ppFileStack)->nREPTBodyFirstLine = nCurrentREPTBodyFirstLine;
		(*ppFileStack)->nREPTBodyLastLine = nCurrentREPTBodyLastLine;
		break;
	default:
		fatalerror("%s: Internal error.", __func__);
	}

	nLineNo = 0;
}

static int32_t popcontext(void)
{
	struct sContext *pLastFile, **ppLastFile;

	if (nCurrentStatus == STAT_isREPTBlock) {
		if (--nCurrentREPTBlockCount) {
			char *pREPTIterationWritePtr;
			unsigned long nREPTIterationNo;
			int nNbCharsWritten;
			int nNbCharsLeft;

			yy_delete_buffer(CurrentFlexHandle);
			CurrentFlexHandle =
				yy_scan_bytes(pCurrentREPTBlock,
					      nCurrentREPTBlockSize);
			yy_switch_to_buffer(CurrentFlexHandle);
			sym_UseCurrentMacroArgs();
			sym_SetMacroArgID(nMacroCount++);
			sym_UseNewMacroArgs();

			/* Increment REPT count in file path */
			pREPTIterationWritePtr =
				strrchr(tzCurrentFileName, '~') + 1;
			nREPTIterationNo =
				strtoul(pREPTIterationWritePtr, NULL, 10);
			nNbCharsLeft = sizeof(tzCurrentFileName)
				- (pREPTIterationWritePtr - tzCurrentFileName);
			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");
			}

			nLineNo = nCurrentREPTBodyFirstLine;
			return 0;
		}
	}

	pLastFile = pFileStack;
	if (pLastFile == NULL)
		return 1;

	ppLastFile = &pFileStack;
	while (pLastFile->pNext) {
		ppLastFile = &(pLastFile->pNext);
		pLastFile = *ppLastFile;
	}

	yy_delete_buffer(CurrentFlexHandle);
	nLineNo = nCurrentStatus == STAT_isREPTBlock ? nCurrentREPTBodyLastLine
						     : pLastFile->nLine;

	if (nCurrentStatus == STAT_isInclude)
		fclose(pCurrentFile);

	if (nCurrentStatus == STAT_isMacro
	 || nCurrentStatus == STAT_isREPTBlock)
		nLineNo++;

	CurrentFlexHandle = pLastFile->FlexHandle;
	strcpy((char *)tzCurrentFileName, (char *)pLastFile->tzFileName);

	switch (nCurrentStatus = pLastFile->nStatus) {
	case STAT_isMacroArg:
	case STAT_isMacro:
		sym_RestoreCurrentMacroArgs(pLastFile->tzMacroArgs);
		pCurrentMacro = pLastFile->pMacro;
		break;
	case STAT_isInclude:
		pCurrentFile = pLastFile->pFile;
		break;
	case STAT_isREPTBlock:
		sym_RestoreCurrentMacroArgs(pLastFile->tzMacroArgs);
		pCurrentREPTBlock = pLastFile->pREPTBlock;
		nCurrentREPTBlockSize = pLastFile->nREPTBlockSize;
		nCurrentREPTBlockCount = pLastFile->nREPTBlockCount;
		nCurrentREPTBodyFirstLine = pLastFile->nREPTBodyFirstLine;
		break;
	default:
		fatalerror("%s: Internal error.", __func__);
	}

	nFileStackDepth--;

	free(*ppLastFile);
	*ppLastFile = NULL;
	yy_switch_to_buffer(CurrentFlexHandle);
	return 0;
}

int32_t fstk_GetLine(void)
{
	struct sContext *pLastFile, **ppLastFile;

	switch (nCurrentStatus) {
	case STAT_isInclude:
		/* This is the normal mode, also used when including a file. */
		return nLineNo;
	case STAT_isMacro:
		break; /* Peek top file of the stack */
	case STAT_isMacroArg:
		return nLineNo; /* ??? */
	case STAT_isREPTBlock:
		break; /* Peek top file of the stack */
	default:
		fatalerror("%s: Internal error.", __func__);
	}

	pLastFile = pFileStack;

	if (pLastFile != NULL) {
		while (pLastFile->pNext) {
			ppLastFile = &(pLastFile->pNext);
			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.", __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(%d) -> ", pLastFile->tzFileName,
			pLastFile->nLine);
		pLastFile = pLastFile->pNext;
	}

	fprintf(stderr, "%s(%d)", tzCurrentFileName, nLineNo);
}

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(%d) -> ",
				   pLastFile->tzFileName, pLastFile->nLine);
		if (retcode < 0)
			fatalerror("Failed to dump file stack to string: %s",
				   strerror(errno));
		else if (retcode >= len)
			len = 0;
		else
			len -= retcode;
		pLastFile = pLastFile->pNext;
	}

	retcode = snprintf(&buf[buflen - len], len, "%s", tzCurrentFileName);
	if (retcode < 0)
		fatalerror("Failed to dump file stack to string: %s",
			   strerror(errno));
	else if (retcode >= len)
		len = 0;
	else
		len -= retcode;

	if (!len)
		warning(WARNING_LONG_STR, "File stack dump too long, got truncated");
}

/*
 * 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)
{
	if (NextIncPath == MAXINCPATHS)
		fatalerror("Too many include directories passed from command line");

	// 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'", s);
}

FILE *fstk_FindFile(char *fname, char **incPathUsed)
{
	char path[_MAX_PATH];
	int32_t i;
	FILE *f;

	if (fname == NULL)
		return NULL;

	f = fopen(fname, "rb");

	if (f != NULL || errno != ENOENT) {
		if (dependfile)
			fprintf(dependfile, "%s: %s\n", tzObjectname, fname);

		return f;
	}

	for (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 (fullpathlen >= (int)sizeof(path))
			continue;

		f = fopen(path, "rb");

		if (f != NULL || errno != ENOENT) {
			if (dependfile) {
				fprintf(dependfile, "%s: %s\n", tzObjectname,
					path);
			}
			if (incPathUsed)
				*incPathUsed = IncludePaths[i];
			return f;
		}
	}

	errno = ENOENT;
	return NULL;
}

/*
 * Set up an include file for parsing
 */
void fstk_RunInclude(char *tzFileName)
{
	char *incPathUsed = "";
	FILE *f = fstk_FindFile(tzFileName, &incPathUsed);

	if (f == NULL)
		err(1, "Unable to open included file '%s'", tzFileName);

	pushcontext();
	nLineNo = 1;
	nCurrentStatus = STAT_isInclude;
	snprintf(tzCurrentFileName, sizeof(tzCurrentFileName), "%s%s",
		 incPathUsed, tzFileName);
	if (CurrentOptions.verbose)
		printf("Assembling %s\n", tzCurrentFileName);
	pCurrentFile = f;
	CurrentFlexHandle = yy_create_buffer(pCurrentFile);
	yy_switch_to_buffer(CurrentFlexHandle);

	/* Dirty hack to give the INCLUDE directive a linefeed */

	yyunput('\n');
	nLineNo -= 1;
}

/*
 * Set up a macro for parsing
 */
uint32_t fstk_RunMacro(char *s)
{
	struct sSymbol *sym = sym_FindMacro(s);
	int nPrintedChars;

	if (sym == NULL || sym->pMacro == NULL)
		return 0;

	pushcontext();
	sym_SetMacroArgID(nMacroCount++);
	/* Minus 1 because there is a newline at the beginning of the buffer */
	nLineNo = sym->nFileLine - 1;
	sym_UseNewMacroArgs();
	nCurrentStatus = STAT_isMacro;
	nPrintedChars = snprintf(tzCurrentFileName, _MAX_PATH + 1,
				 "%s::%s", sym->tzFileName, s);
	if (nPrintedChars > _MAX_PATH) {
		popcontext();
		fatalerror("File name + macro name is too large to fit into buffer");
	}

	pCurrentMacro = sym;
	CurrentFlexHandle = yy_scan_bytes(pCurrentMacro->pMacro,
					  strlen(pCurrentMacro->pMacro));
	yy_switch_to_buffer(CurrentFlexHandle);

	return 1;
}

/*
 * Set up a macroargument for parsing
 */
void fstk_RunMacroArg(int32_t s)
{
	char *sym;

	if (s == '@')
		s = -1;
	else
		s -= '0';

	sym = sym_FindMacroArg(s);

	if (sym == NULL)
		fatalerror("No such macroargument");

	pushcontext();
	nCurrentStatus = STAT_isMacroArg;
	snprintf(tzCurrentFileName, _MAX_PATH + 1, "%c", (uint8_t)s);
	CurrentFlexHandle = yy_scan_bytes(sym, strlen(sym));
	yy_switch_to_buffer(CurrentFlexHandle);
}

/*
 * Set up a stringequate for parsing
 */
void fstk_RunString(char *s)
{
	const struct sSymbol *pSym = sym_FindSymbol(s);

	if (pSym != NULL) {
		pushcontext();
		nCurrentStatus = STAT_isMacroArg;
		strcpy(tzCurrentFileName, s);
		CurrentFlexHandle =
			yy_scan_bytes(pSym->pMacro, strlen(pSym->pMacro));
		yy_switch_to_buffer(CurrentFlexHandle);
	} else {
		yyerror("No such string symbol '%s'", s);
	}
}

/*
 * Set up a repeat block for parsing
 */
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;
		sym_UseCurrentMacroArgs();
		pushcontext();
		sym_SetMacroArgID(nMacroCount++);
		sym_UseNewMacroArgs();
		nCurrentREPTBlockCount = count;
		nCurrentStatus = STAT_isREPTBlock;
		nCurrentREPTBlockSize = ulNewMacroSize;
		pCurrentREPTBlock = tzNewMacro;
		nCurrentREPTBodyFirstLine = nReptLineNo + 1;
		nLineNo = nReptLineNo;

		if (strlen(tzCurrentFileName) + strlen(tzReptStr) > _MAX_PATH)
			fatalerror("Cannot append \"%s\" to file path",
				   tzReptStr);
		strcat(tzCurrentFileName, tzReptStr);

		CurrentFlexHandle =
			yy_scan_bytes(pCurrentREPTBlock, nCurrentREPTBlockSize);
		yy_switch_to_buffer(CurrentFlexHandle);
	}
}

/*
 * Initialize the filestack routines
 */
void fstk_Init(char *pFileName)
{
	char tzSymFileName[_MAX_PATH + 1 + 2];

	snprintf(tzSymFileName, sizeof(tzSymFileName), "\"%s\"", pFileName);
	sym_AddString("__FILE__", tzSymFileName);

	pFileStack = NULL;
	if (strcmp(pFileName, "-") == 0) {
		pCurrentFile = stdin;
	} else {
		pCurrentFile = fopen(pFileName, "rb");
		if (pCurrentFile == NULL)
			err(1, "Unable to open file '%s'", pFileName);
	}
	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;
}