/*
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** Copyright 1993 D. Richard Hipp. All rights reserved.
**
** Redistribution and use in source and binary forms, with or
** without modification, are permitted provided that the following
** conditions are met:
**
**   1. Redistributions of source code must retain the above copyright
**      notice, this list of conditions and the following disclaimer.
**
**   2. Redistributions in binary form must reproduce the above copyright
**      notice, this list of conditions and the following disclaimer in
**      the documentation and/or other materials provided with the
**      distribution.
**
** This software is provided "as is" and any express or implied warranties,
** including, but not limited to, the implied warranties of merchantability
** and fitness for a particular purpose are disclaimed.  In no event shall
** the author or contributors be liable for any direct, indirect, incidental,
** special, exemplary, or consequential damages (including, but not limited
** to, procurement of substitute goods or services; loss of use, data or
** profits; or business interruption) however caused and on any theory of
** liability, whether in contract, strict liability, or tort (including
** negligence or otherwise) arising in any way out of the use of this
** software, even if advised of the possibility of such damage.
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
*/
#include <assert.h>
#include <ctype.h>
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

#if defined(__MINGW32__) || defined(__DMC__) || defined(_MSC_VER) ||           \
    defined(__POCC__)
#ifndef WIN32
#define WIN32
#endif
#else
#include <unistd.h>
#endif

/*
** Macros for debugging.
*/
#ifdef DEBUG
static int debugMask = 0;
#define debug0(F, M)                                                           \
  if ((F)&debugMask) {                                                         \
    fprintf(stderr, M);                                                        \
  }
#define debug1(F, M, A)                                                        \
  if ((F)&debugMask) {                                                         \
    fprintf(stderr, M, A);                                                     \
  }
#define debug2(F, M, A, B)                                                     \
  if ((F)&debugMask) {                                                         \
    fprintf(stderr, M, A, B);                                                  \
  }
#define debug3(F, M, A, B, C)                                                  \
  if ((F)&debugMask) {                                                         \
    fprintf(stderr, M, A, B, C);                                               \
  }
#define PARSER 0x00000001
#define DECL_DUMP 0x00000002
#define TOKENIZER 0x00000004
#else
#define debug0(Flags, Format)
#define debug1(Flags, Format, A)
#define debug2(Flags, Format, A, B)
#define debug3(Flags, Format, A, B, C)
#endif

/*
** The following macros are purely for the purpose of testing this
** program on itself.  They don't really contribute to the code.
*/
// #define INTERFACE 1
// #define EXPORT_INTERFACE 1
// #define EXPORT

/*
** Each token in a source file is represented by an instance of
** the following structure.  Tokens are collected onto a list.
*/
typedef struct Token Token;
struct Token {
  const char *zText; /* The text of the token */
  int nText;         /* Number of characters in the token's text */
  int eType;         /* The type of this token */
  int nLine;         /* The line number on which the token starts */
  Token *pComment;   /* Most recent block comment before this token */
  Token *pNext;      /* Next token on the list */
  Token *pPrev;      /* Previous token on the list */
};

/*
** During tokenization, information about the state of the input
** stream is held in an instance of the following structure
*/
typedef struct InStream InStream;
struct InStream {
  const char *z; /* Complete text of the input */
  int i;         /* Next character to read from the input */
  int nLine;     /* The line number for character z[i] */
};

/*
** Each declaration in the C or C++ source files is parsed out and stored as
** an instance of the following structure.
**
** A "forward declaration" is a declaration that an object exists that
** doesn't tell about the objects structure.  A typical forward declaration
** is:
**
**          struct Xyzzy;
**
** Not every object has a forward declaration.  If it does, thought, the
** forward declaration will be contained in the zFwd field for C and
** the zFwdCpp for C++.  The zDecl field contains the complete
** declaration text.
*/
typedef struct Decl Decl;
struct Decl {
  char *zName;       /* Name of the object being declared.  The appearance
                     ** of this name is a source file triggers the declaration
                     ** to be added to the header for that file. */
  const char *zFile; /* File from which extracted.  */
  char *zIf;         /* Surround the declaration with this #if */
  char *zFwd;        /* A forward declaration.  NULL if there is none. */
  char *zFwdCpp;     /* Use this forward declaration for C++. */
  char *zDecl;       /* A full declaration of this object */
  char *zExtra;      /* Extra declaration text inserted into class objects */
  int extraType;     /* Last public:, protected: or private: in zExtraDecl */
  struct Include *pInclude; /* #includes that come before this declaration */
  int flags;                /* See the "Properties" below */
  Token *pComment; /* A block comment associated with this declaration */
  Token tokenCode; /* Implementation of functions and procedures */
  Decl *pSameName; /* Next declaration with the same "zName" */
  Decl *pSameHash; /* Next declaration with same hash but different zName */
  Decl *pNext;     /* Next declaration with a different name */
};

/*
** Properties associated with declarations.
**
** DP_Forward and DP_Declared are used during the generation of a single
** header file in order to prevent duplicate declarations and definitions.
** DP_Forward is set after the object has been given a forward declaration
** and DP_Declared is set after the object gets a full declarations.
** (Example:  A forward declaration is "typedef struct Abc Abc;" and the
** full declaration is "struct Abc { int a; float b; };".)
**
** The DP_Export and DP_Local flags are more permanent.  They mark objects
** that have EXPORT scope and LOCAL scope respectively.  If both of these
** marks are missing, then the object has library scope.  The meanings of
** the scopes are as follows:
**
**    LOCAL scope         The object is only usable within the file in
**                        which it is declared.
**
**    library scope       The object is visible and usable within other
**                        files in the same project.  By if the project is
**                        a library, then the object is not visible to users
**                        of the library.  (i.e. the object does not appear
**                        in the output when using the -H option.)
**
**    EXPORT scope        The object is visible and usable everywhere.
**
** The DP_Flag is a temporary use flag that is used during processing to
** prevent an infinite loop.  It's use is localized.
**
** The DP_Cplusplus, DP_ExternCReqd and DP_ExternReqd flags are permanent
** and are used to specify what type of declaration the object requires.
*/
#define DP_Forward 0x001  /* Has a forward declaration in this file */
#define DP_Declared 0x002 /* Has a full declaration in this file */
#define DP_Export 0x004   /* Export this declaration */
#define DP_Local 0x008    /* Declare in its home file only */
#define DP_Flag                                                                \
  0x010 /* Use to mark a subset of a Decl list                                 \
        ** for special processing */
#define DP_Cplusplus                                                           \
  0x020 /* Has C++ linkage and cannot appear in a                              \
        ** C header file */
#define DP_ExternCReqd                                                         \
  0x040 /* Prepend 'extern "C"' in a C++ header.                               \
        ** Prepend nothing in a C header */
#define DP_ExternReqd                                                          \
  0x080 /* Prepend 'extern "C"' in a C++ header if                             \
        ** DP_Cplusplus is not also set. If DP_Cplusplus                       \
        ** is set or this is a C header then                                   \
        ** prepend 'extern' */

/*
** Convenience macros for dealing with declaration properties
*/
#define DeclHasProperty(D, P) (((D)->flags & (P)) == (P))
#define DeclHasAnyProperty(D, P) (((D)->flags & (P)) != 0)
#define DeclSetProperty(D, P) (D)->flags |= (P)
#define DeclClearProperty(D, P) (D)->flags &= ~(P)

/*
** These are state properties of the parser.  Each of the values is
** distinct from the DP_ values above so that both can be used in
** the same "flags" field.
**
** Be careful not to confuse PS_Export with DP_Export or
** PS_Local with DP_Local.  Their names are similar, but the meanings
** of these flags are very different.
*/
#define PS_Extern 0x000800 /* "extern" has been seen */
#define PS_Export                                                              \
  0x001000                    /* If between "#if EXPORT_INTERFACE"             \
                              ** and "#endif" */
#define PS_Export2 0x002000   /* If "EXPORT" seen */
#define PS_Typedef 0x004000   /* If "typedef" has been seen */
#define PS_Static 0x008000    /* If "static" has been seen */
#define PS_Interface 0x010000 /* If within #if INTERFACE..#endif */
#define PS_Method 0x020000    /* If "::" token has been seen */
#define PS_Local 0x040000     /* If within #if LOCAL_INTERFACE..#endif */
#define PS_Local2 0x080000    /* If "LOCAL" seen. */
#define PS_Public 0x100000    /* If "PUBLIC" seen. */
#define PS_Protected 0x200000 /* If "PROTECTED" seen. */
#define PS_Private 0x400000   /* If "PRIVATE" seen. */
#define PS_PPP 0x700000       /* If any of PUBLIC, PRIVATE, PROTECTED */

/*
** The following set of flags are ORed into the "flags" field of
** a Decl in order to identify what type of object is being
** declared.
*/
#define TY_Class 0x00100000
#define TY_Subroutine 0x00200000
#define TY_Macro 0x00400000
#define TY_Typedef 0x00800000
#define TY_Variable 0x01000000
#define TY_Structure 0x02000000
#define TY_Union 0x04000000
#define TY_Enumeration 0x08000000
#define TY_Defunct 0x10000000 /* Used to erase a declaration */

/*
** Each nested #if (or #ifdef or #ifndef) is stored in a stack of
** instances of the following structure.
*/
typedef struct Ifmacro Ifmacro;
struct Ifmacro {
  int nLine;        /* Line number where this macro occurs */
  char *zCondition; /* Text of the condition for this macro */
  Ifmacro *pNext;   /* Next down in the stack */
  int flags;        /* Can hold PS_Export, PS_Interface or PS_Local flags */
};

/*
** When parsing a file, we need to keep track of what other files have
** be #include-ed.  For each #include found, we create an instance of
** the following structure.
*/
typedef struct Include Include;
struct Include {
  char *zFile;    /* The name of file include.  Includes "" or <> */
  char *zIf;      /* If not NULL, #include should be enclosed in #if */
  char *zLabel;   /* A unique label used to test if this #include has
                   * appeared already in a file or not */
  Include *pNext; /* Previous include file, or NULL if this is the first */
};

/*
** Identifiers found in a source file that might be used later to provoke
** the copying of a declaration into the corresponding header file are
** stored in a hash table as instances of the following structure.
*/
typedef struct Ident Ident;
struct Ident {
  char *zName;     /* The text of this identifier */
  Ident *pCollide; /* Next identifier with the same hash */
  Ident *pNext;    /* Next identifier in a list of them all */
};

/*
** A complete table of identifiers is stored in an instance of
** the next structure.
*/
#define IDENT_HASH_SIZE 2237
typedef struct IdentTable IdentTable;
struct IdentTable {
  Ident *pList;                    /* List of all identifiers in this table */
  Ident *apTable[IDENT_HASH_SIZE]; /* The hash table */
};

/*
** The following structure holds all information for a single
** source file named on the command line of this program.
*/
typedef struct InFile InFile;
struct InFile {
  char *zSrc;         /* Name of input file */
  char *zHdr;         /* Name of the generated .h file for this input.
                      ** Will be NULL if input is to be scanned only */
  int flags;          /* One or more DP_, PS_ and/or TY_ flags */
  InFile *pNext;      /* Next input file in the list of them all */
  IdentTable idTable; /* All identifiers in this input file */
};

/*
** An unbounded string is able to grow without limit.  We use these
** to construct large in-memory strings from lots of smaller components.
*/
typedef struct String String;
struct String {
  int nAlloc;  /* Number of bytes allocated */
  int nUsed;   /* Number of bytes used (not counting nul terminator) */
  char *zText; /* Text of the string */
};

/*
** The following structure contains a lot of state information used
** while generating a .h file.  We put the information in this structure
** and pass around a pointer to this structure, rather than pass around
** all of the information separately.  This helps reduce the number of
** arguments to generator functions.
*/
typedef struct GenState GenState;
struct GenState {
  String *pStr;          /* Write output to this string */
  IdentTable *pTable;    /* A table holding the zLabel of every #include that
                          * has already been generated.  Used to avoid
                          * generating duplicate #includes. */
  const char *zIf;       /* If not NULL, then we are within a #if with
                          * this argument. */
  int nErr;              /* Number of errors */
  const char *zFilename; /* Name of the source file being scanned */
  int flags;             /* Various flags (DP_ and PS_ flags above) */
};

/*
** The following text line appears at the top of every file generated
** by this program.  By recognizing this line, the program can be sure
** never to read a file that it generated itself.
**
** The "#undef INTERFACE" part is a hack to work around a name collision
** in MSVC 2008.
*/
const char zTopLine[] =
    "/* \aThis file was automatically generated.  Do not edit! */\n"
    "#undef INTERFACE\n";
#define nTopLine (sizeof(zTopLine) - 1)

/*
** The name of the file currently being parsed.
*/
static const char *zFilename;

/*
** The stack of #if macros for the file currently being parsed.
*/
static Ifmacro *ifStack = 0;

/*
** A list of all files that have been #included so far in a file being
** parsed.
*/
static Include *includeList = 0;

/*
** The last block comment seen.
*/
static Token *blockComment = 0;

/*
** The following flag is set if the -doc flag appears on the
** command line.
*/
static int doc_flag = 0;

/*
** If the following flag is set, then makeheaders will attempt to
** generate prototypes for static functions and procedures.
*/
static int proto_static = 0;

/*
** A list of all declarations.  The list is held together using the
** pNext field of the Decl structure.
*/
static Decl *pDeclFirst; /* First on the list */
static Decl *pDeclLast;  /* Last on the list */

/*
** A hash table of all declarations
*/
#define DECL_HASH_SIZE 3371
static Decl *apTable[DECL_HASH_SIZE];

/*
** The TEST macro must be defined to something.  Make sure this is the
** case.
*/
#ifndef TEST
#define TEST 0
#endif

#ifdef NOT_USED
/*
** We do our own assertion macro so that we can have more control
** over debugging.
*/
#define Assert(X)                                                              \
  if (!(X)) {                                                                  \
    CantHappen(__LINE__);                                                      \
  }
#define CANT_HAPPEN CantHappen(__LINE__)
static void CantHappen(int iLine) {
  fprintf(stderr, "Assertion failed on line %d\n", iLine);
  *(char *)1 = 0; /* Force a core-dump */
}
#endif

