/*****************************************************************************/ /* */ /* 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 "expr.h" #include "global.h" #include "ident.h" #include "incpath.h" #include "input.h" #include "lineinfo.h" #include "macrotab.h" #include "preproc.h" #include "scanner.h" #include "standard.h" /*****************************************************************************/ /* Data */ /*****************************************************************************/ /* Set when the preprocessor calls expr() recursively */ unsigned char Preprocessing = 0; /* Management data for #if */ #define MAX_IFS 64 #define IFCOND_NONE 0x00U #define IFCOND_SKIP 0x01U #define IFCOND_ELSE 0x02U #define IFCOND_NEEDTERM 0x04U static unsigned char IfStack[MAX_IFS]; static int IfIndex = -1; /* Buffer for macro expansion */ static StrBuf* MLine; /* Structure used when expanding macros */ typedef struct MacroExp MacroExp; struct MacroExp { Collection ActualArgs; /* Actual arguments */ StrBuf Replacement; /* Replacement with arguments substituted */ Macro* M; /* The macro we're handling */ }; /*****************************************************************************/ /* Forwards */ /*****************************************************************************/ static unsigned Pass1 (StrBuf* Source, StrBuf* Target); /* Preprocessor pass 1. Remove whitespace. Handle old and new style comments * and the "defined" operator. */ static void MacroReplacement (StrBuf* Source, StrBuf* Target); /* Perform macro replacement. */ /*****************************************************************************/ /* Low level preprocessor token handling */ /*****************************************************************************/ /* Types of preprocessor tokens */ typedef enum { PP_ILLEGAL = -1, PP_DEFINE, PP_ELIF, PP_ELSE, PP_ENDIF, PP_ERROR, PP_IF, PP_IFDEF, PP_IFNDEF, PP_INCLUDE, PP_LINE, PP_PRAGMA, PP_UNDEF, PP_WARNING, } pptoken_t; /* Preprocessor keyword to token mapping table */ static const struct PPToken { const char* Key; /* Keyword */ pptoken_t Tok; /* Token */ } PPTokens[] = { { "define", PP_DEFINE }, { "elif", PP_ELIF }, { "else", PP_ELSE }, { "endif", PP_ENDIF }, { "error", PP_ERROR }, { "if", PP_IF }, { "ifdef", PP_IFDEF }, { "ifndef", PP_IFNDEF }, { "include", PP_INCLUDE }, { "line", PP_LINE }, { "pragma", PP_PRAGMA }, { "undef", PP_UNDEF }, { "warning", PP_WARNING }, }; /* Number of preprocessor tokens */ #define PPTOKEN_COUNT (sizeof(PPTokens) / sizeof(PPTokens[0])) static int CmpToken (const void* Key, const void* Elem) /* Compare function for bsearch */ { return strcmp ((const char*) Key, ((const struct PPToken*) Elem)->Key); } static pptoken_t FindPPToken (const char* Ident) /* Find a preprocessor token and return it. Return PP_ILLEGAL if the identifier * is not a valid preprocessor token. */ { struct PPToken* P; P = bsearch (Ident, PPTokens, PPTOKEN_COUNT, sizeof (PPTokens[0]), CmpToken); return P? P->Tok : PP_ILLEGAL; } /*****************************************************************************/ /* struct MacroExp */ /*****************************************************************************/ static MacroExp* InitMacroExp (MacroExp* E, Macro* M) /* Initialize a MacroExp structure */ { InitCollection (&E->ActualArgs); SB_Init (&E->Replacement); E->M = M; return E; } static void DoneMacroExp (MacroExp* E) /* Cleanup after use of a MacroExp structure */ { unsigned I; /* Delete the list with actual arguments */ for (I = 0; I < CollCount (&E->ActualArgs); ++I) { FreeStrBuf (CollAtUnchecked (&E->ActualArgs, I)); } DoneCollection (&E->ActualArgs); SB_Done (&E->Replacement); } static void ME_AppendActual (MacroExp* E, StrBuf* Arg) /* Add a copy of Arg to the list of actual macro arguments. * NOTE: This function will clear Arg! */ { /* Create a new string buffer */ StrBuf* A = NewStrBuf (); /* Move the contents of Arg to A */ SB_Move (A, Arg); /* Add A to the actual arguments */ CollAppend (&E->ActualArgs, A); } static StrBuf* ME_GetActual (MacroExp* E, unsigned Index) /* Return an actual macro argument with the given index */ { return CollAt (&E->ActualArgs, Index); } static int ME_ArgIsVariadic (const MacroExp* E) /* Return true if the next actual argument we will add is a variadic one */ { return (E->M->Variadic && E->M->ArgCount == (int) CollCount (&E->ActualArgs) + 1); } /*****************************************************************************/ /* Code */ /*****************************************************************************/ 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 = GetCurrentLine(); /* 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; } } 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. */ { /* Beware: Because line continuation chars are handled when reading * lines, we may only skip til 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 in the input stream. Do also skip newlines if SkipLines * is true. Return zero if nothing was skipped, otherwise return a * value != zero. */ { int Skipped = 0; while (1) { if (IsSpace (CurC)) { NextChar (); Skipped = 1; } else if (CurC == '\0' && SkipLines) { /* End of line, read next */ if (NextLine () != 0) { Skipped = 1; } else { /* End of input */ break; } } else { /* No more white space */ break; } } return Skipped; } 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 = 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 (); } } /*****************************************************************************/ /* Macro stuff */ /*****************************************************************************/ 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) { PPError ("Identifier expected"); ClearLine (); return 0; } else { return 1; } } static void ReadMacroArgs (MacroExp* E) /* Identify the arguments to a macro call */ { unsigned Parens; /* Number of open parenthesis */ StrBuf Arg = STATIC_STRBUF_INITIALIZER; /* Read the actual macro arguments */ Parens = 0; while (1) { if (CurC == '(') { /* Nested parenthesis */ SB_AppendChar (&Arg, CurC); NextChar (); ++Parens; } else if (IsQuote (CurC)) { /* Quoted string - just copy */ CopyQuotedString (&Arg); } else if (CurC == ',' || CurC == ')') { if (Parens) { /* Comma or right paren inside nested parenthesis */ if (CurC == ')') { --Parens; } SB_AppendChar (&Arg, CurC); NextChar (); } else if (CurC == ',' && ME_ArgIsVariadic (E)) { /* It's a comma, but we're inside a variadic macro argument, so * just copy it and proceed. */ SB_AppendChar (&Arg, CurC); NextChar (); } else { /* End of actual argument. Remove whitespace from the end. */ while (IsSpace (SB_LookAtLast (&Arg))) { SB_Drop (&Arg, 1); } /* If this is not the single empty argument for a macro with * an empty argument list, remember it. */ if (CurC != ')' || SB_NotEmpty (&Arg) || E->M->ArgCount > 0) { ME_AppendActual (E, &Arg); } /* Check for end of macro param list */ if (CurC == ')') { NextChar (); break; } /* Start the next param */ NextChar (); SB_Clear (&Arg); } } else if (SkipWhitespace (1)) { /* Squeeze runs of blanks within an arg */ if (SB_NotEmpty (&Arg)) { SB_AppendChar (&Arg, ' '); } } else if (CurC == '/' && NextC == '*') { if (SB_NotEmpty (&Arg)) { SB_AppendChar (&Arg, ' '); } OldStyleComment (); } else if (IS_Get (&Standard) >= STD_C99 && CurC == '/' && NextC == '/') { if (SB_NotEmpty (&Arg)) { SB_AppendChar (&Arg, ' '); } NewStyleComment (); } else if (CurC == '\0') { /* End of input inside macro argument list */ PPError ("Unterminated argument list invoking macro `%s'", E->M->Name); ClearLine (); break; } else { /* Just copy the character */ SB_AppendChar (&Arg, CurC); NextChar (); } } /* Deallocate string buf resources */ SB_Done (&Arg); } static void MacroArgSubst (MacroExp* E) /* Argument substitution according to ISO/IEC 9899:1999 (E), 6.10.3.1ff */ { ident Ident; int ArgIdx; StrBuf* OldSource; StrBuf* Arg; int HaveSpace; /* Remember the current input and switch to the macro replacement. */ int OldIndex = SB_GetIndex (&E->M->Replacement); SB_Reset (&E->M->Replacement); OldSource = InitLine (&E->M->Replacement); /* Argument handling loop */ while (CurC != '\0') { /* If we have an identifier, check if it's a macro */ if (IsSym (Ident)) { /* Check if it's a macro argument */ if ((ArgIdx = FindMacroArg (E->M, Ident)) >= 0) { /* A macro argument. Get the corresponding actual argument. */ Arg = ME_GetActual (E, ArgIdx); /* Copy any following whitespace */ HaveSpace = SkipWhitespace (0); /* If a ## operator follows, we have to insert the actual * argument as is, otherwise it must be macro replaced. */ if (CurC == '#' && NextC == '#') { /* ### Add placemarker if necessary */ SB_Append (&E->Replacement, Arg); } else { /* Replace the formal argument by a macro replaced copy * of the actual. */ SB_Reset (Arg); MacroReplacement (Arg, &E->Replacement); /* If we skipped whitespace before, re-add it now */ if (HaveSpace) { SB_AppendChar (&E->Replacement, ' '); } } } else { /* An identifier, keep it */ SB_AppendStr (&E->Replacement, Ident); } } else if (CurC == '#' && NextC == '#') { /* ## operator. */ NextChar (); NextChar (); SkipWhitespace (0); /* Since we need to concatenate the token sequences, remove * any whitespace that was added to target, since it must come * from the input. */ while (IsSpace (SB_LookAtLast (&E->Replacement))) { SB_Drop (&E->Replacement, 1); } /* If the next token is an identifier which is a macro argument, * replace it, otherwise do nothing. */ if (IsSym (Ident)) { /* Check if it's a macro argument */ if ((ArgIdx = FindMacroArg (E->M, Ident)) >= 0) { /* Get the corresponding actual argument and add it. */ SB_Append (&E->Replacement, ME_GetActual (E, ArgIdx)); } else { /* Just an ordinary identifier - add as is */ SB_AppendStr (&E->Replacement, Ident); } } } else if (CurC == '#' && E->M->ArgCount >= 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) || (ArgIdx = FindMacroArg (E->M, Ident)) < 0) { PPError ("`#' is not followed by a macro parameter"); } else { /* Make a valid string from Replacement */ Arg = ME_GetActual (E, ArgIdx); SB_Reset (Arg); Stringize (Arg, &E->Replacement); } } else if (IsQuote (CurC)) { CopyQuotedString (&E->Replacement); } else { SB_AppendChar (&E->Replacement, CurC); NextChar (); } } #if 0 /* Remove whitespace from the end of the line */ while (IsSpace (SB_LookAtLast (&E->Replacement))) { SB_Drop (&E->Replacement, 1); } #endif /* Switch back the input */ InitLine (OldSource); SB_SetIndex (&E->M->Replacement, OldIndex); } static void MacroCall (StrBuf* Target, Macro* M) /* Process a function like macro */ { MacroExp E; /* Eat the left paren */ NextChar (); /* Initialize our MacroExp structure */ InitMacroExp (&E, M); /* Read the actual macro arguments */ ReadMacroArgs (&E); /* Compare formal and actual argument count */ if (CollCount (&E.ActualArgs) != (unsigned) M->ArgCount) { StrBuf Arg = STATIC_STRBUF_INITIALIZER; /* Argument count mismatch */ PPError ("Macro argument count mismatch"); /* Be sure to make enough empty arguments available */ while (CollCount (&E.ActualArgs) < (unsigned) M->ArgCount) { ME_AppendActual (&E, &Arg); } } /* Replace macro arguments handling the # and ## operators */ MacroArgSubst (&E); /* Do macro replacement on the macro that already has the parameters * substituted. */ M->Expanding = 1; MacroReplacement (&E.Replacement, Target); M->Expanding = 0; /* Free memory allocated for the macro expansion structure */ DoneMacroExp (&E); } static void ExpandMacro (StrBuf* Target, Macro* M) /* Expand a macro into Target */ { #if 0 static unsigned V = 0; printf ("Expanding %s(%u)\n", M->Name, ++V); #endif /* Check if this is a function like macro */ if (M->ArgCount >= 0) { int Whitespace = SkipWhitespace (1); if (CurC != '(') { /* Function like macro but no parameter list */ SB_AppendStr (Target, M->Name); if (Whitespace) { SB_AppendChar (Target, ' '); } } else { /* Function like macro */ MacroCall (Target, M); } } else { MacroExp E; InitMacroExp (&E, M); /* Handle # and ## operators for object like macros */ MacroArgSubst (&E); /* Do macro replacement on the macro that already has the parameters * substituted. */ M->Expanding = 1; MacroReplacement (&E.Replacement, Target); M->Expanding = 0; /* Free memory allocated for the macro expansion structure */ DoneMacroExp (&E); } #if 0 printf ("Done with %s(%u)\n", M->Name, V--); #endif } static void DefineMacro (void) /* Handle a macro definition. */ { ident Ident; Macro* M; Macro* Existing; int C89; /* Read the macro name */ SkipWhitespace (0); if (!MacName (Ident)) { return; } /* Remember if we're in C89 mode */ C89 = (IS_Get (&Standard) == STD_C89); /* Get an existing macro definition with this name */ Existing = FindMacro (Ident); /* 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->ArgCount = 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 (!C89 && CurC == '.') { /* Ellipsis */ NextChar (); if (CurC != '.' || NextC != '.') { PPError ("`...' expected"); ClearLine (); return; } NextChar (); NextChar (); /* Remember that the macro is variadic and use __VA_ARGS__ as * the argument name. */ AddMacroArg (M, "__VA_ARGS__"); M->Variadic = 1; } else { /* Must be macro argument name */ if (MacName (Ident) == 0) { return; } /* __VA_ARGS__ is only allowed in C89 mode */ if (!C89 && strcmp (Ident, "__VA_ARGS__") == 0) { PPWarning ("`__VA_ARGS__' can only appear in the expansion " "of a C99 variadic macro"); } /* Add the macro argument */ AddMacroArg (M, Ident); } /* If we had an ellipsis, or the next char is not a comma, we've * reached the end of the macro argument 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"); ClearLine (); return; } NextChar (); } /* Skip whitespace before the macro replacement */ SkipWhitespace (0); /* Insert the macro into the macro table and allocate the ActualArgs array */ InsertMacro (M); /* Remove whitespace and comments from the line, store the preprocessed * line into the macro replacement buffer. */ Pass1 (Line, &M->Replacement); /* Remove whitespace from the end of the line */ while (IsSpace (SB_LookAtLast (&M->Replacement))) { SB_Drop (&M->Replacement, 1); } #if 0 printf ("%s: <%.*s>\n", M->Name, SB_GetLen (&M->Replacement), SB_GetConstBuf (&M->Replacement)); #endif /* If we have an existing macro, check if the redefinition is identical. * Print a diagnostic if not. */ if (Existing && MacroCmp (M, Existing) != 0) { PPError ("Macro redefinition is not identical"); } } /*****************************************************************************/ /* Preprocessing */ /*****************************************************************************/ static unsigned Pass1 (StrBuf* Source, StrBuf* Target) /* Preprocessor pass 1. Remove whitespace. Handle old and new style comments * and the "defined" operator. */ { unsigned IdentCount; ident Ident; int HaveParen; /* Switch to the new input source */ StrBuf* OldSource = InitLine (Source); /* Loop removing ws and comments */ IdentCount = 0; while (CurC != '\0') { if (SkipWhitespace (0)) { /* Squeeze runs of blanks */ if (!IsSpace (SB_LookAtLast (Target))) { SB_AppendChar (Target, ' '); } } else if (IsSym (Ident)) { if (Preprocessing && strcmp (Ident, "defined") == 0) { /* Handle the "defined" operator */ SkipWhitespace (0); HaveParen = 0; if (CurC == '(') { HaveParen = 1; NextChar (); SkipWhitespace (0); } if (IsSym (Ident)) { SB_AppendChar (Target, IsMacro (Ident)? '1' : '0'); if (HaveParen) { SkipWhitespace (0); if (CurC != ')') { PPError ("`)' expected"); } else { NextChar (); } } } else { PPError ("Identifier expected"); SB_AppendChar (Target, '0'); } } else { ++IdentCount; SB_AppendStr (Target, Ident); } } else if (IsQuote (CurC)) { CopyQuotedString (Target); } else if (CurC == '/' && NextC == '*') { if (!IsSpace (SB_LookAtLast (Target))) { SB_AppendChar (Target, ' '); } OldStyleComment (); } else if (IS_Get (&Standard) >= STD_C99 && CurC == '/' && NextC == '/') { if (!IsSpace (SB_LookAtLast (Target))) { SB_AppendChar (Target, ' '); } NewStyleComment (); } else { SB_AppendChar (Target, CurC); NextChar (); } } /* Switch back to the old source */ InitLine (OldSource); /* Return the number of identifiers found in the line */ return IdentCount; } static void MacroReplacement (StrBuf* Source, StrBuf* Target) /* Perform macro replacement. */ { ident Ident; Macro* M; /* Remember the current input and switch to Source */ StrBuf* OldSource = InitLine (Source); /* Loop substituting macros */ while (CurC != '\0') { /* If we have an identifier, check if it's a macro */ if (IsSym (Ident)) { /* Check if it's a macro */ if ((M = FindMacro (Ident)) != 0 && !M->Expanding) { /* It's a macro, expand it */ ExpandMacro (Target, M); } else { /* An identifier, keep it */ SB_AppendStr (Target, Ident); } } else if (IsQuote (CurC)) { CopyQuotedString (Target); } else if (IsSpace (CurC)) { if (!IsSpace (SB_LookAtLast (Target))) { SB_AppendChar (Target, CurC); } NextChar (); } else { SB_AppendChar (Target, CurC); NextChar (); } } /* Switch back the input */ InitLine (OldSource); } static void PreprocessLine (void) /* Translate one line. */ { /* Trim whitespace and remove comments. The function returns the number of * identifiers found. If there were any, we will have to check for macros. */ SB_Clear (MLine); if (Pass1 (Line, MLine) > 0) { MLine = InitLine (MLine); SB_Reset (Line); SB_Clear (MLine); MacroReplacement (Line, MLine); } /* Read from the new line */ SB_Reset (MLine); MLine = InitLine (MLine); } 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 (IfIndex >= MAX_IFS-1) { PPError ("Too many nested #if clauses"); return 1; } /* Push the #if condition */ ++IfIndex; if (Skip) { IfStack[IfIndex] = IFCOND_SKIP | IFCOND_NEEDTERM; return 1; } else { IfStack[IfIndex] = IFCOND_NONE | IFCOND_NEEDTERM; return (Invert ^ Cond); } } static void DoError (void) /* Print an error */ { SkipWhitespace (0); if (CurC == '\0') { PPError ("Invalid #error directive"); } else { PPError ("#error: %s", SB_GetConstBuf (Line) + SB_GetIndex (Line)); } /* Clear the rest of line */ ClearLine (); } static int DoIf (int Skip) /* Process #if directive */ { ExprDesc Expr; /* We're about to abuse the compiler expression parser to evaluate the * #if expression. Save the current tokens to come back here later. * NOTE: Yes, this is a hack, but it saves a complete separate expression * evaluation for the preprocessor. */ 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); } /* Switch into special preprocessing mode */ Preprocessing = 1; /* Expand macros in this line */ PreprocessLine (); /* 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 */ ConstExpr (hie1, &Expr); /* End preprocessing mode */ Preprocessing = 0; /* 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. */ { ident Ident; SkipWhitespace (0); if (MacName (Ident) == 0) { return 0; } else { return PushIf (skip, flag, IsMacro(Ident)); } } static void DoInclude (void) /* Open an include file. */ { char RTerm; InputType IT; StrBuf Filename = STATIC_STRBUF_INITIALIZER; /* Preprocess the remainder of the line */ PreprocessLine (); /* Skip blanks */ SkipWhitespace (0); /* 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 ("`\"' or `<' expected"); 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) { /* Open the include file */ OpenIncludeFile (SB_GetConstBuf (&Filename), IT); } else if (CurC == '\0') { /* No terminator found */ PPError ("#include expects \"FILENAME\" or "); } Done: /* Free the allocated filename data */ SB_Done (&Filename); /* Clear the remaining line so the next input will come from the new * file (if open) */ ClearLine (); } static void DoPragma (void) /* Handle a #pragma line by converting the #pragma preprocessor directive into * the _Pragma() compiler operator. */ { /* Skip blanks following the #pragma directive */ SkipWhitespace (0); /* Copy the remainder of the line into MLine removing comments and ws */ SB_Clear (MLine); Pass1 (Line, MLine); /* Convert the directive into the operator */ SB_CopyStr (Line, "_Pragma ("); SB_Reset (MLine); Stringize (MLine, Line); SB_AppendChar (Line, ')'); /* Initialize reading from line */ SB_Reset (Line); InitLine (Line); } static void DoUndef (void) /* Process the #undef directive */ { ident Ident; SkipWhitespace (0); if (MacName (Ident)) { UndefineMacro (Ident); } } static void DoWarning (void) /* Print a warning */ { SkipWhitespace (0); if (CurC == '\0') { PPError ("Invalid #warning directive"); } else { PPWarning ("#warning: %s", SB_GetConstBuf (Line) + SB_GetIndex (Line)); } /* Clear the rest of line */ ClearLine (); } void Preprocess (void) /* Preprocess a line */ { int Skip; ident Directive; /* Create the output buffer if we don't already have one */ if (MLine == 0) { MLine = NewStrBuf (); } /* Skip white space at the beginning of the line */ SkipWhitespace (0); /* Check for stuff to skip */ Skip = 0; while (CurC == '\0' || CurC == '#' || Skip) { /* Check for preprocessor lines lines */ if (CurC == '#') { NextChar (); SkipWhitespace (0); if (CurC == '\0') { /* Ignore the empty preprocessor directive */ continue; } if (!IsSym (Directive)) { PPError ("Preprocessor directive expected"); ClearLine (); } else { switch (FindPPToken (Directive)) { case PP_DEFINE: if (!Skip) { DefineMacro (); } break; case PP_ELIF: if (IfIndex >= 0) { if ((IfStack[IfIndex] & IFCOND_ELSE) == 0) { /* Handle as #else/#if combination */ if ((IfStack[IfIndex] & IFCOND_SKIP) == 0) { Skip = !Skip; } IfStack[IfIndex] |= IFCOND_ELSE; Skip = DoIf (Skip); /* #elif doesn't need a terminator */ IfStack[IfIndex] &= ~IFCOND_NEEDTERM; } else { PPError ("Duplicate #else/#elif"); } } else { PPError ("Unexpected #elif"); } break; case PP_ELSE: if (IfIndex >= 0) { if ((IfStack[IfIndex] & IFCOND_ELSE) == 0) { if ((IfStack[IfIndex] & IFCOND_SKIP) == 0) { Skip = !Skip; } IfStack[IfIndex] |= IFCOND_ELSE; } else { PPError ("Duplicate #else"); } } else { PPError ("Unexpected `#else'"); } break; case PP_ENDIF: if (IfIndex >= 0) { /* Remove any clauses on top of stack that do not * need a terminating #endif. */ while (IfIndex >= 0 && (IfStack[IfIndex] & IFCOND_NEEDTERM) == 0) { --IfIndex; } /* Stack may not be empty here or something is wrong */ CHECK (IfIndex >= 0); /* Remove the clause that needs a terminator */ Skip = (IfStack[IfIndex--] & IFCOND_SKIP) != 0; } else { PPError ("Unexpected `#endif'"); } break; case PP_ERROR: if (!Skip) { DoError (); } break; case PP_IF: Skip = DoIf (Skip); break; case PP_IFDEF: Skip = DoIfDef (Skip, 1); break; case PP_IFNDEF: Skip = DoIfDef (Skip, 0); break; case PP_INCLUDE: if (!Skip) { DoInclude (); } break; case PP_LINE: /* Should do something in C99 at least, but we ignore it */ if (!Skip) { ClearLine (); } break; case PP_PRAGMA: if (!Skip) { DoPragma (); goto Done; } break; case PP_UNDEF: if (!Skip) { DoUndef (); } break; case PP_WARNING: /* #warning is a non standard extension */ if (IS_Get (&Standard) > STD_C99) { if (!Skip) { DoWarning (); } } else { if (!Skip) { PPError ("Preprocessor directive expected"); } ClearLine (); } break; default: if (!Skip) { PPError ("Preprocessor directive expected"); } ClearLine (); } } } if (NextLine () == 0) { if (IfIndex >= 0) { PPError ("`#endif' expected"); } return; } SkipWhitespace (0); } PreprocessLine (); Done: if (Verbosity > 1 && SB_NotEmpty (Line)) { printf ("%s(%u): %.*s\n", GetCurrentFile (), GetCurrentLine (), (int) SB_GetLen (Line), SB_GetConstBuf (Line)); } }