3423 lines
98 KiB
C
3423 lines
98 KiB
C
/*
|
|
** 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <memory.h>
|
|
#include <sys/stat.h>
|
|
#include <assert.h>
|
|
#include <string.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.
|
|
*/
|
|
static 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
|