/*
** Memory allocation functions that are guaranteed never to return NULL.
*/
static void *SafeMalloc(int nByte) {
  void *p = malloc(nByte);
  if (p == 0) {
    fprintf(stderr, "Out of memory.  Can't allocate %d bytes.\n", nByte);
    exit(1);
  }
  return p;
}
static void SafeFree(void *pOld) {
  if (pOld) {
    free(pOld);
  }
}
static void *SafeRealloc(void *pOld, int nByte) {
  void *p;
  if (pOld == 0) {
    p = SafeMalloc(nByte);
  } else {
    p = realloc(pOld, nByte);
    if (p == 0) {
      fprintf(stderr,
              "Out of memory.  Can't enlarge an allocation to %d bytes\n",
              nByte);
      exit(1);
    }
  }
  return p;
}
static char *StrDup(const char *zSrc, int nByte) {
  char *zDest;
  if (nByte <= 0) {
    nByte = strlen(zSrc);
  }
  zDest = SafeMalloc(nByte + 1);
  strncpy(zDest, zSrc, nByte);
  zDest[nByte] = 0;
  return zDest;
}

/*
** Return TRUE if the character X can be part of an identifier
*/
#define ISALNUM(X) ((X) == '_' || isalnum(X))

/*
** Routines for dealing with unbounded strings.
*/
static void StringInit(String *pStr) {
  pStr->nAlloc = 0;
  pStr->nUsed = 0;
  pStr->zText = 0;
}
static void StringReset(String *pStr) {
  SafeFree(pStr->zText);
  StringInit(pStr);
}
static void StringAppend(String *pStr, const char *zText, int nByte) {
  if (nByte <= 0) {
    nByte = strlen(zText);
  }
  if (pStr->nUsed + nByte >= pStr->nAlloc) {
    if (pStr->nAlloc == 0) {
      pStr->nAlloc = nByte + 100;
      pStr->zText = SafeMalloc(pStr->nAlloc);
    } else {
      pStr->nAlloc = pStr->nAlloc * 2 + nByte;
      pStr->zText = SafeRealloc(pStr->zText, pStr->nAlloc);
    }
  }
  strncpy(&pStr->zText[pStr->nUsed], zText, nByte);
  pStr->nUsed += nByte;
  pStr->zText[pStr->nUsed] = 0;
}
#define StringGet(S) ((S)->zText ? (S)->zText : "")

/*
** Compute a hash on a string.  The number returned is a non-negative
** value between 0 and 2**31 - 1
*/
static int Hash(const char *z, int n) {
  int h = 0;
  if (n <= 0) {
    n = strlen(z);
  }
  while (n--) {
    h = h ^ (h << 5) ^ *z++;
  }
  return h & 0x7fffffff;
}

/*
** Given an identifier name, try to find a declaration for that
** identifier in the hash table.  If found, return a pointer to
** the Decl structure.  If not found, return 0.
*/
Decl *FindDecl(const char *zName, int len) {
  int h;
  Decl *p;

  if (len <= 0) {
    len = strlen(zName);
  }
  h = Hash(zName, len) % DECL_HASH_SIZE;
  p = apTable[h];
  while (p && (strncmp(p->zName, zName, len) != 0 || p->zName[len] != 0)) {
    p = p->pSameHash;
  }
  return p;
}

/*
** Install the given declaration both in the hash table and on
** the list of all declarations.
*/
static void InstallDecl(Decl *pDecl) {
  int h;
  Decl *pOther;

  h = Hash(pDecl->zName, 0) % DECL_HASH_SIZE;
  pOther = apTable[h];
  while (pOther && strcmp(pDecl->zName, pOther->zName) != 0) {
    pOther = pOther->pSameHash;
  }
  if (pOther) {
    pDecl->pSameName = pOther->pSameName;
    pOther->pSameName = pDecl;
  } else {
    pDecl->pSameName = 0;
    pDecl->pSameHash = apTable[h];
    apTable[h] = pDecl;
  }
  pDecl->pNext = 0;
  if (pDeclFirst == 0) {
    pDeclFirst = pDeclLast = pDecl;
  } else {
    pDeclLast->pNext = pDecl;
    pDeclLast = pDecl;
  }
}

/*
** Look at the current ifStack.  If anything declared at the current
** position must be surrounded with
**
**      #if   STUFF
**      #endif
**
** Then this routine computes STUFF and returns a pointer to it.  Memory
** to hold the value returned is obtained from malloc().
*/
static char *GetIfString(void) {
  Ifmacro *pIf;
  char *zResult = 0;
  int hasIf = 0;
  String str;

  for (pIf = ifStack; pIf; pIf = pIf->pNext) {
    if (pIf->zCondition == 0 || *pIf->zCondition == 0)
      continue;
    if (!hasIf) {
      hasIf = 1;
      StringInit(&str);
    } else {
      StringAppend(&str, " && ", 4);
    }
    StringAppend(&str, pIf->zCondition, 0);
  }
  if (hasIf) {
    zResult = StrDup(StringGet(&str), 0);
    StringReset(&str);
  } else {
    zResult = 0;
  }
  return zResult;
}

/*
** Create a new declaration and put it in the hash table.  Also
** return a pointer to it so that we can fill in the zFwd and zDecl
** fields, and so forth.
*/
static Decl *
CreateDecl(const char *zName, /* Name of the object being declared. */
           int nName          /* Length of the name */
) {
  Decl *pDecl;

  pDecl = SafeMalloc(sizeof(Decl) + nName + 1);
  memset(pDecl, 0, sizeof(Decl));
  pDecl->zName = (char *)&pDecl[1];
  sprintf(pDecl->zName, "%.*s", nName, zName);
  pDecl->zFile = zFilename;
  pDecl->pInclude = includeList;
  pDecl->zIf = GetIfString();
  InstallDecl(pDecl);
  return pDecl;
}

/*
** Insert a new identifier into an table of identifiers.  Return TRUE if
** a new identifier was inserted and return FALSE if the identifier was
** already in the table.
*/
static int
IdentTableInsert(IdentTable *pTable, /* The table into which we will insert */
                 const char *zId,    /* Name of the identifiers */
                 int nId             /* Length of the identifier name */
) {
  int h;
  Ident *pId;

  if (nId <= 0) {
    nId = strlen(zId);
  }
  h = Hash(zId, nId) % IDENT_HASH_SIZE;
  for (pId = pTable->apTable[h]; pId; pId = pId->pCollide) {
    if (strncmp(zId, pId->zName, nId) == 0 && pId->zName[nId] == 0) {
      /* printf("Already in table: %.*s\n",nId,zId); */
      return 0;
    }
  }
  pId = SafeMalloc(sizeof(Ident) + nId + 1);
  pId->zName = (char *)&pId[1];
  sprintf(pId->zName, "%.*s", nId, zId);
  pId->pNext = pTable->pList;
  pTable->pList = pId;
  pId->pCollide = pTable->apTable[h];
  pTable->apTable[h] = pId;
  /* printf("Add to table: %.*s\n",nId,zId); */
  return 1;
}

/*
** Check to see if the given value is in the given IdentTable.  Return
** true if it is and false if it is not.
*/
static int IdentTableTest(IdentTable *pTable, /* The table in which to search */
                          const char *zId,    /* Name of the identifiers */
                          int nId /* Length of the identifier name */
) {
  int h;
  Ident *pId;

  if (nId <= 0) {
    nId = strlen(zId);
  }
  h = Hash(zId, nId) % IDENT_HASH_SIZE;
  for (pId = pTable->apTable[h]; pId; pId = pId->pCollide) {
    if (strncmp(zId, pId->zName, nId) == 0 && pId->zName[nId] == 0) {
      return 1;
    }
  }
  return 0;
}

/*
** Remove every identifier from the given table.   Reset the table to
** its initial state.
*/
static void IdentTableReset(IdentTable *pTable) {
  Ident *pId, *pNext;

  for (pId = pTable->pList; pId; pId = pNext) {
    pNext = pId->pNext;
    SafeFree(pId);
  }
  memset(pTable, 0, sizeof(IdentTable));
}

#ifdef DEBUG
/*
** Print the name of every identifier in the given table, one per line
*/
static void IdentTablePrint(IdentTable *pTable, FILE *pOut) {
  Ident *pId;

  for (pId = pTable->pList; pId; pId = pId->pNext) {
    fprintf(pOut, "%s\n", pId->zName);
  }
}
#endif

/*
** Read an entire file into memory.  Return a pointer to the memory.
**
** The memory is obtained from SafeMalloc and must be freed by the
** calling function.
**
** If the read fails for any reason, 0 is returned.
*/
static char *ReadFile(const char *zFilename) {
  struct stat sStat;
  FILE *pIn;
  char *zBuf;
  int n;

  if (stat(zFilename, &sStat) != 0
#ifndef WIN32
      || !S_ISREG(sStat.st_mode)
#endif
  ) {
    return 0;
  }
  pIn = fopen(zFilename, "r");
  if (pIn == 0) {
    return 0;
  }
  zBuf = SafeMalloc(sStat.st_size + 1);
  n = fread(zBuf, 1, sStat.st_size, pIn);
  zBuf[n] = 0;
  fclose(pIn);
  return zBuf;
}

/*
** Write the contents of a string into a file.  Return the number of
** errors
*/
static int WriteFile(const char *zFilename, const char *zOutput) {
  FILE *pOut;
  pOut = fopen(zFilename, "w");
  if (pOut == 0) {
    return 1;
  }
  fwrite(zOutput, 1, strlen(zOutput), pOut);
  fclose(pOut);
  return 0;
}

/*
** Major token types
*/
#define TT_Space 1        /* Contiguous white space */
#define TT_Id 2           /* An identifier */
#define TT_Preprocessor 3 /* Any C preprocessor directive */
#define TT_Comment 4      /* Either C or C++ style comment */
#define TT_Number 5       /* Any numeric constant */
#define TT_String 6       /* String or character constants. ".." or '.' */
#define TT_Braces 7       /* All text between { and a matching } */
#define TT_EOF 8          /* End of file */
#define TT_Error 9        /* An error condition */
#define TT_BlockComment                                                        \
  10               /* A C-Style comment at the left margin that                \
                    * spans multiple lines */
#define TT_Other 0 /* None of the above */

/*
** Get a single low-level token from the input file.  Update the
** file pointer so that it points to the first character beyond the
** token.
**
** A "low-level token" is any token except TT_Braces.  A TT_Braces token
** consists of many smaller tokens and is assembled by a routine that
** calls this one.
**
** The function returns the number of errors.  An error is an
** unterminated string or character literal or an unterminated
** comment.
**
** Profiling shows that this routine consumes about half the
** CPU time on a typical run of makeheaders.
*/
static int GetToken(InStream *pIn, Token *pToken) {
  int i;
  const char *z;
  int cStart;
  int c;
  int startLine; /* Line on which a structure begins */
  int nlisc = 0; /* True if there is a new-line in a ".." or '..' */
  int nErr = 0;  /* Number of errors seen */

  z = pIn->z;
  i = pIn->i;
  pToken->nLine = pIn->nLine;
  pToken->zText = &z[i];
  switch (z[i]) {
  case 0:
    pToken->eType = TT_EOF;
    pToken->nText = 0;
    break;

  case '#':
    if (i == 0 || z[i - 1] == '\n' ||
        (i > 1 && z[i - 1] == '\r' && z[i - 2] == '\n')) {
      /* We found a preprocessor statement */
      pToken->eType = TT_Preprocessor;
      i++;
      while (z[i] != 0 && z[i] != '\n') {
        if (z[i] == '\\') {
          i++;
          if (z[i] == '\n')
            pIn->nLine++;
        }
        i++;
      }
      pToken->nText = i - pIn->i;
    } else {
      /* Just an operator */
      pToken->eType = TT_Other;
      pToken->nText = 1;
    }
    break;

  case ' ':
  case '\t':
  case '\r':
  case '\f':
  case '\n':
    while (isspace(z[i])) {
      if (z[i] == '\n')
        pIn->nLine++;
      i++;
    }
    pToken->eType = TT_Space;
    pToken->nText = i - pIn->i;
    break;

  case '\\':
    pToken->nText = 2;
    pToken->eType = TT_Other;
    if (z[i + 1] == '\n') {
      pIn->nLine++;
      pToken->eType = TT_Space;
    } else if (z[i + 1] == 0) {
      pToken->nText = 1;
    }
    break;

  case '\'':
  case '\"':
    cStart = z[i];
    startLine = pIn->nLine;
    do {
      i++;
      c = z[i];
      if (c == '\n') {
        if (!nlisc) {
          fprintf(stderr,
                  "%s:%d: (warning) Newline in string or character literal.\n",
                  zFilename, pIn->nLine);
          nlisc = 1;
        }
        pIn->nLine++;
      }
      if (c == '\\') {
        i++;
        c = z[i];
        if (c == '\n') {
          pIn->nLine++;
        }
      } else if (c == cStart) {
        i++;
        c = 0;
      } else if (c == 0) {
        fprintf(stderr, "%s:%d: Unterminated string or character literal.\n",
                zFilename, startLine);
        nErr++;
      }
    } while (c);
    pToken->eType = TT_String;
    pToken->nText = i - pIn->i;
    break;

  case '/':
    if (z[i + 1] == '/') {
      /* C++ style comment */
      while (z[i] && z[i] != '\n') {
        i++;
      }
      pToken->eType = TT_Comment;
      pToken->nText = i - pIn->i;
    } else if (z[i + 1] == '*') {
      /* C style comment */
      int isBlockComment = i == 0 || z[i - 1] == '\n';
      i += 2;
      startLine = pIn->nLine;
      while (z[i] && (z[i] != '*' || z[i + 1] != '/')) {
        if (z[i] == '\n') {
          pIn->nLine++;
          if (isBlockComment) {
            if (z[i + 1] == '*' || z[i + 2] == '*') {
              isBlockComment = 2;
            } else {
              isBlockComment = 0;
            }
          }
        }
        i++;
      }
      if (z[i]) {
        i += 2;
      } else {
        isBlockComment = 0;
        fprintf(stderr, "%s:%d: Unterminated comment\n", zFilename, startLine);
        nErr++;
      }
      pToken->eType = isBlockComment == 2 ? TT_BlockComment : TT_Comment;
      pToken->nText = i - pIn->i;
    } else {
      /* A divide operator */
      pToken->eType = TT_Other;
      pToken->nText = 1 + (z[i + 1] == '+');
    }
    break;

  case '0':
    if (z[i + 1] == 'x' || z[i + 1] == 'X') {
      /* A hex constant */
      i += 2;
      while (isxdigit(z[i])) {
        i++;
      }
    } else {
      /* An octal constant */
      while (isdigit(z[i])) {
        i++;
      }
    }
    pToken->eType = TT_Number;
    pToken->nText = i - pIn->i;
    break;

  case '1':
  case '2':
  case '3':
  case '4':
  case '5':
  case '6':
  case '7':
  case '8':
  case '9':
    while (isdigit(z[i])) {
      i++;
    }
    if ((c = z[i]) == '.') {
      i++;
      while (isdigit(z[i])) {
        i++;
      }
      c = z[i];
      if (c == 'e' || c == 'E') {
        i++;
        if (((c = z[i]) == '+' || c == '-') && isdigit(z[i + 1])) {
          i++;
        }
        while (isdigit(z[i])) {
          i++;
        }
        c = z[i];
      }
      if (c == 'f' || c == 'F' || c == 'l' || c == 'L') {
        i++;
      }
    } else if (c == 'e' || c == 'E') {
      i++;
      if (((c = z[i]) == '+' || c == '-') && isdigit(z[i + 1])) {
        i++;
      }
      while (isdigit(z[i])) {
        i++;
      }
    } else if (c == 'L' || c == 'l') {
      i++;
      c = z[i];
      if (c == 'u' || c == 'U') {
        i++;
      }
    } else if (c == 'u' || c == 'U') {
      i++;
      c = z[i];
      if (c == 'l' || c == 'L') {
        i++;
      }
    }
    pToken->eType = TT_Number;
    pToken->nText = i - pIn->i;
    break;

  case 'a':
  case 'b':
  case 'c':
  case 'd':
  case 'e':
  case 'f':
  case 'g':
  case 'h':
  case 'i':
  case 'j':
  case 'k':
  case 'l':
  case 'm':
  case 'n':
  case 'o':
  case 'p':
  case 'q':
  case 'r':
  case 's':
  case 't':
  case 'u':
  case 'v':
  case 'w':
  case 'x':
  case 'y':
  case 'z':
  case 'A':
  case 'B':
  case 'C':
  case 'D':
  case 'E':
  case 'F':
  case 'G':
  case 'H':
  case 'I':
  case 'J':
  case 'K':
  case 'L':
  case 'M':
  case 'N':
  case 'O':
  case 'P':
  case 'Q':
  case 'R':
  case 'S':
  case 'T':
  case 'U':
  case 'V':
  case 'W':
  case 'X':
  case 'Y':
  case 'Z':
  case '_':
    while (isalnum(z[i]) || z[i] == '_') {
      i++;
    };
    pToken->eType = TT_Id;
    pToken->nText = i - pIn->i;
    break;

  case ':':
    pToken->eType = TT_Other;
    pToken->nText = 1 + (z[i + 1] == ':');
    break;

  case '=':
  case '<':
  case '>':
  case '+':
  case '-':
  case '*':
  case '%':
  case '^':
  case '&':
  case '|':
    pToken->eType = TT_Other;
    pToken->nText = 1 + (z[i + 1] == '=');
    break;

  default:
    pToken->eType = TT_Other;
    pToken->nText = 1;
    break;
  }
  pIn->i += pToken->nText;
  return nErr;
}

