mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 18:22:07 +00:00
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...
This commit is contained in:
2
Makefile
2
Makefile
@@ -72,7 +72,7 @@ rgbasm_obj := \
|
||||
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 \
|
||||
|
||||
@@ -41,13 +41,7 @@ struct sContext {
|
||||
|
||||
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 @@ void fstk_RunRept(uint32_t count, int32_t nReptLineNo, char const *body, size_t
|
||||
* @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 */
|
||||
|
||||
@@ -31,7 +31,8 @@ static inline void lexer_SetStateAtEOL(struct LexerState *state)
|
||||
}
|
||||
|
||||
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_GetLineNo(void);
|
||||
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 */
|
||||
|
||||
@@ -28,6 +28,7 @@ char const *macro_GetArg(uint32_t i);
|
||||
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);
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ struct Symbol {
|
||||
};
|
||||
struct { /* For SYM_MACRO */
|
||||
size_t macroSize;
|
||||
char const *macro;
|
||||
char *macro;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -117,7 +117,7 @@ uint32_t sym_GetPCValue(void);
|
||||
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);
|
||||
|
||||
@@ -597,21 +597,21 @@ load : T_POP_LOAD string ',' sectiontype sectorg sectattrs {
|
||||
|
||||
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"));
|
||||
}
|
||||
;
|
||||
|
||||
|
||||
592
src/asm/fstack.c
592
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);
|
||||
|
||||
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;
|
||||
|
||||
if (*end++ != '/')
|
||||
*end++ = '/';
|
||||
*end = '\0';
|
||||
includePaths[nbIncPaths++] = str;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 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)
|
||||
static void printDep(char const *path)
|
||||
{
|
||||
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 @@ bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
|
||||
}
|
||||
|
||||
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 @@ bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
|
||||
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 @@ bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
|
||||
|
||||
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];
|
||||
|
||||
char *c = pFileName;
|
||||
int fileNameIndex = 0;
|
||||
|
||||
tzSymFileName[fileNameIndex++] = '"';
|
||||
|
||||
// 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++] = '\\';
|
||||
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);
|
||||
}
|
||||
|
||||
tzSymFileName[fileNameIndex++] = *c;
|
||||
++c;
|
||||
static void dumpToStream(FILE *stream)
|
||||
{
|
||||
struct Context *context = topLevelContext;
|
||||
|
||||
while (context != contextStack) {
|
||||
printContext(stream, context);
|
||||
fprintf(stream, " -> ");
|
||||
context = context->child;
|
||||
}
|
||||
contextStack->lineNo = lexer_GetLineNo();
|
||||
printContext(stream, contextStack);
|
||||
}
|
||||
|
||||
tzSymFileName[fileNameIndex++] = '"';
|
||||
tzSymFileName[fileNameIndex] = '\0';
|
||||
|
||||
sym_AddString("__FILE__", tzSymFileName);
|
||||
|
||||
pFileStack = NULL;
|
||||
nFileStackDepth = 0;
|
||||
|
||||
nMacroCount = 0;
|
||||
nCurrentStatus = STAT_isInclude;
|
||||
void fstk_Dump(void)
|
||||
{
|
||||
dumpToStream(stderr);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (!stream)
|
||||
fatalerror("Failed to dump file stack to string: %s\n", strerror(errno));
|
||||
dumpToStream(stream);
|
||||
fclose(stream);
|
||||
return str;
|
||||
}
|
||||
|
||||
uint32_t fstk_GetLine(void)
|
||||
{
|
||||
return lexer_GetLineNo();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,6 +258,8 @@ struct LexerState {
|
||||
};
|
||||
|
||||
/* Common state */
|
||||
bool isFile;
|
||||
|
||||
enum LexerMode mode;
|
||||
bool atLineStart;
|
||||
uint32_t lineNo;
|
||||
@@ -278,6 +280,21 @@ struct LexerState {
|
||||
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 @@ struct LexerState *lexer_OpenFile(char const *path)
|
||||
}
|
||||
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 @@ struct LexerState *lexer_OpenFile(char const *path)
|
||||
state->index = 0;
|
||||
}
|
||||
|
||||
state->mode = LEXER_NORMAL;
|
||||
state->atLineStart = true; /* yylex() will init colNo due to this */
|
||||
state->lineNo = 0;
|
||||
state->lastToken = 0;
|
||||
|
||||
state->capturing = false;
|
||||
state->captureBuf = NULL;
|
||||
|
||||
state->nbChars = 0;
|
||||
state->expandStrings = true;
|
||||
state->expansions = NULL;
|
||||
state->expansionOfs = 0;
|
||||
initState(state);
|
||||
state->lineNo = 0; /* Will be incremented at first line start */
|
||||
return state;
|
||||
}
|
||||
|
||||
struct LexerState *lexer_OpenFileView(void)
|
||||
struct LexerState *lexer_OpenFileView(char *buf, size_t size, uint32_t lineNo)
|
||||
{
|
||||
struct LexerState *state = malloc(sizeof(*state));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void lexer_RestartRept(uint32_t lineNo)
|
||||
{
|
||||
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 @@ static void beginExpansion(size_t distance, uint8_t skip,
|
||||
#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 @@ static void beginExpansion(size_t distance, uint8_t skip,
|
||||
|
||||
*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 @@ static int yylex_NORMAL(void)
|
||||
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 @@ static int yylex_SKIP_TO_ENDC(void)
|
||||
|
||||
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 yylex(void)
|
||||
int token = lexerModeFuncs[lexerState->mode]();
|
||||
|
||||
/* Make sure to terminate files with a line feed */
|
||||
if (token == 0 && lexerState->lastToken != '\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,16 +1584,18 @@ int yylex(void)
|
||||
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;
|
||||
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();
|
||||
|
||||
@@ -29,7 +29,8 @@ struct MacroArgs {
|
||||
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,15 +108,23 @@ char const *macro_GetUniqueIDStr(void)
|
||||
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)
|
||||
{
|
||||
if (macroArgs->shift != macroArgs->nbArgs)
|
||||
|
||||
@@ -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 @@ int main(int argc, char *argv[])
|
||||
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 @@ int main(int argc, char *argv[])
|
||||
|
||||
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 @@ int main(int argc, char *argv[])
|
||||
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 @@ int main(int argc, char *argv[])
|
||||
nIFDepth = 0;
|
||||
sym_Init();
|
||||
sym_SetExportAll(exportall);
|
||||
fstk_Init(tzMainfile);
|
||||
|
||||
opt_ParseDefines();
|
||||
charmap_New("main", NULL);
|
||||
|
||||
@@ -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 @@ static struct Patch *allocpatch(uint32_t type, struct Expression const *expr,
|
||||
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();
|
||||
|
||||
@@ -477,7 +477,7 @@ void sym_Export(char const *symName)
|
||||
/*
|
||||
* 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);
|
||||
|
||||
|
||||
1
test/asm/file-sym.asm
Normal file
1
test/asm/file-sym.asm
Normal file
@@ -0,0 +1 @@
|
||||
PRINTT "{__FILE__}\n"
|
||||
0
test/asm/file-sym.err
Normal file
0
test/asm/file-sym.err
Normal file
1
test/asm/file-sym.out
Normal file
1
test/asm/file-sym.out
Normal file
@@ -0,0 +1 @@
|
||||
"test/asm/file-sym.asm"
|
||||
Reference in New Issue
Block a user