]> git.sur5r.net Git - cc65/blobdiff - src/cc65/preproc.c
Removed (pretty inconsistently used) tab chars from source code base.
[cc65] / src / cc65 / preproc.c
index 2a9935ac50c6a509d58114b3544fddb35e5bd377..199388deb2a16c2d52185e11ed8345ee1459a67f 100644 (file)
@@ -1,5 +1,37 @@
+/*****************************************************************************/
+/*                                                                           */
+/*                                  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.                                                          */
+/*                                                                           */
+/*****************************************************************************/
+
 
-/* C pre-processor functions */
 
 #include <stdio.h>
 #include <string.h>
@@ -8,6 +40,9 @@
 
 /* common */
 #include "chartype.h"
+#include "check.h"
+#include "inline.h"
+#include "print.h"
 #include "xmalloc.h"
 
 /* cc65 */
 #include "ident.h"
 #include "incpath.h"
 #include "input.h"
+#include "lineinfo.h"
 #include "macrotab.h"
-#include "scanner.h"
-#include "util.h"
 #include "preproc.h"
+#include "scanner.h"
+#include "standard.h"
 
 
 
 /*****************************************************************************/
-/*                                Forwards                                  */
+/*                                   Data                                    */
 /*****************************************************************************/
 
 
 
-static int Pass1 (const char* From, char* To);
-/* Preprocessor pass 1. Remove whitespace and comments. */
+/* 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 */
+};
 
 
 
 /*****************************************************************************/
-/*                                  data                                    */
+/*                                 Forwards                                  */
 /*****************************************************************************/
 
 
 
-/* Set when the pp calls expr() recursively */
-unsigned char Preprocessing = 0;
+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. */
 
-/* Management data for #if */
-#define N_IFDEF                16
-static int i_ifdef = -1;
-static char s_ifdef[N_IFDEF];
 
-/* Buffer for macro expansion */
-static char mlinebuf [LINESIZE];
-static char* mline = mlinebuf;
-static char* mptr;
 
-/* Flag: Expand macros in this line */
-static int ExpandMacros = 1;
+/*****************************************************************************/
+/*                   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;
+}
 
 
 
 /*****************************************************************************/
-/*                                  code                                    */
+/*                              struct MacroExp                              */
 /*****************************************************************************/
 
 
 
-static int keepch (char c)
-/* Put character c into translation buffer. */
+static MacroExp* InitMacroExp (MacroExp* E, Macro* M)
+/* Initialize a MacroExp structure */
 {
-    return (*mptr++ = c);
+    InitCollection (&E->ActualArgs);
+    SB_Init (&E->Replacement);
+    E->M = M;
+    return E;
 }
 
 
 