/*
** This routine recovers the next token from the input file which is
** not a space or a comment or any text between an "#if 0" and "#endif".
**
** This routine returns the number of errors encountered.  An error
** is an unterminated token or unmatched "#if 0".
**
** Profiling shows that this routine uses about a quarter of the
** CPU time in a typical run.
*/
static int GetNonspaceToken(InStream *pIn, Token *pToken) {
  int nIf = 0;
  int inZero = 0;
  const char *z;
  int value;
  int startLine;
  int nErr = 0;

  startLine = pIn->nLine;
  while (1) {
    nErr += GetToken(pIn, pToken);
    /* printf("%04d: Type=%d nIf=%d [%.*s]\n",
       pToken->nLine,pToken->eType,nIf,pToken->nText,
       pToken->eType!=TT_Space ? pToken->zText : "<space>"); */
    pToken->pComment = blockComment;
    switch (pToken->eType) {
    case TT_Comment: /*0123456789 12345678 */
      if (strncmp(pToken->zText, "/*MAKEHEADERS-STOP", 18) == 0)
        return nErr;
      break;

    case TT_Space:
      break;

    case TT_BlockComment:
      if (doc_flag) {
        blockComment = SafeMalloc(sizeof(Token));
        *blockComment = *pToken;
      }
      break;

    case TT_EOF:
      if (nIf) {
        fprintf(stderr, "%s:%d: Unterminated \"#if\"\n", zFilename, startLine);
        nErr++;
      }
      return nErr;

    case TT_Preprocessor:
      z = &pToken->zText[1];
      while (*z == ' ' || *z == '\t')
        z++;
      if (sscanf(z, "if %d", &value) == 1 && value == 0) {
        nIf++;
        inZero = 1;
      } else if (inZero) {
        if (strncmp(z, "if", 2) == 0) {
          nIf++;
        } else if (strncmp(z, "endif", 5) == 0) {
          nIf--;
          if (nIf == 0)
            inZero = 0;
        }
      } else {
        return nErr;
      }
      break;

    default:
      if (!inZero) {
        return nErr;
      }
      break;
    }
  }
  /* NOT REACHED */
}

/*
** This routine looks for identifiers (strings of contiguous alphanumeric
** characters) within a preprocessor directive and adds every such string
** found to the given identifier table
*/
static void FindIdentifiersInMacro(Token *pToken, IdentTable *pTable) {
  Token sToken;
  InStream sIn;
  int go = 1;

  sIn.z = pToken->zText;
  sIn.i = 1;
  sIn.nLine = 1;
  while (go && sIn.i < pToken->nText) {
    GetToken(&sIn, &sToken);
    switch (sToken.eType) {
    case TT_Id:
      IdentTableInsert(pTable, sToken.zText, sToken.nText);
      break;

    case TT_EOF:
      go = 0;
      break;

    default:
      break;
    }
  }
}

/*
** This routine gets the next token.  Everything contained within
** {...} is collapsed into a single TT_Braces token.  Whitespace is
** omitted.
**
** If pTable is not NULL, then insert every identifier seen into the
** IdentTable.  This includes any identifiers seen inside of {...}.
**
** The number of errors encountered is returned.  An error is an
** unterminated token.
*/
static int GetBigToken(InStream *pIn, Token *pToken, IdentTable *pTable) {
  const char *zStart;
  int iStart;
  int nBrace;
  int c;
  int nLine;
  int nErr;

  nErr = GetNonspaceToken(pIn, pToken);
  switch (pToken->eType) {
  case TT_Id:
    if (pTable != 0) {
      IdentTableInsert(pTable, pToken->zText, pToken->nText);
    }
    return nErr;

  case TT_Preprocessor:
    if (pTable != 0) {
      FindIdentifiersInMacro(pToken, pTable);
    }
    return nErr;

  case TT_Other:
    if (pToken->zText[0] == '{')
      break;
    return nErr;

  default:
    return nErr;
  }

  iStart = pIn->i;
  zStart = pToken->zText;
  nLine = pToken->nLine;
  nBrace = 1;
  while (nBrace) {
    nErr += GetNonspaceToken(pIn, pToken);
    /* printf("%04d: nBrace=%d [%.*s]\n",pToken->nLine,nBrace,
       pToken->nText,pToken->zText); */
    switch (pToken->eType) {
    case TT_EOF:
      fprintf(stderr, "%s:%d: Unterminated \"{\"\n", zFilename, nLine);
      nErr++;
      pToken->eType = TT_Error;
      return nErr;

    case TT_Id:
      if (pTable) {
        IdentTableInsert(pTable, pToken->zText, pToken->nText);
      }
      break;

    case TT_Preprocessor:
      if (pTable != 0) {
        FindIdentifiersInMacro(pToken, pTable);
      }
      break;

    case TT_Other:
      if ((c = pToken->zText[0]) == '{') {
        nBrace++;
      } else if (c == '}') {
        nBrace--;
      }
      break;

    default:
      break;
    }
  }
  pToken->eType = TT_Braces;
  pToken->nText = 1 + pIn->i - iStart;
  pToken->zText = zStart;
  pToken->nLine = nLine;
  return nErr;
}

/*
** This routine frees up a list of Tokens.  The pComment tokens are
** not cleared by this.  So we leak a little memory when using the -doc
** option.  So what.
*/
static void FreeTokenList(Token *pList) {
  Token *pNext;
  while (pList) {
    pNext = pList->pNext;
    SafeFree(pList);
    pList = pNext;
  }
}

/*
** Tokenize an entire file.  Return a pointer to the list of tokens.
**
** Space for each token is obtained from a separate malloc() call.  The
** calling function is responsible for freeing this space.
**
** If pTable is not NULL, then fill the table with all identifiers seen in
** the input file.
*/
static Token *TokenizeFile(const char *zFile, IdentTable *pTable) {
  InStream sIn;
  Token *pFirst = 0, *pLast = 0, *pNew;
  int nErr = 0;

  sIn.z = zFile;
  sIn.i = 0;
  sIn.nLine = 1;
  blockComment = 0;

  while (sIn.z[sIn.i] != 0) {
    pNew = SafeMalloc(sizeof(Token));
    nErr += GetBigToken(&sIn, pNew, pTable);
    debug3(TOKENIZER, "Token on line %d: [%.*s]\n", pNew->nLine,
           pNew->nText < 50 ? pNew->nText : 50, pNew->zText);
    if (pFirst == 0) {
      pFirst = pLast = pNew;
      pNew->pPrev = 0;
    } else {
      pLast->pNext = pNew;
      pNew->pPrev = pLast;
      pLast = pNew;
    }
    if (pNew->eType == TT_EOF)
      break;
  }
  if (pLast)
    pLast->pNext = 0;
  blockComment = 0;
  if (nErr) {
    FreeTokenList(pFirst);
    pFirst = 0;
  }

  return pFirst;
}

#if TEST == 1
/*
** Use the following routine to test or debug the tokenizer.
*/
void main(int argc, char **argv) {
  char *zFile;
  Token *pList, *p;
  IdentTable sTable;

  if (argc != 2) {
    fprintf(stderr, "Usage: %s filename\n", *argv);
    exit(1);
  }
  memset(&sTable, 0, sizeof(sTable));
  zFile = ReadFile(argv[1]);
  if (zFile == 0) {
    fprintf(stderr, "Can't read file \"%s\"\n", argv[1]);
    exit(1);
  }
  pList = TokenizeFile(zFile, &sTable);
  for (p = pList; p; p = p->pNext) {
    int j;
    switch (p->eType) {
    case TT_Space:
      printf("%4d: Space\n", p->nLine);
      break;
    case TT_Id:
      printf("%4d: Id           %.*s\n", p->nLine, p->nText, p->zText);
      break;
    case TT_Preprocessor:
      printf("%4d: Preprocessor %.*s\n", p->nLine, p->nText, p->zText);
      break;
    case TT_Comment:
      printf("%4d: Comment\n", p->nLine);
      break;
    case TT_BlockComment:
      printf("%4d: Block Comment\n", p->nLine);
      break;
    case TT_Number:
      printf("%4d: Number       %.*s\n", p->nLine, p->nText, p->zText);
      break;
    case TT_String:
      printf("%4d: String       %.*s\n", p->nLine, p->nText, p->zText);
      break;
    case TT_Other:
      printf("%4d: Other        %.*s\n", p->nLine, p->nText, p->zText);
      break;
    case TT_Braces:
      for (j = 0; j < p->nText && j < 30 && p->zText[j] != '\n'; j++) {
      }
      printf("%4d: Braces       %.*s...}\n", p->nLine, j, p->zText);
      break;
    case TT_EOF:
      printf("%4d: End of file\n", p->nLine);
      break;
    default:
      printf("%4d: type %d\n", p->nLine, p->eType);
      break;
    }
  }
  FreeTokenList(pList);
  SafeFree(zFile);
  IdentTablePrint(&sTable, stdout);
}
#endif

#ifdef DEBUG
/*
** For debugging purposes, write out a list of tokens.
*/
static void PrintTokens(Token *pFirst, Token *pLast) {
  int needSpace = 0;
  int c;

  pLast = pLast->pNext;
  while (pFirst != pLast) {
    switch (pFirst->eType) {
    case TT_Preprocessor:
      printf("\n%.*s\n", pFirst->nText, pFirst->zText);
      needSpace = 0;
      break;

    case TT_Id:
    case TT_Number:
      printf("%s%.*s", needSpace ? " " : "", pFirst->nText, pFirst->zText);
      needSpace = 1;
      break;

    default:
      c = pFirst->zText[0];
      printf("%s%.*s", (needSpace && (c == '*' || c == '{')) ? " " : "",
             pFirst->nText, pFirst->zText);
      needSpace = pFirst->zText[0] == ',';
      break;
    }
    pFirst = pFirst->pNext;
  }
}
#endif

