/*****************************************************************************/ /* */ /* preproc.c */ /* */ /* cc65 preprocessor */ /* */ /* */ /* */ /* (C) 1998-2010, 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 /* common */ #include "chartype.h" #include "check.h" #include "inline.h" #include "print.h" #include "xmalloc.h" /* cc65 */ #include "codegen.h" #include "error.h" #include "global.h" #include "ident.h" #include "incpath.h" #include "input.h" #include "lineinfo.h" #include "macrotab.h" #include "ppexpr.h" #include "preproc.h" #include "scanner.h" #include "scanstrbuf.h" #include "standard.h" /*****************************************************************************/ /* Data */ /*****************************************************************************/ /* Macro scanner mode flags */ #define MSM_NONE 0x00U /* Default */ #define MSM_MULTILINE 0x01U /* Read from multiple lines */ #define MSM_IN_DIRECTIVE 0x02U /* In PP directives scan */ #define MSM_IN_ARG_LIST 0x04U /* In macro argument scan */ #define MSM_IN_ARG_EXPANSION 0x08U /* In expansion on arguments */ #define MSM_OP_DEFINED 0x10U /* Handle the "defined" operator */ #define MSM_OP_HAS_INCLUDE 0x20U /* Handle the "__has_include" operator */ #define MSM_OP_HAS_C_ATTRIBUTE 0x40U /* Handle the "__has_c_attribute" operator */ #define MSM_TOK_HEADER 0x80U /* Support header tokens */ /* Macro expansion state flags */ #define MES_NONE 0x00U /* Nothing */ #define MES_FIRST_TOKEN 0x01U /* Mark for detecting pp-token count in the sequence */ #define MES_MULTIPLE_TOKEN 0x02U /* Multiple pp-tokens are detected in the sequence */ #define MES_BEGIN_WITH_IDENT 0x04U /* The first pp-token of the sequence is an identifier */ #define MES_HAS_REPLACEMENT 0x10U /* Macro argument has cached replacement result */ #define MES_NO_VA_COMMA 0x20U /* Variadic macro called w/o the ',' in front of variable argument */ #define MES_ERROR 0x80U /* Error has occurred in macro expansion */ /* Management data for #if */ #define IFCOND_NONE 0x00U #define IFCOND_SKIP 0x01U #define IFCOND_ELSE 0x02U #define IFCOND_NEEDTERM 0x04U /* Current PP if stack */ static PPIfStack* PPStack; /* Struct for rescan */ typedef struct RescanInputStack RescanInputStack; struct RescanInputStack { Collection Lines; Collection LastTokLens; StrBuf* PrevTok; }; /* Input backup for rescan */ static RescanInputStack* CurRescanStack; /* Intermediate input buffers */ static StrBuf* PLine; /* Buffer for macro expansion */ static StrBuf* MLine; /* Buffer for macro expansion in #pragma */ static StrBuf* OLine; /* Buffer for #pragma output */ /* Newlines to be added to preprocessed text */ static unsigned PendingNewLines; static unsigned ContinuedLines; static int FileChanged; /* Structure used when expanding macros */ typedef struct MacroExp MacroExp; struct MacroExp { Collection Args; /* Actual arguments (for function-like) */ Collection HideSets; /* Macros hidden from expansion */ StrBuf Tokens; /* Originally read sequence */ unsigned IdentCount; /* Count of identifiers in the pp-token sequence */ unsigned Flags; /* Macro argument flags */ MacroExp* Replaced; /* Macro-replaced version of this pp-token sequence */ unsigned FirstTokLen; /* Length of the first pp-token */ unsigned LastTokLen; /* Length of the last pp-token */ }; typedef struct HideRange HideRange; struct HideRange { HideRange* Next; unsigned Start; unsigned End; }; typedef struct HiddenMacro HiddenMacro; struct HiddenMacro { const Macro* M; HideRange* HS; }; /*****************************************************************************/ /* Forwards */ /*****************************************************************************/ static void TranslationPhase3 (StrBuf* Source, StrBuf* Target); /* Mimic Translation Phase 3. Handle old and new style comments. Collapse ** non-newline whitespace sequences. */ static void PreprocessDirective (StrBuf* Source, StrBuf* Target, unsigned ModeFlags); /* Preprocess a single line. Handle specified tokens and operators, remove ** whitespace and comments, then do macro replacement. */ static int ParseDirectives (unsigned ModeFlags); /* Handle directives. Return 1 if any whitespace or newlines are parsed. */ static unsigned ReplaceMacros (StrBuf* Source, StrBuf* Target, MacroExp* E, unsigned ModeFlags); /* Scan for and perform macro replacement. Return the count of identifiers and ** right parentheses in the replacement result. */ static MacroExp* InitMacroExp (MacroExp* E); /* Initialize a MacroExp structure */ static void DoneMacroExp (MacroExp* E); /* Cleanup after use of a MacroExp structure */ static int CheckPastePPTok (StrBuf* Target, unsigned TokLen, char Next); /* Return 1 if the last pp-tokens from Source could be concatenated with any ** characters from Appended to form a new valid one. */ static void LazyCheckNextPPTok (const StrBuf* Prev, unsigned LastTokLen); /* Memorize the previous pp-token(s) to later check for potential pp-token ** concatenation. */ /*****************************************************************************/ /* Low level preprocessor token handling */ /*****************************************************************************/ /* Types of preprocessor directives */ typedef enum { PPD_ILLEGAL = -1, PPD_DEFINE, PPD_ELIF, PPD_ELSE, PPD_ENDIF, PPD_ERROR, PPD_IF, PPD_IFDEF, PPD_IFNDEF, PPD_INCLUDE, PPD_LINE, PPD_PRAGMA, PPD_UNDEF, PPD_WARNING, } ppdirective_t; /* Preprocessor directive tokens mapping table */ static const struct PPDType { const char* Tok; /* Token */ ppdirective_t Type; /* Type */ } PPDTypes[] = { { "define", PPD_DEFINE }, { "elif", PPD_ELIF }, { "else", PPD_ELSE }, { "endif", PPD_ENDIF }, { "error", PPD_ERROR }, { "if", PPD_IF }, { "ifdef", PPD_IFDEF }, { "ifndef", PPD_IFNDEF }, { "include", PPD_INCLUDE }, { "line", PPD_LINE }, { "pragma", PPD_PRAGMA }, { "undef", PPD_UNDEF }, { "warning", PPD_WARNING }, }; /* Number of preprocessor directive types */ #define PPDTOKEN_COUNT (sizeof(PPDTypes) / sizeof(PPDTypes[0])) static int CmpToken (const void* Key, const void* Elem) /* Compare function for bsearch */ { return strcmp ((const char*) Key, ((const struct PPDType*) Elem)->Tok); } static ppdirective_t FindPPDirectiveType (const char* Ident) /* Find a preprocessor directive type and return it. Return PPD_ILLEGAL if the ** identifier is not a valid preprocessor directive token. */ { struct PPDType* P; P = bsearch (Ident, PPDTypes, PPDTOKEN_COUNT, sizeof (PPDTypes[0]), CmpToken); return P? P->Type : PPD_ILLEGAL; } /*****************************************************************************/ /* MacroExp helpers */ /*****************************************************************************/ static HideRange* NewHideRange (unsigned Start, unsigned Len) /* Create a hide range */ { HideRange* HS = xmalloc (sizeof (HideRange)); HS->Next = 0; HS->Start = Start; HS->End = Start + Len; return HS; } static void FreeHideRange (HideRange* HS) /* Free a hide range */ { xfree (HS); } static HiddenMacro* NewHiddenMacro (const Macro* M) /* Create a new struct for the hidden macro */ { HiddenMacro* MHS = xmalloc (sizeof (HiddenMacro)); MHS->M = M; MHS->HS = 0; return MHS; } static void FreeHiddenMacro (HiddenMacro* MHS) /* Free the struct and all ranges of the hidden macro */ { HideRange* This; HideRange* Next; if (MHS == 0) { return; } for (This = MHS->HS; This != 0; This = Next) { Next = This->Next; FreeHideRange (This); } xfree (MHS); } /*****************************************************************************/ /* struct MacroExp */ /*****************************************************************************/ static HiddenMacro* ME_FindHiddenMacro (const MacroExp* E, const Macro* M) /* Find the macro hide set */ { unsigned I; for (I = 0; I < CollCount (&E->HideSets); ++I) { HiddenMacro* MHS = CollAtUnchecked (&E->HideSets, I); if (MHS->M == M) { return MHS; } } return 0; } static void ME_HideMacro (unsigned Idx, unsigned Count, MacroExp* E, const Macro* M) /* Hide the macro from the Idx'th identifier */ { if (Count > 0) { /* Find the macro hideset */ HiddenMacro* MHS = ME_FindHiddenMacro (E, M); HideRange** This; /* New hidden section */ HideRange* NewHS = NewHideRange (Idx, Count); /* New macro to hide */ if (MHS == 0) { MHS = NewHiddenMacro (M); CollAppend (&E->HideSets, MHS); } This = &MHS->HS; if (*This == 0) { *This = NewHS; } else { /* Insert */ while (1) { if (*This == 0 || NewHS->Start <= (*This)->Start) { /* Insert before */ NewHS->Next = *This; *This = NewHS; break; } else if (NewHS->Start <= (*This)->End) { /* Insert after */ NewHS->Next = (*This)->Next; (*This)->Next = NewHS; break; } /* Advance */ This = &(*This)->Next; } /* Merge */ while (*This != 0) { HideRange* Next = (*This)->Next; if (Next != 0 && (*This)->End >= Next->Start) { /* Expand this to the next */ if ((*This)->End < Next->End) { (*This)->End = Next->End; } /* Remove next */ (*This)->Next = Next->Next; FreeHideRange (Next); /* Advance */ This = &(*This)->Next; } else { /* No more */ break; } } } } } static int ME_CanExpand (unsigned Idx, const MacroExp* E, const Macro* M) /* Return 1 if the macro can be expanded with the Idx'th identifier */ { if (E != 0) { /* Find the macro hideset */ HiddenMacro* MHS = ME_FindHiddenMacro (E, M); if (MHS != 0) { /* Check if the macro is hidden from this identifier */ HideRange* HS = MHS->HS; while (HS != 0) { /* If the macro name overlaps with the range where the macro is hidden, ** the macro cannot be expanded. */ if (Idx >= HS->Start && Idx < HS->End) { return 0; } HS = HS->Next; } } } return 1; } static void ME_OffsetHideSets (unsigned Idx, unsigned Offs, MacroExp* E) /* Adjust all macro hide set ranges for the macro expansion when the identifier ** at Idx is replaced with a count of Offs + 1 (if Offs > 0) of identifiers. */ { if (Offs != 0) { unsigned I; for (I = 0; I < CollCount (&E->HideSets); ++I) { HiddenMacro* MHS = CollAtUnchecked (&E->HideSets, I); HideRange* This; for (This = MHS->HS; This != 0; This = This->Next) { if (Idx < This->Start) { This->Start += Offs; This->End += Offs; } else if (Idx < This->End) { This->End += Offs; } } } } } static void ME_RemoveToken (unsigned Idx, unsigned Count, MacroExp* E) /* Remove the Idx'th identifier token from tracking and offset all hidden ** ranges accordingly. */ { unsigned I; for (I = 0; I < CollCount (&E->HideSets); ++I) { HiddenMacro* MHS = CollAtUnchecked (&E->HideSets, I); HideRange* This; HideRange** Prev; for (Prev = &MHS->HS, This = *Prev; This != 0; This = *Prev) { if (Idx < This->Start) { if (This->Start - Idx >= Count) { This->Start -= Count; This->End -= Count; } else { if (This->End - Idx > Count) { This->Start = Idx; This->End -= Count; } else { /* Remove */ (*Prev) = This->Next; FreeHideRange (This); continue; } } } else if (Idx < This->End) { if (This->End - Idx > Count) { This->End -= Count; } else { This->End = Idx; } if (This->End == This->Start) { /* Remove */ (*Prev) = This->Next; FreeHideRange (This); continue; } } Prev = &This->Next; } } } static void ME_HandleSemiNestedMacro (unsigned NameIdx, unsigned LastIdx, MacroExp* E) /* Unhide the macro name from all hidesets if it was expanded with an unhidden ** right parenthesis. This is unspecified but allowed behavior according to ** ISO/IEC 9899:2018, 6.10.3.4ff. */ { unsigned I; for (I = 0; I < CollCount (&E->HideSets); ++I) { HiddenMacro* MHS = CollAtUnchecked (&E->HideSets, I); HideRange* This; HideRange** Prev; for (Prev = &MHS->HS, This = *Prev; This != 0; This = *Prev) { if (NameIdx < This->End) { if (NameIdx >= This->Start && LastIdx >= This->End) { This->End = NameIdx; if (This->End == This->Start) { /* Remove */ (*Prev) = This->Next; FreeHideRange (This); continue; } } Prev = &This->Next; } else { break; } } } } static void ME_AddArgHideSets (unsigned Idx, const MacroExp* A, MacroExp* Parent) /* Propagate the macro hide sets of the substituted argument starting as the ** Idx'th identifier of the result. */ { unsigned I; /* Move the hide set generated with in the argument as it will be freed later */ for (I = 0; I < CollCount (&A->HideSets); ++I) { HiddenMacro* MHS = CollAtUnchecked (&A->HideSets, I); HideRange* HS; for (HS = MHS->HS; HS != 0; HS = HS->Next) { ME_HideMacro (Idx + HS->Start, HS->End - HS->Start, Parent, MHS->M); } } } static void ME_DoneHideSets (MacroExp* E) /* Free all of hidden macros for the macro expansion */ { unsigned I; for (I = 0; I < CollCount (&E->HideSets); ++I) { HiddenMacro* MHS = CollAtUnchecked (&E->HideSets, I); FreeHiddenMacro (MHS); } DoneCollection (&E->HideSets); } static void ME_SetTokLens (MacroExp* E, unsigned TokLen) /* Set token lengths and flags for macro expansion struct */ { E->LastTokLen = TokLen; if ((E->Flags & MES_FIRST_TOKEN) != 0) { E->Flags &= ~MES_FIRST_TOKEN; E->FirstTokLen = E->LastTokLen; } else { E->Flags |= MES_MULTIPLE_TOKEN; } } static MacroExp* ME_MakeReplaced (MacroExp* A) /* Make a replaced version of the argument */ { /* Replace the parameter with actual argument tokens */ if ((A->Flags & MES_HAS_REPLACEMENT) == 0) { A->Replaced = xmalloc (sizeof (MacroExp)); InitMacroExp (A->Replaced); SB_Reset (&A->Tokens); /* Propagate the hide sets */ ME_AddArgHideSets (0, A, A->Replaced); /* Do macro expansion on the argument */ A->Replaced->IdentCount = ReplaceMacros (&A->Tokens, &A->Replaced->Tokens, A->Replaced, MSM_IN_ARG_EXPANSION); A->Flags |= MES_HAS_REPLACEMENT; } return A->Replaced != 0 ? A->Replaced : A; } static MacroExp* ME_GetOriginalArg (const MacroExp* E, unsigned Index) /* Return an actual macro argument with the given index */ { return CollAt (&E->Args, Index); } static MacroExp* ME_GetReplacedArg (const MacroExp* E, unsigned Index) /* Return a replaced macro argument with the given index */ { return ME_MakeReplaced (CollAt (&E->Args, Index)); } static MacroExp* ME_AppendArg (MacroExp* E, MacroExp* Arg) /* Add a copy of Arg to the list of actual macro arguments. ** NOTE: This function will clear the token sequence of Arg! */ { MacroExp* A = xmalloc (sizeof (MacroExp)); /* Initialize our MacroExp structure */ InitMacroExp (A); /* Copy info about the original strings */ A->IdentCount = Arg->IdentCount; A->Flags = Arg->Flags; A->FirstTokLen = Arg->FirstTokLen; A->LastTokLen = Arg->LastTokLen; /* Move the contents of Arg to A */ SB_Move (&A->Tokens, &Arg->Tokens); /* Add A to the list of actual arguments */ CollAppend (&E->Args, A); return A; } static void ME_ClearArgs (MacroExp* E) /* Clear all read arguments for macro expansion */ { unsigned I; /* Delete the list with actual arguments */ for (I = 0; I < CollCount (&E->Args); ++I) { MacroExp* A = CollAtUnchecked (&E->Args, I); /* Destroy the macro expansion structure and then free memory allocated ** for it. */ DoneMacroExp (A); xfree (A); } DoneCollection (&E->Args); InitCollection (&E->Args); } static int ME_IsNextArgVariadic (const MacroExp* E, const Macro* M) /* Return true if the next actual argument we will add is a variadic one */ { return (M->Variadic && M->ParamCount == (int) CollCount (&E->Args) + 1); } static MacroExp* InitMacroExp (MacroExp* E) /* Initialize a MacroExp structure */ { InitCollection (&E->Args); InitCollection (&E->HideSets); SB_Init (&E->Tokens); E->IdentCount = 0; E->Flags = MES_FIRST_TOKEN; E->Replaced = 0; E->FirstTokLen = 0; E->LastTokLen = 0; return E; } static void DoneMacroExp (MacroExp* E) /* Cleanup after use of a MacroExp structure */ { ME_ClearArgs (E); ME_DoneHideSets (E); SB_Done (&E->Tokens); if (E->Replaced != 0) { DoneMacroExp (E->Replaced); } } /*****************************************************************************/ /* Rescan input stack */ /*****************************************************************************/ static void PushRescanLine (RescanInputStack* RIS, StrBuf* L, unsigned LastTokLen) /* Push an input line to the rescan input stack */ { CollAppend (&RIS->Lines, L); /* Abuse the pointer to store an unsigned */ CollAppend (&RIS->LastTokLens, (void*)(uintptr_t)LastTokLen); } static void PopRescanLine (void) /* Pop and free a rescan input line if it reaches the end */ { if (CurC == '\0' && CollCount (&CurRescanStack->Lines) > 1) { FreeStrBuf (CollPop (&CurRescanStack->Lines)); InitLine (CollLast (&CurRescanStack->Lines)); CollPop (&CurRescanStack->LastTokLens); } } static void InitRescanInputStack (RescanInputStack* RIS) /* Init a RescanInputStack struct */ { InitCollection (&RIS->Lines); InitCollection (&RIS->LastTokLens); RIS->PrevTok = 0; } static void DoneRescanInputStack (RescanInputStack* RIS) /* Free a RescanInputStack struct. RIS must be non-NULL. */ { /* Free pushed input lines */ while (CollCount (&RIS->Lines) > 1) { FreeStrBuf (CollPop (&RIS->Lines)); } /* Switch back to the old input stack */ InitLine (CollPop (&RIS->Lines)); /* Free any remaining pp-tokens used for concatenation check */ FreeStrBuf (RIS->PrevTok); /* Done */ DoneCollection (&RIS->Lines); DoneCollection (&RIS->LastTokLens); } /*****************************************************************************/ /* Code */ /*****************************************************************************/ static int MacName (char* Ident) /* Get a macro symbol name into Ident. If we have an error, print a ** diagnostic message and clear the line. */ { if (IsSym (Ident) == 0) { if (CurC != '\0') { PPError ("Macro name must be an identifier"); } else { PPError ("Missing macro name"); } ClearLine (); return 0; } else { return 1; } } static void CheckForBadIdent (const char* Ident, int Std, const Macro* M) /* Check for and warning on problematic identifiers */ { if (Std >= STD_C99 && (M == 0 || !M->Variadic) && strcmp (Ident, "__VA_ARGS__") == 0) { /* __VA_ARGS__ cannot be used as a macro parameter name in post-C89 ** mode. */ PPWarning ("__VA_ARGS__ can only appear in the expansion of a C99 variadic macro"); } } static void AddPreLine (StrBuf* Str) /* Add newlines to the string buffer */ { /* No need to prettify the non-exist output */ if (!PreprocessOnly) { PendingNewLines = 0; ContinuedLines = 0; return; } /* We'll adjust the line number later if necessary */ PendingNewLines += ContinuedLines; if (FileChanged || PendingNewLines > 6) { /* Output #line directives as source info */ StrBuf Comment = AUTO_STRBUF_INITIALIZER; if (SB_NotEmpty (Str) && SB_LookAtLast (Str) != '\n') { SB_AppendChar (Str, '\n'); } SB_Printf (&Comment, "#line %u \"%s\"\n", GetCurrentLineNum () - ContinuedLines, GetCurrentFileName ()); SB_Append (Str, &Comment); } else { /* Output new lines */ while (PendingNewLines > 0) { SB_AppendChar (Str, '\n'); --PendingNewLines; } } FileChanged = 0; PendingNewLines = 0; ContinuedLines = 0; } static void AppendIndent (StrBuf* Str, int Count) /* Add Count of spaces ' ' to the string buffer */ { while (Count > 0) { SB_AppendChar (Str, ' '); --Count; } } static void Stringize (StrBuf* Source, StrBuf* Target) /* Stringize the given string: Add double quotes at start and end and preceed ** each occurance of " and \ by a backslash. */ { char C; /* Add a starting quote */ SB_AppendChar (Target, '\"'); /* Replace any characters inside the string may not be part of a string ** unescaped. */ while ((C = SB_Get (Source)) != '\0') { switch (C) { case '\"': case '\\': SB_AppendChar (Target, '\\'); /* FALLTHROUGH */ default: SB_AppendChar (Target, C); break; } } /* Add the closing quote */ SB_AppendChar (Target, '\"'); } static void OldStyleComment (void) /* Remove an old style C comment from line */ { /* Remember the current line number, so we can output better error ** messages if the comment is not terminated in the current file. */ unsigned StartingLine = GetCurrentLineNum (); /* Skip the start of comment chars */ NextChar (); NextChar (); /* Skip the comment */ while (CurC != '*' || NextC != '/') { if (CurC == '\0') { if (NextLine () == 0) { PPError ("End-of-file reached in comment starting at line %u", StartingLine); return; } ++PendingNewLines; } else { if (CurC == '/' && NextC == '*') { PPWarning ("'/*' found inside a comment"); } NextChar (); } } /* Skip the end of comment chars */ NextChar (); NextChar (); } static void NewStyleComment (void) /* Remove a new style C comment from line */ { /* Diagnose if this is unsupported */ if (IS_Get (&Standard) < STD_C99 && !AllowNewComments) { PPError ("C++ style comments are not allowed in C89"); PPNote ("(this will be reported only once per input file)"); AllowNewComments = 1; } /* Beware: Because line continuation chars are handled when reading ** lines, we may only skip until the end of the source line, which ** may not be the same as the end of the input line. The end of the ** source line is denoted by a lf (\n) character. */ do { NextChar (); } while (CurC != '\n' && CurC != '\0'); if (CurC == '\n') { NextChar (); } } static int SkipWhitespace (int SkipLines) /* Skip white space and comments in the input stream. If skipLines is true, ** also skip newlines and add that count to global PendingNewLines. Return 1 ** if the last skipped character was a white space other than a newline '\n', ** otherwise return -1 if there were any newline characters skipped, otherwise ** return 0 if nothing was skipped. */ { int Skipped = 0; int NewLine = 0; /* Rescanning */ while (1) { if (IsSpace (CurC)) { NextChar (); Skipped = 1; } else if (CurC == '/' && NextC == '*') { OldStyleComment (); Skipped = 1; } else if (CurC == '/' && NextC == '/') { NewStyleComment (); Skipped = 1; } else if (CurC == '\0') { /* End of line */ if (CurRescanStack != 0 && CollCount (&CurRescanStack->Lines) > 1 && Line == CollLast (&CurRescanStack->Lines)) { unsigned LastTokLen = (unsigned)(uintptr_t)CollLast (&CurRescanStack->LastTokLens); /* Check for potentially merged tokens */ if (Skipped == 0 && LastTokLen != 0) { /* Get the following input */ StrBuf* Next = CollAtUnchecked (&CurRescanStack->Lines, CollCount (&CurRescanStack->Lines) - 2); char C = SB_Peek (Next); /* We cannot check right now if the next pp-token may be a ** macro. */ if (IsIdent (C)) { /* Memorize the previous pp-token and check it later */ LazyCheckNextPPTok (Line, LastTokLen); } else if (C != '\0' && !IsSpace (C)) { /* If the two adjacent pp-tokens could be put together ** to form a new one, we have to separate them with an ** additional space. */ Skipped = CheckPastePPTok (Line, LastTokLen, SB_Peek (Next)); } } /* switch back to previous input */ PopRescanLine (); } else if (SkipLines) { /* Read next line */ if (NextLine () != 0) { ++PendingNewLines; NewLine = 1; Skipped = 0; } else { /* End of input */ break; } } else { break; } } else { /* No more white space */ break; } } return Skipped != 0 ? Skipped : -(NewLine != 0); } static void CopyHeaderNameToken (StrBuf* Target) /* Copy a header name from the input to Target. */ { /* Remember the quote character, copy it to the target buffer and skip it */ char Quote = CurC == '"' ? '"' : '>'; SB_AppendChar (Target, CurC); NextChar (); /* Copy the characters inside the string */ while (CurC != '\0' && CurC != Quote) { /* Keep an escaped char */ if (CurC == '\\') { SB_AppendChar (Target, CurC); NextChar (); } /* Copy the character */ SB_AppendChar (Target, CurC); NextChar (); } /* If we had a terminating quote, copy it */ if (CurC != '\0') { SB_AppendChar (Target, CurC); NextChar (); } } static int IsQuotedString (void) /* Retrun 1 if the incoming characters indicate a string literal or character ** constant, otherwise return 0. */ { return IsQuote (CurC) || IsWideQuoted (CurC, NextC); } static void CopyQuotedString (StrBuf* Target) /* Copy a single or double quoted string from the input to Target. */ { /* Remember the quote character, copy it to the target buffer and skip it */ char Quote; if (CurC == 'L') { SB_AppendChar (Target, CurC); NextChar (); } Quote = CurC; SB_AppendChar (Target, CurC); NextChar (); /* Copy the characters inside the string */ while (CurC != '\0' && CurC != Quote) { /* Keep an escaped char */ if (CurC == '\\') { SB_AppendChar (Target, CurC); NextChar (); } /* Copy the character */ SB_AppendChar (Target, CurC); NextChar (); } /* If we had a terminating quote, copy it */ if (CurC != '\0') { SB_AppendChar (Target, CurC); NextChar (); } else { PPWarning ("Missing terminating %c character", Quote); } } static int GetPunc (char* S) /* Parse a punctuator token. Return 1 and store the parsed token string into S ** on success, otherwise just return 0. */ { char C; switch (CurC) { case '[': case ']': case '(': case ')': case '{': case '}': case '~': case '?': case ':': case ';': case ',': /* C */ *S++ = CurC; NextChar (); break; case '=': case '#': /* C or CC */ C = *S++ = CurC; NextChar (); if (CurC == C) { *S++ = C; NextChar (); } break; case '*': case '/': case '%': case '^': case '!': /* C or C= */ *S++ = CurC; NextChar (); if (CurC == '=') { *S++ = CurC; NextChar (); } break; case '+': case '&': case '|': /* C, CC or C= */ C = *S++ = CurC; NextChar (); if (CurC == C || CurC == '=') { *S++ = CurC; NextChar (); } break; case '<': case '>': /* C, CC, C= or CC= */ C = *S++ = CurC; NextChar (); if (CurC == C) { *S++ = CurC; if (NextC == '=') { *S++ = NextC; NextChar (); } NextChar (); } else if (CurC == '=') { *S++ = CurC; NextChar (); } break; case '-': /* C, CC, C= or C> */ *S++ = CurC; NextChar (); switch (CurC) { case '-': case '=': case '>': *S++ = CurC; NextChar (); break; default: break; } break; case '.': /* C or CCC */ *S++ = CurC; NextChar (); if (CurC == '.' && NextC == '.') { *S++ = CurC; *S++ = NextC; NextChar (); NextChar (); } break; default: return 0; } *S = '\0'; return 1; } static int CheckPastePPTok (StrBuf* Source, unsigned TokLen, char Next) /* Return 1 if the last pp-tokens from Source could be concatenated with any ** characters from Appended to form a new valid one. */ { char C; unsigned NewTokLen; StrBuf* OldSource; StrBuf Src = AUTO_STRBUF_INITIALIZER; StrBuf Buf = AUTO_STRBUF_INITIALIZER; if (TokLen == 0 || IsBlank (SB_LookAtLast (Source))) { return 0; } PRECONDITION (SB_GetLen (Source) >= TokLen); /* Special casing "..", "/ /" and "/ *" that are not pp-tokens but still ** need be separated. */ C = SB_LookAt (Source, SB_GetLen (Source) - TokLen); if ((C == '.' && Next == '.') || (C == '/' && (Next == '/' || Next == '*'))) { return 1; } SB_CopyBuf (&Src, SB_GetConstBuf (Source) + SB_GetLen (Source) - TokLen, TokLen); SB_AppendChar (&Src, Next); SB_Reset (&Src); OldSource = InitLine (&Src); if (IsPPNumber (CurC, NextC)) { /* PP-number */ CopyPPNumber (&Buf); } else if (IsQuotedString ()) { /* Quoted string */ CopyQuotedString (&Buf); } else { ident Ident; if (GetPunc (Ident)) { /* Punctuator */ SB_CopyStr (&Buf, Ident); } else if (IsSym (Ident)) { /* Identifier */ SB_CopyStr (&Buf, Ident); } } NewTokLen = SB_GetLen (&Buf); SB_Done (&Buf); SB_Done (&Src); /* Restore old source */ InitLine (OldSource); /* Return if concatenation succeeded */ return NewTokLen != TokLen; } static int TryPastePPTok (StrBuf* Target, StrBuf* Appended, unsigned FirstTokLen, unsigned SecondTokLen) /* Paste the whole appened pp-token sequence onto the end of the target ** pp-token sequence. Diagnose if it fails to form a valid pp-token with the ** two pp-tokens pasted together. Return 1 if succeeds. */ { unsigned TokLen; StrBuf* OldSource; StrBuf Src = AUTO_STRBUF_INITIALIZER; StrBuf Buf = AUTO_STRBUF_INITIALIZER; if (FirstTokLen == 0 || SecondTokLen == 0) { SB_Append (Target, Appended); return 1; } /* Since we need to concatenate the token sequences, remove the ** last whitespace that was added to target, since it must come ** from the input. */ if (IsBlank (SB_LookAtLast (Target))) { SB_Drop (Target, 1); } PRECONDITION (SB_GetLen (Target) >= FirstTokLen && SB_GetLen (Appended) >= SecondTokLen); /* Special casing "..", "/ /" and "/ *" */ if (FirstTokLen == 1) { char C = SB_LookAt (Target, SB_GetLen (Target) - FirstTokLen); char N = SB_LookAt (Appended, 0); /* Avoid forming a comment introducer or an ellipsis. Note that an ** ellipsis pp-token cannot be formed with macros anyway. */ if ((C == '.' && N == '.') || (C == '/' && (N == '/' || N == '*'))) { SB_AppendChar (Target, ' '); SB_Append (Target, Appended); PPWarning ("Pasting formed \"%c%c\", an invalid preprocessing token", C, N); return 0; } } SB_CopyBuf (&Src, SB_GetConstBuf (Target) + SB_GetLen (Target) - FirstTokLen, FirstTokLen); if (SecondTokLen == SB_GetLen (Appended) || IsSpace (SB_LookAt (Appended, SecondTokLen))) { SB_AppendBuf (&Src, SB_GetConstBuf (Appended), SecondTokLen); } else { SB_AppendBuf (&Src, SB_GetConstBuf (Appended), SecondTokLen + 1); } SB_Reset (&Src); OldSource = InitLine (&Src); if (IsPPNumber (CurC, NextC)) { /* PP-number */ CopyPPNumber (&Buf); } else if (IsQuotedString ()) { /* Quoted string */ CopyQuotedString (&Buf); } else { ident Ident; if (GetPunc (Ident)) { /* Punctuator */ SB_CopyStr (&Buf, Ident); } else if (IsSym (Ident)) { /* Identifier */ SB_CopyStr (&Buf, Ident); } else { /* Unknown */ } } TokLen = SB_GetLen (&Buf); if (TokLen < FirstTokLen + SecondTokLen) { /* The pasting doesn't form a valid pp-token */ while (SB_GetLen (&Buf) < FirstTokLen + SecondTokLen) { SB_AppendChar (&Buf, CurC); NextChar (); } SB_Terminate (&Buf); PPWarning ("Pasting formed \"%s\", an invalid preprocessing token", SB_GetConstBuf (&Buf)); /* Add a space between the tokens to avoid problems in rescanning */ if (TokLen > FirstTokLen) { SB_AppendChar (Target, ' '); } /* Append all remaining tokens */ SB_Append (Target, Appended); /* No concatenation */ TokLen = FirstTokLen; } else { /* Add a space after the merged token if necessary */ SB_AppendBuf (Target, SB_GetConstBuf (Appended), SecondTokLen); if (TokLen > FirstTokLen + SecondTokLen) { SB_AppendChar (Target, ' '); } /* Append all remaining tokens */ SB_AppendBuf (Target, SB_GetConstBuf (Appended) + SecondTokLen, SB_GetLen (Appended) - SecondTokLen); } SB_Done (&Buf); SB_Done (&Src); /* Restore old source */ InitLine (OldSource); /* Return if concatenation succeeded */ return TokLen != FirstTokLen; } static void SeparatePPTok (StrBuf* Target, char Next) /* Add a space to target if the previous pp-token could be concatenated with ** the following character. */ { if (CurRescanStack->PrevTok != 0) { unsigned Len = SB_GetLen (CurRescanStack->PrevTok) - SB_GetIndex (CurRescanStack->PrevTok); /* Check for pp-token pasting */ if (CheckPastePPTok (CurRescanStack->PrevTok, Len, Next)) { SB_AppendChar (Target, ' '); } FreeStrBuf (CurRescanStack->PrevTok); CurRescanStack->PrevTok = 0; } } static void LazyCheckNextPPTok (const StrBuf* Prev, unsigned LastTokLen) /* Memorize the previous pp-token(s) to later check for potential pp-token ** concatenation. */ { char C; int CheckEllipsis = 0; unsigned NewIndex = SB_GetLen (Prev) - LastTokLen; PRECONDITION (SB_GetLen (Prev) >= LastTokLen); /* Check for some special cases */ C = SB_AtUnchecked (Prev, NewIndex); /* We may exclude certain punctuators for speedups. As newer C standards ** could add more punctuators such as "[[", "]]", "::" and so on, this ** check might need changes accordingly. */ if (C == '[' || C == ']' || C == '(' || C == ')' || C == '{' || C == '}' || C == '~' || C == '?' || C == ':' || C == ';' || C == ',') { /* These punctuators cannot be concatenated */ return; } /* Special check for .. */ if (NewIndex > 0 && C == '.' && SB_AtUnchecked (Prev, NewIndex - 1) == '.') { /* Save the preceding '.' as well */ CheckEllipsis = 1; } if (CurRescanStack->PrevTok != 0) { unsigned OldIndex = SB_GetIndex (CurRescanStack->PrevTok); unsigned OldLen = SB_GetLen (CurRescanStack->PrevTok) - OldIndex; unsigned NewLen = SB_GetLen (Prev) - NewIndex; if (OldLen == NewLen && strncmp (SB_GetConstBuf (CurRescanStack->PrevTok) + OldIndex - CheckEllipsis, SB_GetConstBuf (Prev) + NewIndex - CheckEllipsis, OldLen + CheckEllipsis) == 0) { /* Same pp-token, keep using the old one */ } else { /* Logic error */ SB_Terminate (CurRescanStack->PrevTok); Internal ("Unchecked pp-token concatenation: \"%s\"", SB_GetConstBuf (CurRescanStack->PrevTok) + SB_GetIndex (CurRescanStack->PrevTok)); } } else { /* Memorize the current line */ CurRescanStack->PrevTok = NewStrBuf (); SB_CopyBuf (CurRescanStack->PrevTok, SB_GetConstBuf (Prev) + NewIndex - CheckEllipsis, LastTokLen + CheckEllipsis); SB_Reset (CurRescanStack->PrevTok); } } static int CheckExtraTokens (const char* Name) /* Check for extra tokens at the end of the directive. Return 1 if there are ** extra tokens, otherwise 0. */ { SkipWhitespace (0); if (SB_GetIndex (Line) != SB_GetLen (Line)) { PPWarning ("Extra tokens at end of #%s directive", Name); return 1; } return 0; } /*****************************************************************************/ /* Macro stuff */ /*****************************************************************************/ static unsigned ReadMacroArgs (unsigned NameIdx, MacroExp* E, const Macro* M, int MultiLine) /* Identify the arguments to a macro call as-is. Return the total count of ** identifiers and right parentheses in the read argument list. */ { unsigned Idx = 0; unsigned CountInArg = 0; unsigned Parens = 0; /* Number of open parenthesis */ ident Ident; MacroExp Arg; InitMacroExp (&Arg); /* Eat the left paren */ NextChar (); /* Read the actual macro arguments */ while (1) { /* Squeeze runs of blanks within an arg */ unsigned OldPendingNewLines = PendingNewLines; int Skipped = SkipWhitespace (MultiLine); /* Directives can only be found in an argument list that spans ** multiple lines. */ if (MultiLine && OldPendingNewLines < PendingNewLines && CurC == '#') { unsigned Newlines = 0; while (OldPendingNewLines < PendingNewLines && CurC == '#') { Newlines += PendingNewLines - OldPendingNewLines; PendingNewLines = OldPendingNewLines; OldPendingNewLines = 0; Skipped = ParseDirectives (MSM_IN_ARG_LIST) || Skipped; } PendingNewLines += Newlines; } /* Append a space as a separator */ if (Skipped && SB_NotEmpty (&Arg.Tokens)) { SB_AppendChar (&Arg.Tokens, ' '); Skipped = 1; } else { Skipped = 0; } /* Finish reading the current argument if we are not inside nested ** parentheses or a variadic macro argument. */ if (Parens == 0 && ((CurC == ',' && !ME_IsNextArgVariadic (E, M)) || CurC == ')')) { /* End of actual argument. Remove whitespace from the end. */ while (IsBlank (SB_LookAtLast (&Arg.Tokens))) { SB_Drop (&Arg.Tokens, 1); } /* If this is not the single empty argument for a macro with an ** empty argument list, remember it. */ if (CurC != ')' || CollCount (&E->Args) > 0 || SB_NotEmpty (&Arg.Tokens) || M->ParamCount > 0) { MacroExp* A = ME_AppendArg (E, &Arg); unsigned I; /* Copy the hide sets from the argument list */ for (I = 0; I < CollCount (&E->HideSets); ++I) { HiddenMacro* MHS = CollAtUnchecked (&E->HideSets, I); HideRange* HS; for (HS = MHS->HS; HS != 0; HS = HS->Next) { /* Get the correct hide range */ unsigned Start = NameIdx + 1 + Idx; unsigned Len; if (HS->Start < Start) { if (HS->End > Start) { Len = HS->End - Start; } else { /* Out of the range */ continue; } Start = 0; } else { Len = HS->End - HS->Start; Start = HS->Start - Start; } if (Start + Len > Idx + CountInArg) { if (Idx + CountInArg > Start) { Len = Idx + CountInArg - Start; } else { /* Out of the range */ break; } } ME_HideMacro (Start, Len, A, MHS->M); } } /* More argument info */ A->IdentCount = CountInArg; } Idx += CountInArg; /* Check for end of macro param list */ if (CurC == ')') { /* Count right parens */ ++Idx; NextChar (); break; } /* Start the next param */ NextChar (); DoneMacroExp (&Arg); InitMacroExp (&Arg); CountInArg = 0; continue; } else if (CurC == '\0') { /* End of input inside macro argument list */ PPError ("Unterminated argument list invoking macro '%s'", M->Name); Parens = -1; ClearLine (); E->Flags |= MES_ERROR; Idx = 0; break; } else { unsigned LastLen = SB_GetLen (&Arg.Tokens); if (IsSym (Ident)) { /* Just copy the identifier */ SB_AppendStr (&Arg.Tokens, Ident); /* Count identifiers */ ++CountInArg; /* Used for concatentation check */ if ((Arg.Flags & MES_FIRST_TOKEN) != 0) { Arg.Flags |= MES_BEGIN_WITH_IDENT; } } else if (IsPPNumber (CurC, NextC)) { /* Copy a pp-number */ CopyPPNumber (&Arg.Tokens); } else if (IsQuotedString ()) { /* Quoted string - just copy */ CopyQuotedString (&Arg.Tokens); } else if (GetPunc (Ident)) { /* Check nested parentheses */ if (Ident[0] == '(') { /* Opening nested parenthesis */ ++Parens; } else if (Ident[0] == ')') { /* Closing nested parenthesis */ --Parens; /* Count right parens */ ++CountInArg; } /* Just copy the punctuator */ SB_AppendStr (&Arg.Tokens, Ident); } else { /* Just copy the character */ SB_AppendChar (&Arg.Tokens, CurC); NextChar (); /* But don't count it */ ++LastLen; } /* Used for concatentation check */ ME_SetTokLens (&Arg, SB_GetLen (&Arg.Tokens) - LastLen - Skipped); } } /* Compare formal and actual argument count */ if (CollCount (&E->Args) < (unsigned) M->ParamCount) { /* Check further only when the parentheses are paired */ if (Parens == 0) { /* Specially casing variable argument */ if (M->Variadic && M->ParamCount > 0 && CollCount (&E->Args) + 1 == (unsigned) M->ParamCount) { /* The variable argument is left out entirely */ E->Flags |= MES_NO_VA_COMMA; if (IS_Get (&Standard) < STD_CC65) { PPWarning ("ISO C does not permit leaving out the comma before the variable argument"); } } else { /* Too few argument */ PPError ("Macro \"%s\" passed only %u arguments, but requires %u", M->Name, CollCount (&E->Args), (unsigned) M->ParamCount); } } /* Be sure to make enough empty arguments available */ DoneMacroExp (&Arg); InitMacroExp (&Arg); Arg.Flags |= MES_HAS_REPLACEMENT; while (CollCount (&E->Args) < (unsigned) M->ParamCount) { ME_AppendArg (E, &Arg); } } else if (Parens == 0 && CollCount (&E->Args) > (unsigned) M->ParamCount) { /* Too many arguments */ PPError ("Macro \"%s\" passed %u arguments, but takes just %u", M->Name, CollCount (&E->Args), (unsigned) M->ParamCount); } /* Deallocate argument resources */ DoneMacroExp (&Arg); /* Return the total count of identifiers and right parentheses in the ** argument list. */ return Idx; } static unsigned SubstMacroArgs (unsigned NameIdx, StrBuf* Target, MacroExp* E, Macro* M, unsigned* IdentCount) /* Argument substitution according to ISO/IEC 9899:1999 (E), 6.10.3.1ff. ** Return the length of the last pp-token in the result and output the count ** of identifiers and right parentheses in the result to *IdentCount. */ { unsigned Idx = NameIdx; ident Ident; unsigned TokLen = 0; int ParamIdx; StrBuf* OldSource; int HaveSpace = 0; StrBuf Buf = AUTO_STRBUF_INITIALIZER; /* Temporary buffer */ /* Remember the current input stack and disable it for now */ Collection* OldInputStack = UseInputStack (0); /* Remember the current input and switch to the macro replacement */ unsigned OldIndex = SB_GetIndex (&M->Replacement); SB_Reset (&M->Replacement); OldSource = InitLine (&M->Replacement); /* If the macro expansion replaces an function-like macro with an argument ** list containing a right parenthesis outside the hidesets of previously ** replaced macros, stop those hidesets from this replacement. This is not ** required by the standard but just to match up with other major C ** compilers. */ ME_HandleSemiNestedMacro (NameIdx, NameIdx + *IdentCount, E); /* Substitution loop */ while (CurC != '\0') { int NeedPaste = 0; /* If we have an identifier, check if it's a macro */ if (IsSym (Ident)) { /* Remember and skip any following whitespace */ HaveSpace = SkipWhitespace (0); /* Check if it's a macro parameter */ if ((ParamIdx = FindMacroParam (M, Ident)) >= 0) { /* If a ## operator follows, we have to insert the actual ** argument as-is, otherwise it must be macro-replaced. */ if (CurC == '#' && NextC == '#') { /* Get the corresponding actual argument */ const MacroExp* A = ME_GetOriginalArg (E, ParamIdx); /* Separate with a white space if necessary */ if (CheckPastePPTok (Target, TokLen, SB_Peek (&A->Tokens))) { SB_AppendChar (Target, ' '); } /* For now we need no placemarkers */ SB_Append (Target, &A->Tokens); /* Adjust tracking */ ME_OffsetHideSets (Idx, A->IdentCount, E); Idx += A->IdentCount; /* This will be used for concatenation */ TokLen = A->LastTokLen; } else { /* Get the corresponding macro-replaced argument */ const MacroExp* A = ME_GetReplacedArg (E, ParamIdx); /* Separate with a white space if necessary */ if (CheckPastePPTok (Target, TokLen, SB_Peek (&A->Tokens))) { SB_AppendChar (Target, ' '); } /* Append the replaced string */ SB_Append (Target, &A->Tokens); /* Insert the range of identifiers to parent preceding this argument */ ME_OffsetHideSets (Idx, A->IdentCount, E); /* Add hide range */ ME_AddArgHideSets (Idx, A, E); /* Adjust tracking */ Idx += A->IdentCount; /* May be used for later pp-token merge check */ TokLen = A->LastTokLen; } } else { /* An identifier, keep it */ SB_AppendStr (Target, Ident); /* Adjust tracking */ ME_OffsetHideSets (Idx, 1, E); ++Idx; /* May be used for later concatenation */ TokLen = strlen (Ident); } /* Special casing for 'L' prefixing '#' */ if (TokLen == 1 && SB_LookAtLast (Target) == 'L' && CurC == '#') { HaveSpace = 1; } /* Squeeze and add the skipped whitespace back for consistency */ if (HaveSpace && !IsBlank (SB_LookAtLast (Target))) { SB_AppendChar (Target, ' '); } /* Done with this substituted argument */ continue; } else if (CurC == '#' && NextC == '#') { /* ## operator */ NextChar (); NextChar (); SkipWhitespace (0); /* If the next token is an identifier which is a macro argument, ** replace it, otherwise just add it. */ if (IsSym (Ident)) { unsigned NewCount = 1; /* Check if it's a macro parameter */ if ((ParamIdx = FindMacroParam (M, Ident)) >= 0) { /* Get the corresponding actual argument */ MacroExp* A = ME_GetOriginalArg (E, ParamIdx); /* Insert the range of identifiers to parent preceding this argument */ ME_OffsetHideSets (Idx, A->IdentCount, E); /* Add hide range */ ME_AddArgHideSets (Idx, A, E); /* Adjust tracking */ NewCount = A->IdentCount; /* If the preceding pp-token is not a placemarker and is ** concatenated to with an identifier, the count of tracked ** identifiers is then one less. */ if (TryPastePPTok (Target, &A->Tokens, TokLen, A->FirstTokLen)) { if (TokLen > 0 && (A->Flags & MES_BEGIN_WITH_IDENT) != 0) { --NewCount; ME_RemoveToken (Idx, 1, E); } if ((A->Flags & MES_MULTIPLE_TOKEN) == 0) { TokLen += A->FirstTokLen; } else { TokLen = A->LastTokLen; } } else { TokLen = A->LastTokLen; } } else { unsigned Len; /* Just an ordinary identifier - add as-is */ SB_CopyStr (&Buf, Ident); /* If the preceding pp-token is not a placemarker and is ** concatenated to with an identifier, the count of tracked ** identifiers is then one less. */ Len = SB_GetLen (&Buf); if (TryPastePPTok (Target, &Buf, TokLen, Len)) { if (TokLen > 0) { --NewCount; } TokLen += Len; } else { TokLen = Len; } /* Adjust tracking */ ME_OffsetHideSets (Idx, NewCount, E); } /* Adjust tracking */ Idx += NewCount; /* Keep the whitespace for consistency */ HaveSpace = SkipWhitespace (0); if (HaveSpace && !IsBlank (SB_LookAtLast (Target))) { SB_AppendChar (Target, ' '); } /* Done with this concatenated identifier */ continue; } if (CurC != '\0') { /* Non-identifiers may still be pasted together */ NeedPaste = 1; } } /* Use the temporary buffer */ SB_Clear (&Buf); if (IsPPNumber (CurC, NextC)) { CopyPPNumber (&Buf); } else if (IsQuotedString ()) { CopyQuotedString (&Buf); } else { if (CurC == '#' && M->ParamCount >= 0) { /* A # operator within a macro expansion of a function-like ** macro. Read the following identifier and check if it's a ** macro parameter. */ NextChar (); SkipWhitespace (0); if (!IsSym (Ident) || (ParamIdx = FindMacroParam (M, Ident)) < 0) { /* Should not happen, but still */ Internal ("'#' is not followed by a macro parameter"); } else { /* Make a valid string from Replacement */ MacroExp* A = ME_GetOriginalArg (E, ParamIdx); SB_Reset (&A->Tokens); Stringize (&A->Tokens, &Buf); } } else if (GetPunc (Ident)) { /* Count right parens. This is OK since they cannot be pasted ** to form different punctuators with others. */ if (Ident[0] == ')') { /* Adjust tracking */ ME_OffsetHideSets (Idx, 1, E); ++Idx; } SB_AppendStr (&Buf, Ident); } else if (CurC != '\0') { SB_AppendChar (&Buf, CurC); NextChar (); } } /* Squeeze any whitespace for consistency. Especially, comments must ** be consumed before fed to the punctuator parser, or their leading ** '/' characters would be parsed wrongly as division operators. */ HaveSpace = SkipWhitespace (0); if (NeedPaste) { unsigned Len = SB_GetLen (&Buf); /* Concatenate pp-tokens */ if (TryPastePPTok (Target, &Buf, TokLen, Len)) { TokLen += Len; } else { TokLen = Len; } } else { /* Just append the token */ SB_Append (Target, &Buf); TokLen = SB_GetLen (&Buf); } if (HaveSpace && !IsBlank (SB_LookAtLast (Target))) { SB_AppendChar (Target, ' '); } } /* Done with the temporary buffer */ SB_Done (&Buf); /* Remove the macro name itself together with the arguments (if any) */ ME_RemoveToken (Idx, 1 + *IdentCount, E); /* Hide this macro for the whole result of this expansion */ ME_HideMacro (NameIdx, Idx - NameIdx, E, M); /* Switch back the input */ UseInputStack (OldInputStack); InitLine (OldSource); SB_SetIndex (&M->Replacement, OldIndex); /* Set the count of identifiers and right parentheses in the result */ *IdentCount = Idx - NameIdx; /* Return the length of the last pp-token */ return TokLen; } static unsigned ExpandMacro (unsigned Idx, StrBuf* Target, MacroExp* E, Macro* M, int MultiLine) /* Expand a macro into Target. Return the length of the last pp-token in the ** result of the expansion. */ { unsigned Count = 0; /* Count of identifiers and right parentheses */ unsigned Len = 0; /* Length of the last pp-token in the result */ /* Disable previous pp-token spacing checking */ StrBuf* PrevTok = CurRescanStack->PrevTok; CurRescanStack->PrevTok = 0; #if DEV_CC65_DEBUG static unsigned V = 0; printf ("Expanding (%u) %s\n", ++V, M->Name); #endif /* Check if this is a function like macro */ if (M->ParamCount >= 0) { /* Read the actual macro arguments (with the enclosing parentheses) */ Count = ReadMacroArgs (Idx, E, M, MultiLine); } if ((E->Flags & MES_ERROR) == 0) { /* Replace macro parameters with arguments handling the # and ## operators */ Len = SubstMacroArgs (Idx, Target, E, M, &Count); } else { SB_CopyStr (Target, M->Name); } if (CollCount (&E->Args) > 0) { /* Clear all arguments */ ME_ClearArgs (E); } #if DEV_CC65_DEBUG printf ("Expanded (%u) %s to %d ident(s) at %u: %s\n", V--, M->Name, Count, Idx, SB_GetConstBuf (Target)); #endif /* Reenable previous pp-token concatenation checking */ FreeStrBuf (CurRescanStack->PrevTok); CurRescanStack->PrevTok = PrevTok; /* Return the length of the last pp-token in the expansion result */ return Len; } static unsigned ReplaceMacros (StrBuf* Source, StrBuf* Target, MacroExp* E, unsigned ModeFlags) /* Scan for and perform macro replacement. Return the count of identifiers and ** right parentheses in the replacement result. */ { unsigned Count = 0; StrBuf* TmpTarget = NewStrBuf (); /* Remember the current input and switch to Source */ StrBuf* OldSource = InitLine (Source); RescanInputStack RescanStack; RescanInputStack* OldRescanStack = CurRescanStack; InitRescanInputStack (&RescanStack); PushRescanLine (&RescanStack, Line, 0); CurRescanStack = &RescanStack; /* Loop substituting macros */ while (CurC != '\0') { int Skipped = 0; ident Ident; /* If we have an identifier, check if it's a macro */ if (IsSym (Ident)) { /* Check for bad identifier names */ if ((ModeFlags & (MSM_MULTILINE | MSM_IN_DIRECTIVE | MSM_IN_ARG_LIST)) != 0 && (CollCount (&CurRescanStack->Lines) == 1 || CurC == '\0')) { CheckForBadIdent (Ident, IS_Get (&Standard), 0); } if ((ModeFlags & MSM_OP_DEFINED) != 0 && strcmp (Ident, "defined") == 0) { /* Handle the "defined" operator */ int HaveParen = 0; /* Eat the "defined" operator */ ME_RemoveToken (Count, 1, E); SkipWhitespace (0); if (CurC == '(') { HaveParen = 1; NextChar (); SkipWhitespace (0); } /* Add a space to separate the result if necessary */ SeparatePPTok (Target, '0'); if (IsSym (Ident)) { /* Eat the identifier */ ME_RemoveToken (Count, 1, E); SB_AppendChar (Target, IsMacro (Ident) ? '1' : '0'); if (HaveParen) { SkipWhitespace (0); if (CurC != ')') { PPError ("')' expected"); ClearLine (); } else { /* Eat the right parenthesis */ ME_RemoveToken (Count, 1, E); NextChar (); } } } else { PPError ("Macro name must be an identifier"); ClearLine (); SB_AppendChar (Target, '0'); } } else { Macro* M = FindMacro (Ident); /* Check if it's an expandable macro */ if (M != 0 && ME_CanExpand (Count, E, M)) { int MultiLine = (ModeFlags & MSM_MULTILINE) != 0; unsigned LastTokLen; /* Check if this is a function-like macro */ if (M->ParamCount >= 0) { int HaveSpace = SkipWhitespace (MultiLine) > 0; /* A function-like macro name without an immediately ** following argument list is not subject to expansion. */ if (CurC != '(') { /* No expansion */ ++Count; /* Add a space to separate the macro name if necessary */ SeparatePPTok (Target, M->Name[0]); SB_AppendStr (Target, M->Name); /* Keep tracking pp-token lengths */ if ((ModeFlags & MSM_IN_ARG_EXPANSION) != 0) { /* Used for concatentation check */ if ((E->Flags & MES_FIRST_TOKEN) != 0) { E->Flags |= MES_BEGIN_WITH_IDENT; } ME_SetTokLens (E, strlen (M->Name)); } /* Since we have already got on hold of the next ** line, we have to reuse it as the next line ** instead of reading a new line from the source. */ if (PendingNewLines > 0 && MultiLine) { unsigned I = SB_GetIndex (Line); /* There is no way a function-like macro call ** detection could span multiple lines within ** the range of another just expanded macro. */ CHECK (CollCount (&CurRescanStack->Lines) == 1); /* Revert one newline */ --PendingNewLines; /* Align indention */ while (I > 0) { --I; if (SB_GetBuf (Line)[I] == '\n') { ++I; break; } SB_GetBuf (Line)[I] = ' '; } /* Set start index */ SB_SetIndex (Line, I); /* Add newlines */ AddPreLine (Target); /* Reuse this line as the next line */ ReuseInputLine (); /* Quit this loop */ break; } /* Append back the whitespace */ if (HaveSpace) { SB_AppendChar (Target, ' '); } /* Loop */ goto Loop; } } /* Either an object-like or function-like macro */ MultiLine = MultiLine && M->ParamCount >= 0; /* If we were going to support #pragma in macro argument ** list, it would be output to OLine. */ if (MultiLine && OLine == 0) { OLine = TmpTarget; LastTokLen = ExpandMacro (Count, TmpTarget, E, M, MultiLine); OLine = 0; } else { LastTokLen = ExpandMacro (Count, TmpTarget, E, M, MultiLine); } /* Check for errors in expansion */ if ((E->Flags & MES_ERROR) != 0) { break; } /* Pop the current line if it is at the end */ PopRescanLine (); if (SB_GetLen (TmpTarget) > 0) { /* Start rescanning from the temporary result */ SB_Reset (TmpTarget); InitLine (TmpTarget); PushRescanLine (CurRescanStack, TmpTarget, LastTokLen); /* Add a space before a '#' at the beginning of the line */ if (CurC == '#' && NextC != '#' && (SB_IsEmpty (Target) || SB_LookAtLast (Target) == '\n')) { SB_AppendChar (Target, ' '); } /* Switch the buffers */ TmpTarget = NewStrBuf (); } else if (PendingNewLines > 0 && MultiLine) { /* Cancel remaining check for pp-tokens separation ** if there is since ther have been newlines that ** can always separate them. */ if (CurRescanStack->PrevTok != 0) { FreeStrBuf (CurRescanStack->PrevTok); CurRescanStack->PrevTok = 0; } /* Squeeze whitespace */ SkipWhitespace (0); /* Add indention to preprocessor output if needed */ if (CurC != '\0' && CollCount (&CurRescanStack->Lines) == 1) { /* Add newlines */ AddPreLine (Target); /* Align indention */ AppendIndent (Target, SB_GetIndex (Line)); } } /* Since we are rescanning, we needn't add the ** count of just replaced identifiers right now. */ continue; } /* An unexpandable identifier. Keep it. */ ++Count; /* Add a space to separate the macro name if necessary */ SeparatePPTok (Target, Ident[0]); SB_AppendStr (Target, Ident); /* Keep tracking pp-token lengths */ if ((ModeFlags & MSM_IN_ARG_EXPANSION) != 0) { /* Used for concatentation check */ if ((E->Flags & MES_FIRST_TOKEN) != 0) { E->Flags |= MES_BEGIN_WITH_IDENT; } ME_SetTokLens (E, strlen (Ident)); } } } else { unsigned LastLen; /* Add a space to separate the macro name if necessary */ SeparatePPTok (Target, CurC); LastLen = SB_GetLen (Target); if ((ModeFlags & MSM_TOK_HEADER) != 0 && (CurC == '<' || CurC == '\"')) { CopyHeaderNameToken (Target); } else if (IsPPNumber (CurC, NextC)) { CopyPPNumber (Target); } else if (IsQuotedString ()) { CopyQuotedString (Target); } else { /* We want to squeeze whitespace until the end of the current ** input line, so we have to deal with such cases specially. */ if (CollCount (&CurRescanStack->Lines) > 1) { RescanInputStack* RIS = CurRescanStack; /* Temporarily disable input popping */ CurRescanStack = 0; Skipped = SkipWhitespace (0); CurRescanStack = RIS; if (CurC == '\0') { /* Now we are at the end of the input line */ goto Loop; } } else { Skipped = SkipWhitespace (0); } /* Punctuators must be checked after whitespace since comments ** introducers may be misinterpreted as division operators. */ if (!Skipped) { if (GetPunc (Ident)) { if (Ident[0] == ')') { /* Count right parens */ ++Count; } SB_AppendStr (Target, Ident); /* If an identifier follows immediately, it could be a macro ** expanded later that occasionally need a space to separate. */ if (IsIdent (CurC)) { /* Memorize the previous pp-token and check it later */ LazyCheckNextPPTok (Target, strlen (Ident)); } } else { SB_AppendChar (Target, CurC); NextChar (); /* Don't count this character */ ++LastLen; } } } /* Keep tracking pp-token lengths */ if ((ModeFlags & MSM_IN_ARG_EXPANSION) != 0) { ME_SetTokLens (E, SB_GetLen (Target) - LastLen); } } Loop: /* Switch back to the previous input stream if we have finished ** rescanning the current one. */ if (CurC == '\0' && CollCount (&CurRescanStack->Lines) > 1) { /* Check for rescan sequence end and pp-token pasting */ Skipped = SkipWhitespace (0) || Skipped; /* Add indention to preprocessor output if needed */ if (CurC != '\0' && PendingNewLines > 0 && (ModeFlags & MSM_MULTILINE) != 0 && CollCount (&CurRescanStack->Lines) == 1) { /* Add newlines */ AddPreLine (Target); /* Align indention */ AppendIndent (Target, SB_GetIndex (Line)); Skipped = 0; } } /* Append a space if there hasn't been one */ if (Skipped && !IsSpace (SB_LookAtLast (Target))) { SB_AppendChar (Target, ' '); } } /* Append the remaining result */ SB_Append (Target, TmpTarget); /* Done with the temporary buffer */ SB_Done (TmpTarget); /* Drop whitespace at the end */ if (IsBlank (SB_LookAtLast (Target))) { SB_Drop (Target, 1); } /* Sanity check */ if ((E->Flags & MES_ERROR) == 0) { CHECK (CollCount (&CurRescanStack->Lines) == 1); } /* Done with the current input stack */ DoneRescanInputStack (CurRescanStack); CurRescanStack = OldRescanStack; /* Switch back the input */ InitLine (OldSource); /* Return the count of identifiers and right parentheses */ return Count; } /*****************************************************************************/ /* Directives */ /*****************************************************************************/ static int ParseMacroReplacement (StrBuf* Source, Macro* M) /* Check correctness of macro definition while squeezing old and new style ** comments and other non-newline whitespace sequences. Return 1 on success ** or 0 on failure. */ { /* Switch to the new input source */ StrBuf* OldSource = InitLine (Source); int HasWhiteSpace = 0; unsigned Len; ident Ident; int Std = IS_Get (&Standard); /* Skip whitespace before the macro replacement */ SkipWhitespace (0); /* Check for ## at start */ if (CurC == '#' && NextC == '#') { /* Diagnose and bail out */ PPError ("'##' cannot appear at start of macro expansion"); goto Error_Handler; } /* Loop removing ws and comments */ while (CurC != '\0') { if (HasWhiteSpace) { SB_AppendChar (&M->Replacement, ' '); } else if (IsQuotedString ()) { CopyQuotedString (&M->Replacement); } else if (IsSym (Ident)) { CheckForBadIdent (Ident, Std, M); SB_AppendStr (&M->Replacement, Ident); } else { if (M->ParamCount >= 0 && GetPunc (Ident)) { Len = strlen (Ident); /* Check for # */ if (Len == 1 && Ident[0] == '#') { HasWhiteSpace = SkipWhitespace (0); /* Check next pp-token */ if (!IsSym (Ident) || FindMacroParam (M, Ident) < 0) { PPError ("'#' is not followed by a macro parameter"); goto Error_Handler; } /* Make the replacement */ SB_AppendChar (&M->Replacement, '#'); if (HasWhiteSpace) { SB_AppendChar (&M->Replacement, ' '); } SB_AppendStr (&M->Replacement, Ident); } else { SB_AppendBuf (&M->Replacement, Ident, Len); } } else { SB_AppendChar (&M->Replacement, CurC); NextChar (); } } HasWhiteSpace = SkipWhitespace (0); } /* Check for ## at end */ Len = SB_GetLen (&M->Replacement); if (Len >= 2) { if (SB_LookAt (&M->Replacement, Len - 1) == '#' && SB_LookAt (&M->Replacement, Len - 2) == '#') { /* Diagnose and bail out */ PPError ("'##' cannot appear at end of macro expansion"); goto Error_Handler; } } /* Terminate the new input line */ SB_Terminate (&M->Replacement); /* Switch back to the old source */ InitLine (OldSource); /* Success */ return 1; Error_Handler: /* Switch back to the old source */ InitLine (OldSource); /* Failure */ return 0; } static void DoDefine (void) /* Process #define directive */ { ident Ident; Macro* M = 0; Macro* Existing; int Std; /* Read the macro name */ SkipWhitespace (0); if (!MacName (Ident)) { goto Error_Handler; } /* Remember the language standard we are in */ Std = IS_Get (&Standard); /* Check for forbidden macro names */ if (strcmp (Ident, "defined") == 0) { PPError ("'%s' cannot be used as a macro name", Ident); goto Error_Handler; } /* Check for and warn on special identifiers */ CheckForBadIdent (Ident, Std, 0); /* Create a new macro definition */ M = NewMacro (Ident); /* Check if this is a function-like macro */ if (CurC == '(') { /* Skip the left paren */ NextChar (); /* Set the marker that this is a function-like macro */ M->ParamCount = 0; /* Read the formal parameter list */ while (1) { /* Skip white space and check for end of parameter list */ SkipWhitespace (0); if (CurC == ')') { break; } /* The next token must be either an identifier, or - if not in ** C89 mode - the ellipsis. */ if (Std >= STD_C99 && CurC == '.') { /* Ellipsis */ NextChar (); if (CurC != '.' || NextC != '.') { PPError ("'...' expected"); goto Error_Handler; } NextChar (); NextChar (); /* Remember that the macro is variadic and use __VA_ARGS__ as ** the parameter name. */ AddMacroParam (M, "__VA_ARGS__"); M->Variadic = 1; } else { /* Must be macro parameter name */ if (MacName (Ident) == 0) { goto Error_Handler; } /* Check for and warn on special identifiers */ CheckForBadIdent (Ident, Std, 0); /* Add the macro parameter */ AddMacroParam (M, Ident); } /* If we had an ellipsis, or the next char is not a comma, we've ** reached the end of the macro parameter list. */ SkipWhitespace (0); if (M->Variadic || CurC != ',') { break; } NextChar (); } /* Check for a right paren and eat it if we find one */ if (CurC != ')') { PPError ("')' expected for macro definition"); goto Error_Handler; } NextChar (); } /* Remove whitespace and comments from the line, store the preprocessed ** line into the macro replacement buffer. */ if (ParseMacroReplacement (Line, M) == 0) { goto Error_Handler; } #if 0 printf ("%s: <%.*s>\n", M->Name, SB_GetLen (&M->Replacement), SB_GetConstBuf (&M->Replacement)); #endif /* Get an existing macro definition with this name */ Existing = FindMacro (M->Name); /* If we have an existing macro, check if the redefinition is identical. ** Print a diagnostic if not. */ if (Existing != 0) { if (MacroCmp (M, Existing) != 0) { PPError ("Macro redefinition is not identical"); } /* Undefine the existing macro anyways */ UndefineMacro (Existing->Name); } /* Insert the new macro into the macro table */ InsertMacro (M); /* Success */ return; Error_Handler: /* Cleanup */ ClearLine (); if (M != 0) { FreeMacro (M); } } static int PushIf (int Skip, int Invert, int Cond) /* Push a new if level onto the if stack */ { /* Check for an overflow of the if stack */ if (PPStack->Index >= MAX_PP_IFS-1) { PPError ("Too many nested #if clauses"); return 1; } /* Push the #if condition */ ++PPStack->Index; if (Skip) { PPStack->Stack[PPStack->Index] = IFCOND_SKIP | IFCOND_NEEDTERM; return 1; } else { PPStack->Stack[PPStack->Index] = IFCOND_NONE | IFCOND_NEEDTERM; return (Invert ^ Cond); } } static void DoError (void) /* Print an error */ { SkipWhitespace (0); if (CurC == '\0') { PPError ("#error"); } else { StrBuf MsgLine = AUTO_STRBUF_INITIALIZER; TranslationPhase3 (Line, &MsgLine); PPError ("#error: %s", SB_GetConstBuf (&MsgLine) + SB_GetIndex (&MsgLine)); SB_Done (&MsgLine); } /* Clear the rest of line */ ClearLine (); } static int DoIf (int Skip) /* Process #if directive */ { PPExpr Expr = AUTO_PPEXPR_INITIALIZER; if (!Skip) { /* We're about to use a dedicated expression parser to evaluate the #if ** expression. Save the current tokens to come back here later. */ Token SavedCurTok = CurTok; Token SavedNextTok = NextTok; /* Make sure the line infos for the tokens won't get removed */ if (SavedCurTok.LI) { UseLineInfo (SavedCurTok.LI); } if (SavedNextTok.LI) { UseLineInfo (SavedNextTok.LI); } /* Macro-replace a single line with support for the "defined" operator */ SB_Clear (MLine); PreprocessDirective (Line, MLine, MSM_OP_DEFINED); /* Read from the processed line */ SB_Reset (MLine); MLine = InitLine (MLine); /* Add two semicolons as sentinels to the line, so the following ** expression evaluation will eat these two tokens but nothing from ** the following line. */ SB_AppendStr (Line, ";;"); SB_Terminate (Line); /* Load CurTok and NextTok with tokens from the new input */ NextToken (); NextToken (); /* Call the expression parser */ ParsePPExprInLine (&Expr); /* Restore input source */ MLine = InitLine (MLine); /* Reset the old tokens */ CurTok = SavedCurTok; NextTok = SavedNextTok; } /* Set the #if condition according to the expression result */ return PushIf (Skip, 1, Expr.IVal != 0); } static int DoIfDef (int skip, int flag) /* Process #ifdef if flag == 1, or #ifndef if flag == 0. */ { int Value = 0; if (!skip) { ident Ident; SkipWhitespace (0); if (MacName (Ident)) { CheckForBadIdent (Ident, IS_Get (&Standard), 0); Value = IsMacro (Ident); /* Check for extra tokens */ CheckExtraTokens (flag ? "ifdef" : "ifndef"); } } return PushIf (skip, flag, Value); } static void DoInclude (void) /* Open an include file. */ { char RTerm; InputType IT; StrBuf Filename = AUTO_STRBUF_INITIALIZER; /* Macro-replace a single line with special support for */ SB_Clear (MLine); PreprocessDirective (Line, MLine, MSM_TOK_HEADER); /* Read from the processed line */ SB_Reset (MLine); MLine = InitLine (MLine); /* Get the next char and check for a valid file name terminator. Setup ** the include directory spec (SYS/USR) by looking at the terminator. */ switch (CurC) { case '\"': RTerm = '\"'; IT = IT_USRINC; break; case '<': RTerm = '>'; IT = IT_SYSINC; break; default: PPError ("#include expects \"FILENAME\" or "); goto Done; } NextChar (); /* Get a copy of the filename */ while (CurC != '\0' && CurC != RTerm) { SB_AppendChar (&Filename, CurC); NextChar (); } SB_Terminate (&Filename); /* Check if we got a terminator */ if (CurC == RTerm) { /* Skip the terminator */ NextChar (); /* Check for extra tokens following the filename */ CheckExtraTokens ("include"); /* Open the include file */ OpenIncludeFile (SB_GetConstBuf (&Filename), IT); } else { /* No terminator found */ PPError ("#include expects \"FILENAME\" or "); } Done: /* Free the allocated filename data */ SB_Done (&Filename); /* Restore input source */ MLine = InitLine (MLine); /* Clear the remaining line so the next input will come from the new ** file (if open) */ ClearLine (); } static unsigned GetLineDirectiveNum (void) /* Get a decimal digit-sequence from the input. Return 0 on errors. */ { unsigned long Num = 0; StrBuf Buf = AUTO_STRBUF_INITIALIZER; /* The only non-decimal-numeric character allowed in the digit-sequence is ** the digit separator '\'' as of C23, but we haven't supported it yet. */ SkipWhitespace (0); while (IsDigit (CurC)) { SB_AppendChar (&Buf, CurC); NextChar (); } /* Ensure the buffer is terminated with a '\0' */ SB_Terminate (&Buf); if (SkipWhitespace (0) != 0 || CurC == '\0') { const char* Str = SB_GetConstBuf (&Buf); if (Str[0] == '\0') { PPWarning ("#line directive interprets number as decimal, not octal"); } else { Num = strtoul (Str, 0, 10); if (Num > 2147483647) { PPError ("#line directive requires an integer argument not greater than 2147483647"); ClearLine (); Num = 0; } else if (Num == 0) { PPError ("#line directive requires a positive integer argument"); ClearLine (); } } } else { PPError ("#line directive requires a simple decimal digit sequence"); ClearLine (); } /* Done with the buffer */ SB_Done (&Buf); return (unsigned)Num; } static void DoLine (void) /* Process #line directive */ { unsigned LineNum; /* Macro-replace a single line with support for the "defined" operator */ SB_Clear (MLine); PreprocessDirective (Line, MLine, MSM_NONE); /* Read from the processed line */ SB_Reset (MLine); MLine = InitLine (MLine); /* Parse and check the specified line number */ LineNum = GetLineDirectiveNum (); if (LineNum != 0) { /* Parse and check the optional filename argument */ if (SB_GetIndex (Line) < SB_GetLen (Line)) { StrBuf Filename = AUTO_STRBUF_INITIALIZER; if (SB_GetString (Line, &Filename)) { SB_Terminate (&Filename); SetCurrentFileName (SB_GetConstBuf (&Filename)); } else { PPError ("Invalid filename for #line directive"); LineNum = 0; } SB_Done (&Filename); } /* #line actually sets the line number of the next line */ if (LineNum > 0) { SetCurrentLineNum (LineNum - 1); /* Check for extra tokens at the end */ CheckExtraTokens ("line"); } } /* Restore input source */ MLine = InitLine (MLine); } static void DoPragma (void) /* Handle a #pragma line by converting the #pragma preprocessor directive into ** the _Pragma() compiler operator. */ { StrBuf* PragmaLine = OLine; PRECONDITION (PragmaLine != 0); /* Add the source info to preprocessor output if needed */ AddPreLine (PragmaLine); /* Macro-replace a single line */ SB_Clear (MLine); PreprocessDirective (Line, MLine, MSM_NONE); /* Convert #pragma to _Pragma () */ SB_AppendStr (PragmaLine, "_Pragma ("); SB_Reset (MLine); Stringize (MLine, PragmaLine); SB_AppendChar (PragmaLine, ')'); /* End this line */ SB_SetIndex (PragmaLine, SB_GetLen (PragmaLine)); } static void DoUndef (void) /* Process the #undef directive */ { ident Ident; SkipWhitespace (0); if (MacName (Ident)) { CheckForBadIdent (Ident, IS_Get (&Standard), 0); UndefineMacro (Ident); } /* Check for extra tokens */ CheckExtraTokens ("undef"); } static void DoWarning (void) /* Print a warning */ { SkipWhitespace (0); if (CurC == '\0') { PPWarning ("#warning"); } else { StrBuf MsgLine = AUTO_STRBUF_INITIALIZER; TranslationPhase3 (Line, &MsgLine); PPWarning ("#warning: %s", SB_GetConstBuf (&MsgLine) + SB_GetIndex (&MsgLine)); SB_Done (&MsgLine); } /* Clear the rest of line */ ClearLine (); } static int ParseDirectives (unsigned ModeFlags) /* Handle directives. Return 1 if any whitespace or newlines are parsed. */ { int PPSkip = 0; ident Directive; /* Skip white space at the beginning of the first line */ int Whitespace = SkipWhitespace (0); /* Check for stuff to skip */ while (CurC == '\0' || CurC == '#' || PPSkip) { /* Check for preprocessor lines lines */ if (CurC == '#') { NextChar (); SkipWhitespace (0); if (CurC == '\0') { /* Ignore the empty preprocessor directive */ continue; } if (!IsSym (Directive)) { if (!PPSkip) { PPError ("Preprocessor directive expected"); } ClearLine (); } else { switch (FindPPDirectiveType (Directive)) { case PPD_DEFINE: if (!PPSkip) { DoDefine (); } break; case PPD_ELIF: if (PPStack->Index >= 0) { if ((PPStack->Stack[PPStack->Index] & IFCOND_ELSE) == 0) { /* Handle as #else/#if combination */ if ((PPStack->Stack[PPStack->Index] & IFCOND_SKIP) == 0) { PPSkip = !PPSkip; } PPStack->Stack[PPStack->Index] |= IFCOND_ELSE; PPSkip = DoIf (PPSkip); /* #elif doesn't need a terminator */ PPStack->Stack[PPStack->Index] &= ~IFCOND_NEEDTERM; } else { PPError ("Duplicate #else/#elif"); } } else { PPError ("Unexpected #elif"); } break; case PPD_ELSE: if (PPStack->Index >= 0) { if ((PPStack->Stack[PPStack->Index] & IFCOND_ELSE) == 0) { if ((PPStack->Stack[PPStack->Index] & IFCOND_SKIP) == 0) { PPSkip = !PPSkip; } PPStack->Stack[PPStack->Index] |= IFCOND_ELSE; /* Check for extra tokens */ CheckExtraTokens ("else"); } else { PPError ("Duplicate #else"); } } else { PPError ("Unexpected '#else'"); } break; case PPD_ENDIF: if (PPStack->Index >= 0) { /* Remove any clauses on top of stack that do not ** need a terminating #endif. */ while (PPStack->Index >= 0 && (PPStack->Stack[PPStack->Index] & IFCOND_NEEDTERM) == 0) { --PPStack->Index; } /* Stack may not be empty here or something is wrong */ CHECK (PPStack->Index >= 0); /* Remove the clause that needs a terminator */ PPSkip = (PPStack->Stack[PPStack->Index--] & IFCOND_SKIP) != 0; /* Check for extra tokens */ CheckExtraTokens ("endif"); } else { PPError ("Unexpected '#endif'"); } break; case PPD_ERROR: if (!PPSkip) { DoError (); } break; case PPD_IF: PPSkip = DoIf (PPSkip); break; case PPD_IFDEF: PPSkip = DoIfDef (PPSkip, 1); break; case PPD_IFNDEF: PPSkip = DoIfDef (PPSkip, 0); break; case PPD_INCLUDE: if (!PPSkip) { DoInclude (); } break; case PPD_LINE: if (!PPSkip) { DoLine (); } break; case PPD_PRAGMA: if (!PPSkip) { if ((ModeFlags & MSM_IN_ARG_LIST) == 0) { DoPragma (); return Whitespace; } else { PPError ("Embedded #pragma directive within macro arguments is unsupported"); } } break; case PPD_UNDEF: if (!PPSkip) { DoUndef (); } break; case PPD_WARNING: /* #warning is a non standard extension */ if (IS_Get (&Standard) > STD_C99) { if (!PPSkip) { DoWarning (); } } else { if (!PPSkip) { PPError ("Preprocessor directive expected"); } ClearLine (); } break; default: if (!PPSkip) { PPError ("Preprocessor directive expected"); } ClearLine (); } } } if (NextLine () == 0) { break; } ++PendingNewLines; Whitespace = SkipWhitespace (0) || Whitespace; } return Whitespace; } void HandleSpecialMacro (Macro* M, const char* Name) /* Handle special "magic" macros that may change */ { if (strcmp (Name, "__COUNTER__") == 0) { /* Replace __COUNTER__ with the current counter number */ if (IS_Get (&Standard) < STD_CC65) { PPWarning ("__COUNTER__ is a cc65 extension"); } SB_Printf (&M->Replacement, "%u", GetCurrentCounter ()); } else if (strcmp (Name, "__LINE__") == 0) { /* Replace __LINE__ with the current line number */ SB_Printf (&M->Replacement, "%u", GetCurrentLineNum ()); } else if (strcmp (Name, "__FILE__") == 0) { /* Replace __FILE__ with the current filename */ StrBuf B = AUTO_STRBUF_INITIALIZER; SB_InitFromString (&B, GetCurrentFileName ()); SB_Clear (&M->Replacement); Stringize (&B, &M->Replacement); SB_Done (&B); } } /*****************************************************************************/ /* Preprocessing */ /*****************************************************************************/ static void TranslationPhase3 (StrBuf* Source, StrBuf* Target) /* Mimic Translation Phase 3. Handle old and new style comments. Collapse ** non-newline whitespace sequences. */ { /* Switch to the new input source */ StrBuf* OldSource = InitLine (Source); /* Loop removing ws and comments */ while (CurC != '\0') { int HasWhiteSpace = 0; while (1) { /* Squeeze runs of blanks */ if (IsSpace (CurC)) { NextChar (); HasWhiteSpace = 1; } else if (CurC == '/' && NextC == '*') { OldStyleComment (); HasWhiteSpace = 1; } else if (CurC == '/' && NextC == '/') { NewStyleComment (); HasWhiteSpace = 1; } else { /* No more white space */ break; } } if (HasWhiteSpace) { SB_AppendChar (Target, ' '); } else if (IsQuotedString ()) { CopyQuotedString (Target); } else { SB_AppendChar (Target, CurC); NextChar (); } } /* Terminate the new input line */ SB_Terminate (Target); /* Switch back to the old source */ InitLine (OldSource); } static void PreprocessDirective (StrBuf* Source, StrBuf* Target, unsigned ModeFlags) /* Preprocess a single line. Handle specified tokens and operators, remove ** whitespace and comments, then do macro replacement. */ { MacroExp E; SkipWhitespace (0); InitMacroExp (&E); ReplaceMacros (Source, Target, &E, ModeFlags | MSM_IN_DIRECTIVE); DoneMacroExp (&E); } void Preprocess (void) /* Preprocess lines count of which is affected by directives */ { MacroExp E; SB_Clear (PLine); /* Add the source info to preprocessor output if needed */ AddPreLine (PLine); /* Parse any directives */ OLine = PLine; ParseDirectives (MSM_MULTILINE); OLine = 0; /* Add the source info to preprocessor output if needed */ AddPreLine (PLine); /* Add leading whitespace to prettify preprocessor output */ if (CurC != '\0') { AppendIndent (PLine, SB_GetIndex (Line)); } /* Expand macros if any */ InitMacroExp (&E); ReplaceMacros (Line, PLine, &E, MSM_MULTILINE); DoneMacroExp (&E); /* Add the source info to preprocessor output if needed */ AddPreLine (PLine); /* Read from the new line */ SB_Reset (PLine); PLine = InitLine (PLine); if (Verbosity > 1 && SB_NotEmpty (Line)) { printf ("%s:%u: %.*s\n", GetCurrentFileName (), GetCurrentLineNum (), (int) SB_GetLen (Line), SB_GetConstBuf (Line)); } /* Free all undefined macros */ FreeUndefinedMacros (); } void InitPreprocess (void) /* Init preprocessor */ { /* Create the output buffers */ MLine = NewStrBuf (); PLine = NewStrBuf (); } void DonePreprocess (void) /* Done with preprocessor */ { /* Done with the output buffers */ SB_Done (MLine); SB_Done (PLine); } void SetPPIfStack (PPIfStack* Stack) /* Specify which PP #if stack to use */ { PPStack = Stack; } void ContinueLine (void) /* Continue the current line ended with a '\\' */ { ++ContinuedLines; } void PreprocessBegin (void) /* Initialize preprocessor with current file */ { /* Reset #if depth */ PPStack->Index = -1; /* Remember to update source file location in preprocess-only mode */ FileChanged = 1; /* Enable diagnostics on new style comments in C89 mode */ AllowNewComments = 0; } void PreprocessEnd (void) /* Preprocessor done with current file */ { /* Check for missing #endif */ while (PPStack->Index >= 0) { if ((PPStack->Stack[PPStack->Index] & IFCOND_NEEDTERM) != 0) { PPError ("#endif expected"); } --PPStack->Index; } /* Remember to update source file location in preprocess-only mode */ FileChanged = 1; }