e814c484c3
git-svn-id: svn://svn.cc65.org/cc65/trunk@3388 b7a2c559-68d2-44c3-8de9-860c34a00d81
539 lines
12 KiB
C
539 lines
12 KiB
C
/*****************************************************************************/
|
|
/* */
|
|
/* scanner.c */
|
|
/* */
|
|
/* Configuration file scanner for the da65 disassembler */
|
|
/* */
|
|
/* */
|
|
/* */
|
|
/* (C) 2000-2005 Ullrich von Bassewitz */
|
|
/* Römerstrasse 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 <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
/* common */
|
|
#include "chartype.h"
|
|
#include "xsprintf.h"
|
|
|
|
/* ld65 */
|
|
#include "global.h"
|
|
#include "error.h"
|
|
#include "scanner.h"
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* Data */
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
/* Current token and attributes */
|
|
unsigned InfoTok;
|
|
char InfoSVal [CFG_MAX_IDENT_LEN+1];
|
|
long InfoIVal;
|
|
|
|
/* Error location */
|
|
unsigned InfoErrorLine;
|
|
unsigned InfoErrorCol;
|
|
|
|
/* Input sources for the configuration */
|
|
static const char* InfoFile = 0;
|
|
|
|
/* Other input stuff */
|
|
static int C = ' ';
|
|
static unsigned InputLine = 1;
|
|
static unsigned InputCol = 0;
|
|
static FILE* InputFile = 0;
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* Error handling */
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
void InfoWarning (const char* Format, ...)
|
|
/* Print a warning message adding file name and line number of the config file */
|
|
{
|
|
char Buf [512];
|
|
va_list ap;
|
|
|
|
va_start (ap, Format);
|
|
xvsprintf (Buf, sizeof (Buf), Format, ap);
|
|
va_end (ap);
|
|
|
|
Warning ("%s(%u): %s", InfoFile, InfoErrorLine, Buf);
|
|
}
|
|
|
|
|
|
|
|
void InfoError (const char* Format, ...)
|
|
/* Print an error message adding file name and line number of the config file */
|
|
{
|
|
char Buf [512];
|
|
va_list ap;
|
|
|
|
va_start (ap, Format);
|
|
xvsprintf (Buf, sizeof (Buf), Format, ap);
|
|
va_end (ap);
|
|
|
|
Error ("%s(%u): %s", InfoFile, InfoErrorLine, Buf);
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* Code */
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
static void NextChar (void)
|
|
/* Read the next character from the input file */
|
|
{
|
|
/* Read from the file */
|
|
C = getc (InputFile);
|
|
|
|
/* Count columns */
|
|
if (C != EOF) {
|
|
++InputCol;
|
|
}
|
|
|
|
/* Count lines */
|
|
if (C == '\n') {
|
|
++InputLine;
|
|
InputCol = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static unsigned DigitVal (int C)
|
|
/* Return the value for a numeric digit */
|
|
{
|
|
if (IsDigit (C)) {
|
|
return C - '0';
|
|
} else {
|
|
return toupper (C) - 'A' + 10;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void InfoNextTok (void)
|
|
/* Read the next token from the input stream */
|
|
{
|
|
unsigned I;
|
|
int Esc;
|
|
|
|
Again:
|
|
/* Skip whitespace */
|
|
while (IsSpace (C)) {
|
|
NextChar ();
|
|
}
|
|
|
|
/* Remember the current position */
|
|
InfoErrorLine = InputLine;
|
|
InfoErrorCol = InputCol;
|
|
|
|
/* Identifier? */
|
|
if (C == '_' || IsAlpha (C)) {
|
|
|
|
/* Read the identifier */
|
|
I = 0;
|
|
while (C == '_' || IsAlNum (C)) {
|
|
if (I < CFG_MAX_IDENT_LEN) {
|
|
InfoSVal [I++] = C;
|
|
}
|
|
NextChar ();
|
|
}
|
|
InfoSVal [I] = '\0';
|
|
InfoTok = INFOTOK_IDENT;
|
|
return;
|
|
}
|
|
|
|
/* Hex number? */
|
|
if (C == '$') {
|
|
NextChar ();
|
|
if (!IsXDigit (C)) {
|
|
InfoError ("Hex digit expected");
|
|
}
|
|
InfoIVal = 0;
|
|
while (IsXDigit (C)) {
|
|
InfoIVal = InfoIVal * 16 + DigitVal (C);
|
|
NextChar ();
|
|
}
|
|
InfoTok = INFOTOK_INTCON;
|
|
return;
|
|
}
|
|
|
|
/* Decimal number? */
|
|
if (IsDigit (C)) {
|
|
InfoIVal = 0;
|
|
while (IsDigit (C)) {
|
|
InfoIVal = InfoIVal * 10 + DigitVal (C);
|
|
NextChar ();
|
|
}
|
|
InfoTok = INFOTOK_INTCON;
|
|
return;
|
|
}
|
|
|
|
/* Other characters */
|
|
switch (C) {
|
|
|
|
case '{':
|
|
NextChar ();
|
|
InfoTok = INFOTOK_LCURLY;
|
|
break;
|
|
|
|
case '}':
|
|
NextChar ();
|
|
InfoTok = INFOTOK_RCURLY;
|
|
break;
|
|
|
|
case ';':
|
|
NextChar ();
|
|
InfoTok = INFOTOK_SEMI;
|
|
break;
|
|
|
|
case '.':
|
|
NextChar ();
|
|
InfoTok = INFOTOK_DOT;
|
|
break;
|
|
|
|
case ',':
|
|
NextChar ();
|
|
InfoTok = INFOTOK_COMMA;
|
|
break;
|
|
|
|
case '=':
|
|
NextChar ();
|
|
InfoTok = INFOTOK_EQ;
|
|
break;
|
|
|
|
case ':':
|
|
NextChar ();
|
|
InfoTok = INFOTOK_COLON;
|
|
break;
|
|
|
|
case '\"':
|
|
NextChar ();
|
|
I = 0;
|
|
while (C != '\"') {
|
|
Esc = (C == '\\');
|
|
if (Esc) {
|
|
NextChar ();
|
|
}
|
|
if (C == EOF || C == '\n') {
|
|
InfoError ("Unterminated string");
|
|
}
|
|
if (Esc) {
|
|
switch (C) {
|
|
case '\"': C = '\"'; break;
|
|
case '\'': C = '\''; break;
|
|
default: InfoError ("Invalid escape char: %c", C);
|
|
}
|
|
}
|
|
if (I < CFG_MAX_IDENT_LEN) {
|
|
InfoSVal [I++] = C;
|
|
}
|
|
NextChar ();
|
|
}
|
|
NextChar ();
|
|
InfoSVal [I] = '\0';
|
|
InfoTok = INFOTOK_STRCON;
|
|
break;
|
|
|
|
case '\'':
|
|
NextChar ();
|
|
if (C == EOF || IsControl (C)) {
|
|
InfoError ("Invalid character constant");
|
|
}
|
|
InfoIVal = C;
|
|
NextChar ();
|
|
if (C != '\'') {
|
|
InfoError ("Unterminated character constant");
|
|
}
|
|
NextChar ();
|
|
InfoTok = INFOTOK_CHARCON;
|
|
break;
|
|
|
|
case '#':
|
|
/* Comment */
|
|
while (C != '\n' && C != EOF) {
|
|
NextChar ();
|
|
}
|
|
if (C != EOF) {
|
|
goto Again;
|
|
}
|
|
InfoTok = INFOTOK_EOF;
|
|
break;
|
|
|
|
case EOF:
|
|
InfoTok = INFOTOK_EOF;
|
|
break;
|
|
|
|
default:
|
|
InfoError ("Invalid character `%c'", C);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void InfoConsume (unsigned T, const char* Msg)
|
|
/* Skip a token, print an error message if not found */
|
|
{
|
|
if (InfoTok != T) {
|
|
InfoError (Msg);
|
|
}
|
|
InfoNextTok ();
|
|
}
|
|
|
|
|
|
|
|
void InfoConsumeLCurly (void)
|
|
/* Consume a left curly brace */
|
|
{
|
|
InfoConsume (INFOTOK_LCURLY, "`{' expected");
|
|
}
|
|
|
|
|
|
|
|
void InfoConsumeRCurly (void)
|
|
/* Consume a right curly brace */
|
|
{
|
|
InfoConsume (INFOTOK_RCURLY, "`}' expected");
|
|
}
|
|
|
|
|
|
|
|
void InfoConsumeSemi (void)
|
|
/* Consume a semicolon */
|
|
{
|
|
InfoConsume (INFOTOK_SEMI, "`;' expected");
|
|
}
|
|
|
|
|
|
|
|
void InfoConsumeColon (void)
|
|
/* Consume a colon */
|
|
{
|
|
InfoConsume (INFOTOK_COLON, "`:' expected");
|
|
}
|
|
|
|
|
|
|
|
void InfoOptionalComma (void)
|
|
/* Consume a comma if there is one */
|
|
{
|
|
if (InfoTok == INFOTOK_COMMA) {
|
|
InfoNextTok ();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void InfoOptionalAssign (void)
|
|
/* Consume an equal sign if there is one */
|
|
{
|
|
if (InfoTok == INFOTOK_EQ) {
|
|
InfoNextTok ();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void InfoAssureInt (void)
|
|
/* Make sure the next token is an integer */
|
|
{
|
|
if (InfoTok != INFOTOK_INTCON) {
|
|
InfoError ("Integer constant expected");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void InfoAssureStr (void)
|
|
/* Make sure the next token is a string constant */
|
|
{
|
|
if (InfoTok != INFOTOK_STRCON) {
|
|
InfoError ("String constant expected");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void InfoAssureChar (void)
|
|
/* Make sure the next token is a char constant */
|
|
{
|
|
if (InfoTok != INFOTOK_STRCON) {
|
|
InfoError ("Character constant expected");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void InfoAssureIdent (void)
|
|
/* Make sure the next token is an identifier */
|
|
{
|
|
if (InfoTok != INFOTOK_IDENT) {
|
|
InfoError ("Identifier expected");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void InfoRangeCheck (long Lo, long Hi)
|
|
/* Check the range of InfoIVal */
|
|
{
|
|
if (InfoIVal < Lo || InfoIVal > Hi) {
|
|
InfoError ("Range error");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void InfoSpecialToken (const IdentTok* Table, unsigned Size, const char* Name)
|
|
/* Map an identifier to one of the special tokens in the table */
|
|
{
|
|
unsigned I;
|
|
|
|
/* We need an identifier */
|
|
if (InfoTok == INFOTOK_IDENT) {
|
|
|
|
/* Make it upper case */
|
|
I = 0;
|
|
while (InfoSVal [I]) {
|
|
InfoSVal [I] = toupper (InfoSVal [I]);
|
|
++I;
|
|
}
|
|
|
|
/* Linear search */
|
|
for (I = 0; I < Size; ++I) {
|
|
if (strcmp (InfoSVal, Table [I].Ident) == 0) {
|
|
InfoTok = Table [I].Tok;
|
|
return;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* Not found or no identifier */
|
|
InfoError ("%s expected", Name);
|
|
}
|
|
|
|
|
|
|
|
void InfoBoolToken (void)
|
|
/* Map an identifier or integer to a boolean token */
|
|
{
|
|
static const IdentTok Booleans [] = {
|
|
{ "YES", INFOTOK_TRUE },
|
|
{ "NO", INFOTOK_FALSE },
|
|
{ "TRUE", INFOTOK_TRUE },
|
|
{ "FALSE", INFOTOK_FALSE },
|
|
{ "ON", INFOTOK_TRUE },
|
|
{ "OFF", INFOTOK_FALSE },
|
|
};
|
|
|
|
/* If we have an identifier, map it to a boolean token */
|
|
if (InfoTok == INFOTOK_IDENT) {
|
|
InfoSpecialToken (Booleans, ENTRY_COUNT (Booleans), "Boolean");
|
|
} else {
|
|
/* We expected an integer here */
|
|
if (InfoTok != INFOTOK_INTCON) {
|
|
InfoError ("Boolean value expected");
|
|
}
|
|
InfoTok = (InfoIVal == 0)? INFOTOK_FALSE : INFOTOK_TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void InfoSetName (const char* Name)
|
|
/* Set a name for a config file */
|
|
{
|
|
InfoFile = Name;
|
|
}
|
|
|
|
|
|
|
|
const char* InfoGetName (void)
|
|
/* Get the name of the config file */
|
|
{
|
|
return InfoFile? InfoFile : "";
|
|
}
|
|
|
|
|
|
|
|
int InfoAvail ()
|
|
/* Return true if we have an info file given */
|
|
{
|
|
return (InfoFile != 0);
|
|
}
|
|
|
|
|
|
|
|
void InfoOpenInput (void)
|
|
/* Open the input file */
|
|
{
|
|
/* Open the file */
|
|
InputFile = fopen (InfoFile, "r");
|
|
if (InputFile == 0) {
|
|
Error ("Cannot open `%s': %s", InfoFile, strerror (errno));
|
|
}
|
|
|
|
/* Initialize variables */
|
|
C = ' ';
|
|
InputLine = 1;
|
|
InputCol = 0;
|
|
|
|
/* Start the ball rolling ... */
|
|
InfoNextTok ();
|
|
}
|
|
|
|
|
|
|
|
void InfoCloseInput (void)
|
|
/* Close the input file if we have one */
|
|
{
|
|
/* Close the input file if we had one */
|
|
if (InputFile) {
|
|
(void) fclose (InputFile);
|
|
InputFile = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|