/*
** Convert a sequence of tokens into a string and return a pointer
** to that string.  Space to hold the string is obtained from malloc()
** and must be freed by the calling function.
**
** Certain keywords (EXPORT, PRIVATE, PUBLIC, PROTECTED) are always
** skipped.
**
** If pSkip!=0 then skip over nSkip tokens beginning with pSkip.
**
** If zTerm!=0 then append the text to the end.
*/
static char *TokensToString(
    Token *pFirst, /* First token in the string */
    Token *pLast,  /* Last token in the string */
    char *zTerm,   /* Terminate the string with this text if not NULL */
    Token *pSkip,  /* Skip this token if not NULL */
    int nSkip      /* Skip a total of this many tokens */
) {
  char *zReturn;
  String str;
  int needSpace = 0;
  int c;
  int iSkip = 0;
  int skipOne = 0;

  StringInit(&str);
  pLast = pLast->pNext;
  while (pFirst != pLast) {
    if (pFirst == pSkip) {
      iSkip = nSkip;
    }
    if (iSkip > 0) {
      iSkip--;
      pFirst = pFirst->pNext;
      continue;
    }
    switch (pFirst->eType) {
    case TT_Preprocessor:
      StringAppend(&str, "\n", 1);
      StringAppend(&str, pFirst->zText, pFirst->nText);
      StringAppend(&str, "\n", 1);
      needSpace = 0;
      break;

    case TT_Id:
      switch (pFirst->zText[0]) {
      case 'E':
        if (pFirst->nText == 6 && strncmp(pFirst->zText, "EXPORT", 6) == 0) {
          skipOne = 1;
        }
        break;
      case 'P':
        switch (pFirst->nText) {
        case 6:
          skipOne = !strncmp(pFirst->zText, "PUBLIC", 6);
          break;
        case 7:
          skipOne = !strncmp(pFirst->zText, "PRIVATE", 7);
          break;
        case 9:
          skipOne = !strncmp(pFirst->zText, "PROTECTED", 9);
          break;
        default:
          break;
        }
        break;
      default:
        break;
      }
      if (skipOne) {
        pFirst = pFirst->pNext;
        skipOne = 0;
        continue;
      }
      /* Fall thru to the next case */
    case TT_Number:
      if (needSpace) {
        StringAppend(&str, " ", 1);
      }
      StringAppend(&str, pFirst->zText, pFirst->nText);
      needSpace = 1;
      break;

    default:
      c = pFirst->zText[0];
      if (needSpace && (c == '*' || c == '{')) {
        StringAppend(&str, " ", 1);
      }
      StringAppend(&str, pFirst->zText, pFirst->nText);
      /* needSpace = pFirst->zText[0]==','; */
      needSpace = 0;
      break;
    }
    pFirst = pFirst->pNext;
  }
  if (zTerm && *zTerm) {
    StringAppend(&str, zTerm, strlen(zTerm));
  }
  zReturn = StrDup(StringGet(&str), 0);
  StringReset(&str);
  return zReturn;
}

/*
** This routine is called when we see one of the keywords "struct",
** "enum", "union" or "class".  This might be the beginning of a
** type declaration.  This routine will process the declaration and
** remove the declaration tokens from the input stream.
**
** If this is a type declaration that is immediately followed by a
** semicolon (in other words it isn't also a variable definition)
** then set *pReset to ';'.  Otherwise leave *pReset at 0.  The
** *pReset flag causes the parser to skip ahead to the next token
** that begins with the value placed in the *pReset flag, if that
** value is different from 0.
*/
static int ProcessTypeDecl(Token *pList, int flags, int *pReset) {
  Token *pName, *pEnd;
  Decl *pDecl;
  String str;
  int need_to_collapse = 1;
  int type = 0;

  *pReset = 0;
  if (pList == 0 || pList->pNext == 0 || pList->pNext->eType != TT_Id) {
    return 0;
  }
  pName = pList->pNext;

  /* Catch the case of "struct Foo;" and skip it. */
  if (pName->pNext && pName->pNext->zText[0] == ';') {
    *pReset = ';';
    return 0;
  }

  for (pEnd = pName->pNext; pEnd && pEnd->eType != TT_Braces;
       pEnd = pEnd->pNext) {
    switch (pEnd->zText[0]) {
    case '(':
    case ')':
    case '*':
    case '[':
    case '=':
    case ';':
      return 0;
    }
  }
  if (pEnd == 0) {
    return 0;
  }

  /*
  ** At this point, we know we have a type declaration that is bounded
  ** by pList and pEnd and has the name pName.
  */

  /*
  ** If the braces are followed immediately by a semicolon, then we are
  ** dealing a type declaration only.  There is not variable definition
  ** following the type declaration.  So reset...
  */
  if (pEnd->pNext == 0 || pEnd->pNext->zText[0] == ';') {
    *pReset = ';';
    need_to_collapse = 0;
  } else {
    need_to_collapse = 1;
  }

  if (proto_static == 0 &&
      (flags & (PS_Local | PS_Export | PS_Interface)) == 0) {
    /* Ignore these objects unless they are explicitly declared as interface,
    ** or unless the "-local" command line option was specified. */
    *pReset = ';';
    return 0;
  }

#ifdef DEBUG
  if (debugMask & PARSER) {
    printf("**** Found type: %.*s %.*s...\n", pList->nText, pList->zText,
           pName->nText, pName->zText);
    PrintTokens(pList, pEnd);
    printf(";\n");
  }
#endif

  /*
  ** Create a new Decl object for this definition.  Actually, if this
  ** is a C++ class definition, then the Decl object might already exist,
  ** so check first for that case before creating a new one.
  */
  switch (*pList->zText) {
  case 'c':
    type = TY_Class;
    break;
  case 's':
    type = TY_Structure;
    break;
  case 'e':
    type = TY_Enumeration;
    break;
  case 'u':
    type = TY_Union;
    break;
  default: /* Can't Happen */
    break;
  }
  if (type != TY_Class) {
    pDecl = 0;
  } else {
    pDecl = FindDecl(pName->zText, pName->nText);
    if (pDecl && (pDecl->flags & type) != type)
      pDecl = 0;
  }
  if (pDecl == 0) {
    pDecl = CreateDecl(pName->zText, pName->nText);
  }
  if ((flags & PS_Static) || !(flags & (PS_Interface | PS_Export))) {
    DeclSetProperty(pDecl, DP_Local);
  }
  DeclSetProperty(pDecl, type);

  /* The object has a full declaration only if it is contained within
  ** "#if INTERFACE...#endif" or "#if EXPORT_INTERFACE...#endif" or
  ** "#if LOCAL_INTERFACE...#endif".  Otherwise, we only give it a
  ** forward declaration.
  */
  if (flags & (PS_Local | PS_Export | PS_Interface)) {
    pDecl->zDecl = TokensToString(pList, pEnd, ";\n", 0, 0);
  } else {
    pDecl->zDecl = 0;
  }
  pDecl->pComment = pList->pComment;
  StringInit(&str);
  StringAppend(&str, "typedef ", 0);
  StringAppend(&str, pList->zText, pList->nText);
  StringAppend(&str, " ", 0);
  StringAppend(&str, pName->zText, pName->nText);
  StringAppend(&str, " ", 0);
  StringAppend(&str, pName->zText, pName->nText);
  StringAppend(&str, ";\n", 2);
  pDecl->zFwd = StrDup(StringGet(&str), 0);
  StringReset(&str);
  StringInit(&str);
  StringAppend(&str, pList->zText, pList->nText);
  StringAppend(&str, " ", 0);
  StringAppend(&str, pName->zText, pName->nText);
  StringAppend(&str, ";\n", 2);
  pDecl->zFwdCpp = StrDup(StringGet(&str), 0);
  StringReset(&str);
  if (flags & PS_Export) {
    DeclSetProperty(pDecl, DP_Export);
  } else if (flags & PS_Local) {
    DeclSetProperty(pDecl, DP_Local);
  }

  /* Here's something weird.  ANSI-C doesn't allow a forward declaration
  ** of an enumeration.  So we have to build the typedef into the
  ** definition.
  */
  if (pDecl->zDecl && DeclHasProperty(pDecl, TY_Enumeration)) {
    StringInit(&str);
    StringAppend(&str, pDecl->zDecl, 0);
    StringAppend(&str, pDecl->zFwd, 0);
    SafeFree(pDecl->zDecl);
    SafeFree(pDecl->zFwd);
    pDecl->zFwd = 0;
    pDecl->zDecl = StrDup(StringGet(&str), 0);
    StringReset(&str);
  }

  if (pName->pNext->zText[0] == ':') {
    DeclSetProperty(pDecl, DP_Cplusplus);
  }
  if (pName->nText == 5 && strncmp(pName->zText, "class", 5) == 0) {
    DeclSetProperty(pDecl, DP_Cplusplus);
  }

  /*
  ** Remove all but pList and pName from the input stream.
  */
  if (need_to_collapse) {
    while (pEnd != pName) {
      Token *pPrev = pEnd->pPrev;
      pPrev->pNext = pEnd->pNext;
      pEnd->pNext->pPrev = pPrev;
      SafeFree(pEnd);
      pEnd = pPrev;
    }
  }
  return 0;
}

/*
** Given a list of tokens that declare something (a function, procedure,
** variable or typedef) find the token which contains the name of the
** thing being declared.
**
** Algorithm:
**
**   The name is:
**
**     1.  The first identifier that is followed by a "[", or
**
**     2.  The first identifier that is followed by a "(" where the
**         "(" is followed by another identifier, or
**
**     3.  The first identifier followed by "::", or
**
**     4.  If none of the above, then the last identifier.
**
**   In all of the above, certain reserved words (like "char") are
**   not considered identifiers.
*/
static Token *FindDeclName(Token *pFirst, Token *pLast) {
  Token *pName = 0;
  Token *p;
  int c;

  if (pFirst == 0 || pLast == 0) {
    return 0;
  }
  pLast = pLast->pNext;
  for (p = pFirst; p && p != pLast; p = p->pNext) {
    if (p->eType == TT_Id) {
      static IdentTable sReserved;
      static int isInit = 0;
      static const char *aWords[] = {
          "char",      "class",   "const",    "double",   "enum",    "extern",
          "EXPORT",    "ET_PROC", "float",    "int",      "long",    "PRIVATE",
          "PROTECTED", "PUBLIC",  "register", "static",   "struct",  "sizeof",
          "signed",    "typedef", "union",    "volatile", "virtual", "void",
      };

      if (!isInit) {
        int i;
        for (i = 0; i < sizeof(aWords) / sizeof(aWords[0]); i++) {
          IdentTableInsert(&sReserved, aWords[i], 0);
        }
        isInit = 1;
      }
      if (!IdentTableTest(&sReserved, p->zText, p->nText)) {
        pName = p;
      }
    } else if (p == pFirst) {
      continue;
    } else if ((c = p->zText[0]) == '[' && pName) {
      break;
    } else if (c == '(' && p->pNext && p->pNext->eType == TT_Id && pName) {
      break;
    } else if (c == ':' && p->zText[1] == ':' && pName) {
      break;
    }
  }
  return pName;
}

/*
** This routine is called when we see a method for a class that begins
** with the PUBLIC, PRIVATE, or PROTECTED keywords.  Such methods are
** added to their class definitions.
*/
static int ProcessMethodDef(Token *pFirst, Token *pLast, int flags) {
  Token *pClass;
  char *zDecl;
  Decl *pDecl;
  String str;
  int type;

  pLast = pLast->pPrev;
  while (pFirst->zText[0] == 'P') {
    int rc = 1;
    switch (pFirst->nText) {
    case 6:
      rc = strncmp(pFirst->zText, "PUBLIC", 6);
      break;
    case 7:
      rc = strncmp(pFirst->zText, "PRIVATE", 7);
      break;
    case 9:
      rc = strncmp(pFirst->zText, "PROTECTED", 9);
      break;
    default:
      break;
    }
    if (rc)
      break;
    pFirst = pFirst->pNext;
  }
  pClass = FindDeclName(pFirst, pLast);
  if (pClass == 0) {
    fprintf(stderr, "%s:%d: Unable to find the class name for this method\n",
            zFilename, pFirst->nLine);
    return 1;
  }
  pDecl = FindDecl(pClass->zText, pClass->nText);
  if (pDecl == 0 || (pDecl->flags & TY_Class) != TY_Class) {
    pDecl = CreateDecl(pClass->zText, pClass->nText);
    DeclSetProperty(pDecl, TY_Class);
  }
  StringInit(&str);
  if (pDecl->zExtra) {
    StringAppend(&str, pDecl->zExtra, 0);
    SafeFree(pDecl->zExtra);
    pDecl->zExtra = 0;
  }
  type = flags & PS_PPP;
  if (pDecl->extraType != type) {
    if (type & PS_Public) {
      StringAppend(&str, "public:\n", 0);
      pDecl->extraType = PS_Public;
    } else if (type & PS_Protected) {
      StringAppend(&str, "protected:\n", 0);
      pDecl->extraType = PS_Protected;
    } else if (type & PS_Private) {
      StringAppend(&str, "private:\n", 0);
      pDecl->extraType = PS_Private;
    }
  }
  StringAppend(&str, "  ", 0);
  zDecl = TokensToString(pFirst, pLast, ";\n", pClass, 2);
  if (strncmp(zDecl, pClass->zText, pClass->nText) == 0) {
    /* If member initializer list is found after a constructor,
    ** skip that part. */
    char *colon = strchr(zDecl, ':');
    if (colon != 0 && colon[1] != 0) {
      *colon++ = ';';
      *colon++ = '\n';
      *colon = 0;
    }
  }
  StringAppend(&str, zDecl, 0);
  SafeFree(zDecl);
  pDecl->zExtra = StrDup(StringGet(&str), 0);
  StringReset(&str);
  return 0;
}

/*
** This routine is called when we see a function or procedure definition.
** We make an entry in the declaration table that is a prototype for this
** function or procedure.
*/
static int ProcessProcedureDef(Token *pFirst, Token *pLast, int flags) {
  Token *pName;
  Decl *pDecl;
  Token *pCode;

  if (pFirst == 0 || pLast == 0) {
    return 0;
  }
  if (flags & PS_Method) {
    if (flags & PS_PPP) {
      return ProcessMethodDef(pFirst, pLast, flags);
    } else {
      return 0;
    }
  }
  if ((flags & PS_Static) != 0 && !proto_static) {
    return 0;
  }
  pCode = pLast;
  while (pLast && pLast != pFirst && pLast->zText[0] != ')') {
    pLast = pLast->pPrev;
  }
  if (pLast == 0 || pLast == pFirst || pFirst->pNext == pLast) {
    fprintf(stderr, "%s:%d: Unrecognized syntax.\n", zFilename, pFirst->nLine);
    return 1;
  }
  if (flags & (PS_Interface | PS_Export | PS_Local)) {
    fprintf(stderr, "%s:%d: Missing \"inline\" on function or procedure.\n",
            zFilename, pFirst->nLine);
    return 1;
  }
  pName = FindDeclName(pFirst, pLast);
  if (pName == 0) {
    fprintf(stderr, "%s:%d: Malformed function or procedure definition.\n",
            zFilename, pFirst->nLine);
    return 1;
  }
  if (strncmp(pName->zText, "main", pName->nText) == 0) {
    /* skip main() decl. */
    return 0;
  }
  /*
  ** At this point we've isolated a procedure declaration between pFirst
  ** and pLast with the name pName.
  */
#ifdef DEBUG
  if (debugMask & PARSER) {
    printf("**** Found routine: %.*s on line %d...\n", pName->nText,
           pName->zText, pFirst->nLine);
    PrintTokens(pFirst, pLast);
    printf(";\n");
  }
#endif
  pDecl = CreateDecl(pName->zText, pName->nText);
  pDecl->pComment = pFirst->pComment;
  if (pCode && pCode->eType == TT_Braces) {
    pDecl->tokenCode = *pCode;
  }
  DeclSetProperty(pDecl, TY_Subroutine);
  pDecl->zDecl = TokensToString(pFirst, pLast, ";\n", 0, 0);
  if ((flags & (PS_Static | PS_Local2)) != 0) {
    DeclSetProperty(pDecl, DP_Local);
  } else if ((flags & (PS_Export2)) != 0) {
    DeclSetProperty(pDecl, DP_Export);
  }

  if (flags & DP_Cplusplus) {
    DeclSetProperty(pDecl, DP_Cplusplus);
  } else {
    DeclSetProperty(pDecl, DP_ExternCReqd);
  }

  return 0;
}

