the .include statement went into the listing file, before the contents of the include file. The problem was caused by the several levels of lookahead in the scanner, and the only way to avoid it was to remove the calls to NextTok () in relevant places - and add compensation for it in others. git-svn-id: svn://svn.cc65.org/cc65/trunk@3609 b7a2c559-68d2-44c3-8de9-860c34a00d81
1342 lines
32 KiB
C
1342 lines
32 KiB
C
/*****************************************************************************/
|
|
/* */
|
|
/* scanner.c */
|
|
/* */
|
|
/* The scanner for the ca65 macroassembler */
|
|
/* */
|
|
/* */
|
|
/* */
|
|
/* (C) 1998-2005 Ullrich von Bassewitz */
|
|
/* Römerstraße 52 */
|
|
/* D-70794 Filderstadt */
|
|
/* EMail: uz@cc65.org */
|
|
/* */
|
|
/* */
|
|
/* This software is provided 'as-is', without any expressed or implied */
|
|
/* warranty. In no event will the authors be held liable for any damages */
|
|
/* arising from the use of this software. */
|
|
/* */
|
|
/* Permission is granted to anyone to use this software for any purpose, */
|
|
/* including commercial applications, and to alter it and redistribute it */
|
|
/* freely, subject to the following restrictions: */
|
|
/* */
|
|
/* 1. The origin of this software must not be misrepresented; you must not */
|
|
/* claim that you wrote the original software. If you use this software */
|
|
/* in a product, an acknowledgment in the product documentation would be */
|
|
/* appreciated but is not required. */
|
|
/* 2. Altered source versions must be plainly marked as such, and must not */
|
|
/* be misrepresented as being the original software. */
|
|
/* 3. This notice may not be removed or altered from any source */
|
|
/* distribution. */
|
|
/* */
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h> /* EMX needs this */
|
|
#include <sys/stat.h>
|
|
|
|
/* common */
|
|
#include "addrsize.h"
|
|
#include "chartype.h"
|
|
#include "check.h"
|
|
#include "fname.h"
|
|
#include "xmalloc.h"
|
|
|
|
/* ca65 */
|
|
#include "condasm.h"
|
|
#include "error.h"
|
|
#include "filetab.h"
|
|
#include "global.h"
|
|
#include "incpath.h"
|
|
#include "instr.h"
|
|
#include "istack.h"
|
|
#include "listing.h"
|
|
#include "macro.h"
|
|
#include "toklist.h"
|
|
#include "scanner.h"
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* Data */
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
enum Token Tok = TOK_NONE; /* Current token */
|
|
int WS; /* Flag: Whitespace before token */
|
|
long IVal; /* Integer token attribute */
|
|
char SVal[MAX_STR_LEN+1]; /* String token attribute */
|
|
|
|
FilePos CurPos = { 0, 0, 0 }; /* Name and position in current file */
|
|
|
|
|
|
|
|
/* Struct to handle include files. */
|
|
typedef struct InputFile InputFile;
|
|
struct InputFile {
|
|
FILE* F; /* Input file descriptor */
|
|
FilePos Pos; /* Position in file */
|
|
enum Token Tok; /* Last token */
|
|
int C; /* Last character */
|
|
char Line[256]; /* The current input line */
|
|
InputFile* Next; /* Linked list of input files */
|
|
};
|
|
|
|
/* Struct to handle textual input data */
|
|
typedef struct InputData InputData;
|
|
struct InputData {
|
|
char* Data; /* Pointer to the data */
|
|
const char* Pos; /* Pointer to current position */
|
|
int Malloced; /* Memory was malloced */
|
|
enum Token Tok; /* Last token */
|
|
int C; /* Last character */
|
|
InputData* Next; /* Linked list of input data */
|
|
};
|
|
|
|
/* Current input variables */
|
|
static InputFile* IFile = 0; /* Current input file */
|
|
static InputData* IData = 0; /* Current input memory data */
|
|
static unsigned ICount = 0; /* Count of input files */
|
|
static int C = 0; /* Current input character */
|
|
|
|
/* Force end of assembly */
|
|
int ForcedEnd = 0;
|
|
|
|
/* List of dot keywords with the corresponding tokens */
|
|
struct DotKeyword {
|
|
const char* Key; /* MUST be first field */
|
|
enum Token Tok;
|
|
} DotKeywords [] = {
|
|
{ ".A16", TOK_A16 },
|
|
{ ".A8", TOK_A8 },
|
|
{ ".ADDR", TOK_ADDR },
|
|
{ ".ALIGN", TOK_ALIGN },
|
|
{ ".AND", TOK_BOOLAND },
|
|
{ ".ASCIIZ", TOK_ASCIIZ },
|
|
{ ".ASSERT", TOK_ASSERT },
|
|
{ ".AUTOIMPORT", TOK_AUTOIMPORT },
|
|
{ ".BANKBYTE", TOK_BANKBYTE },
|
|
{ ".BITAND", TOK_AND },
|
|
{ ".BITNOT", TOK_NOT },
|
|
{ ".BITOR", TOK_OR },
|
|
{ ".BITXOR", TOK_XOR },
|
|
{ ".BLANK", TOK_BLANK },
|
|
{ ".BSS", TOK_BSS },
|
|
{ ".BYT", TOK_BYTE },
|
|
{ ".BYTE", TOK_BYTE },
|
|
{ ".CASE", TOK_CASE },
|
|
{ ".CHARMAP", TOK_CHARMAP },
|
|
{ ".CODE", TOK_CODE },
|
|
{ ".CONCAT", TOK_CONCAT },
|
|
{ ".CONDES", TOK_CONDES },
|
|
{ ".CONST", TOK_CONST },
|
|
{ ".CONSTRUCTOR", TOK_CONSTRUCTOR },
|
|
{ ".CPU", TOK_CPU },
|
|
{ ".DATA", TOK_DATA },
|
|
{ ".DBG", TOK_DBG },
|
|
{ ".DBYT", TOK_DBYT },
|
|
{ ".DEBUGINFO", TOK_DEBUGINFO },
|
|
{ ".DEF", TOK_DEFINED },
|
|
{ ".DEFINE", TOK_DEFINE },
|
|
{ ".DEFINED", TOK_DEFINED },
|
|
{ ".DESTRUCTOR", TOK_DESTRUCTOR },
|
|
{ ".DWORD", TOK_DWORD },
|
|
{ ".ELSE", TOK_ELSE },
|
|
{ ".ELSEIF", TOK_ELSEIF },
|
|
{ ".END", TOK_END },
|
|
{ ".ENDENUM", TOK_ENDENUM },
|
|
{ ".ENDIF", TOK_ENDIF },
|
|
{ ".ENDMAC", TOK_ENDMACRO },
|
|
{ ".ENDMACRO", TOK_ENDMACRO },
|
|
{ ".ENDPROC", TOK_ENDPROC },
|
|
{ ".ENDREP", TOK_ENDREP },
|
|
{ ".ENDREPEAT", TOK_ENDREP },
|
|
{ ".ENDSCOPE", TOK_ENDSCOPE },
|
|
{ ".ENDSTRUCT", TOK_ENDSTRUCT },
|
|
{ ".ENDUNION", TOK_ENDUNION },
|
|
{ ".ENUM", TOK_ENUM },
|
|
{ ".ERROR", TOK_ERROR },
|
|
{ ".EXITMAC", TOK_EXITMACRO },
|
|
{ ".EXITMACRO", TOK_EXITMACRO },
|
|
{ ".EXPORT", TOK_EXPORT },
|
|
{ ".EXPORTZP", TOK_EXPORTZP },
|
|
{ ".FARADDR", TOK_FARADDR },
|
|
{ ".FEATURE", TOK_FEATURE },
|
|
{ ".FILEOPT", TOK_FILEOPT },
|
|
{ ".FOPT", TOK_FILEOPT },
|
|
{ ".FORCEIMPORT", TOK_FORCEIMPORT },
|
|
{ ".FORCEWORD", TOK_FORCEWORD },
|
|
{ ".GLOBAL", TOK_GLOBAL },
|
|
{ ".GLOBALZP", TOK_GLOBALZP },
|
|
{ ".HIBYTE", TOK_HIBYTE },
|
|
{ ".HIWORD", TOK_HIWORD },
|
|
{ ".I16", TOK_I16 },
|
|
{ ".I8", TOK_I8 },
|
|
{ ".IDENT", TOK_MAKEIDENT },
|
|
{ ".IF", TOK_IF },
|
|
{ ".IFBLANK", TOK_IFBLANK },
|
|
{ ".IFCONST", TOK_IFCONST },
|
|
{ ".IFDEF", TOK_IFDEF },
|
|
{ ".IFNBLANK", TOK_IFNBLANK },
|
|
{ ".IFNCONST", TOK_IFNCONST },
|
|
{ ".IFNDEF", TOK_IFNDEF },
|
|
{ ".IFNREF", TOK_IFNREF },
|
|
{ ".IFP02", TOK_IFP02 },
|
|
{ ".IFP816", TOK_IFP816 },
|
|
{ ".IFPC02", TOK_IFPC02 },
|
|
{ ".IFPSC02", TOK_IFPSC02 },
|
|
{ ".IFREF", TOK_IFREF },
|
|
{ ".IMPORT", TOK_IMPORT },
|
|
{ ".IMPORTZP", TOK_IMPORTZP },
|
|
{ ".INCBIN", TOK_INCBIN },
|
|
{ ".INCLUDE", TOK_INCLUDE },
|
|
{ ".INTERRUPTOR", TOK_INTERRUPTOR },
|
|
{ ".LEFT", TOK_LEFT },
|
|
{ ".LINECONT", TOK_LINECONT },
|
|
{ ".LIST", TOK_LIST },
|
|
{ ".LISTBYTES", TOK_LISTBYTES },
|
|
{ ".LOBYTE", TOK_LOBYTE },
|
|
{ ".LOCAL", TOK_LOCAL },
|
|
{ ".LOCALCHAR", TOK_LOCALCHAR },
|
|
{ ".LOWORD", TOK_LOWORD },
|
|
{ ".MAC", TOK_MACRO },
|
|
{ ".MACPACK", TOK_MACPACK },
|
|
{ ".MACRO", TOK_MACRO },
|
|
{ ".MATCH", TOK_MATCH },
|
|
{ ".MID", TOK_MID },
|
|
{ ".MOD", TOK_MOD },
|
|
{ ".NOT", TOK_BOOLNOT },
|
|
{ ".NULL", TOK_NULL },
|
|
{ ".OR", TOK_BOOLOR },
|
|
{ ".ORG", TOK_ORG },
|
|
{ ".OUT", TOK_OUT },
|
|
{ ".P02", TOK_P02 },
|
|
{ ".P816", TOK_P816 },
|
|
{ ".PAGELEN", TOK_PAGELENGTH },
|
|
{ ".PAGELENGTH", TOK_PAGELENGTH },
|
|
{ ".PARAMCOUNT", TOK_PARAMCOUNT },
|
|
{ ".PC02", TOK_PC02 },
|
|
{ ".POPSEG", TOK_POPSEG },
|
|
{ ".PROC", TOK_PROC },
|
|
{ ".PSC02", TOK_PSC02 },
|
|
{ ".PUSHSEG", TOK_PUSHSEG },
|
|
{ ".REF", TOK_REFERENCED },
|
|
{ ".REFERENCED", TOK_REFERENCED },
|
|
{ ".RELOC", TOK_RELOC },
|
|
{ ".REPEAT", TOK_REPEAT },
|
|
{ ".RES", TOK_RES },
|
|
{ ".RIGHT", TOK_RIGHT },
|
|
{ ".RODATA", TOK_RODATA },
|
|
{ ".SCOPE", TOK_SCOPE },
|
|
{ ".SEGMENT", TOK_SEGMENT },
|
|
{ ".SET", TOK_SET },
|
|
{ ".SETCPU", TOK_SETCPU },
|
|
{ ".SHL", TOK_SHL },
|
|
{ ".SHR", TOK_SHR },
|
|
{ ".SIZEOF", TOK_SIZEOF },
|
|
{ ".SMART", TOK_SMART },
|
|
{ ".SPRINTF", TOK_SPRINTF },
|
|
{ ".STRAT", TOK_STRAT },
|
|
{ ".STRING", TOK_STRING },
|
|
{ ".STRLEN", TOK_STRLEN },
|
|
{ ".STRUCT", TOK_STRUCT },
|
|
{ ".SUNPLUS", TOK_SUNPLUS },
|
|
{ ".TAG", TOK_TAG },
|
|
{ ".TCOUNT", TOK_TCOUNT },
|
|
{ ".TIME", TOK_TIME },
|
|
{ ".UNION", TOK_UNION },
|
|
{ ".VERSION", TOK_VERSION },
|
|
{ ".WARNING", TOK_WARNING },
|
|
{ ".WORD", TOK_WORD },
|
|
{ ".XMATCH", TOK_XMATCH },
|
|
{ ".XOR", TOK_BOOLXOR },
|
|
{ ".ZEROPAGE", TOK_ZEROPAGE },
|
|
};
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* Forwards */
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
static void NextChar (void);
|
|
/* Read the next character from the input file */
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* Character classification functions */
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
int IsIdChar (int C)
|
|
/* Return true if the character is a valid character for an identifier */
|
|
{
|
|
return IsAlNum (C) ||
|
|
(C == '_') ||
|
|
(C == '@' && AtInIdents) ||
|
|
(C == '$' && DollarInIdents);
|
|
}
|
|
|
|
|
|
|
|
int IsIdStart (int C)
|
|
/* Return true if the character may start an identifier */
|
|
{
|
|
return IsAlpha (C) || C == '_';
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* Code */
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
void NewInputFile (const char* Name)
|
|
/* Open a new input file */
|
|
{
|
|
InputFile* I;
|
|
FILE* F;
|
|
|
|
/* First try to open the file */
|
|
F = fopen (Name, "r");
|
|
if (F == 0) {
|
|
|
|
char* PathName;
|
|
|
|
/* Error (fatal error if this is the main file) */
|
|
if (ICount == 0) {
|
|
Fatal ("Cannot open input file `%s': %s", Name, strerror (errno));
|
|
}
|
|
|
|
/* We are on include level. Search for the file in the include
|
|
* directories.
|
|
*/
|
|
PathName = FindInclude (Name);
|
|
if (PathName == 0 || (F = fopen (PathName, "r")) == 0) {
|
|
/* Not found or cannot open, print an error and bail out */
|
|
Error ("Cannot open include file `%s': %s", Name, strerror (errno));
|
|
}
|
|
|
|
/* Free the allocated memory */
|
|
xfree (PathName);
|
|
|
|
}
|
|
|
|
/* check again if we do now have an open file */
|
|
if (F != 0) {
|
|
|
|
unsigned FileIdx;
|
|
|
|
/* Stat the file and remember the values */
|
|
struct stat Buf;
|
|
if (fstat (fileno (F), &Buf) != 0) {
|
|
Fatal ("Cannot stat input file `%s': %s", Name, strerror (errno));
|
|
}
|
|
|
|
/* Add the file to the input file table and remember the index */
|
|
FileIdx = AddFile (Name, Buf.st_size, Buf.st_mtime);
|
|
|
|
/* Create a new state variable and initialize it */
|
|
I = xmalloc (sizeof (*I));
|
|
I->F = F;
|
|
I->Pos.Line = 0;
|
|
I->Pos.Col = 0;
|
|
I->Pos.Name = FileIdx;
|
|
I->Tok = Tok;
|
|
I->C = C;
|
|
I->Line[0] = '\0';
|
|
|
|
/* Use the new file */
|
|
I->Next = IFile;
|
|
IFile = I;
|
|
++ICount;
|
|
|
|
/* Setup the next token and character so it will be skipped on the
|
|
* next call to NextRawTok().
|
|
*/
|
|
C = ' ';
|
|
Tok = TOK_SEP;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void DoneInputFile (void)
|
|
/* Close the current input file */
|
|
{
|
|
InputFile* I;
|
|
|
|
/* Restore the old token */
|
|
Tok = IFile->Tok;
|
|
C = IFile->C;
|
|
|
|
/* Save a pointer to the current struct, then set it back */
|
|
I = IFile;
|
|
IFile = I->Next;
|
|
|
|
/* Cleanup the current stuff */
|
|
fclose (I->F);
|
|
xfree (I);
|
|
--ICount;
|
|
}
|
|
|
|
|
|
|
|
void NewInputData (char* Data, int Malloced)
|
|
/* Add a chunk of input data to the input stream */
|
|
{
|
|
InputData* I;
|
|
|
|
/* Create a new state variable and initialize it */
|
|
I = xmalloc (sizeof (*I));
|
|
I->Data = Data;
|
|
I->Pos = Data;
|
|
I->Malloced = Malloced;
|
|
I->Tok = Tok;
|
|
I->C = C;
|
|
|
|
/* Use the new data */
|
|
I->Next = IData;
|
|
IData = I;
|
|
|
|
/* Setup the next token and character so it will be skipped on the
|
|
* next call to NextRawTok().
|
|
*/
|
|
C = ' ';
|
|
Tok = TOK_SEP;
|
|
}
|
|
|
|
|
|
|
|
static void DoneInputData (void)
|
|
/* End the current input data stream */
|
|
{
|
|
InputData* I;
|
|
|
|
/* Restore the old token */
|
|
Tok = IData->Tok;
|
|
C = IData->C;
|
|
|
|
/* Save a pointer to the current struct, then set it back */
|
|
I = IData;
|
|
IData = I->Next;
|
|
|
|
/* Cleanup the current stuff */
|
|
if (I->Malloced) {
|
|
xfree (I->Data);
|
|
}
|
|
xfree (I);
|
|
}
|
|
|
|
|
|
|
|
static unsigned DigitVal (unsigned char C)
|
|
/* Convert a digit into it's numerical representation */
|
|
{
|
|
if (IsDigit (C)) {
|
|
return C - '0';
|
|
} else {
|
|
return toupper (C) - 'A' + 10;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void NextChar (void)
|
|
/* Read the next character from the input file */
|
|
{
|
|
/* If we have an input data structure, read from there */
|
|
if (IData) {
|
|
|
|
C = *IData->Pos++;
|
|
if (C == '\0') {
|
|
/* End of input data */
|
|
C = EOF;
|
|
}
|
|
|
|
} else {
|
|
|
|
/* Check for end of line, read the next line if needed */
|
|
while (IFile->Line [IFile->Pos.Col] == '\0') {
|
|
|
|
unsigned Len, Removed;
|
|
|
|
/* End of current line reached, read next line */
|
|
if (fgets (IFile->Line, sizeof (IFile->Line), IFile->F) == 0) {
|
|
/* End of file. Add an empty line to the listing. This is a
|
|
* small hack needed to keep the PC output in sync.
|
|
*/
|
|
NewListingLine ("", IFile->Pos.Name, ICount);
|
|
C = EOF;
|
|
return;
|
|
}
|
|
|
|
/* For better handling of files with unusual line endings (DOS
|
|
* files that are accidently translated on Unix for example),
|
|
* first remove all whitespace at the end, then add a single
|
|
* newline.
|
|
*/
|
|
Len = strlen (IFile->Line);
|
|
Removed = 0;
|
|
while (Len > 0 && IsSpace (IFile->Line[Len-1])) {
|
|
++Removed;
|
|
--Len;
|
|
}
|
|
if (Removed) {
|
|
IFile->Line[Len+0] = '\n';
|
|
IFile->Line[Len+1] = '\0';
|
|
}
|
|
|
|
/* One more line */
|
|
IFile->Pos.Line++;
|
|
IFile->Pos.Col = 0;
|
|
|
|
/* Remember the new line for the listing */
|
|
NewListingLine (IFile->Line, IFile->Pos.Name, ICount);
|
|
|
|
}
|
|
|
|
/* Return the next character from the file */
|
|
C = IFile->Line [IFile->Pos.Col++];
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void LocaseSVal (void)
|
|
/* Make SVal lower case */
|
|
{
|
|
unsigned I = 0;
|
|
while (SVal [I]) {
|
|
SVal [I] = tolower (SVal [I]);
|
|
++I;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void UpcaseSVal (void)
|
|
/* Make SVal upper case */
|
|
{
|
|
unsigned I = 0;
|
|
while (SVal [I]) {
|
|
SVal [I] = toupper (SVal [I]);
|
|
++I;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static int CmpDotKeyword (const void* K1, const void* K2)
|
|
/* Compare function for the dot keyword search */
|
|
{
|
|
return strcmp (((struct DotKeyword*)K1)->Key, ((struct DotKeyword*)K2)->Key);
|
|
}
|
|
|
|
|
|
|
|
static unsigned char FindDotKeyword (void)
|
|
/* Find the dot keyword in SVal. Return the corresponding token if found,
|
|
* return TOK_NONE if not found.
|
|
*/
|
|
{
|
|
static const struct DotKeyword K = { SVal, 0 };
|
|
struct DotKeyword* R;
|
|
|
|
/* If we aren't in ignore case mode, we have to uppercase the keyword */
|
|
if (!IgnoreCase) {
|
|
UpcaseSVal ();
|
|
}
|
|
|
|
/* Search for the keyword */
|
|
R = bsearch (&K, DotKeywords, sizeof (DotKeywords) / sizeof (DotKeywords [0]),
|
|
sizeof (DotKeywords [0]), CmpDotKeyword);
|
|
if (R != 0) {
|
|
return R->Tok;
|
|
} else {
|
|
return TOK_NONE;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void ReadIdent (unsigned Index)
|
|
/* Read an identifier from the current input position into Ident. Filling SVal
|
|
* starts at Index with the current character in C. It is assumed that any
|
|
* characters already filled in are ok, and the character in C is checked.
|
|
*/
|
|
{
|
|
/* Read the identifier */
|
|
do {
|
|
if (Index < MAX_STR_LEN) {
|
|
SVal [Index++] = C;
|
|
}
|
|
NextChar ();
|
|
} while (IsIdChar (C));
|
|
SVal [Index] = '\0';
|
|
|
|
/* If we should ignore case, convert the identifier to upper case */
|
|
if (IgnoreCase) {
|
|
UpcaseSVal ();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static unsigned ReadStringConst (int StringTerm)
|
|
/* Read a string constant into SVal. Check for maximum string length and all
|
|
* other stuff. The length of the string is returned.
|
|
*/
|
|
{
|
|
unsigned I;
|
|
|
|
/* Skip the leading string terminator */
|
|
NextChar ();
|
|
|
|
/* Read the string */
|
|
I = 0;
|
|
while (1) {
|
|
if (C == StringTerm) {
|
|
break;
|
|
}
|
|
if (C == '\n' || C == EOF) {
|
|
Error ("Newline in string constant");
|
|
break;
|
|
}
|
|
|
|
/* Check for string length, print an error message once */
|
|
if (I == MAX_STR_LEN) {
|
|
Error ("Maximum string size exceeded");
|
|
} else if (I < MAX_STR_LEN) {
|
|
SVal [I] = C;
|
|
}
|
|
++I;
|
|
|
|
/* Skip the character */
|
|
NextChar ();
|
|
}
|
|
|
|
/* Skip the trailing terminator */
|
|
NextChar ();
|
|
|
|
/* Terminate the string */
|
|
if (I >= MAX_STR_LEN) {
|
|
I = MAX_STR_LEN;
|
|
}
|
|
SVal [I] = '\0';
|
|
|
|
/* Return the length of the string */
|
|
return I;
|
|
}
|
|
|
|
|
|
|
|
static int Sweet16Reg (const char* Ident)
|
|
/* Check if the given identifier is a sweet16 register. Return -1 if this is
|
|
* not the case, return the register number otherwise.
|
|
*/
|
|
{
|
|
unsigned RegNum;
|
|
char Check;
|
|
|
|
if (Ident[0] != 'r' && Ident[0] != 'R') {
|
|
return -1;
|
|
}
|
|
if (!IsDigit (Ident[1])) {
|
|
return -1;
|
|
}
|
|
|
|
if (sscanf (Ident+1, "%u%c", &RegNum, &Check) != 1 || RegNum > 15) {
|
|
/* Invalid register */
|
|
return -1;
|
|
}
|
|
|
|
/* The register number is valid */
|
|
return (int) RegNum;
|
|
}
|
|
|
|
|
|
|
|
void NextRawTok (void)
|
|
/* Read the next raw token from the input stream */
|
|
{
|
|
/* If we've a forced end of assembly, don't read further */
|
|
if (ForcedEnd) {
|
|
Tok = TOK_EOF;
|
|
return;
|
|
}
|
|
|
|
Restart:
|
|
/* Check if we have tokens from another input source */
|
|
if (InputFromStack ()) {
|
|
return;
|
|
}
|
|
|
|
Again:
|
|
/* Skip whitespace, remember if we had some */
|
|
if ((WS = IsBlank (C)) != 0) {
|
|
do {
|
|
NextChar ();
|
|
} while (IsBlank (C));
|
|
}
|
|
|
|
/* If we're reading from the file, update the location from where the
|
|
* next token will be read. If we're reading from input data, keep the
|
|
* current position.
|
|
*/
|
|
if (IData == 0) {
|
|
CurPos = IFile->Pos;
|
|
}
|
|
|
|
/* Hex number or PC symbol? */
|
|
if (C == '$') {
|
|
NextChar ();
|
|
|
|
/* Hex digit must follow or DollarIsPC must be enabled */
|
|
if (!IsXDigit (C)) {
|
|
if (DollarIsPC) {
|
|
Tok = TOK_PC;
|
|
return;
|
|
} else {
|
|
Error ("Hexadecimal digit expected");
|
|
}
|
|
}
|
|
|
|
/* Read the number */
|
|
IVal = 0;
|
|
while (IsXDigit (C)) {
|
|
if (IVal & 0xF0000000) {
|
|
Error ("Overflow in hexadecimal number");
|
|
IVal = 0;
|
|
}
|
|
IVal = (IVal << 4) + DigitVal (C);
|
|
NextChar ();
|
|
}
|
|
|
|
/* This is an integer constant */
|
|
Tok = TOK_INTCON;
|
|
return;
|
|
}
|
|
|
|
/* Binary number? */
|
|
if (C == '%') {
|
|
NextChar ();
|
|
|
|
/* 0 or 1 must follow */
|
|
if (!IsBDigit (C)) {
|
|
Error ("Binary digit expected");
|
|
}
|
|
|
|
/* Read the number */
|
|
IVal = 0;
|
|
while (IsBDigit (C)) {
|
|
if (IVal & 0x80000000) {
|
|
Error ("Overflow in binary number");
|
|
IVal = 0;
|
|
}
|
|
IVal = (IVal << 1) + DigitVal (C);
|
|
NextChar ();
|
|
}
|
|
|
|
/* This is an integer constant */
|
|
Tok = TOK_INTCON;
|
|
return;
|
|
}
|
|
|
|
/* Number? */
|
|
if (IsDigit (C)) {
|
|
|
|
char Buf[16];
|
|
unsigned Digits;
|
|
unsigned Base;
|
|
unsigned I;
|
|
long Max;
|
|
unsigned DVal;
|
|
|
|
/* Ignore leading zeros */
|
|
while (C == '0') {
|
|
NextChar ();
|
|
}
|
|
|
|
/* Read the number into Buf counting the digits */
|
|
Digits = 0;
|
|
while (IsXDigit (C)) {
|
|
|
|
/* Buf is big enough to allow any decimal and hex number to
|
|
* overflow, so ignore excess digits here, they will be detected
|
|
* when we convert the value.
|
|
*/
|
|
if (Digits < sizeof (Buf)) {
|
|
Buf[Digits++] = C;
|
|
}
|
|
|
|
NextChar ();
|
|
}
|
|
|
|
/* Allow zilog/intel style hex numbers with a 'h' suffix */
|
|
if (C == 'h' || C == 'H') {
|
|
NextChar ();
|
|
Base = 16;
|
|
Max = 0xFFFFFFFFUL / 16;
|
|
} else {
|
|
Base = 10;
|
|
Max = 0xFFFFFFFFUL / 10;
|
|
}
|
|
|
|
/* Convert the number using the given base */
|
|
IVal = 0;
|
|
for (I = 0; I < Digits; ++I) {
|
|
if (IVal > Max) {
|
|
Error ("Number out of range");
|
|
IVal = 0;
|
|
break;
|
|
}
|
|
DVal = DigitVal (Buf[I]);
|
|
if (DVal > Base) {
|
|
Error ("Invalid digits in number");
|
|
IVal = 0;
|
|
break;
|
|
}
|
|
IVal = (IVal * Base) + DVal;
|
|
}
|
|
|
|
/* This is an integer constant */
|
|
Tok = TOK_INTCON;
|
|
return;
|
|
}
|
|
|
|
/* Control command? */
|
|
if (C == '.') {
|
|
|
|
/* Remember and skip the dot */
|
|
NextChar ();
|
|
|
|
/* Check if it's just a dot */
|
|
if (!IsIdStart (C)) {
|
|
|
|
/* Just a dot */
|
|
Tok = TOK_DOT;
|
|
|
|
} else {
|
|
|
|
/* Read the remainder of the identifier */
|
|
SVal[0] = '.';
|
|
ReadIdent (1);
|
|
|
|
/* Dot keyword, search for it */
|
|
Tok = FindDotKeyword ();
|
|
if (Tok == TOK_NONE) {
|
|
|
|
/* Not found */
|
|
if (!LeadingDotInIdents) {
|
|
/* Invalid pseudo instruction */
|
|
Error ("`%s' is not a recognized control command", SVal);
|
|
goto Again;
|
|
}
|
|
|
|
/* An identifier with a dot. Check if it's a define style
|
|
* macro.
|
|
*/
|
|
if (IsDefine (SVal)) {
|
|
/* This is a define style macro - expand it */
|
|
MacExpandStart ();
|
|
goto Restart;
|
|
}
|
|
|
|
/* Just an identifier with a dot */
|
|
Tok = TOK_IDENT;
|
|
}
|
|
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Indirect op for sweet16 cpu. Must check this before checking for local
|
|
* symbols, because these may also use the '@' symbol.
|
|
*/
|
|
if (CPU == CPU_SWEET16 && C == '@') {
|
|
NextChar ();
|
|
Tok = TOK_AT;
|
|
return;
|
|
}
|
|
|
|
/* Local symbol? */
|
|
if (C == LocalStart) {
|
|
|
|
/* Read the identifier */
|
|
ReadIdent (0);
|
|
|
|
/* Start character alone is not enough */
|
|
if (SVal [1] == '\0') {
|
|
Error ("Invalid cheap local symbol");
|
|
goto Again;
|
|
}
|
|
|
|
/* A local identifier */
|
|
Tok = TOK_LOCAL_IDENT;
|
|
return;
|
|
}
|
|
|
|
|
|
/* Identifier or keyword? */
|
|
if (IsIdStart (C)) {
|
|
|
|
/* Read the identifier */
|
|
ReadIdent (0);
|
|
|
|
/* Check for special names. Bail out if we have identified the type of
|
|
* the token. Go on if the token is an identifier.
|
|
*/
|
|
if (SVal[1] == '\0') {
|
|
switch (toupper (SVal [0])) {
|
|
|
|
case 'A':
|
|
if (C == ':') {
|
|
NextChar ();
|
|
Tok = TOK_OVERRIDE_ABS;
|
|
} else {
|
|
Tok = TOK_A;
|
|
}
|
|
return;
|
|
|
|
case 'F':
|
|
if (C == ':') {
|
|
NextChar ();
|
|
Tok = TOK_OVERRIDE_FAR;
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case 'S':
|
|
Tok = TOK_S;
|
|
return;
|
|
|
|
case 'X':
|
|
Tok = TOK_X;
|
|
return;
|
|
|
|
case 'Y':
|
|
Tok = TOK_Y;
|
|
return;
|
|
|
|
case 'Z':
|
|
if (C == ':') {
|
|
NextChar ();
|
|
Tok = TOK_OVERRIDE_ZP;
|
|
return;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
} else if (CPU == CPU_SWEET16 && (IVal = Sweet16Reg (SVal)) >= 0) {
|
|
|
|
/* A sweet16 register number in sweet16 mode */
|
|
Tok = TOK_REG;
|
|
return;
|
|
|
|
}
|
|
|
|
/* Check for define style macro */
|
|
if (IsDefine (SVal)) {
|
|
/* Macro - expand it */
|
|
MacExpandStart ();
|
|
goto Restart;
|
|
} else {
|
|
/* An identifier */
|
|
Tok = TOK_IDENT;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Ok, let's do the switch */
|
|
CharAgain:
|
|
switch (C) {
|
|
|
|
case '+':
|
|
NextChar ();
|
|
Tok = TOK_PLUS;
|
|
return;
|
|
|
|
case '-':
|
|
NextChar ();
|
|
Tok = TOK_MINUS;
|
|
return;
|
|
|
|
case '/':
|
|
NextChar ();
|
|
Tok = TOK_DIV;
|
|
return;
|
|
|
|
case '*':
|
|
NextChar ();
|
|
Tok = TOK_MUL;
|
|
return;
|
|
|
|
case '^':
|
|
NextChar ();
|
|
Tok = TOK_XOR;
|
|
return;
|
|
|
|
case '&':
|
|
NextChar ();
|
|
if (C == '&') {
|
|
NextChar ();
|
|
Tok = TOK_BOOLAND;
|
|
} else {
|
|
Tok = TOK_AND;
|
|
}
|
|
return;
|
|
|
|
case '|':
|
|
NextChar ();
|
|
if (C == '|') {
|
|
NextChar ();
|
|
Tok = TOK_BOOLOR;
|
|
} else {
|
|
Tok = TOK_OR;
|
|
}
|
|
return;
|
|
|
|
case ':':
|
|
NextChar ();
|
|
switch (C) {
|
|
|
|
case ':':
|
|
NextChar ();
|
|
Tok = TOK_NAMESPACE;
|
|
break;
|
|
|
|
case '-':
|
|
IVal = 0;
|
|
do {
|
|
--IVal;
|
|
NextChar ();
|
|
} while (C == '-');
|
|
Tok = TOK_ULABEL;
|
|
break;
|
|
|
|
case '+':
|
|
IVal = 0;
|
|
do {
|
|
++IVal;
|
|
NextChar ();
|
|
} while (C == '+');
|
|
Tok = TOK_ULABEL;
|
|
break;
|
|
|
|
case '=':
|
|
NextChar ();
|
|
Tok = TOK_ASSIGN;
|
|
break;
|
|
|
|
default:
|
|
Tok = TOK_COLON;
|
|
break;
|
|
}
|
|
return;
|
|
|
|
case ',':
|
|
NextChar ();
|
|
Tok = TOK_COMMA;
|
|
return;
|
|
|
|
case ';':
|
|
NextChar ();
|
|
while (C != '\n' && C != EOF) {
|
|
NextChar ();
|
|
}
|
|
goto CharAgain;
|
|
|
|
case '#':
|
|
NextChar ();
|
|
Tok = TOK_HASH;
|
|
return;
|
|
|
|
case '(':
|
|
NextChar ();
|
|
Tok = TOK_LPAREN;
|
|
return;
|
|
|
|
case ')':
|
|
NextChar ();
|
|
Tok = TOK_RPAREN;
|
|
return;
|
|
|
|
case '[':
|
|
NextChar ();
|
|
Tok = TOK_LBRACK;
|
|
return;
|
|
|
|
case ']':
|
|
NextChar ();
|
|
Tok = TOK_RBRACK;
|
|
return;
|
|
|
|
case '{':
|
|
NextChar ();
|
|
Tok = TOK_LCURLY;
|
|
return;
|
|
|
|
case '}':
|
|
NextChar ();
|
|
Tok = TOK_RCURLY;
|
|
return;
|
|
|
|
case '<':
|
|
NextChar ();
|
|
if (C == '=') {
|
|
NextChar ();
|
|
Tok = TOK_LE;
|
|
} else if (C == '<') {
|
|
NextChar ();
|
|
Tok = TOK_SHL;
|
|
} else if (C == '>') {
|
|
NextChar ();
|
|
Tok = TOK_NE;
|
|
} else {
|
|
Tok = TOK_LT;
|
|
}
|
|
return;
|
|
|
|
case '=':
|
|
NextChar ();
|
|
Tok = TOK_EQ;
|
|
return;
|
|
|
|
case '!':
|
|
NextChar ();
|
|
Tok = TOK_BOOLNOT;
|
|
return;
|
|
|
|
case '>':
|
|
NextChar ();
|
|
if (C == '=') {
|
|
NextChar ();
|
|
Tok = TOK_GE;
|
|
} else if (C == '>') {
|
|
NextChar ();
|
|
Tok = TOK_SHR;
|
|
} else {
|
|
Tok = TOK_GT;
|
|
}
|
|
return;
|
|
|
|
case '~':
|
|
NextChar ();
|
|
Tok = TOK_NOT;
|
|
return;
|
|
|
|
case '\'':
|
|
/* Hack: If we allow ' as terminating character for strings, read
|
|
* the following stuff as a string, and check for a one character
|
|
* string later.
|
|
*/
|
|
if (LooseStringTerm) {
|
|
if (ReadStringConst ('\'') == 1) {
|
|
IVal = SVal[0];
|
|
Tok = TOK_CHARCON;
|
|
} else {
|
|
Tok = TOK_STRCON;
|
|
}
|
|
} else {
|
|
/* Always a character constant */
|
|
NextChar ();
|
|
if (C == EOF || IsControl (C)) {
|
|
Error ("Illegal character constant");
|
|
goto CharAgain;
|
|
}
|
|
IVal = C;
|
|
Tok = TOK_CHARCON;
|
|
NextChar ();
|
|
if (C != '\'') {
|
|
if (!MissingCharTerm) {
|
|
Error ("Illegal character constant");
|
|
}
|
|
} else {
|
|
NextChar ();
|
|
}
|
|
}
|
|
return;
|
|
|
|
case '\"':
|
|
ReadStringConst ('\"');
|
|
Tok = TOK_STRCON;
|
|
return;
|
|
|
|
case '\\':
|
|
/* Line continuation? */
|
|
if (LineCont) {
|
|
NextChar ();
|
|
if (C == '\n') {
|
|
/* Handle as white space */
|
|
NextChar ();
|
|
C = ' ';
|
|
goto Again;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case '\n':
|
|
NextChar ();
|
|
Tok = TOK_SEP;
|
|
return;
|
|
|
|
case EOF:
|
|
/* Check if we have any open .IFs in this file */
|
|
CheckOpenIfs ();
|
|
/* Check if we have any open token lists in this file */
|
|
CheckInputStack ();
|
|
|
|
/* If this was an include file, then close it and read the next
|
|
* token. When an include file is opened, the last token of the
|
|
* old file is not skipped, to prevent the lookahead to read
|
|
* the next line of the old input file. So we do effectively
|
|
* skip the last token in the old file (the file name of the
|
|
* include statement).
|
|
* In case of the main file, do not close it, but return EOF.
|
|
*/
|
|
if (IData) {
|
|
/* Input came from internal data */
|
|
DoneInputData ();
|
|
goto Again;
|
|
} else if (ICount > 1) {
|
|
DoneInputFile ();
|
|
goto Again;
|
|
} else {
|
|
Tok = TOK_EOF;
|
|
}
|
|
return;
|
|
|
|
}
|
|
|
|
/* If we go here, we could not identify the current character. Skip it
|
|
* and try again.
|
|
*/
|
|
Error ("Invalid input character: 0x%02X", C & 0xFF);
|
|
NextChar ();
|
|
goto Again;
|
|
}
|
|
|
|
|
|
|
|
int TokHasSVal (enum Token Tok)
|
|
/* Return true if the given token has an attached SVal */
|
|
{
|
|
return (Tok == TOK_IDENT || TOK_LOCAL_IDENT || Tok == TOK_STRCON);
|
|
}
|
|
|
|
|
|
|
|
int TokHasIVal (enum Token Tok)
|
|
/* Return true if the given token has an attached IVal */
|
|
{
|
|
return (Tok == TOK_INTCON || Tok == TOK_CHARCON || Tok == TOK_REG);
|
|
}
|
|
|
|
|
|
|
|
int GetSubKey (const char** Keys, unsigned Count)
|
|
/* Search for a subkey in a table of keywords. The current token must be an
|
|
* identifier and all keys must be in upper case. The identifier will be
|
|
* uppercased in the process. The function returns the index of the keyword,
|
|
* or -1 if the keyword was not found.
|
|
*/
|
|
{
|
|
unsigned I;
|
|
|
|
/* Must have an identifier */
|
|
PRECONDITION (Tok == TOK_IDENT);
|
|
|
|
/* If we aren't in ignore case mode, we have to uppercase the identifier */
|
|
if (!IgnoreCase) {
|
|
UpcaseSVal ();
|
|
}
|
|
|
|
/* Do a linear search (a binary search is not worth the effort) */
|
|
for (I = 0; I < Count; ++I) {
|
|
if (strcmp (SVal, Keys [I]) == 0) {
|
|
/* Found it */
|
|
return I;
|
|
}
|
|
}
|
|
|
|
/* Not found */
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
unsigned char ParseAddrSize (void)
|
|
/* Check if the next token is a keyword that denotes an address size specifier.
|
|
* If so, return the corresponding address size constant, otherwise output an
|
|
* error message and return ADDR_SIZE_DEFAULT.
|
|
*/
|
|
{
|
|
static const char* Keys[] = {
|
|
"DIRECT", "ZEROPAGE", "ZP",
|
|
"ABSOLUTE", "ABS", "NEAR",
|
|
"FAR",
|
|
"LONG", "DWORD",
|
|
};
|
|
|
|
/* Check for an identifier */
|
|
if (Tok != TOK_IDENT) {
|
|
Error ("Address size specifier expected");
|
|
return ADDR_SIZE_DEFAULT;
|
|
}
|
|
|
|
/* Search for the attribute */
|
|
switch (GetSubKey (Keys, sizeof (Keys) / sizeof (Keys [0]))) {
|
|
case 0:
|
|
case 1:
|
|
case 2: return ADDR_SIZE_ZP;
|
|
case 3:
|
|
case 4:
|
|
case 5: return ADDR_SIZE_ABS;
|
|
case 6: return ADDR_SIZE_FAR;
|
|
case 7:
|
|
case 8: return ADDR_SIZE_LONG;
|
|
default:
|
|
Error ("Address size specifier expected");
|
|
return ADDR_SIZE_DEFAULT;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void InitScanner (const char* InFile)
|
|
/* Initialize the scanner, open the given input file */
|
|
{
|
|
/* Open the input file */
|
|
NewInputFile (InFile);
|
|
}
|
|
|
|
|
|
|
|
void DoneScanner (void)
|
|
/* Release scanner resources */
|
|
{
|
|
DoneInputFile ();
|
|
}
|
|
|
|
|
|
|