/*****************************************************************************/
/*                                                                           */
/*                                 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;
    }
}