/*
** This routine is called whenever we see the "inline" keyword.  We
** need to seek-out the inline function or procedure and make a
** declaration out of the entire definition.
*/
static int ProcessInlineProc(Token *pFirst, int flags, int *pReset) {
  Token *pName;
  Token *pEnd;
  Decl *pDecl;

  for (pEnd = pFirst; pEnd; pEnd = pEnd->pNext) {
    if (pEnd->zText[0] == '{' || pEnd->zText[0] == ';') {
      *pReset = pEnd->zText[0];
      break;
    }
  }
  if (pEnd == 0) {
    *pReset = ';';
    fprintf(stderr, "%s:%d: incomplete inline procedure definition\n",
            zFilename, pFirst->nLine);
    return 1;
  }
  pName = FindDeclName(pFirst, pEnd);
  if (pName == 0) {
    fprintf(stderr, "%s:%d: malformed inline procedure definition\n", zFilename,
            pFirst->nLine);
    return 1;
  }

#ifdef DEBUG
  if (debugMask & PARSER) {
    printf("**** Found inline routine: %.*s on line %d...\n", pName->nText,
           pName->zText, pFirst->nLine);
    PrintTokens(pFirst, pEnd);
    printf("\n");
  }
#endif
  pDecl = CreateDecl(pName->zText, pName->nText);
  pDecl->pComment = pFirst->pComment;
  DeclSetProperty(pDecl, TY_Subroutine);
  pDecl->zDecl = TokensToString(pFirst, pEnd, ";\n", 0, 0);
  if ((flags & (PS_Static | PS_Local | PS_Local2))) {
    DeclSetProperty(pDecl, DP_Local);
  } else if (flags & (PS_Export | PS_Export2)) {
    DeclSetProperty(pDecl, DP_Export);
  }

  if (flags & DP_Cplusplus) {
    DeclSetProperty(pDecl, DP_Cplusplus);
  } else {
    DeclSetProperty(pDecl, DP_ExternCReqd);
  }

  return 0;
}

/*
** Determine if the tokens between pFirst and pEnd form a variable
** definition or a function prototype.  Return TRUE if we are dealing
** with a variable defintion and FALSE for a prototype.
**
** pEnd is the token that ends the object.  It can be either a ';' or
** a '='.  If it is '=', then assume we have a variable definition.
**
** If pEnd is ';', then the determination is more difficult.  We have
** to search for an occurrence of an ID followed immediately by '('.
** If found, we have a prototype.  Otherwise we are dealing with a
** variable definition.
*/
static int isVariableDef(Token *pFirst, Token *pEnd) {
  if (pEnd && pEnd->zText[0] == '=' &&
      (pEnd->pPrev->nText != 8 ||
       strncmp(pEnd->pPrev->zText, "operator", 8) != 0)) {
    return 1;
  }
  while (pFirst && pFirst != pEnd && pFirst->pNext && pFirst->pNext != pEnd) {
    if (pFirst->eType == TT_Id && pFirst->pNext->zText[0] == '(') {
      return 0;
    }
    pFirst = pFirst->pNext;
  }
  return 1;
}

/*
** Return TRUE if pFirst is the first token of a static assert.
*/
static int isStaticAssert(Token *pFirst) {
  if ((pFirst->nText == 13 &&
       strncmp(pFirst->zText, "static_assert", 13) == 0) ||
      (pFirst->nText == 14 &&
       strncmp(pFirst->zText, "_Static_assert", 14) == 0)) {
    return 1;
  } else {
    return 0;
  }
}

/*
** This routine is called whenever we encounter a ";" or "=".  The stuff
** between pFirst and pLast constitutes either a typedef or a global
** variable definition.  Do the right thing.
*/
static int ProcessDecl(Token *pFirst, Token *pEnd, int flags) {
  Token *pName;
  Decl *pDecl;
  int isLocal = 0;
  int isVar;
  int nErr = 0;

  if (pFirst == 0 || pEnd == 0) {
    return 0;
  }
  if (flags & PS_Typedef) {
    if ((flags & (PS_Export2 | PS_Local2)) != 0) {
      fprintf(stderr,
              "%s:%d: \"EXPORT\" or \"LOCAL\" ignored before typedef.\n",
              zFilename, pFirst->nLine);
      nErr++;
    }
    if ((flags & (PS_Interface | PS_Export | PS_Local | DP_Cplusplus)) == 0) {
      /* It is illegal to duplicate a typedef in C (but OK in C++).
      ** So don't record typedefs that aren't within a C++ file or
      ** within #if INTERFACE..#endif */
      return nErr;
    }
    if ((flags & (PS_Interface | PS_Export | PS_Local)) == 0 &&
        proto_static == 0) {
      /* Ignore typedefs that are not with "#if INTERFACE..#endif" unless
      ** the "-local" command line option is used. */
      return nErr;
    }
    if ((flags & (PS_Interface | PS_Export)) == 0) {
      /* typedefs are always local, unless within #if INTERFACE..#endif */
      isLocal = 1;
    }
  } else if (flags & (PS_Static | PS_Local2)) {
    if (proto_static == 0 && (flags & PS_Local2) == 0) {
      /* Don't record static variables unless the "-local" command line
      ** option was specified or the "LOCAL" keyword is used. */
      return nErr;
    }
    while (pFirst != 0 && pFirst->pNext != pEnd &&
           ((pFirst->nText == 6 && strncmp(pFirst->zText, "static", 6) == 0) ||
            (pFirst->nText == 5 && strncmp(pFirst->zText, "LOCAL", 6) == 0))) {
      /* Lose the initial "static" or local from local variables.
      ** We'll prepend "extern" later. */
      pFirst = pFirst->pNext;
      isLocal = 1;
    }
    if (pFirst == 0 || !isLocal) {
      return nErr;
    }
  } else if (flags & PS_Method) {
    /* Methods are declared by their class.  Don't declare separately. */
    return nErr;
  } else if (isStaticAssert(pFirst)) {
    return 0;
  }
  isVar =
      (flags & (PS_Typedef | PS_Method)) == 0 && isVariableDef(pFirst, pEnd);
  if (isVar && (flags & (PS_Interface | PS_Export | PS_Local)) != 0 &&
      (flags & PS_Extern) == 0) {
    fprintf(stderr, "%s:%d: Can't define a variable in this context\n",
            zFilename, pFirst->nLine);
    nErr++;
  }
  pName = FindDeclName(pFirst, pEnd->pPrev);
  if (pName == 0) {
    if (pFirst->nText == 4 && strncmp(pFirst->zText, "enum", 4) == 0) {
      /* Ignore completely anonymous enums.  See documentation section 3.8.1. */
      return nErr;
    } else {
      fprintf(stderr,
              "%s:%d: Can't find a name for the object declared here.\n",
              zFilename, pFirst->nLine);
      return nErr + 1;
    }
  }

#ifdef DEBUG
  if (debugMask & PARSER) {
    if (flags & PS_Typedef) {
      printf("**** Found typedef %.*s at line %d...\n", pName->nText,
             pName->zText, pName->nLine);
    } else if (isVar) {
      printf("**** Found variable %.*s at line %d...\n", pName->nText,
             pName->zText, pName->nLine);
    } else {
      printf("**** Found prototype %.*s at line %d...\n", pName->nText,
             pName->zText, pName->nLine);
    }
    PrintTokens(pFirst, pEnd->pPrev);
    printf(";\n");
  }
#endif

  pDecl = CreateDecl(pName->zText, pName->nText);
  if ((flags & PS_Typedef)) {
    DeclSetProperty(pDecl, TY_Typedef);
  } else if (isVar) {
    DeclSetProperty(pDecl, DP_ExternReqd | TY_Variable);
    if (!(flags & DP_Cplusplus)) {
      DeclSetProperty(pDecl, DP_ExternCReqd);
    }
  } else {
    DeclSetProperty(pDecl, TY_Subroutine);
    if (!(flags & DP_Cplusplus)) {
      DeclSetProperty(pDecl, DP_ExternCReqd);
    }
  }
  pDecl->pComment = pFirst->pComment;
  pDecl->zDecl = TokensToString(pFirst, pEnd->pPrev, ";\n", 0, 0);
  if (isLocal || (flags & (PS_Local | PS_Local2)) != 0) {
    DeclSetProperty(pDecl, DP_Local);
  } else if (flags & (PS_Export | PS_Export2)) {
    DeclSetProperty(pDecl, DP_Export);
  }
  if (flags & DP_Cplusplus) {
    DeclSetProperty(pDecl, DP_Cplusplus);
  }
  return nErr;
}

/*
** Push an if condition onto the if stack
*/
static void
PushIfMacro(const char *zPrefix, /* A prefix, like "define" or "!" */
            const char *zText,   /* The condition */
            int nText,           /* Number of characters in zText */
            int nLine,           /* Line number where this macro occurs */
            int flags /* Either 0, PS_Interface, PS_Export or PS_Local */
) {
  Ifmacro *pIf;
  int nByte;

  nByte = sizeof(Ifmacro);
  if (zText) {
    if (zPrefix) {
      nByte += strlen(zPrefix) + 2;
    }
    nByte += nText + 1;
  }
  pIf = SafeMalloc(nByte);
  if (zText) {
    pIf->zCondition = (char *)&pIf[1];
    if (zPrefix) {
      sprintf(pIf->zCondition, "%s(%.*s)", zPrefix, nText, zText);
    } else {
      sprintf(pIf->zCondition, "%.*s", nText, zText);
    }
  } else {
    pIf->zCondition = 0;
  }
  pIf->nLine = nLine;
  pIf->flags = flags;
  pIf->pNext = ifStack;
  ifStack = pIf;
}