-static void keepstr (const char* S)
-/* Put string str into translation buffer. */
+static void DoneMacroExp (MacroExp* E)
+/* Cleanup after use of a MacroExp structure */
 {
-    while (*S) {
-       keepch (*S++);
+    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 Comment (void)
-/* Remove a C comment from line. */
+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.
@@ -98,18 +296,18 @@ static void Comment (void)
 
     /* 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 ();
-       }
+        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 */
@@ -119,253 +317,440 @@ static void Comment (void)
 
 
 
-static void SkipBlank (void)
-/* Skip blanks and tabs in the input stream. */
+static void NewStyleComment (void)
+/* Remove a new style C comment from line. */
 {
-    while (IsBlank (CurC)) {
-       NextChar ();
+    /* 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 char* CopyQuotedString (char* Target)
-/* Copy a single or double quoted string from the input to Target. Return the
- * new target pointer. Target will not be terminated after the copy.
+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;
-    *Target++  = CurC;
+    SB_AppendChar (Target, CurC);
     NextChar ();
 
     /* Copy the characters inside the string */
     while (CurC != '\0' && CurC != Quote) {
-               /* Keep an escaped char */
-       if (CurC == '\\') {
-           *Target++ = CurC;
-           NextChar ();
-       }
-       /* Copy the character */
-       *Target++ = CurC;
-       NextChar ();
+        /* 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') {
-       *Target++ = CurC;
-       NextChar ();
+        SB_AppendChar (Target, CurC);
+        NextChar ();
     }
-
-    /* Return the new target pointer */
-    return Target;
 }
 
 
 
 /*****************************************************************************/
-/*                               Macro stuff                                */
+/*                                Macro stuff                                */
 /*****************************************************************************/
 
 
 
 static int MacName (char* Ident)
-/* Get macro symbol name.  If error, print message and clear line. */
+/* 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;
+        PPError ("Identifier expected");
+        ClearLine ();
+        return 0;
     } else {
-       return 1;
+        return 1;
     }
 }
 
 
 
-static void ExpandMacroArgs (Macro* M)
-/* Preprocessor pass 2.  Perform macro substitution. */
+static void ReadMacroArgs (MacroExp* E)
+/* Identify the arguments to a macro call */
 {
-    ident      Ident;
-    const char* Replacement;
-    const char*        SavePtr;
+    unsigned    Parens;         /* Number of open parenthesis */
+    StrBuf      Arg = STATIC_STRBUF_INITIALIZER;
 
-    /* Save the current line pointer and setup the new ones */
-    SavePtr = lptr;
-    InitLine (M->Replacement);
+    /* 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 ();
+        }
+    }
 
-    /* Copy the macro replacement checking for parameters to replace */
-    while (CurC != '\0') {
-       /* If the next token is an identifier, check for a macro arg */
-       if (IsIdent (CurC)) {
-           SymName (Ident);
-           Replacement = FindMacroArg (M, Ident);
-           if (Replacement) {
-               /* Macro arg, keep the replacement */
-               keepstr (Replacement);
-           } else {
-               /* No macro argument, keep the original identifier */
-               keepstr (Ident);
-           }
-       } else if (CurC == '#' && IsIdent (NextC)) {
-                   NextChar ();
-           SymName (Ident);
-           Replacement = FindMacroArg (M, Ident);
-                   if (Replacement) {
-               keepch ('\"');
-               keepstr (Replacement);
-               keepch ('\"');
-           } else {
-               keepch ('#');
-               keepstr (Ident);
-           }
-       } else if (IsQuoteChar (CurC)) {
-           mptr = CopyQuotedString (mptr);
-       } else {
-           *mptr++ = CurC;
-           NextChar ();
-       }
-    }
-
-    /* Reset the line pointer */
-    InitLine (SavePtr);
+    /* Deallocate string buf resources */
+    SB_Done (&Arg);
 }
 
 
 
-static int MacroCall (Macro* M)
-/* Process a function like macro */
+static void MacroArgSubst (MacroExp* E)
+/* Argument substitution according to ISO/IEC 9899:1999 (E), 6.10.3.1ff */
 {
-    unsigned   ArgCount;       /* Macro argument count */
-    unsigned   ParCount;       /* Number of open parenthesis */
-    char       Buf[LINESIZE];  /* Argument buffer */
-    const char* ArgStart;
-    char*      B;
+    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, ' ');
+                    }
+                }
+
 
-    /* Expect an argument list */
-    SkipBlank ();
-    if (CurC != '(') {
-       PPError ("Illegal macro call");
-       return 0;
+            } 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 ();
 
-    /* Read the actual macro arguments and store pointers to these arguments
-     * into the array of actual arguments in the macro definition.
+    /* 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.
      */
-    ArgCount = 0;
-    ParCount = 0;
-    ArgStart = Buf;
-    B       = Buf;
-    while (1) {
-               if (CurC == '(') {
-           /* Nested parenthesis */
-           *B++ = CurC;
-           NextChar ();
-           ++ParCount;
-       } else if (IsQuoteChar (CurC)) {
-           B = CopyQuotedString (B);
-       } else if (CurC == ',' || CurC == ')') {
-           if (ParCount == 0) {
-               /* End of actual argument */
-               *B++ = '\0';
-               while (IsBlank(*ArgStart)) {
-                   ++ArgStart;
-               }
-               if (ArgCount < M->ArgCount) {
-                   M->ActualArgs[ArgCount++] = ArgStart;
-                       } else if (CurC != ')' || *ArgStart != '\0' || M->ArgCount > 0) {
-                   /* Be sure not to count the single empty argument for a
-                    * macro that does not have arguments.
-                    */
-                   ++ArgCount;
-               }
-
-               /* Check for end of macro param list */
-               if (CurC == ')') {
-                   NextChar ();
-                   break;
-               }
-
-                       /* Start the next param */
-               ArgStart = B;
-               NextChar ();
-           } else {
-               /* Comma or right paren inside nested parenthesis */
-               if (CurC == ')') {
-                   --ParCount;
-               }
-               *B++ = CurC;
-               NextChar ();
-           }
-       } else if (IsBlank (CurC)) {
-           /* Squeeze runs of blanks */
-           *B++ = ' ';
-           SkipBlank ();
-       } else if (CurC == '\0') {
-           /* End of line inside macro argument list - read next line */
-           if (NextLine () == 0) {
-               return 0;
-           }
-       } else {
-           /* Just copy the character */
-           *B++ = CurC;
-           NextChar ();
-       }
-    }
-
-    /* Compare formal argument count with actual */
-    if (M->ArgCount != ArgCount) {
-       PPError ("Macro argument count mismatch");
-       /* Be sure to make enough empty arguments available */
-       while (ArgCount < M->ArgCount) {
-           M->ActualArgs [ArgCount++] = "";
-       }
-    }
-
-    /* Preprocess the line, replacing macro parameters */
-    ExpandMacroArgs (M);
-
-    /* Done */
-    return 1;
+    M->Expanding = 1;
+    MacroReplacement (&E.Replacement, Target);
+    M->Expanding = 0;
+
+    /* Free memory allocated for the macro expansion structure */
+    DoneMacroExp (&E);
 }
 
 
 
-static void ExpandMacro (Macro* M)
-/* Expand a macro */
+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) {
-       /* Function like macro */
-               if (MacroCall (M) == 0) {
-           ClearLine ();
-       }
+
+        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 {
-       /* Just copy the replacement text */
-       keepstr (M->Replacement);
+
+        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 addmac (void)
-/* Add a macro to the macro table. */
+static void DefineMacro (void)
+/* Handle a macro definition. */
 {
-    char*      saveptr;
-    ident      Ident;
-    char       Buf[LINESIZE];
-    Macro*     M;
-    Macro*     Existing;
+    ident       Ident;
+    Macro*      M;
+    Macro*      Existing;
+    int         C89;
 
     /* Read the macro name */
-    SkipBlank ();
+    SkipWhitespace (0);
     if (!MacName (Ident)) {
-       return;
+        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);
 
@@ -375,361 +760,409 @@ static void addmac (void)
     /* 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) {
-           SkipBlank ();
-           if (CurC == ')')
-               break;
-           if (MacName (Ident) == 0) {
-               return;
-           }
-           AddMacroArg (M, Ident);
-           SkipBlank ();
-           if (CurC != ',')
-               break;
-           NextChar ();
-       }
-
-       /* Check for a right paren and eat it if we find one */
-       if (CurC != ')') {
-                   PPError ("`)' expected");
-           ClearLine ();
-           return;
-       }
-       NextChar ();
+        /* 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 Buf.
+     * line into the macro replacement buffer.
      */
-    SkipBlank ();
-    saveptr = mptr;
-    Pass1 (lptr, Buf);
-    mptr = saveptr;
+    Pass1 (Line, &M->Replacement);
 
-    /* Create a copy of the replacement */
-    M->Replacement = xstrdup (Buf);
+    /* 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) {
-       if (MacroCmp (M, Existing) != 0) {
-           PPError ("Macro redefinition is not identical");
-       }
+    if (Existing && MacroCmp (M, Existing) != 0) {
+        PPError ("Macro redefinition is not identical");
     }
 }
 
 
 
 /*****************************************************************************/
-
+/*                               Preprocessing                               */
 /*****************************************************************************/
 
 
 
-static int Pass1 (const char* From, char* To)
-/* Preprocessor pass 1. Remove whitespace and comments. */
+static unsigned Pass1 (StrBuf* Source, StrBuf* Target)
+/* Preprocessor pass 1. Remove whitespace. Handle old and new style comments
+ * and the "defined" operator.
+ */
 {
-    int        done;
-    ident      Ident;
-    int        HaveParen;
-
-    /* Initialize reading from "From" */
-    InitLine (From);
+    unsigned    IdentCount;
+    ident       Ident;
+    int         HaveParen;
 
-    /* Target is "To" */
-    mptr = To;
+    /* Switch to the new input source */
+    StrBuf* OldSource = InitLine (Source);
 
     /* Loop removing ws and comments */
-    done = 1;
+    IdentCount = 0;
     while (CurC != '\0') {
-       if (IsBlank (CurC)) {
-           keepch (' ');
-           SkipBlank ();
-               } else if (IsIdent (CurC)) {
-           SymName (Ident);
-           if (Preprocessing && strcmp(Ident, "defined") == 0) {
-               /* Handle the "defined" operator */
-               SkipBlank();
-               HaveParen = 0;
-               if (CurC == '(') {
-                   HaveParen = 1;
-                   NextChar ();
-                   SkipBlank();
-               }
-               if (!IsIdent (CurC)) {
-                   PPError ("Identifier expected");
-                   *mptr++ = '0';
-               } else {
-                   SymName (Ident);
-                   *mptr++ = IsMacro (Ident)? '1' : '0';
-                   if (HaveParen) {
-                       SkipBlank();
-                       if (CurC != ')') {
-                           PPError ("`)' expected");
-                       } else {
-                           NextChar ();
-                       }
-                   }
-               }
-           } else {
-               if (MaybeMacro (Ident[0])) {
-                   done = 0;
-               }
-               keepstr (Ident);
-           }
-       } else if (IsQuoteChar (CurC)) {
-           mptr = CopyQuotedString (mptr);
-       } else if (CurC == '/' && NextC == '*') {
-           keepch (' ');
-           Comment ();
-       } else if (ANSI == 0 && CurC == '/' && NextC == '/') {
-           keepch (' ');
-           /* 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 ();
-           }
-       } else {
-           *mptr++ = CurC;
-           NextChar ();
-       }
-    }
-    keepch ('\0');
-    return done;
+        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 int Pass2 (const char* From, char* To)
-/* Preprocessor pass 2.  Perform macro substitution. */
+static void MacroReplacement (StrBuf* Source, StrBuf* Target)
+/* Perform macro replacement. */
 {
-    int        no_chg;
-    ident      Ident;
-    Macro*     M;
-
-    /* Initialize reading from "From" */
-    InitLine (From);
+    ident       Ident;
+    Macro*      M;
 
-    /* Target is "To" */
-    mptr = To;
+    /* Remember the current input and switch to Source */
+    StrBuf* OldSource = InitLine (Source);
 
     /* Loop substituting macros */
-    no_chg = 1;
     while (CurC != '\0') {
-       /* If we have an identifier, check if it's a macro */
-       if (IsIdent (CurC)) {
-           SymName (Ident);
-           M = FindMacro (Ident);
-           if (M) {
-               ExpandMacro (M);
-               no_chg = 0;
-           } else {
-               keepstr (Ident);
-           }
-       } else if (IsQuoteChar(CurC)) {
-           mptr = CopyQuotedString (mptr);
-       } else {
-           *mptr++ = CurC;
-           NextChar ();
-       }
-    }
-    return no_chg;
+        /* 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 xlateline (void)
+static void PreprocessLine (void)
 /* Translate one line. */
 {
-    int cnt;
-    int Done;
-
-    Done = Pass1 (line, mline);
-    if (ExpandMacros == 0) {
-       Done = 1;
-       ExpandMacros = 1;       /* Reset to default */
+    /* 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);
     }
-    cnt = 5;
-    do {
-       /* Swap mline and line */
-               char* p = line;
-       line = mline;
-       mline = p;
-       if (Done)
-           break;
-       Done = Pass2 (line, mline);
-       keepch ('\0');
-    } while (--cnt);
-
-    /* Reinitialize line parsing */
-    InitLine (line);
+
+    /* Read from the new line */
+    SB_Reset (MLine);
+    MLine = InitLine (MLine);
 }
 
 
 
-static void doundef (void)
-/* Process #undef directive */
+static int PushIf (int Skip, int Invert, int Cond)
+/* Push a new if level onto the if stack */
 {
-    ident Ident;
+    /* Check for an overflow of the if stack */
+    if (IfIndex >= MAX_IFS-1) {
+        PPError ("Too many nested #if clauses");
+        return 1;
+    }
 
-    SkipBlank ();
-    if (MacName (Ident)) {
-       UndefineMacro (Ident);
+    /* 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 int setmflag (int skip, int flag, int cond)
-/* setmflag( skip, flag, cond ) */
+static void DoError (void)
+/* Print an error */
 {
-    if (skip) {
-       s_ifdef[++i_ifdef] = 3;
-       return (1);
+    SkipWhitespace (0);
+    if (CurC == '\0') {
+        PPError ("Invalid #error directive");
     } else {
-       s_ifdef[++i_ifdef] = 6;
-       return (flag ^ cond);
+        PPError ("#error: %s", SB_GetConstBuf (Line) + SB_GetIndex (Line));
     }
+
+    /* Clear the rest of line */
+    ClearLine ();
 }
 
 
 
-static int doiff (int skip)
+static int DoIf (int Skip)
 /* Process #if directive */
 {
-    struct expent lval;
-    char* S;
+    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 sv1 = CurTok;
-    Token sv2 = NextTok;
+    Token SavedCurTok  = CurTok;
+    Token SavedNextTok = NextTok;
 
-    /* Remove the #if from the line and add two semicolons as sentinels */
-    SkipBlank ();
-    S = line;
-    while (CurC != '\0') {
-       *S++ = CurC;
-       NextChar ();
+    /* Make sure the line infos for the tokens won't get removed */
+    if (SavedCurTok.LI) {
+        UseLineInfo (SavedCurTok.LI);
+    }
+    if (SavedNextTok.LI) {
+        UseLineInfo (SavedNextTok.LI);
     }
-    *S++ = ';';
-    *S++ = ';';
-    *S   = '\0';
-
-    /* Start over parsing from line */
-    InitLine (line);
 
     /* Switch into special preprocessing mode */
     Preprocessing = 1;
 
     /* Expand macros in this line */
-    xlateline ();
+    PreprocessLine ();
 
-    /* Prime the token pump (remove old tokens from the stream) */
+    /* 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 (&lval);
+    ConstExpr (hie1, &Expr);
 
     /* End preprocessing mode */
     Preprocessing = 0;
 
     /* Reset the old tokens */
-    CurTok  = sv1;
-    NextTok = sv2;
+    CurTok  = SavedCurTok;
+    NextTok = SavedNextTok;
 
     /* Set the #if condition according to the expression result */
-    return (setmflag (skip, 1, lval.e_const != 0));
+    return PushIf (Skip, 1, Expr.IVal != 0);
 }
 
 
 
-static int doifdef (int skip, int flag)
+static int DoIfDef (int skip, int flag)
 /* Process #ifdef if flag == 1, or #ifndef if flag == 0. */
 {
     ident Ident;
 
-    SkipBlank ();
+    SkipWhitespace (0);
     if (MacName (Ident) == 0) {
-               return 0;
+        return 0;
     } else {
-       return setmflag (skip, flag, IsMacro(Ident));
+        return PushIf (skip, flag, IsMacro(Ident));
     }
 }
 
 
 
-static void doinclude (void)
+static void DoInclude (void)
 /* Open an include file. */
 {
-    char       RTerm;
-    unsigned   DirSpec;
+    char        RTerm;
+    InputType   IT;
+    StrBuf      Filename = STATIC_STRBUF_INITIALIZER;
 
 
+    /* Preprocess the remainder of the line */
+    PreprocessLine ();
+
     /* Skip blanks */
-    SkipBlank ();
+    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   = '\"';
-                   DirSpec = INC_USER;
-                   break;
+        case '\"':
+            RTerm   = '\"';
+            IT = IT_USRINC;
+            break;
 
-               case '<':
-                   RTerm   = '>';
-                   DirSpec = INC_SYS;
-                   break;
+        case '<':
+            RTerm   = '>';
+            IT = IT_SYSINC;
+            break;
 
-               default:
-                   PPError ("`\"' or `<' expected");
-                   goto Done;
+        default:
+            PPError ("`\"' or `<' expected");
+            goto Done;
     }
     NextChar ();
 
-    /* Copy the filename into mline. Since mline has the same size as the
-     * input line, we don't need to check for an overflow here.
-     */
-    mptr = mline;
+    /* Get a copy of the filename */
     while (CurC != '\0' && CurC != RTerm) {
-       *mptr++ = CurC;
-       NextChar ();
+        SB_AppendChar (&Filename, CurC);
+        NextChar ();
     }
-    *mptr = '\0';
+    SB_Terminate (&Filename);
 
     /* Check if we got a terminator */
-    if (CurC != RTerm) {
-               /* No terminator found */
-               PPError ("Missing terminator or file name too long");
-               goto Done;
+    if (CurC == RTerm) {
+        /* Open the include file */
+        OpenIncludeFile (SB_GetConstBuf (&Filename), IT);
+    } else if (CurC == '\0') {
+        /* No terminator found */
+        PPError ("#include expects \"FILENAME\" or <FILENAME>");
     }
 
-    /* Open the include file */
-    OpenIncludeFile (mline, DirSpec);
-
 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)
      */
@@ -738,63 +1171,56 @@ Done:
 
 
 
-static void doerror (void)
-/* Print an error */
+static void DoPragma (void)
+/* Handle a #pragma line by converting the #pragma preprocessor directive into
+ * the _Pragma() compiler operator.
+ */
 {
-    SkipBlank ();
-    if (CurC == '\0') {
-       PPError ("Invalid #error directive");
-    } else {
-        PPError ("#error: %s", lptr);
-    }
-
-    /* clear rest of line */
-    ClearLine ();
+    /* 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);
 }
 
 
 
-/* C preprocessor. */
-
-/* stuff used to bum the keyword dispatching stuff */
-enum {
-    PP_DEFINE,
-    PP_ELSE,
-    PP_ENDIF,
-    PP_ERROR,
-    PP_IF,
-    PP_IFDEF,
-    PP_IFNDEF,
-    PP_INCLUDE,
-    PP_LINE,
-    PP_PRAGMA,
-    PP_UNDEF,
-    PP_ILLEGAL
-};
+static void DoUndef (void)
+/* Process the #undef directive */
+{
+    ident Ident;
 
-static const struct tok_elt pre_toks[] = {
-    {          "define",       PP_DEFINE       },
-    {          "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        },
-    {          0,              PP_ILLEGAL      }
-};
+    SkipWhitespace (0);
+    if (MacName (Ident)) {
+        UndefineMacro (Ident);
+    }
+}
 
 
 
-static int searchtok (const char *sym, const struct tok_elt *toks)
-/* Search a token in a table */
+static void DoWarning (void)
+/* Print a warning */
 {
-    while (toks->toknam && strcmp (toks->toknam, sym))
-       ++toks;
-    return (toks->toknbr);
+    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 ();
 }
 
 
@@ -802,122 +1228,178 @@ static int searchtok (const char *sym, const struct tok_elt *toks)
 void Preprocess (void)
 /* Preprocess a line */
 {
-    int        Skip;
-    ident      Directive;
+    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 */
-    SkipBlank ();
+    SkipWhitespace (0);
 
     /* Check for stuff to skip */
     Skip = 0;
     while (CurC == '\0' || CurC == '#' || Skip) {
 
-               /* Check for preprocessor lines lines */
-               if (CurC == '#') {
-                   NextChar ();
-                   SkipBlank ();
-                   if (CurC == '\0') {
-                       /* Ignore the empty preprocessor directive */
-                       continue;
-                   }
-                   if (!IsSym (Directive)) {
-                       PPError ("Preprocessor directive expected");
-                       ClearLine ();
-                   } else {
-                       switch (searchtok (Directive, pre_toks)) {
-
-                           case PP_DEFINE:
-                               if (!Skip) {
-                                   addmac ();
-                               }
-                               break;
-
-                           case PP_ELSE:
-                               if (s_ifdef[i_ifdef] & 2) {
-                                   if (s_ifdef[i_ifdef] & 4) {
-                                       Skip = !Skip;
-                                   }
-                                   s_ifdef[i_ifdef] ^= 2;
-                               } else {
-                                   PPError ("Unexpected `#else'");
-                               }
-                               break;
-
-                           case PP_ENDIF:
-                               if (i_ifdef >= 0) {
-                                   Skip = s_ifdef[i_ifdef--] & 1;
-                               } else {
-                                   PPError ("Unexpected `#endif'");
-                               }
-                               break;
-
-                           case PP_ERROR:
-                               if (!Skip) {
-                                   doerror ();
-                       }
-                       break;
-
-                           case PP_IF:
-                       Skip = doiff (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:
-                       /* Not allowed in strict ANSI mode */
-                       if (ANSI) {
-                           PPError ("Preprocessor directive expected");
-                           ClearLine ();
-                       }
-                       break;
-
-                           case PP_PRAGMA:
-                       if (!Skip) {
-                           /* Don't expand macros in this line */
-                           ExpandMacros = 0;
-                           /* #pragma is handled on the scanner level */
-                           goto Done;
-                       }
-                       break;
-
-                           case PP_UNDEF:
-                       if (!Skip) {
-                           doundef ();
-                       }
-                       break;
-
-                   default:
-                       PPError ("Preprocessor directive expected");
-                       ClearLine ();
-               }
-           }
-
-       }
-       if (NextLine () == 0) {
-           if (i_ifdef >= 0) {
-               PPError ("`#endif' expected");
-           }
-           return;
-       }
-       SkipBlank ();
+        /* 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:
-    xlateline ();
-    if (Verbose > 1) {
-       printf ("line: %s\n", line);
+    if (Verbosity > 1 && SB_NotEmpty (Line)) {
+        printf ("%s(%u): %.*s\n", GetCurrentFile (), GetCurrentLine (),
+                (int) SB_GetLen (Line), SB_GetConstBuf (Line));
     }
 }