/*****************************************************************************/ /* */ /* scanner.c */ /* */ /* Configuration file scanner for the ld65 linker */ /* */ /* */ /* */ /* (C) 1998-2013, Ullrich von Bassewitz */ /* Roemerstrasse 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 #include #include #include #include /* common */ #include "chartype.h" #include "strbuf.h" #include "xsprintf.h" /* ld65 */ #include "global.h" #include "error.h" #include "scanner.h" #include "spool.h" /*****************************************************************************/ /* Data */ /*****************************************************************************/ /* Current token and attributes */ cfgtok_t CfgTok; StrBuf CfgSVal = STATIC_STRBUF_INITIALIZER; unsigned long CfgIVal; /* Error location */ FilePos CfgErrorPos; /* Input source for the configuration */ static const char* CfgName = 0; /* Other input stuff */ static int C = ' '; static FilePos InputPos; static FILE* InputFile = 0; /*****************************************************************************/ /* Error handling */ /*****************************************************************************/ void CfgWarning (const FilePos* Pos, const char* Format, ...) /* Print a warning message adding file name and line number of a given file */ { StrBuf Buf = STATIC_STRBUF_INITIALIZER; va_list ap; va_start (ap, Format); SB_VPrintf (&Buf, Format, ap); va_end (ap); Warning ("%s:%u: %s", GetString (Pos->Name), Pos->Line, SB_GetConstBuf (&Buf)); SB_Done (&Buf); } void CfgError (const FilePos* Pos, const char* Format, ...) /* Print an error message adding file name and line number of a given file */ { StrBuf Buf = STATIC_STRBUF_INITIALIZER; va_list ap; va_start (ap, Format); SB_VPrintf (&Buf, Format, ap); va_end (ap); Error ("%s:%u: %s", GetString (Pos->Name), Pos->Line, SB_GetConstBuf (&Buf)); SB_Done (&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) { ++InputPos.Col; } /* Count lines */ if (C == '\n') { ++InputPos.Line; InputPos.Col = 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; } } static void StrVal (void) /* Parse a string value and expand escape sequences */ { /* Skip the starting double quotes */ NextChar (); /* Read input chars */ SB_Clear (&CfgSVal); while (C != '\"') { switch (C) { case EOF: case '\n': CfgError (&CfgErrorPos, "Unterminated string"); break; case '%': NextChar (); switch (C) { case EOF: case '\n': case '\"': CfgError (&CfgErrorPos, "Unterminated '%%' escape sequence"); break; case '%': SB_AppendChar (&CfgSVal, '%'); NextChar (); break; case 'O': /* Replace by output file */ if (OutputName) { SB_AppendStr (&CfgSVal, OutputName); } OutputNameUsed = 1; NextChar (); break; default: CfgWarning (&CfgErrorPos, "Unkown escape sequence '%%%c'", C); SB_AppendChar (&CfgSVal, '%'); SB_AppendChar (&CfgSVal, C); NextChar (); break; } break; default: SB_AppendChar (&CfgSVal, C); NextChar (); } } /* Skip the terminating double quotes */ NextChar (); /* Terminate the string */ SB_Terminate (&CfgSVal); /* We've read a string value */ CfgTok = CFGTOK_STRCON; } void CfgNextTok (void) /* Read the next token from the input stream */ { Again: /* Skip whitespace */ while (isspace (C)) { NextChar (); } /* Remember the current position */ CfgErrorPos = InputPos; /* Identifier? */ if (C == '_' || IsAlpha (C)) { /* Read the identifier */ SB_Clear (&CfgSVal); while (C == '_' || IsAlNum (C)) { SB_AppendChar (&CfgSVal, C); NextChar (); } SB_Terminate (&CfgSVal); CfgTok = CFGTOK_IDENT; return; } /* Hex number? */ if (C == '$') { NextChar (); if (!isxdigit (C)) { CfgError (&CfgErrorPos, "Hex digit expected"); } CfgIVal = 0; while (isxdigit (C)) { CfgIVal = CfgIVal * 16 + DigitVal (C); NextChar (); } CfgTok = CFGTOK_INTCON; return; } /* Decimal number? */ if (isdigit (C)) { CfgIVal = 0; while (isdigit (C)) { CfgIVal = CfgIVal * 10 + DigitVal (C); NextChar (); } CfgTok = CFGTOK_INTCON; return; } /* Other characters */ switch (C) { case '-': NextChar (); CfgTok = CFGTOK_MINUS; break; case '+': NextChar (); CfgTok = CFGTOK_PLUS; break; case '*': NextChar (); CfgTok = CFGTOK_MUL; break; case '/': NextChar (); CfgTok = CFGTOK_DIV; break; case '(': NextChar (); CfgTok = CFGTOK_LPAR; break; case ')': NextChar (); CfgTok = CFGTOK_RPAR; break; case '{': NextChar (); CfgTok = CFGTOK_LCURLY; break; case '}': NextChar (); CfgTok = CFGTOK_RCURLY; break; case ';': NextChar (); CfgTok = CFGTOK_SEMI; break; case '.': NextChar (); CfgTok = CFGTOK_DOT; break; case ',': NextChar (); CfgTok = CFGTOK_COMMA; break; case '=': NextChar (); CfgTok = CFGTOK_EQ; break; case ':': NextChar (); CfgTok = CFGTOK_COLON; break; case '\"': StrVal (); break; case '#': /* Comment */ while (C != '\n' && C != EOF) { NextChar (); } if (C != EOF) { goto Again; } CfgTok = CFGTOK_EOF; break; case '%': NextChar (); switch (C) { case 'O': NextChar (); if (OutputName) { SB_CopyStr (&CfgSVal, OutputName); } else { SB_Clear (&CfgSVal); } SB_Terminate (&CfgSVal); OutputNameUsed = 1; CfgTok = CFGTOK_STRCON; break; case 'S': NextChar (); CfgIVal = StartAddr; CfgTok = CFGTOK_INTCON; break; default: CfgError (&CfgErrorPos, "Invalid format specification"); } break; case EOF: CfgTok = CFGTOK_EOF; break; default: CfgError (&CfgErrorPos, "Invalid character '%c'", C); } } void CfgConsume (cfgtok_t T, const char* Msg) /* Skip a token, print an error message if not found */ { if (CfgTok != T) { CfgError (&CfgErrorPos, "%s", Msg); } CfgNextTok (); } void CfgConsumeSemi (void) /* Consume a semicolon */ { CfgConsume (CFGTOK_SEMI, "';' expected"); } void CfgConsumeColon (void) /* Consume a colon */ { CfgConsume (CFGTOK_COLON, "':' expected"); } void CfgOptionalComma (void) /* Consume a comma if there is one */ { if (CfgTok == CFGTOK_COMMA) { CfgNextTok (); } } void CfgOptionalAssign (void) /* Consume an equal sign if there is one */ { if (CfgTok == CFGTOK_EQ) { CfgNextTok (); } } void CfgAssureInt (void) /* Make sure the next token is an integer */ { if (CfgTok != CFGTOK_INTCON) { CfgError (&CfgErrorPos, "Integer constant expected"); } } void CfgAssureStr (void) /* Make sure the next token is a string constant */ { if (CfgTok != CFGTOK_STRCON) { CfgError (&CfgErrorPos, "String constant expected"); } } void CfgAssureIdent (void) /* Make sure the next token is an identifier */ { if (CfgTok != CFGTOK_IDENT) { CfgError (&CfgErrorPos, "Identifier expected"); } } void CfgRangeCheck (unsigned long Lo, unsigned long Hi) /* Check the range of CfgIVal */ { if (CfgIVal < Lo || CfgIVal > Hi) { CfgError (&CfgErrorPos, "Range error"); } } void CfgSpecialToken (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 (CfgTok == CFGTOK_IDENT) { /* Make it upper case */ SB_ToUpper (&CfgSVal); /* Linear search */ for (I = 0; I < Size; ++I) { if (SB_CompareStr (&CfgSVal, Table[I].Ident) == 0) { CfgTok = Table[I].Tok; return; } } /* Not found */ CfgError (&CfgErrorPos, "%s expected, got '%s'", Name, SB_GetConstBuf(&CfgSVal)); return; } /* No identifier */ CfgError (&CfgErrorPos, "%s expected", Name); } void CfgBoolToken (void) /* Map an identifier or integer to a boolean token */ { static const IdentTok Booleans [] = { { "YES", CFGTOK_TRUE }, { "NO", CFGTOK_FALSE }, { "TRUE", CFGTOK_TRUE }, { "FALSE", CFGTOK_FALSE }, }; /* If we have an identifier, map it to a boolean token */ if (CfgTok == CFGTOK_IDENT) { CfgSpecialToken (Booleans, ENTRY_COUNT (Booleans), "Boolean"); } else { /* We expected an integer here */ if (CfgTok != CFGTOK_INTCON) { CfgError (&CfgErrorPos, "Boolean value expected"); } CfgTok = (CfgIVal == 0)? CFGTOK_FALSE : CFGTOK_TRUE; } } void CfgSetName (const char* Name) /* Set a name for a config file */ { CfgName = Name; } int CfgAvail (void) /* Return true if we have a configuration available */ { return CfgName != 0; } void CfgOpenInput (void) /* Open the input file if we have one */ { /* Open the file */ InputFile = fopen (CfgName, "r"); if (InputFile == 0) { Error ("Cannot open '%s': %s", CfgName, strerror (errno)); } /* Initialize variables */ C = ' '; InputPos.Line = 1; InputPos.Col = 0; InputPos.Name = GetStringId (CfgName); /* Start the ball rolling ... */ CfgNextTok (); } void CfgCloseInput (void) /* Close the input file if we have one */ { /* Close the input file if we had one */ if (InputFile) { (void) fclose (InputFile); InputFile = 0; } }