/*
** This routine is called to handle all preprocessor directives.
**
** This routine will recompute the value of *pPresetFlags to be the
** logical or of all flags on all nested #ifs.  The #ifs that set flags
** are as follows:
**
**        conditional                   flag set
**        ------------------------      --------------------
**        #if INTERFACE                 PS_Interface
**        #if EXPORT_INTERFACE          PS_Export
**        #if LOCAL_INTERFACE           PS_Local
**
** For example, if after processing the preprocessor token given
** by pToken there is an "#if INTERFACE" on the preprocessor
** stack, then *pPresetFlags will be set to PS_Interface.
*/
static int ParsePreprocessor(Token *pToken, int flags, int *pPresetFlags) {
  const char *zCmd;
  int nCmd;
  const char *zArg;
  int nArg;
  int nErr = 0;
  Ifmacro *pIf;

  zCmd = &pToken->zText[1];
  while (isspace(*zCmd) && *zCmd != '\n') {
    zCmd++;
  }
  if (!isalpha(*zCmd)) {
    return 0;
  }
  nCmd = 1;
  while (isalpha(zCmd[nCmd])) {
    nCmd++;
  }

  if (nCmd == 5 && strncmp(zCmd, "endif", 5) == 0) {
    /*
    ** Pop the if stack
    */
    pIf = ifStack;
    if (pIf == 0) {
      fprintf(stderr, "%s:%d: extra '#endif'.\n", zFilename, pToken->nLine);
      return 1;
    }
    ifStack = pIf->pNext;
    SafeFree(pIf);
  } else if (nCmd == 6 && strncmp(zCmd, "define", 6) == 0) {
    /*
    ** Record a #define if we are in PS_Interface or PS_Export
    */
    Decl *pDecl;
    if (!(flags & (PS_Local | PS_Interface | PS_Export))) {
      return 0;
    }
    zArg = &zCmd[6];
    while (*zArg && isspace(*zArg) && *zArg != '\n') {
      zArg++;
    }
    if (*zArg == 0 || *zArg == '\n') {
      return 0;
    }
    for (nArg = 0; ISALNUM(zArg[nArg]); nArg++) {
    }
    if (nArg == 0) {
      return 0;
    }
    pDecl = CreateDecl(zArg, nArg);
    pDecl->pComment = pToken->pComment;
    DeclSetProperty(pDecl, TY_Macro);
    pDecl->zDecl = SafeMalloc(pToken->nText + 2);
    sprintf(pDecl->zDecl, "%.*s\n", pToken->nText, pToken->zText);
    if (flags & PS_Export) {
      DeclSetProperty(pDecl, DP_Export);
    } else if (flags & PS_Local) {
      DeclSetProperty(pDecl, DP_Local);
    }
  } else if (nCmd == 7 && strncmp(zCmd, "include", 7) == 0) {
    /*
    ** Record an #include if we are in PS_Interface or PS_Export
    */
    Include *pInclude;
    char *zIf;

    if (!(flags & (PS_Interface | PS_Export))) {
      return 0;
    }
    zArg = &zCmd[7];
    while (*zArg && isspace(*zArg)) {
      zArg++;
    }
    for (nArg = 0; !isspace(zArg[nArg]); nArg++) {
    }
    if ((zArg[0] == '"' && zArg[nArg - 1] != '"') ||
        (zArg[0] == '<' && zArg[nArg - 1] != '>')) {
      fprintf(stderr, "%s:%d: malformed #include statement.\n", zFilename,
              pToken->nLine);
      return 1;
    }
    zIf = GetIfString();
    if (zIf) {
      pInclude = SafeMalloc(sizeof(Include) + nArg * 2 + strlen(zIf) + 10);
      pInclude->zFile = (char *)&pInclude[1];
      pInclude->zLabel = &pInclude->zFile[nArg + 1];
      sprintf(pInclude->zFile, "%.*s", nArg, zArg);
      sprintf(pInclude->zLabel, "%.*s:%s", nArg, zArg, zIf);
      pInclude->zIf = &pInclude->zLabel[nArg + 1];
      SafeFree(zIf);
    } else {
      pInclude = SafeMalloc(sizeof(Include) + nArg + 1);
      pInclude->zFile = (char *)&pInclude[1];
      sprintf(pInclude->zFile, "%.*s", nArg, zArg);
      pInclude->zIf = 0;
      pInclude->zLabel = pInclude->zFile;
    }
    pInclude->pNext = includeList;
    includeList = pInclude;
  } else if (nCmd == 2 && strncmp(zCmd, "if", 2) == 0) {
    /*
    ** Push an #if.  Watch for the special cases of INTERFACE
    ** and EXPORT_INTERFACE and LOCAL_INTERFACE
    */
    zArg = &zCmd[2];
    while (*zArg && isspace(*zArg) && *zArg != '\n') {
      zArg++;
    }
    if (*zArg == 0 || *zArg == '\n') {
      return 0;
    }
    nArg = pToken->nText + (int)(pToken->zText - zArg);
    if (pToken->zText[pToken->nText - 1] == '\r') {
      nArg--;
    }
    if (nArg == 9 && strncmp(zArg, "INTERFACE", 9) == 0) {
      PushIfMacro(0, 0, 0, pToken->nLine, PS_Interface);
    } else if (nArg == 16 && strncmp(zArg, "EXPORT_INTERFACE", 16) == 0) {
      PushIfMacro(0, 0, 0, pToken->nLine, PS_Export);
    } else if (nArg == 15 && strncmp(zArg, "LOCAL_INTERFACE", 15) == 0) {
      PushIfMacro(0, 0, 0, pToken->nLine, PS_Local);
    } else if (nArg == 15 &&
               strncmp(zArg, "MAKEHEADERS_STOPLOCAL_INTERFACE", 15) == 0) {
      PushIfMacro(0, 0, 0, pToken->nLine, PS_Local);
    } else {
      PushIfMacro(0, zArg, nArg, pToken->nLine, 0);
    }
  } else if (nCmd == 5 && strncmp(zCmd, "ifdef", 5) == 0) {
    /*
    ** Push an #ifdef.
    */
    zArg = &zCmd[5];
    while (*zArg && isspace(*zArg) && *zArg != '\n') {
      zArg++;
    }
    if (*zArg == 0 || *zArg == '\n') {
      return 0;
    }
    nArg = pToken->nText + (int)(pToken->zText - zArg);
    if (pToken->zText[pToken->nText - 1] == '\r') {
      nArg--;
    }
    PushIfMacro("defined", zArg, nArg, pToken->nLine, 0);
  } else if (nCmd == 6 && strncmp(zCmd, "ifndef", 6) == 0) {
    /*
    ** Push an #ifndef.
    */
    zArg = &zCmd[6];
    while (*zArg && isspace(*zArg) && *zArg != '\n') {
      zArg++;
    }
    if (*zArg == 0 || *zArg == '\n') {
      return 0;
    }
    nArg = pToken->nText + (int)(pToken->zText - zArg);
    if (pToken->zText[pToken->nText - 1] == '\r') {
      nArg--;
    }
    PushIfMacro("!defined", zArg, nArg, pToken->nLine, 0);
  } else if (nCmd == 4 && strncmp(zCmd, "else", 4) == 0) {
    /*
    ** Invert the #if on the top of the stack
    */
    if (ifStack == 0) {
      fprintf(stderr, "%s:%d: '#else' without an '#if'\n", zFilename,
              pToken->nLine);
      return 1;
    }
    pIf = ifStack;
    if (pIf->zCondition) {
      ifStack = ifStack->pNext;
      PushIfMacro("!", pIf->zCondition, strlen(pIf->zCondition), pIf->nLine, 0);
      SafeFree(pIf);
    } else {
      pIf->flags = 0;
    }
  } else {
    /*
    ** This directive can be safely ignored
    */
    return 0;
  }

  /*
  ** Recompute the preset flags
  */
  *pPresetFlags = 0;
  for (pIf = ifStack; pIf; pIf = pIf->pNext) {
    *pPresetFlags |= pIf->flags;
  }

  return nErr;
}

/*
** Parse an entire file.  Return the number of errors.
**
** pList is a list of tokens in the file.  Whitespace tokens have been
** eliminated, and text with {...} has been collapsed into a
** single TT_Brace token.
**
** initFlags are a set of parse flags that should always be set for this
** file.  For .c files this is normally 0.  For .h files it is PS_Interface.
*/
static int ParseFile(Token *pList, int initFlags) {
  int nErr = 0;
  Token *pStart = 0;
  int flags = initFlags;
  int presetFlags = initFlags;
  int resetFlag = 0;

  includeList = 0;
  while (pList) {
    switch (pList->eType) {
    case TT_EOF:
      goto end_of_loop;

    case TT_Preprocessor:
      nErr += ParsePreprocessor(pList, flags, &presetFlags);
      pStart = 0;
      presetFlags |= initFlags;
      flags = presetFlags;
      break;

    case TT_Other:
      switch (pList->zText[0]) {
      case ';':
        nErr += ProcessDecl(pStart, pList, flags);
        pStart = 0;
        flags = presetFlags;
        break;

      case '=':
        if (pList->pPrev->nText == 8 &&
            strncmp(pList->pPrev->zText, "operator", 8) == 0) {
          break;
        }
        nErr += ProcessDecl(pStart, pList, flags);
        pStart = 0;
        while (pList && pList->zText[0] != ';') {
          pList = pList->pNext;
        }
        if (pList == 0)
          goto end_of_loop;
        flags = presetFlags;
        break;

      case ':':
        if (pList->zText[1] == ':') {
          flags |= PS_Method;
        }
        break;

      default:
        break;
      }
      break;

    case TT_Braces:
      nErr += ProcessProcedureDef(pStart, pList, flags);
      pStart = 0;
      flags = presetFlags;
      break;

    case TT_Id:
      if (pStart == 0) {
        pStart = pList;
        flags = presetFlags;
      }
      resetFlag = 0;
      switch (pList->zText[0]) {
      case 'c':
        if (pList->nText == 5 && strncmp(pList->zText, "class", 5) == 0) {
          nErr += ProcessTypeDecl(pList, flags, &resetFlag);
        }
        break;

      case 'E':
        if (pList->nText == 6 && strncmp(pList->zText, "EXPORT", 6) == 0) {
          flags |= PS_Export2;
          /* pStart = 0; */
        }
        break;

      case 'e':
        if (pList->nText == 4 && strncmp(pList->zText, "enum", 4) == 0) {
          if (pList->pNext && pList->pNext->eType == TT_Braces) {
            pList = pList->pNext;
          } else {
            nErr += ProcessTypeDecl(pList, flags, &resetFlag);
          }
        } else if (pList->nText == 6 &&
                   strncmp(pList->zText, "extern", 6) == 0) {
          pList = pList->pNext;
          if (pList && pList->nText == 3 &&
              strncmp(pList->zText, "\"C\"", 3) == 0) {
            pList = pList->pNext;
            flags &= ~DP_Cplusplus;
          } else {
            flags |= PS_Extern;
          }
          pStart = pList;
        }
        break;

      case 'i':
        if (pList->nText == 6 && strncmp(pList->zText, "inline", 6) == 0 &&
            (flags & PS_Static) == 0) {
          nErr += ProcessInlineProc(pList, flags, &resetFlag);
        }
        break;

      case 'L':
        if (pList->nText == 5 && strncmp(pList->zText, "LOCAL", 5) == 0) {
          flags |= PS_Local2;
          pStart = pList;
        }
        break;

      case 'P':
        if (pList->nText == 6 && strncmp(pList->zText, "PUBLIC", 6) == 0) {
          flags |= PS_Public;
          pStart = pList;
        } else if (pList->nText == 7 &&
                   strncmp(pList->zText, "PRIVATE", 7) == 0) {
          flags |= PS_Private;
          pStart = pList;
        } else if (pList->nText == 9 &&
                   strncmp(pList->zText, "PROTECTED", 9) == 0) {
          flags |= PS_Protected;
          pStart = pList;
        }
        break;

      case 's':
        if (pList->nText == 6 && strncmp(pList->zText, "struct", 6) == 0) {
          if (pList->pNext && pList->pNext->eType == TT_Braces) {
            pList = pList->pNext;
          } else {
            nErr += ProcessTypeDecl(pList, flags, &resetFlag);
          }
        } else if (pList->nText == 6 &&
                   strncmp(pList->zText, "static", 6) == 0) {
          flags |= PS_Static;
        }
        break;

      case 't':
        if (pList->nText == 7 && strncmp(pList->zText, "typedef", 7) == 0) {
          flags |= PS_Typedef;
        }
        break;

      case 'u':
        if (pList->nText == 5 && strncmp(pList->zText, "union", 5) == 0) {
          if (pList->pNext && pList->pNext->eType == TT_Braces) {
            pList = pList->pNext;
          } else {
            nErr += ProcessTypeDecl(pList, flags, &resetFlag);
          }
        }
        break;

      default:
        break;
      }
      if (resetFlag != 0) {
        while (pList && pList->zText[0] != resetFlag) {
          pList = pList->pNext;
        }
        if (pList == 0)
          goto end_of_loop;
        pStart = 0;
        flags = presetFlags;
      }
      break;

    case TT_String:
    case TT_Number:
      break;

    default:
      pStart = pList;
      flags = presetFlags;
      break;
    }
    pList = pList->pNext;
  }
end_of_loop:

  /* Verify that all #ifs have a matching "#endif" */
  while (ifStack) {
    Ifmacro *pIf = ifStack;
    ifStack = pIf->pNext;
    fprintf(stderr, "%s:%d: This '#if' has no '#endif'\n", zFilename,
            pIf->nLine);
    SafeFree(pIf);
  }

  return nErr;
}

/*
** If the given Decl object has a non-null zExtra field, then the text
** of that zExtra field needs to be inserted in the middle of the
** zDecl field before the last "}" in the zDecl.  This routine does that.
** If the zExtra is NULL, this routine is a no-op.
**
** zExtra holds extra method declarations for classes.  The declarations
** have to be inserted into the class definition.
*/
static void InsertExtraDecl(Decl *pDecl) {
  int i;
  String str;

  if (pDecl == 0 || pDecl->zExtra == 0 || pDecl->zDecl == 0)
    return;
  i = strlen(pDecl->zDecl) - 1;
  while (i > 0 && pDecl->zDecl[i] != '}') {
    i--;
  }
  StringInit(&str);
  StringAppend(&str, pDecl->zDecl, i);
  StringAppend(&str, pDecl->zExtra, 0);
  StringAppend(&str, &pDecl->zDecl[i], 0);
  SafeFree(pDecl->zDecl);
  SafeFree(pDecl->zExtra);
  pDecl->zDecl = StrDup(StringGet(&str), 0);
  StringReset(&str);
  pDecl->zExtra = 0;
}

/*
** Reset the DP_Forward and DP_Declared flags on all Decl structures.
** Set both flags for anything that is tagged as local and isn't
** in the file zFilename so that it won't be printing in other files.
*/
static void ResetDeclFlags(char *zFilename) {
  Decl *pDecl;

  for (pDecl = pDeclFirst; pDecl; pDecl = pDecl->pNext) {
    DeclClearProperty(pDecl, DP_Forward | DP_Declared);
    if (DeclHasProperty(pDecl, DP_Local) && pDecl->zFile != zFilename) {
      DeclSetProperty(pDecl, DP_Forward | DP_Declared);
    }
  }
}

/*
** Forward declaration of the ScanText() function.
*/
static void ScanText(const char *, GenState *pState);

/*
** The output in pStr is currently within an #if CONTEXT where context
** is equal to *pzIf.  (*pzIf might be NULL to indicate that we are
** not within any #if at the moment.)  We are getting ready to output
** some text that needs to be within the context of "#if NEW" where
** NEW is zIf.  Make an appropriate change to the context.
*/
static void
ChangeIfContext(const char *zIf, /* The desired #if context */
                GenState *pState /* Current state of the code generator */
) {
  if (zIf == 0) {
    if (pState->zIf == 0)
      return;
    StringAppend(pState->pStr, "#endif\n", 0);
    pState->zIf = 0;
  } else {
    if (pState->zIf) {
      if (strcmp(zIf, pState->zIf) == 0)
        return;
      StringAppend(pState->pStr, "#endif\n", 0);
      pState->zIf = 0;
    }
    ScanText(zIf, pState);
    if (pState->zIf != 0) {
      StringAppend(pState->pStr, "#endif\n", 0);
    }
    StringAppend(pState->pStr, "#if ", 0);
    StringAppend(pState->pStr, zIf, 0);
    StringAppend(pState->pStr, "\n", 0);
    pState->zIf = zIf;
  }
}

/*
** Add to the string pStr a #include of every file on the list of
** include files pInclude.  The table pTable contains all files that
** have already been #included at least once.  Don't add any
** duplicates.  Update pTable with every new #include that is added.
*/
static void
AddIncludes(Include *pInclude, /* Write every #include on this list */
            GenState *pState   /* Current state of the code generator */
) {
  if (pInclude) {
    if (pInclude->pNext) {
      AddIncludes(pInclude->pNext, pState);
    }
    if (IdentTableInsert(pState->pTable, pInclude->zLabel, 0)) {
      ChangeIfContext(pInclude->zIf, pState);
      StringAppend(pState->pStr, "#include ", 0);
      StringAppend(pState->pStr, pInclude->zFile, 0);
      StringAppend(pState->pStr, "\n", 1);
    }
  }
}

