]> 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 d27e8e2b9035cb1c6b5ba9a1cbd8d8c6c3496dfe..199388deb2a16c2d52185e11ed8345ee1459a67f 100644 (file)
@@ -1,14 +1,51 @@
+/*****************************************************************************/
+/*                                                                           */
+/*                                  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>
 #include <stdlib.h>
 #include <errno.h>
-#include <ctype.h>
 
-#include "../common/xmalloc.h"
+/* 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 "ident.h"
 #include "incpath.h"
 #include "input.h"
-#include "io.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 (char* from, char* to);
+/* 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.
+ */
 
-/* Management data for #if */
-#define N_IFDEF                16
-static int i_ifdef = -1;
-static char s_ifdef[N_IFDEF];
+static void MacroReplacement (StrBuf* Source, StrBuf* Target);
+/* Perform macro replacement. */
 
-/* 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 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 void comment (void)
-/* Remove comment from line. */
+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();
 
-    gch ();
-    gch ();
-    while (*lptr != '*' || nch () != '/') {
-       if (*lptr == '\0') {
-           if (NextLine () == 0) {
-               PPError (ERR_EOF_IN_COMMENT, StartingLine);
-               return;
-           }
-       } else {
-           if (*lptr == '/' && nch() == '*') {
-               PPWarning (WARN_NESTED_COMMENT);
-           }
-           ++lptr;
-       }
-    }
-    gch ();
-    gch ();
+    /* 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 skipblank (void)
-/* Skip blanks and tabs in the input stream. */
+static void NewStyleComment (void)
+/* Remove a new style C comment from line. */
 {
-    while (IsBlank (*lptr)) {
-       ++lptr;
+    /* 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 (int Quote, char* Target)
-/* Copy a single or double quoted string from lptr 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.
  */
 {
-    /* Copy the starting quote */
-    *Target++ = gch();
+    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 (*lptr != '\0' && *lptr != Quote) {
-               /* Keep an escaped char */
-       if (*lptr == '\\') {
-           *Target++ = gch();
-       }
-       /* Copy the character */
-       *Target++ = cgch();
+    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 (*lptr) {
-       *Target++ = gch();
+    if (CurC != '\0') {
+        SB_AppendChar (Target, CurC);
+        NextChar ();
     }
-
-    /* Return the new target pointer */
-    return Target;
 }
 
 
 
 /*****************************************************************************/
-/*                               Macro stuff                                */
+/*                                Macro stuff                                */
 /*****************************************************************************/
 
 
 
-static int macname (char *sname)
-/* Get macro symbol name.  If error, print message and kill line. */
+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 (sname) == 0) {
-       PPError (ERR_IDENT_EXPECTED);
-       kill ();
-       return 0;
+    if (IsSym (Ident) == 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 */
 {
-    int                C;
-    ident      Ident;
-    const char* Replacement;
-    char*      SavePtr;
-
-    /* Save the current line pointer and setup the new ones */
-    SavePtr = lptr;
-    lptr    = M->Replacement;
-
-    /* Copy the macro replacement checking for parameters to replace */
-    while ((C = *lptr) != '\0') {
-       /* If the next token is an identifier, check for a macro arg */
-       if (IsIdent (C)) {
-           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 (C == '#' && IsIdent (nch ())) {
-           ++lptr;
-           symname (Ident);
-           Replacement = FindMacroArg (M, Ident);
-                   if (Replacement) {
-               keepch ('\"');
-               keepstr (Replacement);
-               keepch ('\"');
-           } else {
-               keepch ('#');
-               keepstr (Ident);
-           }
-       } else if (IsQuoteChar(C)) {
-           mptr = CopyQuotedString (C, mptr);
-       } else {
-           *mptr++ = *lptr++;
-       }
-    }
-
-    /* Reset the line pointer */
-    lptr = SavePtr;
+    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 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 */
-    char       C;
-    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);
 
-    /* Expect an argument list */
-    skipblank ();
-    if (*lptr != '(') {
-       PPError (ERR_ILLEGAL_MACRO_CALL);
-       return 0;
+                } 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 */
-    ++lptr;
+    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) {
 
-    /* Read the actual macro arguments and store pointers to these arguments
-     * into the array of actual arguments in the macro definition.
+        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) {
-       C = *lptr;
-       if (C == '(') {
-           *B++ = gch ();
-           ++ParCount;
-       } else if (IsQuoteChar(C)) {
-           B = CopyQuotedString (C, B);
-       } else if (C == ',' || C == ')') {
-           if (ParCount == 0) {
-               /* End of actual argument */
-               gch ();
-               *B++ = '\0';
-               while (IsBlank(*ArgStart)) {
-                   ++ArgStart;
-               }
-               if (ArgCount < M->ArgCount) {
-                   M->ActualArgs[ArgCount++] = ArgStart;
-                       } else if (C != ')' || *ArgStart != '\0' || M->ArgCount > 0) {
-                   /* Be sure not to count the single empty argument for a
-                    * macro that does not have arguments.
-                    */
-                   ++ArgCount;
-               }
-
-                       /* Start the next one */
-               ArgStart = B;
-               if (C == ')') {
-                   break;
-               }
-           } else {
-               *B++ = gch ();
-               if (C == ')') {
-                   --ParCount;
-               }
-           }
-       } else if (IsBlank (C)) {
-           /* Squeeze runs of blanks */
-           *B++ = ' ';
-           skipblank ();
-       } else if (C == '\0') {
-           /* End of line inside macro argument list - read next line */
-           if (NextLine () == 0) {
-               return 0;
-           }
-       } else {
-           /* Just copy the character */
-           *B++ = *lptr++;
-       }
-    }
-
-    /* Compare formal argument count with actual */
-    if (M->ArgCount != ArgCount) {
-       PPError (ERR_MACRO_ARGCOUNT);
-       /* 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) {
-           kill ();
-       }
+
+        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;
+    ident       Ident;
+    Macro*      M;
+    Macro*      Existing;
+    int         C89;
 
     /* Read the macro name */
-    skipblank ();
-    if (!macname (Ident)) {
-       return;
+    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 (*lptr == '(') {
-
-       /* Skip the left paren */
-       gch ();
-
-               /* Set the marker that this is a function like macro */
-       M->ArgCount = 0;
-
-       /* Read the formal parameter list */
-       while (1) {
-           skipblank ();
-           if (*lptr == ')')
-               break;
-           if (macname (Ident) == 0) {
-               return;
-           }
-           AddMacroArg (M, Ident);
-           skipblank ();
-           if (*lptr != ',')
-               break;
-           gch ();
-       }
-       if (*lptr != ')') {
-                   PPError (ERR_RPAREN_EXPECTED);
-           kill ();
-           return;
-       }
-       gch ();
+    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 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 && MacroCmp (M, Existing) != 0) {
+        PPError ("Macro redefinition is not identical");
+    }
 }
 
 
 
 /*****************************************************************************/
-
+/*                               Preprocessing                               */
 /*****************************************************************************/
 
 
 
-static int Pass1 (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        c;
-    int        done;
-    ident      Ident;
-    int        HaveParen;
-
-    lptr = from;
-    mptr = to;
-    done = 1;
-    while ((c = *lptr) != 0) {
-       if (IsBlank (c)) {
-           keepch (' ');
-           skipblank ();
-               } else if (IsIdent (c)) {
-           symname (Ident);
-           if (Preprocessing && strcmp(Ident, "defined") == 0) {
-               /* Handle the "defined" operator */
-               skipblank();
-               HaveParen = 0;
-               if (*lptr == '(') {
-                   HaveParen = 1;
-                   ++lptr;
-                   skipblank();
-               }
-               if (!IsIdent(c)) {
-                   PPError (ERR_IDENT_EXPECTED);
-                   *mptr++ = '0';
-               } else {
-                   symname (Ident);
-                   *mptr++ = IsMacro(Ident)? '1' : '0';
-                   if (HaveParen) {
-                       skipblank();
-                       if (*lptr != ')') {
-                           PPError (ERR_RPAREN_EXPECTED);
-                       } else {
-                           ++lptr;
-                       }
-                   }
-               }
-           } else {
-               if (MaybeMacro(c)) {
-                   done = 0;
-               }
-               keepstr (Ident);
-           }
-       } else if (IsQuoteChar(c)) {
-           mptr = CopyQuotedString (c, mptr);
-       } else if (c == '/' && nch () == '*') {
-           keepch (' ');
-           comment ();
-       } else if (ANSI == 0 && c == '/' && nch () == '/') {
-           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 {
-               ++lptr;
-           } while (*lptr != '\n' && *lptr != '\0');
-           if (*lptr == '\n') {
-               ++lptr;
-           }
-       } else {
-           *mptr++ = *lptr++;
-       }
-    }
-    keepch ('\0');
-    return done;
+    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 int Pass2 (char *from, char *to)
-/* Preprocessor pass 2.  Perform macro substitution. */
+static void MacroReplacement (StrBuf* Source, StrBuf* Target)
+/* Perform macro replacement. */
 {
-    int        C;
-    int        no_chg;
-    ident      Ident;
-    Macro*     M;
-
-    lptr = from;
-    mptr = to;
-    no_chg = 1;
-    while ((C = *lptr) != '\0') {
-       /* If we have an identifier, check if it's a macro */
-       if (IsIdent (C)) {
-           symname (Ident);
-           M = FindMacro (Ident);
-           if (M) {
-               ExpandMacro (M);
-               no_chg = 0;
-           } else {
-               keepstr (Ident);
-           }
-       } else if (IsQuoteChar(C)) {
-           mptr = CopyQuotedString (C, mptr);
-       } else {
-           *mptr++ = *lptr++;
-       }
-    }
-    return no_chg;
+    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 xlateline (void)
+static void PreprocessLine (void)
 /* Translate one line. */
 {
-    int cnt;
-    int Done;
-    char *p;
-
-    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 {
-       p = line;
-       line = mline;
-       mline = p;
-       if (Done)
-           break;
-       Done = Pass2 (line, mline);
-       keepch ('\0');
-    } while (--cnt);
-    lptr = 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 ((*S++ = *lptr++) != '\0') ;
-    strcpy (S-1, ";;");
-    lptr = line;
+    /* 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 */
-    xlateline ();
+    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);
 
-    /* Prime the token pump (remove old tokens from the stream) */
+    /* 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 ();
-    if (macname (Ident) == 0) {
-               return 0;
+    SkipWhitespace (0);
+    if (MacName (Ident) == 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. */
 {
-    unsigned   Length;
-    char*      End;
-    char*      Name;
-    char       RTerm;
-    unsigned   DirSpec;
+    char        RTerm;
+    InputType   IT;
+    StrBuf      Filename = STATIC_STRBUF_INITIALIZER;
 
 
+    /* Preprocess the remainder of the line */
+    PreprocessLine ();
+
     /* Skip blanks */
-    mptr = mline;
-    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 (cgch()) {
+    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 (ERR_INCLUDE_LTERM_EXPECTED);
-           goto Done;
+        default:
+            PPError ("`\"' or `<' expected");
+            goto Done;
     }
+    NextChar ();
 
-    /* Search for the right terminator */
-    End = strchr (lptr, RTerm);
-    if (End == 0) {
-       /* No terminator found */
-       PPError (ERR_INCLUDE_RTERM_EXPECTED);
-       goto Done;
+    /* 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 <FILENAME>");
     }
-
-    /* Create a temp copy of the filename */
-    Length = End - lptr;
-    Name = xmalloc (Length + 1);
-    memcpy (Name, lptr, Length);
-    Name[Length] = '\0';
-
-    /* Open the include file */
-    OpenIncludeFile (Name, DirSpec);
-
-    /* Delete the temp filename copy */
-    xfree (Name);
 
 Done:
-    /* clear rest of line so next read will come from new file (if open) */
-    kill ();
+    /* 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 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 (*lptr == '\0') {
-       PPError (ERR_INVALID_USER_ERROR);
-    } else {
-        PPError (ERR_USER_ERROR, lptr);
-    }
-
-    /* clear rest of line */
-    kill ();
+    /* 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 {
-    D_DEFINE,
-    D_ELSE,
-    D_ENDIF,
-    D_ERROR,
-    D_IF,
-    D_IFDEF,
-    D_IFNDEF,
-    D_INCLUDE,
-    D_LINE,
-    D_PRAGMA,
-    D_UNDEF,
-    D_ILLEGAL,
-};
+static void DoUndef (void)
+/* Process the #undef directive */
+{
+    ident Ident;
 
-static const struct tok_elt pre_toks[] = {
-    {  "define",       D_DEFINE        },
-    {  "else",         D_ELSE          },
-    {  "endif",        D_ENDIF         },
-    {  "error",        D_ERROR         },
-    {  "if",           D_IF            },
-    {  "ifdef",        D_IFDEF         },
-    {  "ifndef",       D_IFNDEF        },
-    {  "include",      D_INCLUDE       },
-    {   "line",                D_LINE          },
-    {  "pragma",       D_PRAGMA        },
-    {  "undef",        D_UNDEF         },
-    {  0,              D_ILLEGAL       }
-};
+    SkipWhitespace (0);
+    if (MacName (Ident)) {
+        UndefineMacro (Ident);
+    }
+}
 
 
 
-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 ();
 }
 
 
 
-void preprocess (void)
+void Preprocess (void)
 /* Preprocess a line */
 {
-    int        c;
-    int        Skip;
-    ident      sname;
+    int         Skip;
+    ident       Directive;
 
-    /* Process compiler directives, skip empty lines */
-    lptr = line;
+    /* 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 ((c = *lptr) == '\0' || c == '#' || Skip) {
-
-       /* Check for preprocessor lines lines */
-               if (c == '#') {
-           ++lptr;
-           skipblank ();
-           if (*lptr == '\0') {
-               /* ignore the empty preprocessor directive */
-               continue;
-           }
-           if (!issym (sname)) {
-               PPError (ERR_CPP_DIRECTIVE_EXPECTED);
-               kill ();
-           } else {
-                       switch (searchtok (sname, pre_toks)) {
-
-                   case D_DEFINE:
-                       if (!Skip) {
-                           addmac ();
-                       }
-                       break;
-
-                   case D_ELSE:
-                       if (s_ifdef[i_ifdef] & 2) {
-                           if (s_ifdef[i_ifdef] & 4) {
-                               Skip = !Skip;
-                           }
-                           s_ifdef[i_ifdef] ^= 2;
-                       } else {
-                           PPError (ERR_UNEXPECTED_CPP_ELSE);
-                       }
-                       break;
-
-                   case D_ENDIF:
-                       if (i_ifdef >= 0) {
-                           Skip = s_ifdef[i_ifdef--] & 1;
-                       } else {
-                           PPError (ERR_UNEXPECTED_CPP_ENDIF);
-                       }
-                       break;
-
-                           case D_ERROR:
-                       if (!Skip) {
-                           doerror ();
-                       }
-                       break;
-
-                   case D_IF:
-                       Skip = doiff (Skip);
-                       break;
-
-                   case D_IFDEF:
-                       Skip = doifdef (Skip, 1);
-                       break;
-
-                   case D_IFNDEF:
-                       Skip = doifdef (Skip, 0);
-                       break;
-
-                   case D_INCLUDE:
-                       if (!Skip) {
-                           doinclude ();
-                       }
-                       break;
-
-                   case D_LINE:
-                       /* Not allowed in strict ANSI mode */
-                       if (ANSI) {
-                           PPError (ERR_CPP_DIRECTIVE_EXPECTED);
-                           kill ();
-                       }
-                       break;
-
-                   case D_PRAGMA:
-                       if (!Skip) {
-                           /* Don't expand macros in this line */
-                           ExpandMacros = 0;
-                           /* #pragma is handled on the scanner level */
-                           goto Done;
-                       }
-                       break;
-
-                   case D_UNDEF:
-                       if (!Skip) {
-                           doundef ();
-                       }
-                       break;
-
-                   default:
-                       PPError (ERR_CPP_DIRECTIVE_EXPECTED);
-                       kill ();
-               }
-           }
-
-       }
-       if (NextLine () == 0) {
-           if (i_ifdef >= 0) {
-               PPError (ERR_CPP_ENDIF_EXPECTED);
-           }
-           return;
-       }
-       skipblank ();
+    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:
-    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));
     }
 }