/*
** Add to the string pStr a declaration for the object described
** in pDecl.
**
** If pDecl has already been declared in this file, detect that
** fact and abort early.  Do not duplicate a declaration.
**
** If the needFullDecl flag is false and this object has a forward
** declaration, then supply the forward declaration only.  A later
** call to CompleteForwardDeclarations() will finish the declaration
** for us.  But if needFullDecl is true, we must supply the full
** declaration now.  Some objects do not have a forward declaration.
** For those objects, we must print the full declaration now.
**
** Because it is illegal to duplicate a typedef in C, care is taken
** to insure that typedefs for the same identifier are only issued once.
*/
static void
DeclareObject(Decl *pDecl,      /* The thing to be declared */
              GenState *pState, /* Current state of the code generator */
              int needFullDecl  /* Must have the full declaration.  A forward
                                 * declaration isn't enough */
) {
  Decl *p; /* The object to be declared */
  int flag;
  int isCpp;           /* True if generating C++ */
  int doneTypedef = 0; /* True if a typedef has been done for this object */

  /* printf("BEGIN %s of %s\n",needFullDecl?"FULL":"PROTOTYPE",pDecl->zName);*/
  /*
  ** For any object that has a forward declaration, go ahead and do the
  ** forward declaration first.
  */
  isCpp = (pState->flags & DP_Cplusplus) != 0;
  for (p = pDecl; p; p = p->pSameName) {
    if (p->zFwd) {
      if (!DeclHasProperty(p, DP_Forward)) {
        DeclSetProperty(p, DP_Forward);
        if (strncmp(p->zFwd, "typedef", 7) == 0) {
          if (doneTypedef)
            continue;
          doneTypedef = 1;
        }
        ChangeIfContext(p->zIf, pState);
        StringAppend(pState->pStr, isCpp ? p->zFwdCpp : p->zFwd, 0);
      }
    }
  }

  /*
  ** Early out if everything is already suitably declared.
  **
  ** This is a very important step because it prevents us from
  ** executing the code the follows in a recursive call to this
  ** function with the same value for pDecl.
  */
  flag = needFullDecl ? DP_Declared | DP_Forward : DP_Forward;
  for (p = pDecl; p; p = p->pSameName) {
    if (!DeclHasProperty(p, flag))
      break;
  }
  if (p == 0) {
    return;
  }

  /*
  ** Make sure we have all necessary #includes
  */
  for (p = pDecl; p; p = p->pSameName) {
    AddIncludes(p->pInclude, pState);
  }

  /*
  ** Go ahead an mark everything as being declared, to prevent an
  ** infinite loop thru the ScanText() function.  At the same time,
  ** we decide which objects need a full declaration and mark them
  ** with the DP_Flag bit.  We are only able to use DP_Flag in this
  ** way because we know we'll never execute this far into this
  ** function on a recursive call with the same pDecl.  Hence, recursive
  ** calls to this function (through ScanText()) can never change the
  ** value of DP_Flag out from under us.
  */
  for (p = pDecl; p; p = p->pSameName) {
    if (!DeclHasProperty(p, DP_Declared) && (p->zFwd == 0 || needFullDecl) &&
        p->zDecl != 0) {
      DeclSetProperty(p, DP_Forward | DP_Declared | DP_Flag);
    } else {
      DeclClearProperty(p, DP_Flag);
    }
  }

  /*
  ** Call ScanText() recursively (this routine is called from ScanText())
  ** to include declarations required to come before these declarations.
  */
  for (p = pDecl; p; p = p->pSameName) {
    if (DeclHasProperty(p, DP_Flag)) {
      if (p->zDecl[0] == '#') {
        ScanText(&p->zDecl[1], pState);
      } else {
        InsertExtraDecl(p);
        ScanText(p->zDecl, pState);
      }
    }
  }

  /*
  ** Output the declarations.  Do this in two passes.  First
  ** output everything that isn't a typedef.  Then go back and
  ** get the typedefs by the same name.
  */
  for (p = pDecl; p; p = p->pSameName) {
    if (DeclHasProperty(p, DP_Flag) && !DeclHasProperty(p, TY_Typedef)) {
      if (DeclHasAnyProperty(p, TY_Enumeration)) {
        if (doneTypedef)
          continue;
        doneTypedef = 1;
      }
      ChangeIfContext(p->zIf, pState);
      if (!isCpp && DeclHasAnyProperty(p, DP_ExternReqd)) {
        StringAppend(pState->pStr, "extern ", 0);
      } else if (isCpp && DeclHasProperty(p, DP_Cplusplus | DP_ExternReqd)) {
        StringAppend(pState->pStr, "extern ", 0);
      } else if (isCpp &&
                 DeclHasAnyProperty(p, DP_ExternCReqd | DP_ExternReqd)) {
        StringAppend(pState->pStr, "extern \"C\" ", 0);
      }
      InsertExtraDecl(p);
      StringAppend(pState->pStr, p->zDecl, 0);
      if (!isCpp && DeclHasProperty(p, DP_Cplusplus)) {
        fprintf(stderr,
                "%s: C code ought not reference the C++ object \"%s\"\n",
                pState->zFilename, p->zName);
        pState->nErr++;
      }
      DeclClearProperty(p, DP_Flag);
    }
  }
  for (p = pDecl; p && !doneTypedef; p = p->pSameName) {
    if (DeclHasProperty(p, DP_Flag)) {
      /* This has to be a typedef */
      doneTypedef = 1;
      ChangeIfContext(p->zIf, pState);
      InsertExtraDecl(p);
      StringAppend(pState->pStr, p->zDecl, 0);
    }
  }
}

/*
** This routine scans the input text given, and appends to the
** string in pState->pStr the text of any declarations that must
** occur before the text in zText.
**
** If an identifier in zText is immediately followed by '*', then
** only forward declarations are needed for that identifier.  If the
** identifier name is not followed immediately by '*', we must supply
** a full declaration.
*/
static void ScanText(const char *zText, /* The input text to be scanned */
                     GenState *pState /* Current state of the code generator */
) {
  int nextValid = 0; /* True is sNext contains valid data */
  InStream sIn;      /* The input text */
  Token sToken;      /* The current token being examined */
  Token sNext;       /* The next non-space token */

  /* printf("BEGIN SCAN TEXT on %s\n", zText); */

  sIn.z = zText;
  sIn.i = 0;
  sIn.nLine = 1;
  while (sIn.z[sIn.i] != 0) {
    if (nextValid) {
      sToken = sNext;
      nextValid = 0;
    } else {
      GetNonspaceToken(&sIn, &sToken);
    }
    if (sToken.eType == TT_Id) {
      int needFullDecl; /* True if we need to provide the full declaration,
                        ** not just the forward declaration */
      Decl *pDecl;      /* The declaration having the name in sToken */

      /*
      ** See if there is a declaration in the database with the name given
      ** by sToken.
      */
      pDecl = FindDecl(sToken.zText, sToken.nText);
      if (pDecl == 0)
        continue;

      /*
      ** If we get this far, we've found an identifier that has a
      ** declaration in the database.  Now see if we the full declaration
      ** or just a forward declaration.
      */
      GetNonspaceToken(&sIn, &sNext);
      if (sNext.zText[0] == '*') {
        needFullDecl = 0;
      } else {
        needFullDecl = 1;
        nextValid = sNext.eType == TT_Id;
      }

      /*
      ** Generate the needed declaration.
      */
      DeclareObject(pDecl, pState, needFullDecl);
    } else if (sToken.eType == TT_Preprocessor) {
      sIn.i -= sToken.nText - 1;
    }
  }
  /* printf("END SCANTEXT\n"); */
}

/*
** Provide a full declaration to any object which so far has had only
** a forward declaration.
*/
static void CompleteForwardDeclarations(GenState *pState) {
  Decl *pDecl;
  int progress;

  do {
    progress = 0;
    for (pDecl = pDeclFirst; pDecl; pDecl = pDecl->pNext) {
      if (DeclHasProperty(pDecl, DP_Forward) &&
          !DeclHasProperty(pDecl, DP_Declared)) {
        DeclareObject(pDecl, pState, 1);
        progress = 1;
        assert(DeclHasProperty(pDecl, DP_Declared));
      }
    }
  } while (progress);
}

/*
** Generate an include file for the given source file.  Return the number
** of errors encountered.
**
** if nolocal_flag is true, then we do not generate declarations for
** objected marked DP_Local.
*/
static int MakeHeader(InFile *pFile, FILE *report, int nolocal_flag) {
  int nErr = 0;
  GenState sState;
  String outStr;
  IdentTable includeTable;
  Ident *pId;
  char *zNewVersion;
  char *zOldVersion;

  if (pFile->zHdr == 0 || *pFile->zHdr == 0)
    return 0;
  sState.pStr = &outStr;
  StringInit(&outStr);
  StringAppend(&outStr, zTopLine, nTopLine);
  sState.pTable = &includeTable;
  memset(&includeTable, 0, sizeof(includeTable));
  sState.zIf = 0;
  sState.nErr = 0;
  sState.zFilename = pFile->zSrc;
  sState.flags = pFile->flags & DP_Cplusplus;
  ResetDeclFlags(nolocal_flag ? "no" : pFile->zSrc);
  for (pId = pFile->idTable.pList; pId; pId = pId->pNext) {
    Decl *pDecl = FindDecl(pId->zName, 0);
    if (pDecl) {
      DeclareObject(pDecl, &sState, 1);
    }
  }
  CompleteForwardDeclarations(&sState);
  ChangeIfContext(0, &sState);
  nErr += sState.nErr;
  zOldVersion = ReadFile(pFile->zHdr);
  zNewVersion = StringGet(&outStr);
  if (report)
    fprintf(report, "%s: ", pFile->zHdr);
  if (zOldVersion == 0) {
    if (report)
      fprintf(report, "updated\n");
    if (WriteFile(pFile->zHdr, zNewVersion)) {
      fprintf(stderr, "%s: Can't write to file\n", pFile->zHdr);
      nErr++;
    }
  } else if (strncmp(zOldVersion, zTopLine, nTopLine) != 0) {
    if (report)
      fprintf(report, "error!\n");
    fprintf(stderr,
            "%s: Can't overwrite this file because it wasn't previously\n"
            "%*s  generated by 'makeheaders'.\n",
            pFile->zHdr, (int)strlen(pFile->zHdr), "");
    nErr++;
  } else if (strcmp(zOldVersion, zNewVersion) != 0) {
    if (report)
      fprintf(report, "updated\n");
    if (WriteFile(pFile->zHdr, zNewVersion)) {
      fprintf(stderr, "%s: Can't write to file\n", pFile->zHdr);
      nErr++;
    }
  } else if (report) {
    fprintf(report, "unchanged\n");
  }
  SafeFree(zOldVersion);
  IdentTableReset(&includeTable);
  StringReset(&outStr);
  return nErr;
}

/*
** Generate a global header file -- a header file that contains all
** declarations.  If the forExport flag is true, then only those
** objects that are exported are included in the header file.
*/
static int MakeGlobalHeader(int forExport) {
  GenState sState;
  String outStr;
  IdentTable includeTable;
  Decl *pDecl;

  sState.pStr = &outStr;
  StringInit(&outStr);
  /* StringAppend(&outStr,zTopLine,nTopLine); */
  sState.pTable = &includeTable;
  memset(&includeTable, 0, sizeof(includeTable));
  sState.zIf = 0;
  sState.nErr = 0;
  sState.zFilename = "(all)";
  sState.flags = 0;
  ResetDeclFlags(0);
  for (pDecl = pDeclFirst; pDecl; pDecl = pDecl->pNext) {
    if (forExport == 0 || DeclHasProperty(pDecl, DP_Export)) {
      DeclareObject(pDecl, &sState, 1);
    }
  }
  ChangeIfContext(0, &sState);
  printf("<%s>", StringGet(&outStr));
  IdentTableReset(&includeTable);
  StringReset(&outStr);
  return 0;
}

#ifdef DEBUG
/*
** Return the number of characters in the given string prior to the
** first newline.
*/
static int ClipTrailingNewline(char *z) {
  int n = strlen(z);
  while (n > 0 && (z[n - 1] == '\n' || z[n - 1] == '\r')) {
    n--;
  }
  return n;
}

/*
** Dump the entire declaration list for debugging purposes
*/
static void DumpDeclList(void) {
  Decl *pDecl;

  for (pDecl = pDeclFirst; pDecl; pDecl = pDecl->pNext) {
    printf("**** %s from file %s ****\n", pDecl->zName, pDecl->zFile);
    if (pDecl->zIf) {
      printf("If: [%.*s]\n", ClipTrailingNewline(pDecl->zIf), pDecl->zIf);
    }
    if (pDecl->zFwd) {
      printf("Decl: [%.*s]\n", ClipTrailingNewline(pDecl->zFwd), pDecl->zFwd);
    }
    if (pDecl->zDecl) {
      InsertExtraDecl(pDecl);
      printf("Def: [%.*s]\n", ClipTrailingNewline(pDecl->zDecl), pDecl->zDecl);
    }
    if (pDecl->flags) {
      static struct {
        int mask;
        char *desc;
      } flagSet[] = {
          {TY_Class, "class"},       {TY_Enumeration, "enum"},
          {TY_Structure, "struct"},  {TY_Union, "union"},
          {TY_Variable, "variable"}, {TY_Subroutine, "function"},
          {TY_Typedef, "typedef"},   {TY_Macro, "macro"},
          {DP_Export, "export"},     {DP_Local, "local"},
          {DP_Cplusplus, "C++"},
      };
      int i;
      printf("flags:");
      for (i = 0; i < sizeof(flagSet) / sizeof(flagSet[0]); i++) {
        if (flagSet[i].mask & pDecl->flags) {
          printf(" %s", flagSet[i].desc);
        }
      }
      printf("\n");
    }
    if (pDecl->pInclude) {
      Include *p;
      printf("includes:");
      for (p = pDecl->pInclude; p; p = p->pNext) {
        printf(" %s", p->zFile);
      }
      printf("\n");
    }
  }
}
#endif

/*
** When the "-doc" command-line option is used, this routine is called
** to print all of the database information to standard output.
*/
static void DocumentationDump(void) {
  Decl *pDecl;
  static struct {
    int mask;
    char flag;
  } flagSet[] = {
      {TY_Class, 'c'},   {TY_Enumeration, 'e'}, {TY_Structure, 's'},
      {TY_Union, 'u'},   {TY_Variable, 'v'},    {TY_Subroutine, 'f'},
      {TY_Typedef, 't'}, {TY_Macro, 'm'},       {DP_Export, 'x'},
      {DP_Local, 'l'},   {DP_Cplusplus, '+'},
  };

  for (pDecl = pDeclFirst; pDecl; pDecl = pDecl->pNext) {
    int i;
    int nLabel = 0;
    char *zDecl;
    char zLabel[50];
    for (i = 0; i < sizeof(flagSet) / sizeof(flagSet[0]); i++) {
      if (DeclHasProperty(pDecl, flagSet[i].mask)) {
        zLabel[nLabel++] = flagSet[i].flag;
      }
    }
    if (nLabel == 0)
      continue;
    zLabel[nLabel] = 0;
    InsertExtraDecl(pDecl);
    zDecl = pDecl->zDecl;
    if (zDecl == 0)
      zDecl = pDecl->zFwd;
    printf("%s %s %s %p %d %d %d %d %d\n", pDecl->zName, zLabel, pDecl->zFile,
           pDecl->pComment, pDecl->pComment ? pDecl->pComment->nText + 1 : 0,
           pDecl->zIf ? (int)strlen(pDecl->zIf) + 1 : 0,
           zDecl ? (int)strlen(zDecl) : 0,
           pDecl->pComment ? pDecl->pComment->nLine : 0,
           pDecl->tokenCode.nText ? pDecl->tokenCode.nText + 1 : 0);
    if (pDecl->pComment) {
      printf("%.*s\n", pDecl->pComment->nText, pDecl->pComment->zText);
    }
    if (pDecl->zIf) {
      printf("%s\n", pDecl->zIf);
    }
    if (zDecl) {
      printf("%s", zDecl);
    }
    if (pDecl->tokenCode.nText) {
      printf("%.*s\n", pDecl->tokenCode.nText, pDecl->tokenCode.zText);
    }
  }
}

/*
** Given the complete text of an input file, this routine prints a
** documentation record for the header comment at the beginning of the
** file (if the file has a header comment.)
*/
void PrintModuleRecord(const char *zFile, const char *zFilename) {
  int i;
  static int addr = 5;
  while (isspace(*zFile)) {
    zFile++;
  }
  if (*zFile != '/' || zFile[1] != '*')
    return;
  for (i = 2; zFile[i] && (zFile[i - 1] != '/' || zFile[i - 2] != '*'); i++) {
  }
  if (zFile[i] == 0)
    return;
  printf("%s M %s %d %d 0 0 0 0\n%.*s\n", zFilename, zFilename, addr, i + 1, i,
         zFile);
  addr += 4;
}

/*
** Given an input argument to the program, construct a new InFile
** object.
*/
static InFile *CreateInFile(char *zArg, int *pnErr) {
  int nSrc;
  char *zSrc;
  InFile *pFile;
  int i;

  /*
  ** Get the name of the input file to be scanned.  The input file is
  ** everything before the first ':' or the whole file if no ':' is seen.
  **
  ** Except, on windows, ignore any ':' that occurs as the second character
  ** since it might be part of the drive specifier.  So really, the ":' has
  ** to be the 3rd or later character in the name.  This precludes 1-character
  ** file names, which really should not be a problem.
  */
  zSrc = zArg;
  for (nSrc = 2; zSrc[nSrc] && zArg[nSrc] != ':'; nSrc++) {
  }
  pFile = SafeMalloc(sizeof(InFile));
  memset(pFile, 0, sizeof(InFile));
  pFile->zSrc = StrDup(zSrc, nSrc);

  /* Figure out if we are dealing with C or C++ code.  Assume any
  ** file with ".c" or ".h" is C code and all else is C++.
  */
  if (nSrc > 2 && zSrc[nSrc - 2] == '.' &&
      (zSrc[nSrc - 1] == 'c' || zSrc[nSrc - 1] == 'h')) {
    pFile->flags &= ~DP_Cplusplus;
  } else {
    pFile->flags |= DP_Cplusplus;
  }

  /*
  ** If a separate header file is specified, use it
  */
  if (zSrc[nSrc] == ':') {
    int nHdr;
    char *zHdr;
    zHdr = &zSrc[nSrc + 1];
    for (nHdr = 0; zHdr[nHdr]; nHdr++) {
    }
    pFile->zHdr = StrDup(zHdr, nHdr);
  }

  /* Look for any 'c' or 'C' in the suffix of the file name and change
  ** that character to 'h' or 'H' respectively.  If no 'c' or 'C' is found,
  ** then assume we are dealing with a header.
  */
  else {
    int foundC = 0;
    pFile->zHdr = StrDup(zSrc, nSrc);
    for (i = nSrc - 1; i > 0 && pFile->zHdr[i] != '.'; i--) {
      if (pFile->zHdr[i] == 'c') {
        foundC = 1;
        pFile->zHdr[i] = 'h';
      } else if (pFile->zHdr[i] == 'C') {
        foundC = 1;
        pFile->zHdr[i] = 'H';
      }
    }
    if (!foundC) {
      SafeFree(pFile->zHdr);
      pFile->zHdr = 0;
    }
  }

  /*
  ** If pFile->zSrc contains no 'c' or 'C' in its extension, it
  ** must be a header file.   In that case, we need to set the
  ** PS_Interface flag.
  */
  pFile->flags |= PS_Interface;
  for (i = nSrc - 1; i > 0 && zSrc[i] != '.'; i--) {
    if (zSrc[i] == 'c' || zSrc[i] == 'C') {
      pFile->flags &= ~PS_Interface;
      break;
    }
  }

  /* Done!
   */
  return pFile;
}

/* MS-Windows and MS-DOS both have the following serious OS bug:  the
** length of a command line is severely restricted.  But this program
** occasionally requires long command lines.  Hence the following
** work around.
**
** If the parameters "-f FILENAME" appear anywhere on the command line,
** then the named file is scanned for additional command line arguments.
** These arguments are substituted in place of the "FILENAME" argument
** in the original argument list.
**
** This first parameter to this routine is the index of the "-f"
** parameter in the argv[] array.  The argc and argv are passed by
** pointer so that they can be changed.
**
** Parsing of the parameters in the file is very simple.  Parameters
** can be separated by any amount of white-space (including newlines
** and carriage returns.)  There are now quoting characters of any
** kind.  The length of a token is limited to about 1000 characters.
*/
static void AddParameters(int index, int *pArgc, char ***pArgv) {
  int argc = *pArgc;    /* The original argc value */
  char **argv = *pArgv; /* The original argv value */
  int newArgc;          /* Value for argc after inserting new arguments */
  char **zNew = 0;      /* The new argv after this routine is done */
  char *zFile;          /* Name of the input file */
  int nNew = 0;         /* Number of new entries in the argv[] file */
  int nAlloc = 0;       /* Space allocated for zNew[] */
  int i;                /* Loop counter */
  int n;                /* Number of characters in a new argument */
  int c;                /* Next character of input */
  int startOfLine = 1;  /* True if we are where '#' can start a comment */
  FILE *in;             /* The input file */
  char zBuf[1000];      /* A single argument is accumulated here */

  if (index + 1 == argc)
    return;
  zFile = argv[index + 1];
  in = fopen(zFile, "r");
  if (in == 0) {
    fprintf(stderr, "Can't open input file \"%s\"\n", zFile);
    exit(1);
  }
  c = ' ';
  while (c != EOF) {
    while (c != EOF && isspace(c)) {
      if (c == '\n') {
        startOfLine = 1;
      }
      c = getc(in);
      if (startOfLine && c == '#') {
        while (c != EOF && c != '\n') {
          c = getc(in);
        }
      }
    }
    n = 0;
    while (c != EOF && !isspace(c)) {
      if (n < sizeof(zBuf) - 1) {
        zBuf[n++] = c;
      }
      startOfLine = 0;
      c = getc(in);
    }
    zBuf[n] = 0;
    if (n > 0) {
      nNew++;
      if (nNew + argc > nAlloc) {
        if (nAlloc == 0) {
          nAlloc = 100 + argc;
          zNew = malloc(sizeof(char *) * nAlloc);
        } else {
          nAlloc *= 2;
          zNew = realloc(zNew, sizeof(char *) * nAlloc);
        }
      }
      if (zNew) {
        int j = nNew + index;
        zNew[j] = malloc(n + 1);
        if (zNew[j]) {
          strcpy(zNew[j], zBuf);
        }
      }
    }
  }
  fclose(in);
  newArgc = argc + nNew - 1;
  for (i = 0; i <= index; i++) {
    zNew[i] = argv[i];
  }
  for (i = nNew + index + 1; i < newArgc; i++) {
    zNew[i] = argv[i + 1 - nNew];
  }
  zNew[newArgc] = 0;
  *pArgc = newArgc;
  *pArgv = zNew;
}

#ifdef NOT_USED
/*
** Return the time that the given file was last modified.  If we can't
** locate the file (because, for example, it doesn't exist), then
** return 0.
*/
static unsigned int ModTime(const char *zFilename) {
  unsigned int mTime = 0;
  struct stat sStat;
  if (stat(zFilename, &sStat) == 0) {
    mTime = sStat.st_mtime;
  }
  return mTime;
}
#endif

/*
** Print a usage comment for this program.
*/
static void Usage(const char *argv0, const char *argvN) {
  fprintf(stderr, "%s: Illegal argument \"%s\"\n", argv0, argvN);
  fprintf(
      stderr,
      "Usage: %s [options] filename...\n"
      "Options:\n"
      "  -h          Generate a single .h to standard output.\n"
      "  -H          Like -h, but only output EXPORT declarations.\n"
      "  -v          (verbose) Write status information to the screen.\n"
      "  -doc        Generate no header files.  Instead, output information\n"
      "              that can be used by an automatic program documentation\n"
      "              and cross-reference generator.\n"
      "  -local      Generate prototypes for \"static\" functions and\n"
      "              procedures.\n"
      "  -f FILE     Read additional command-line arguments from the file "
      "named\n"
      "              \"FILE\".\n"
#ifdef DEBUG
      "  -! MASK     Set the debugging mask to the number \"MASK\".\n"
#endif
      "  --          Treat all subsequent comment-line parameters as "
      "filenames,\n"
      "              even if they begin with \"-\".\n",
      argv0);
}

/*
** The following text contains a few simple #defines that we want
** to be available to every file.
*/
static const char zInit[] = "#define INTERFACE 0\n"
                            "#define EXPORT_INTERFACE 0\n"
                            "#define LOCAL_INTERFACE 0\n"
                            "#define EXPORT\n"
                            "#define LOCAL static\n"
                            "#define PUBLIC\n"
                            "#define PRIVATE\n"
                            "#define PROTECTED\n";

#if TEST == 0
int main(int argc, char **argv) {
  int i;                 /* Loop counter */
  int nErr = 0;          /* Number of errors encountered */
  Token *pList;          /* List of input tokens for one file */
  InFile *pFileList = 0; /* List of all input files */
  InFile *pTail = 0;     /* Last file on the list */
  InFile *pFile;         /* for looping over the file list */
  int h_flag = 0;        /* True if -h is present.  Output unified header */
  int H_flag = 0;        /* True if -H is present.  Output EXPORT header */
  int v_flag = 0;        /* Verbose */
  int noMoreFlags;       /* True if -- has been seen. */
  FILE *report;          /* Send progress reports to this, if not NULL */

  noMoreFlags = 0;
  for (i = 1; i < argc; i++) {
    if (argv[i][0] == '-' && !noMoreFlags) {
      switch (argv[i][1]) {
      case 'h':
        h_flag = 1;
        break;
      case 'H':
        H_flag = 1;
        break;
      case 'v':
        v_flag = 1;
        break;
      case 'd':
        doc_flag = 1;
        proto_static = 1;
        break;
      case 'l':
        proto_static = 1;
        break;
      case 'f':
        AddParameters(i, &argc, &argv);
        break;
      case '-':
        noMoreFlags = 1;
        break;
#ifdef DEBUG
      case '!':
        i++;
        debugMask = strtol(argv[i], 0, 0);
        break;
#endif
      default:
        Usage(argv[0], argv[i]);
        return 1;
      }
    } else {
      pFile = CreateInFile(argv[i], &nErr);
      if (pFile) {
        if (pFileList) {
          pTail->pNext = pFile;
          pTail = pFile;
        } else {
          pFileList = pTail = pFile;
        }
      }
    }
  }
  if (h_flag && H_flag) {
    h_flag = 0;
  }
  if (v_flag) {
    report = (h_flag || H_flag) ? stderr : stdout;
  } else {
    report = 0;
  }
  if (nErr > 0) {
    return nErr;
  }
  for (pFile = pFileList; pFile; pFile = pFile->pNext) {
    char *zFile;

    zFilename = pFile->zSrc;
    if (zFilename == 0)
      continue;
    zFile = ReadFile(zFilename);
    if (zFile == 0) {
      fprintf(stderr, "Can't read input file \"%s\"\n", zFilename);
      nErr++;
      continue;
    }
    if (strncmp(zFile, zTopLine, nTopLine) == 0) {
      pFile->zSrc = 0;
    } else {
      if (report)
        fprintf(report, "Reading %s...\n", zFilename);
      pList = TokenizeFile(zFile, &pFile->idTable);
      if (pList) {
        nErr += ParseFile(pList, pFile->flags);
        FreeTokenList(pList);
      } else if (zFile[0] == 0) {
        fprintf(stderr, "Input file \"%s\" is empty.\n", zFilename);
        nErr++;
      } else {
        fprintf(stderr, "Errors while processing \"%s\"\n", zFilename);
        nErr++;
      }
    }
    if (!doc_flag)
      SafeFree(zFile);
    if (doc_flag)
      PrintModuleRecord(zFile, zFilename);
  }
  if (nErr > 0) {
    return nErr;
  }
#ifdef DEBUG
  if (debugMask & DECL_DUMP) {
    DumpDeclList();
    return nErr;
  }
#endif
  if (doc_flag) {
    DocumentationDump();
    return nErr;
  }
  zFilename = "--internal--";
  pList = TokenizeFile(zInit, 0);
  if (pList == 0) {
    return nErr + 1;
  }
  ParseFile(pList, PS_Interface);
  FreeTokenList(pList);
  if (h_flag || H_flag) {
    nErr += MakeGlobalHeader(H_flag);
  } else {
    for (pFile = pFileList; pFile; pFile = pFile->pNext) {
      if (pFile->zSrc == 0)
        continue;
      nErr += MakeHeader(pFile, report, 0);
    }
  }
  return nErr;
}
#endif
