Plan 9 from Bell Labs’s /usr/web/sources/contrib/anothy/src/ctags/c.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/*
*   $Id: c.c 614 2007-08-19 22:46:13Z elliotth $
*
*   Copyright (c) 1996-2003, Darren Hiebert
*
*   This source code is released for free distribution under the terms of the
*   GNU General Public License.
*
*   This module contains functions for parsing and scanning C, C++ and Java
*   source files.
*/

/*
*   INCLUDE FILES
*/
#include "general.h"        /* must always come first */

#include <string.h>
#include <setjmp.h>

#include "debug.h"
#include "entry.h"
#include "get.h"
#include "keyword.h"
#include "options.h"
#include "parse.h"
#include "read.h"
#include "routines.h"

/*
*   MACROS
*/

#define activeToken(st)     ((st)->token [(int) (st)->tokenIndex])
#define parentDecl(st)      ((st)->parent == NULL ? \
                            DECL_NONE : (st)->parent->declaration)
#define isType(token,t)     (boolean) ((token)->type == (t))
#define insideEnumBody(st)  ((st)->parent == NULL ? FALSE : \
                            (boolean) ((st)->parent->declaration == DECL_ENUM))
#define isExternCDecl(st,c) (boolean) ((c) == STRING_SYMBOL  && \
                    ! (st)->haveQualifyingName  && (st)->scope == SCOPE_EXTERN)

#define isOneOf(c,s)        (boolean) (strchr ((s), (c)) != NULL)

#define isHighChar(c)       ((c) != EOF && (unsigned char)(c) >= 0xc0)

/*
*   DATA DECLARATIONS
*/

enum { NumTokens = 3 };

typedef enum eException {
	ExceptionNone, ExceptionEOF, ExceptionFormattingError,
	ExceptionBraceFormattingError
} exception_t;

/*  Used to specify type of keyword.
 */
typedef enum eKeywordId {
	KEYWORD_NONE = -1,
	KEYWORD_ATTRIBUTE, KEYWORD_ABSTRACT,
	KEYWORD_BOOLEAN, KEYWORD_BYTE, KEYWORD_BAD_STATE, KEYWORD_BAD_TRANS,
	KEYWORD_BIND, KEYWORD_BIND_VAR, KEYWORD_BIT,
	KEYWORD_CASE, KEYWORD_CATCH, KEYWORD_CHAR, KEYWORD_CLASS, KEYWORD_CONST,
	KEYWORD_CONSTRAINT, KEYWORD_COVERAGE_BLOCK, KEYWORD_COVERAGE_DEF,
	KEYWORD_DEFAULT, KEYWORD_DELEGATE, KEYWORD_DELETE, KEYWORD_DO,
	KEYWORD_DOUBLE,
	KEYWORD_ELSE, KEYWORD_ENUM, KEYWORD_EXPLICIT, KEYWORD_EXTERN,
	KEYWORD_EXTENDS, KEYWORD_EVENT,
	KEYWORD_FINAL, KEYWORD_FLOAT, KEYWORD_FOR, KEYWORD_FRIEND, KEYWORD_FUNCTION,
	KEYWORD_GOTO,
	KEYWORD_IF, KEYWORD_IMPLEMENTS, KEYWORD_IMPORT, KEYWORD_INLINE, KEYWORD_INT,
	KEYWORD_INOUT, KEYWORD_INPUT, KEYWORD_INTEGER, KEYWORD_INTERFACE,
	KEYWORD_INTERNAL,
	KEYWORD_LOCAL, KEYWORD_LONG,
	KEYWORD_M_BAD_STATE, KEYWORD_M_BAD_TRANS, KEYWORD_M_STATE, KEYWORD_M_TRANS,
	KEYWORD_MUTABLE,
	KEYWORD_NAMESPACE, KEYWORD_NEW, KEYWORD_NEWCOV, KEYWORD_NATIVE,
	KEYWORD_OPERATOR, KEYWORD_OUTPUT, KEYWORD_OVERLOAD, KEYWORD_OVERRIDE,
	KEYWORD_PACKED, KEYWORD_PORT, KEYWORD_PACKAGE, KEYWORD_PRIVATE,
	KEYWORD_PROGRAM, KEYWORD_PROTECTED, KEYWORD_PUBLIC,
	KEYWORD_REGISTER, KEYWORD_RETURN,
	KEYWORD_SHADOW, KEYWORD_STATE,
	KEYWORD_SHORT, KEYWORD_SIGNED, KEYWORD_STATIC, KEYWORD_STRING,
	KEYWORD_STRUCT, KEYWORD_SWITCH, KEYWORD_SYNCHRONIZED,
	KEYWORD_TASK, KEYWORD_TEMPLATE, KEYWORD_THIS, KEYWORD_THROW,
	KEYWORD_THROWS, KEYWORD_TRANSIENT, KEYWORD_TRANS, KEYWORD_TRANSITION,
	KEYWORD_TRY, KEYWORD_TYPEDEF, KEYWORD_TYPENAME,
	KEYWORD_UINT, KEYWORD_ULONG, KEYWORD_UNION, KEYWORD_UNSIGNED, KEYWORD_USHORT,
	KEYWORD_USING,
	KEYWORD_VIRTUAL, KEYWORD_VOID, KEYWORD_VOLATILE,
	KEYWORD_WCHAR_T, KEYWORD_WHILE
} keywordId;

/*  Used to determine whether keyword is valid for the current language and
 *  what its ID is.
 */
typedef struct sKeywordDesc {
	const char *name;
	keywordId id;
	short isValid [5]; /* indicates languages for which kw is valid */
} keywordDesc;

/*  Used for reporting the type of object parsed by nextToken ().
 */
typedef enum eTokenType {
	TOKEN_NONE,          /* none */
	TOKEN_ARGS,          /* a parenthetical pair and its contents */
	TOKEN_BRACE_CLOSE,
	TOKEN_BRACE_OPEN,
	TOKEN_COLON,         /* the colon character */
	TOKEN_COMMA,         /* the comma character */
	TOKEN_DOUBLE_COLON,  /* double colon indicates nested-name-specifier */
	TOKEN_KEYWORD,
	TOKEN_NAME,          /* an unknown name */
	TOKEN_PACKAGE,       /* a Java package name */
	TOKEN_PAREN_NAME,    /* a single name in parentheses */
	TOKEN_SEMICOLON,     /* the semicolon character */
	TOKEN_SPEC,          /* a storage class specifier, qualifier, type, etc. */
	TOKEN_COUNT
} tokenType;

/*  This describes the scoping of the current statement.
 */
typedef enum eTagScope {
	SCOPE_GLOBAL,        /* no storage class specified */
	SCOPE_STATIC,        /* static storage class */
	SCOPE_EXTERN,        /* external storage class */
	SCOPE_FRIEND,        /* declares access only */
	SCOPE_TYPEDEF,       /* scoping depends upon context */
	SCOPE_COUNT
} tagScope;

typedef enum eDeclaration {
	DECL_NONE,
	DECL_BASE,           /* base type (default) */
	DECL_CLASS,
	DECL_ENUM,
	DECL_EVENT,
	DECL_FUNCTION,
	DECL_IGNORE,         /* non-taggable "declaration" */
	DECL_INTERFACE,
	DECL_NAMESPACE,
	DECL_NOMANGLE,       /* C++ name demangling block */
	DECL_PACKAGE,
	DECL_PROGRAM,        /* Vera program */
	DECL_STRUCT,
	DECL_TASK,           /* Vera task */
	DECL_UNION,
	DECL_COUNT
} declType;

typedef enum eVisibilityType {
	ACCESS_UNDEFINED,
	ACCESS_LOCAL,
	ACCESS_PRIVATE,
	ACCESS_PROTECTED,
	ACCESS_PUBLIC,
	ACCESS_DEFAULT,      /* Java-specific */
	ACCESS_COUNT
} accessType;

/*  Information about the parent class of a member (if any).
 */
typedef struct sMemberInfo {
	accessType access;           /* access of current statement */
	accessType accessDefault;    /* access default for current statement */
} memberInfo;

typedef struct sTokenInfo {
	tokenType     type;
	keywordId     keyword;
	vString*      name;          /* the name of the token */
	unsigned long lineNumber;    /* line number of tag */
	fpos_t        filePosition;  /* file position of line containing name */
} tokenInfo;

typedef enum eImplementation {
	IMP_DEFAULT,
	IMP_ABSTRACT,
	IMP_VIRTUAL,
	IMP_PURE_VIRTUAL,
	IMP_COUNT
} impType;

/*  Describes the statement currently undergoing analysis.
 */
typedef struct sStatementInfo {
	tagScope	scope;
	declType	declaration;    /* specifier associated with TOKEN_SPEC */
	boolean		gotName;        /* was a name parsed yet? */
	boolean		haveQualifyingName;  /* do we have a name we are considering? */
	boolean		gotParenName;   /* was a name inside parentheses parsed yet? */
	boolean		gotArgs;        /* was a list of parameters parsed yet? */
	boolean		isPointer;      /* is 'name' a pointer? */
	boolean     inFunction;     /* are we inside of a function? */
	boolean		assignment;     /* have we handled an '='? */
	boolean		notVariable;    /* has a variable declaration been disqualified ? */
	impType		implementation; /* abstract or concrete implementation? */
	unsigned int tokenIndex;    /* currently active token */
	tokenInfo*	token [(int) NumTokens];
	tokenInfo*	context;        /* accumulated scope of current statement */
	tokenInfo*	blockName;      /* name of current block */
	memberInfo	member;         /* information regarding parent class/struct */
	vString*	parentClasses;  /* parent classes */
	struct sStatementInfo *parent;  /* statement we are nested within */
} statementInfo;

/*  Describes the type of tag being generated.
 */
typedef enum eTagType {
	TAG_UNDEFINED,
	TAG_CLASS,       /* class name */
	TAG_ENUM,        /* enumeration name */
	TAG_ENUMERATOR,  /* enumerator (enumeration value) */
	TAG_EVENT,       /* event */
	TAG_FIELD,       /* field (Java) */
	TAG_FUNCTION,    /* function definition */
	TAG_INTERFACE,   /* interface declaration */
	TAG_LOCAL,       /* local variable definition */
	TAG_MEMBER,      /* structure, class or interface member */
	TAG_METHOD,      /* method declaration */
	TAG_NAMESPACE,   /* namespace name */
	TAG_PACKAGE,     /* package name */
	TAG_PROGRAM,     /* program name */
	TAG_PROPERTY,    /* property name */
	TAG_PROTOTYPE,   /* function prototype or declaration */
	TAG_STRUCT,      /* structure name */
	TAG_TASK,        /* task name */
	TAG_TYPEDEF,     /* typedef name */
	TAG_UNION,       /* union name */
	TAG_VARIABLE,    /* variable definition */
	TAG_EXTERN_VAR,  /* external variable declaration */
	TAG_COUNT        /* must be last */
} tagType;

typedef struct sParenInfo {
	boolean isPointer;
	boolean isParamList;
	boolean isKnrParamList;
	boolean isNameCandidate;
	boolean invalidContents;
	boolean nestedArgs;
	unsigned int parameterCount;
} parenInfo;

/*
*   DATA DEFINITIONS
*/

static jmp_buf Exception;

static langType Lang_c;
static langType Lang_cpp;
static langType Lang_csharp;
static langType Lang_java;
static langType Lang_vera;
static vString *Signature;
static boolean CollectingSignature;

/* Number used to uniquely identify anonymous structs and unions. */
static int AnonymousID = 0;

/* Used to index into the CKinds table. */
typedef enum {
	CK_UNDEFINED = -1,
	CK_CLASS, CK_DEFINE, CK_ENUMERATOR, CK_FUNCTION,
	CK_ENUMERATION, CK_LOCAL, CK_MEMBER, CK_NAMESPACE, CK_PROTOTYPE,
	CK_STRUCT, CK_TYPEDEF, CK_UNION, CK_VARIABLE,
	CK_EXTERN_VARIABLE
} cKind;

static kindOption CKinds [] = {
	{ TRUE,  'c', "class",      "classes"},
	{ TRUE,  'd', "macro",      "macro definitions"},
	{ TRUE,  'e', "enumerator", "enumerators (values inside an enumeration)"},
	{ TRUE,  'f', "function",   "function definitions"},
	{ TRUE,  'g', "enum",       "enumeration names"},
	{ FALSE, 'l', "local",      "local variables"},
	{ TRUE,  'm', "member",     "class, struct, and union members"},
	{ TRUE,  'n', "namespace",  "namespaces"},
	{ FALSE, 'p', "prototype",  "function prototypes"},
	{ TRUE,  's', "struct",     "structure names"},
	{ TRUE,  't', "typedef",    "typedefs"},
	{ TRUE,  'u', "union",      "union names"},
	{ TRUE,  'v', "variable",   "variable definitions"},
	{ FALSE, 'x', "externvar",  "external and forward variable declarations"},
};

typedef enum {
	CSK_UNDEFINED = -1,
	CSK_CLASS, CSK_DEFINE, CSK_ENUMERATOR, CSK_EVENT, CSK_FIELD,
	CSK_ENUMERATION, CSK_INTERFACE, CSK_LOCAL, CSK_METHOD,
	CSK_NAMESPACE, CSK_PROPERTY, CSK_STRUCT, CSK_TYPEDEF
} csharpKind;

static kindOption CsharpKinds [] = {
	{ TRUE,  'c', "class",      "classes"},
	{ TRUE,  'd', "macro",      "macro definitions"},
	{ TRUE,  'e', "enumerator", "enumerators (values inside an enumeration)"},
	{ TRUE,  'E', "event",      "events"},
	{ TRUE,  'f', "field",      "fields"},
	{ TRUE,  'g', "enum",       "enumeration names"},
	{ TRUE,  'i', "interface",  "interfaces"},
	{ FALSE, 'l', "local",      "local variables"},
	{ TRUE,  'm', "method",     "methods"},
	{ TRUE,  'n', "namespace",  "namespaces"},
	{ TRUE,  'p', "property",   "properties"},
	{ TRUE,  's', "struct",     "structure names"},
	{ TRUE,  't', "typedef",    "typedefs"},
};

/* Used to index into the JavaKinds table. */
typedef enum {
	JK_UNDEFINED = -1,
	JK_CLASS, JK_ENUM_CONSTANT, JK_FIELD, JK_ENUM, JK_INTERFACE,
	JK_LOCAL, JK_METHOD, JK_PACKAGE, JK_ACCESS, JK_CLASS_PREFIX
} javaKind;

static kindOption JavaKinds [] = {
	{ TRUE,  'c', "class",         "classes"},
	{ TRUE,  'e', "enum constant", "enum constants"},
	{ TRUE,  'f', "field",         "fields"},
	{ TRUE,  'g', "enum",          "enum types"},
	{ TRUE,  'i', "interface",     "interfaces"},
	{ FALSE, 'l', "local",         "local variables"},
	{ TRUE,  'm', "method",        "methods"},
	{ TRUE,  'p', "package",       "packages"},
};

/* Used to index into the VeraKinds table. */
typedef enum {
	VK_UNDEFINED = -1,
	VK_CLASS, VK_DEFINE, VK_ENUMERATOR, VK_FUNCTION,
	VK_ENUMERATION, VK_LOCAL, VK_MEMBER, VK_PROGRAM, VK_PROTOTYPE,
	VK_TASK, VK_TYPEDEF, VK_VARIABLE,
	VK_EXTERN_VARIABLE
} veraKind;

static kindOption VeraKinds [] = {
	{ TRUE,  'c', "class",      "classes"},
	{ TRUE,  'd', "macro",      "macro definitions"},
	{ TRUE,  'e', "enumerator", "enumerators (values inside an enumeration)"},
	{ TRUE,  'f', "function",   "function definitions"},
	{ TRUE,  'g', "enum",       "enumeration names"},
	{ FALSE, 'l', "local",      "local variables"},
	{ TRUE,  'm', "member",     "class, struct, and union members"},
	{ TRUE,  'p', "program",    "programs"},
	{ FALSE, 'P', "prototype",  "function prototypes"},
	{ TRUE,  't', "task",       "tasks"},
	{ TRUE,  'T', "typedef",    "typedefs"},
	{ TRUE,  'v', "variable",   "variable definitions"},
	{ FALSE, 'x', "externvar",  "external variable declarations"}
};

static const keywordDesc KeywordTable [] = {
	/*                                              C++            */
	/*                                       ANSI C  |  C# Java    */
	/*                                            |  |  |  |  Vera */
	/* keyword          keyword ID                |  |  |  |  |    */
	{ "__attribute__",  KEYWORD_ATTRIBUTE,      { 1, 1, 1, 0, 0 } },
	{ "abstract",       KEYWORD_ABSTRACT,       { 0, 0, 1, 1, 0 } },
	{ "bad_state",      KEYWORD_BAD_STATE,      { 0, 0, 0, 0, 1 } },
	{ "bad_trans",      KEYWORD_BAD_TRANS,      { 0, 0, 0, 0, 1 } },
	{ "bind",           KEYWORD_BIND,           { 0, 0, 0, 0, 1 } },
	{ "bind_var",       KEYWORD_BIND_VAR,       { 0, 0, 0, 0, 1 } },
	{ "bit",            KEYWORD_BIT,            { 0, 0, 0, 0, 1 } },
	{ "boolean",        KEYWORD_BOOLEAN,        { 0, 0, 0, 1, 0 } },
	{ "byte",           KEYWORD_BYTE,           { 0, 0, 0, 1, 0 } },
	{ "case",           KEYWORD_CASE,           { 1, 1, 1, 1, 0 } },
	{ "catch",          KEYWORD_CATCH,          { 0, 1, 1, 0, 0 } },
	{ "char",           KEYWORD_CHAR,           { 1, 1, 1, 1, 0 } },
	{ "class",          KEYWORD_CLASS,          { 0, 1, 1, 1, 1 } },
	{ "const",          KEYWORD_CONST,          { 1, 1, 1, 1, 0 } },
	{ "constraint",     KEYWORD_CONSTRAINT,     { 0, 0, 0, 0, 1 } },
	{ "coverage_block", KEYWORD_COVERAGE_BLOCK, { 0, 0, 0, 0, 1 } },
	{ "coverage_def",   KEYWORD_COVERAGE_DEF,   { 0, 0, 0, 0, 1 } },
	{ "do",             KEYWORD_DO,             { 1, 1, 1, 1, 0 } },
	{ "default",        KEYWORD_DEFAULT,        { 1, 1, 1, 1, 0 } },
	{ "delegate",       KEYWORD_DELEGATE,       { 0, 0, 1, 0, 0 } },
	{ "delete",         KEYWORD_DELETE,         { 0, 1, 0, 0, 0 } },
	{ "double",         KEYWORD_DOUBLE,         { 1, 1, 1, 1, 0 } },
	{ "else",           KEYWORD_ELSE,           { 1, 1, 0, 1, 0 } },
	{ "enum",           KEYWORD_ENUM,           { 1, 1, 1, 1, 1 } },
	{ "event",          KEYWORD_EVENT,          { 0, 0, 1, 0, 1 } },
	{ "explicit",       KEYWORD_EXPLICIT,       { 0, 1, 1, 0, 0 } },
	{ "extends",        KEYWORD_EXTENDS,        { 0, 0, 0, 1, 1 } },
	{ "extern",         KEYWORD_EXTERN,         { 1, 1, 1, 0, 1 } },
	{ "final",          KEYWORD_FINAL,          { 0, 0, 0, 1, 0 } },
	{ "float",          KEYWORD_FLOAT,          { 1, 1, 1, 1, 0 } },
	{ "for",            KEYWORD_FOR,            { 1, 1, 1, 1, 0 } },
	{ "friend",         KEYWORD_FRIEND,         { 0, 1, 0, 0, 0 } },
	{ "function",       KEYWORD_FUNCTION,       { 0, 0, 0, 0, 1 } },
	{ "goto",           KEYWORD_GOTO,           { 1, 1, 1, 1, 0 } },
	{ "if",             KEYWORD_IF,             { 1, 1, 1, 1, 0 } },
	{ "implements",     KEYWORD_IMPLEMENTS,     { 0, 0, 0, 1, 0 } },
	{ "import",         KEYWORD_IMPORT,         { 0, 0, 0, 1, 0 } },
	{ "inline",         KEYWORD_INLINE,         { 0, 1, 0, 0, 0 } },
	{ "inout",          KEYWORD_INOUT,          { 0, 0, 0, 0, 1 } },
	{ "input",          KEYWORD_INPUT,          { 0, 0, 0, 0, 1 } },
	{ "int",            KEYWORD_INT,            { 1, 1, 1, 1, 0 } },
	{ "integer",        KEYWORD_INTEGER,        { 0, 0, 0, 0, 1 } },
	{ "interface",      KEYWORD_INTERFACE,      { 0, 0, 1, 1, 1 } },
	{ "internal",       KEYWORD_INTERNAL,       { 0, 0, 1, 0, 0 } },
	{ "local",          KEYWORD_LOCAL,          { 0, 0, 0, 0, 1 } },
	{ "long",           KEYWORD_LONG,           { 1, 1, 1, 1, 0 } },
	{ "m_bad_state",    KEYWORD_M_BAD_STATE,    { 0, 0, 0, 0, 1 } },
	{ "m_bad_trans",    KEYWORD_M_BAD_TRANS,    { 0, 0, 0, 0, 1 } },
	{ "m_state",        KEYWORD_M_STATE,        { 0, 0, 0, 0, 1 } },
	{ "m_trans",        KEYWORD_M_TRANS,        { 0, 0, 0, 0, 1 } },
	{ "mutable",        KEYWORD_MUTABLE,        { 0, 1, 0, 0, 0 } },
	{ "namespace",      KEYWORD_NAMESPACE,      { 0, 1, 1, 0, 0 } },
	{ "native",         KEYWORD_NATIVE,         { 0, 0, 0, 1, 0 } },
	{ "new",            KEYWORD_NEW,            { 0, 1, 1, 1, 0 } },
	{ "newcov",         KEYWORD_NEWCOV,         { 0, 0, 0, 0, 1 } },
	{ "operator",       KEYWORD_OPERATOR,       { 0, 1, 1, 0, 0 } },
	{ "output",         KEYWORD_OUTPUT,         { 0, 0, 0, 0, 1 } },
	{ "overload",       KEYWORD_OVERLOAD,       { 0, 1, 0, 0, 0 } },
	{ "override",       KEYWORD_OVERRIDE,       { 0, 0, 1, 0, 0 } },
	{ "package",        KEYWORD_PACKAGE,        { 0, 0, 0, 1, 0 } },
	{ "packed",         KEYWORD_PACKED,         { 0, 0, 0, 0, 1 } },
	{ "port",           KEYWORD_PORT,           { 0, 0, 0, 0, 1 } },
	{ "private",        KEYWORD_PRIVATE,        { 0, 1, 1, 1, 0 } },
	{ "program",        KEYWORD_PROGRAM,        { 0, 0, 0, 0, 1 } },
	{ "protected",      KEYWORD_PROTECTED,      { 0, 1, 1, 1, 1 } },
	{ "public",         KEYWORD_PUBLIC,         { 0, 1, 1, 1, 1 } },
	{ "register",       KEYWORD_REGISTER,       { 1, 1, 0, 0, 0 } },
	{ "return",         KEYWORD_RETURN,         { 1, 1, 1, 1, 0 } },
	{ "shadow",         KEYWORD_SHADOW,         { 0, 0, 0, 0, 1 } },
	{ "short",          KEYWORD_SHORT,          { 1, 1, 1, 1, 0 } },
	{ "signed",         KEYWORD_SIGNED,         { 1, 1, 0, 0, 0 } },
	{ "state",          KEYWORD_STATE,          { 0, 0, 0, 0, 1 } },
	{ "static",         KEYWORD_STATIC,         { 1, 1, 1, 1, 1 } },
	{ "string",         KEYWORD_STRING,         { 0, 0, 1, 0, 1 } },
	{ "struct",         KEYWORD_STRUCT,         { 1, 1, 1, 0, 0 } },
	{ "switch",         KEYWORD_SWITCH,         { 1, 1, 1, 1, 0 } },
	{ "synchronized",   KEYWORD_SYNCHRONIZED,   { 0, 0, 0, 1, 0 } },
	{ "task",           KEYWORD_TASK,           { 0, 0, 0, 0, 1 } },
	{ "template",       KEYWORD_TEMPLATE,       { 0, 1, 0, 0, 0 } },
	{ "this",           KEYWORD_THIS,           { 0, 1, 1, 1, 0 } },
	{ "throw",          KEYWORD_THROW,          { 0, 1, 1, 1, 0 } },
	{ "throws",         KEYWORD_THROWS,         { 0, 0, 0, 1, 0 } },
	{ "trans",          KEYWORD_TRANS,          { 0, 0, 0, 0, 1 } },
	{ "transition",     KEYWORD_TRANSITION,     { 0, 0, 0, 0, 1 } },
	{ "transient",      KEYWORD_TRANSIENT,      { 0, 0, 0, 1, 0 } },
	{ "try",            KEYWORD_TRY,            { 0, 1, 1, 0, 0 } },
	{ "typedef",        KEYWORD_TYPEDEF,        { 1, 1, 1, 0, 1 } },
	{ "typename",       KEYWORD_TYPENAME,       { 0, 1, 0, 0, 0 } },
	{ "uint",           KEYWORD_UINT,           { 0, 0, 1, 0, 0 } },
	{ "ulong",          KEYWORD_ULONG,          { 0, 0, 1, 0, 0 } },
	{ "union",          KEYWORD_UNION,          { 1, 1, 0, 0, 0 } },
	{ "unsigned",       KEYWORD_UNSIGNED,       { 1, 1, 1, 0, 0 } },
	{ "ushort",         KEYWORD_USHORT,         { 0, 0, 1, 0, 0 } },
	{ "using",          KEYWORD_USING,          { 0, 1, 1, 0, 0 } },
	{ "virtual",        KEYWORD_VIRTUAL,        { 0, 1, 1, 0, 1 } },
	{ "void",           KEYWORD_VOID,           { 1, 1, 1, 1, 1 } },
	{ "volatile",       KEYWORD_VOLATILE,       { 1, 1, 1, 1, 0 } },
	{ "wchar_t",        KEYWORD_WCHAR_T,        { 1, 1, 1, 0, 0 } },
	{ "while",          KEYWORD_WHILE,          { 1, 1, 1, 1, 0 } }
};

/*
*   FUNCTION PROTOTYPES
*/
static void createTags (const unsigned int nestLevel, statementInfo *const parent);

/*
*   FUNCTION DEFINITIONS
*/

extern boolean includingDefineTags (void)
{
	return CKinds [CK_DEFINE].enabled;
}

/*
*   Token management
*/

static void initToken (tokenInfo* const token)
{
	token->type			= TOKEN_NONE;
	token->keyword		= KEYWORD_NONE;
	token->lineNumber	= getSourceLineNumber ();
	token->filePosition	= getInputFilePosition ();
	vStringClear (token->name);
}

static void advanceToken (statementInfo* const st)
{
	if (st->tokenIndex >= (unsigned int) NumTokens - 1)
		st->tokenIndex = 0;
	else
		++st->tokenIndex;
	initToken (st->token [st->tokenIndex]);
}

static tokenInfo *prevToken (const statementInfo *const st, unsigned int n)
{
	unsigned int tokenIndex;
	unsigned int num = (unsigned int) NumTokens;
	Assert (n < num);
	tokenIndex = (st->tokenIndex + num - n) % num;
	return st->token [tokenIndex];
}

static void setToken (statementInfo *const st, const tokenType type)
{
	tokenInfo *token;
	token = activeToken (st);
	initToken (token);
	token->type = type;
}

static void retardToken (statementInfo *const st)
{
	if (st->tokenIndex == 0)
		st->tokenIndex = (unsigned int) NumTokens - 1;
	else
		--st->tokenIndex;
	setToken (st, TOKEN_NONE);
}

static tokenInfo *newToken (void)
{
	tokenInfo *const token = xMalloc (1, tokenInfo);
	token->name = vStringNew ();
	initToken (token);
	return token;
}

static void deleteToken (tokenInfo *const token)
{
	if (token != NULL)
	{
		vStringDelete (token->name);
		eFree (token);
	}
}

static const char *accessString (const accessType access)
{
	static const char *const names [] = {
		"?", "local", "private", "protected", "public", "default"
	};
	Assert (sizeof (names) / sizeof (names [0]) == ACCESS_COUNT);
	Assert ((int) access < ACCESS_COUNT);
	return names [(int) access];
}

static const char *implementationString (const impType imp)
{
	static const char *const names [] ={
		"?", "abstract", "virtual", "pure virtual"
	};
	Assert (sizeof (names) / sizeof (names [0]) == IMP_COUNT);
	Assert ((int) imp < IMP_COUNT);
	return names [(int) imp];
}

/*
*   Debugging functions
*/

#ifdef DEBUG

#define boolString(c)   ((c) ? "TRUE" : "FALSE")

static const char *tokenString (const tokenType type)
{
	static const char *const names [] = {
		"none", "args", "}", "{", "colon", "comma", "double colon", "keyword",
		"name", "package", "paren-name", "semicolon", "specifier"
	};
	Assert (sizeof (names) / sizeof (names [0]) == TOKEN_COUNT);
	Assert ((int) type < TOKEN_COUNT);
	return names [(int) type];
}

static const char *scopeString (const tagScope scope)
{
	static const char *const names [] = {
		"global", "static", "extern", "friend", "typedef"
	};
	Assert (sizeof (names) / sizeof (names [0]) == SCOPE_COUNT);
	Assert ((int) scope < SCOPE_COUNT);
	return names [(int) scope];
}

static const char *declString (const declType declaration)
{
	static const char *const names [] = {
		"?", "base", "class", "enum", "event", "function", "ignore",
		"interface", "namespace", "no mangle", "package", "program",
		"struct", "task", "union",
	};
	Assert (sizeof (names) / sizeof (names [0]) == DECL_COUNT);
	Assert ((int) declaration < DECL_COUNT);
	return names [(int) declaration];
}

static const char *keywordString (const keywordId keyword)
{
	const size_t count = sizeof (KeywordTable) / sizeof (KeywordTable [0]);
	const char *name = "none";
	size_t i;
	for (i = 0  ;  i < count  ;  ++i)
	{
		const keywordDesc *p = &KeywordTable [i];
		if (p->id == keyword)
		{
			name = p->name;
			break;
		}
	}
	return name;
}

static void __unused__ pt (tokenInfo *const token)
{
	if (isType (token, TOKEN_NAME))
		printf ("type: %-12s: %-13s   line: %lu\n",
			tokenString (token->type), vStringValue (token->name),
			token->lineNumber);
	else if (isType (token, TOKEN_KEYWORD))
		printf ("type: %-12s: %-13s   line: %lu\n",
			tokenString (token->type), keywordString (token->keyword),
			token->lineNumber);
	else
		printf ("type: %-12s                  line: %lu\n",
			tokenString (token->type), token->lineNumber);
}

static void __unused__ ps (statementInfo *const st)
{
	unsigned int i;
	printf ("scope: %s   decl: %s   gotName: %s   gotParenName: %s\n",
		scopeString (st->scope), declString (st->declaration),
		boolString (st->gotName), boolString (st->gotParenName));
	printf ("haveQualifyingName: %s\n", boolString (st->haveQualifyingName));
	printf ("access: %s   default: %s\n", accessString (st->member.access),
		accessString (st->member.accessDefault));
	printf ("token  : ");
	pt (activeToken (st));
	for (i = 1  ;  i < (unsigned int) NumTokens  ;  ++i)
	{
		printf ("prev %u : ", i);
		pt (prevToken (st, i));
	}
	printf ("context: ");
	pt (st->context);
}

#endif

/*
*   Statement management
*/

static boolean isContextualKeyword (const tokenInfo *const token)
{
	boolean result;
	switch (token->keyword)
	{
		case KEYWORD_CLASS:
		case KEYWORD_ENUM:
		case KEYWORD_INTERFACE:
		case KEYWORD_NAMESPACE:
		case KEYWORD_STRUCT:
		case KEYWORD_UNION:
			result = TRUE;
			break;

		default: result = FALSE; break;
	}
	return result;
}

static boolean isContextualStatement (const statementInfo *const st)
{
	boolean result = FALSE;
	if (st != NULL) switch (st->declaration)
	{
		case DECL_CLASS:
		case DECL_ENUM:
		case DECL_INTERFACE:
		case DECL_STRUCT:
		case DECL_UNION:
			result = TRUE;
			break;

		default: result = FALSE; break;
	}
	return result;
}

static boolean isMember (const statementInfo *const st)
{
	boolean result;
	if (isType (st->context, TOKEN_NAME))
		result = TRUE;
	else
		result = (boolean)
			(st->parent != NULL && isContextualStatement (st->parent));
	return result;
}

static void initMemberInfo (statementInfo *const st)
{
	accessType accessDefault = ACCESS_UNDEFINED;

	if (st->parent != NULL) switch (st->parent->declaration)
	{
		case DECL_ENUM:
			accessDefault = (isLanguage (Lang_java) ? ACCESS_PUBLIC : ACCESS_UNDEFINED);
			break;
		case DECL_NAMESPACE:
			accessDefault = ACCESS_UNDEFINED;
			break;

		case DECL_CLASS:
			if (isLanguage (Lang_java))
				accessDefault = ACCESS_DEFAULT;
			else
				accessDefault = ACCESS_PRIVATE;
			break;

		case DECL_INTERFACE:
		case DECL_STRUCT:
		case DECL_UNION:
			accessDefault = ACCESS_PUBLIC;
			break;

		default: break;
	}
	st->member.accessDefault = accessDefault;
	st->member.access		 = accessDefault;
}

static void reinitStatement (statementInfo *const st, const boolean partial)
{
	unsigned int i;

	if (! partial)
	{
		st->scope = SCOPE_GLOBAL;
		if (isContextualStatement (st->parent))
			st->declaration = DECL_BASE;
		else
			st->declaration = DECL_NONE;
	}
	st->gotParenName	= FALSE;
	st->isPointer		= FALSE;
	st->inFunction		= FALSE;
	st->assignment		= FALSE;
	st->notVariable		= FALSE;
	st->implementation	= IMP_DEFAULT;
	st->gotArgs			= FALSE;
	st->gotName			= FALSE;
	st->haveQualifyingName = FALSE;
	st->tokenIndex		= 0;

	if (st->parent != NULL)
		st->inFunction = st->parent->inFunction;

	for (i = 0  ;  i < (unsigned int) NumTokens  ;  ++i)
		initToken (st->token [i]);

	initToken (st->context);

	/*	Keep the block name, so that a variable following after a comma will
	 *	still have the structure name.
	 */
	if (! partial)
		initToken (st->blockName);

	vStringClear (st->parentClasses);

	/*  Init member info.
	 */
	if (! partial)
		st->member.access = st->member.accessDefault;
}

static void initStatement (statementInfo *const st, statementInfo *const parent)
{
	st->parent = parent;
	initMemberInfo (st);
	reinitStatement (st, FALSE);
}

/*
*   Tag generation functions
*/
static cKind cTagKind (const tagType type)
{
	cKind result = CK_UNDEFINED;
	switch (type)
	{
		case TAG_CLASS:      result = CK_CLASS;       break;
		case TAG_ENUM:       result = CK_ENUMERATION; break;
		case TAG_ENUMERATOR: result = CK_ENUMERATOR;  break;
		case TAG_FUNCTION:   result = CK_FUNCTION;    break;
		case TAG_LOCAL:      result = CK_LOCAL;       break;
		case TAG_MEMBER:     result = CK_MEMBER;      break;
		case TAG_NAMESPACE:  result = CK_NAMESPACE;   break;
		case TAG_PROTOTYPE:  result = CK_PROTOTYPE;   break;
		case TAG_STRUCT:     result = CK_STRUCT;      break;
		case TAG_TYPEDEF:    result = CK_TYPEDEF;     break;
		case TAG_UNION:      result = CK_UNION;       break;
		case TAG_VARIABLE:   result = CK_VARIABLE;    break;
		case TAG_EXTERN_VAR: result = CK_EXTERN_VARIABLE; break;

		default: Assert ("Bad C tag type" == NULL); break;
	}
	return result;
}

static csharpKind csharpTagKind (const tagType type)
{
	csharpKind result = CSK_UNDEFINED;
	switch (type)
	{
		case TAG_CLASS:      result = CSK_CLASS;           break;
		case TAG_ENUM:       result = CSK_ENUMERATION;     break;
		case TAG_ENUMERATOR: result = CSK_ENUMERATOR;      break;
		case TAG_EVENT:      result = CSK_EVENT;           break;
		case TAG_FIELD:      result = CSK_FIELD ;          break;
		case TAG_INTERFACE:  result = CSK_INTERFACE;       break;
		case TAG_LOCAL:      result = CSK_LOCAL;           break;
		case TAG_METHOD:     result = CSK_METHOD;          break;
		case TAG_NAMESPACE:  result = CSK_NAMESPACE;       break;
		case TAG_PROPERTY:   result = CSK_PROPERTY;        break;
		case TAG_STRUCT:     result = CSK_STRUCT;          break;
		case TAG_TYPEDEF:    result = CSK_TYPEDEF;         break;

		default: Assert ("Bad C# tag type" == NULL); break;
	}
	return result;
}

static javaKind javaTagKind (const tagType type)
{
	javaKind result = JK_UNDEFINED;
	switch (type)
	{
		case TAG_CLASS:      result = JK_CLASS;         break;
		case TAG_ENUM:       result = JK_ENUM;          break;
		case TAG_ENUMERATOR: result = JK_ENUM_CONSTANT; break;
		case TAG_FIELD:      result = JK_FIELD;         break;
		case TAG_INTERFACE:  result = JK_INTERFACE;     break;
		case TAG_LOCAL:      result = JK_LOCAL;         break;
		case TAG_METHOD:     result = JK_METHOD;        break;
		case TAG_PACKAGE:    result = JK_PACKAGE;       break;

		default: Assert ("Bad Java tag type" == NULL); break;
	}
	return result;
}

static veraKind veraTagKind (const tagType type) {
	veraKind result = VK_UNDEFINED;
	switch (type)
	{
		case TAG_CLASS:      result = VK_CLASS;           break;
		case TAG_ENUM:       result = VK_ENUMERATION;     break;
		case TAG_ENUMERATOR: result = VK_ENUMERATOR;      break;
		case TAG_FUNCTION:   result = VK_FUNCTION;        break;
		case TAG_LOCAL:      result = VK_LOCAL;           break;
		case TAG_MEMBER:     result = VK_MEMBER;          break;
		case TAG_PROGRAM:    result = VK_PROGRAM;         break;
		case TAG_PROTOTYPE:  result = VK_PROTOTYPE;       break;
		case TAG_TASK:       result = VK_TASK;            break;
		case TAG_TYPEDEF:    result = VK_TYPEDEF;         break;
		case TAG_VARIABLE:   result = VK_VARIABLE;        break;
		case TAG_EXTERN_VAR: result = VK_EXTERN_VARIABLE; break;

		default: Assert ("Bad Vera tag type" == NULL); break;
	}
	return result;
}

static const char *tagName (const tagType type)
{
	const char* result;
	if (isLanguage (Lang_csharp))
		result = CsharpKinds [csharpTagKind (type)].name;
	else if (isLanguage (Lang_java))
		result = JavaKinds [javaTagKind (type)].name;
	else if (isLanguage (Lang_vera))
		result = VeraKinds [veraTagKind (type)].name;
	else
		result = CKinds [cTagKind (type)].name;
	return result;
}

static int tagLetter (const tagType type)
{
	int result;
	if (isLanguage (Lang_csharp))
		result = CsharpKinds [csharpTagKind (type)].letter;
	else if (isLanguage (Lang_java))
		result = JavaKinds [javaTagKind (type)].letter;
	else if (isLanguage (Lang_vera))
		result = VeraKinds [veraTagKind (type)].letter;
	else
		result = CKinds [cTagKind (type)].letter;
	return result;
}

static boolean includeTag (const tagType type, const boolean isFileScope)
{
	boolean result;
	if (isFileScope  &&  ! Option.include.fileScope)
		result = FALSE;
	else if (isLanguage (Lang_csharp))
		result = CsharpKinds [csharpTagKind (type)].enabled;
	else if (isLanguage (Lang_java))
		result = JavaKinds [javaTagKind (type)].enabled;
	else if (isLanguage (Lang_vera))
		result = VeraKinds [veraTagKind (type)].enabled;
	else
		result = CKinds [cTagKind (type)].enabled;
	return result;
}

static tagType declToTagType (const declType declaration)
{
	tagType type = TAG_UNDEFINED;

	switch (declaration)
	{
		case DECL_CLASS:        type = TAG_CLASS;       break;
		case DECL_ENUM:         type = TAG_ENUM;        break;
		case DECL_EVENT:        type = TAG_EVENT;       break;
		case DECL_FUNCTION:     type = TAG_FUNCTION;    break;
		case DECL_INTERFACE:    type = TAG_INTERFACE;   break;
		case DECL_NAMESPACE:    type = TAG_NAMESPACE;   break;
		case DECL_PROGRAM:      type = TAG_PROGRAM;     break;
		case DECL_TASK:         type = TAG_TASK;        break;
		case DECL_STRUCT:       type = TAG_STRUCT;      break;
		case DECL_UNION:        type = TAG_UNION;       break;

		default: Assert ("Unexpected declaration" == NULL); break;
	}
	return type;
}

static const char* accessField (const statementInfo *const st)
{
	const char* result = NULL;
	if (isLanguage (Lang_cpp)  &&  st->scope == SCOPE_FRIEND)
		result = "friend";
	else if (st->member.access != ACCESS_UNDEFINED)
		result = accessString (st->member.access);
	return result;
}

static void addContextSeparator (vString *const scope)
{
	if (isLanguage (Lang_c)  ||  isLanguage (Lang_cpp))
		vStringCatS (scope, "::");
	else if (isLanguage (Lang_java) || isLanguage (Lang_csharp))
		vStringCatS (scope, ".");
}

static void addOtherFields (tagEntryInfo* const tag, const tagType type,
							const statementInfo *const st,
							vString *const scope, vString *const typeRef)
{
	/*  For selected tag types, append an extension flag designating the
	 *  parent object in which the tag is defined.
	 */
	switch (type)
	{
		default: break;

		case TAG_FUNCTION:
		case TAG_METHOD:
		case TAG_PROTOTYPE:
			if (vStringLength (Signature) > 0)
				tag->extensionFields.signature = vStringValue (Signature);
		case TAG_CLASS:
		case TAG_ENUM:
		case TAG_ENUMERATOR:
		case TAG_EVENT:
		case TAG_FIELD:
		case TAG_INTERFACE:
		case TAG_MEMBER:
		case TAG_NAMESPACE:
		case TAG_PROPERTY:
		case TAG_STRUCT:
		case TAG_TASK:
		case TAG_TYPEDEF:
		case TAG_UNION:
			if (vStringLength (scope) > 0  &&
				(isMember (st) || st->parent->declaration == DECL_NAMESPACE))
			{
				if (isType (st->context, TOKEN_NAME))
					tag->extensionFields.scope [0] = tagName (TAG_CLASS);
				else
					tag->extensionFields.scope [0] =
						tagName (declToTagType (parentDecl (st)));
				tag->extensionFields.scope [1] = vStringValue (scope);
			}
			if ((type == TAG_CLASS  ||  type == TAG_INTERFACE  ||
				 type == TAG_STRUCT) && vStringLength (st->parentClasses) > 0)
			{

				tag->extensionFields.inheritance =
						vStringValue (st->parentClasses);
			}
			if (st->implementation != IMP_DEFAULT &&
				(isLanguage (Lang_cpp) || isLanguage (Lang_csharp) ||
				 isLanguage (Lang_java)))
			{
				tag->extensionFields.implementation =
						implementationString (st->implementation);
			}
			if (isMember (st))
			{
				tag->extensionFields.access = accessField (st);
			}
			break;
	}

	/* Add typename info, type of the tag and name of struct/union/etc. */
	if ((type == TAG_TYPEDEF || type == TAG_VARIABLE || type == TAG_MEMBER)
			&& isContextualStatement(st))
	{
		char *p;

		tag->extensionFields.typeRef [0] =
						tagName (declToTagType (st->declaration));
		p = vStringValue (st->blockName->name);

		/*  If there was no {} block get the name from the token before the
		 *  name (current token is ';' or ',', previous token is the name).
		 */
		if (p == NULL || *p == '\0')
		{
			tokenInfo *const prev2 = prevToken (st, 2);
			if (isType (prev2, TOKEN_NAME))
				p = vStringValue (prev2->name);
		}

		/* Prepend the scope name if there is one. */
		if (vStringLength (scope) > 0)
		{
			vStringCopy(typeRef, scope);
			addContextSeparator (typeRef);
			vStringCatS(typeRef, p);
			p = vStringValue (typeRef);
		}
		tag->extensionFields.typeRef [1] = p;
	}
}

static void findScopeHierarchy (vString *const string,
								const statementInfo *const st)
{
	vStringClear (string);
	if (isType (st->context, TOKEN_NAME))
		vStringCopy (string, st->context->name);
	if (st->parent != NULL)
	{
		vString *temp = vStringNew ();
		const statementInfo *s;
		for (s = st->parent  ;  s != NULL  ;  s = s->parent)
		{
			if (isContextualStatement (s) ||
				s->declaration == DECL_NAMESPACE ||
				s->declaration == DECL_PROGRAM)
			{
				vStringCopy (temp, string);
				vStringClear (string);
				Assert (isType (s->blockName, TOKEN_NAME));
				if (isType (s->context, TOKEN_NAME) &&
					vStringLength (s->context->name) > 0)
				{
					vStringCat (string, s->context->name);
					addContextSeparator (string);
				}
				vStringCat (string, s->blockName->name);
				if (vStringLength (temp) > 0)
					addContextSeparator (string);
				vStringCat (string, temp);
			}
		}
		vStringDelete (temp);
	}
}

static void makeExtraTagEntry (const tagType type, tagEntryInfo *const e,
							   vString *const scope)
{
	if (Option.include.qualifiedTags  &&
		scope != NULL  &&  vStringLength (scope) > 0)
	{
		vString *const scopedName = vStringNew ();

		if (type != TAG_ENUMERATOR)
			vStringCopy (scopedName, scope);
		else
		{
			/* remove last component (i.e. enumeration name) from scope */
			const char* const sc = vStringValue (scope);
			const char* colon = strrchr (sc, ':');
			if (colon != NULL)
			{
				while (*colon == ':'  &&  colon > sc)
					--colon;
				vStringNCopy (scopedName, scope, colon + 1 - sc);
			}
		}
		if (vStringLength (scopedName) > 0)
		{
			addContextSeparator (scopedName);
			vStringCatS (scopedName, e->name);
			e->name = vStringValue (scopedName);
			makeTagEntry (e);
		}
		vStringDelete (scopedName);
	}
}

static void makeTag (const tokenInfo *const token,
					 const statementInfo *const st,
					 boolean isFileScope, const tagType type)
{
	/*  Nothing is really of file scope when it appears in a header file.
	 */
	isFileScope = (boolean) (isFileScope && ! isHeaderFile ());

	if (isType (token, TOKEN_NAME)  &&  vStringLength (token->name) > 0  &&
		includeTag (type, isFileScope))
	{
		vString *scope = vStringNew ();
		/* Use "typeRef" to store the typename from addOtherFields() until
		 * it's used in makeTagEntry().
		 */
		vString *typeRef = vStringNew ();
		tagEntryInfo e;

		initTagEntry (&e, vStringValue (token->name));

		e.lineNumber	= token->lineNumber;
		e.filePosition	= token->filePosition;
		e.isFileScope	= isFileScope;
		e.kindName		= tagName (type);
		e.kind			= tagLetter (type);

		findScopeHierarchy (scope, st);
		addOtherFields (&e, type, st, scope, typeRef);

		makeTagEntry (&e);
		makeExtraTagEntry (type, &e, scope);
		vStringDelete (scope);
		vStringDelete (typeRef);
	}
}

static boolean isValidTypeSpecifier (const declType declaration)
{
	boolean result;
	switch (declaration)
	{
		case DECL_BASE:
		case DECL_CLASS:
		case DECL_ENUM:
		case DECL_EVENT:
		case DECL_STRUCT:
		case DECL_UNION:
			result = TRUE;
			break;

		default:
			result = FALSE;
			break;
	}
	return result;
}

static void qualifyEnumeratorTag (const statementInfo *const st,
								  const tokenInfo *const nameToken)
{
	if (isType (nameToken, TOKEN_NAME))
		makeTag (nameToken, st, TRUE, TAG_ENUMERATOR);
}

static void qualifyFunctionTag (const statementInfo *const st,
								const tokenInfo *const nameToken)
{
	if (isType (nameToken, TOKEN_NAME))
	{
		tagType type;
		const boolean isFileScope =
						(boolean) (st->member.access == ACCESS_PRIVATE ||
						(!isMember (st)  &&  st->scope == SCOPE_STATIC));
		if (isLanguage (Lang_java) || isLanguage (Lang_csharp))
			type = TAG_METHOD;
		else if (isLanguage (Lang_vera)  &&  st->declaration == DECL_TASK)
			type = TAG_TASK;
		else
			type = TAG_FUNCTION;
		makeTag (nameToken, st, isFileScope, type);
	}
}

static void qualifyFunctionDeclTag (const statementInfo *const st,
									const tokenInfo *const nameToken)
{
	if (! isType (nameToken, TOKEN_NAME))
		;
	else if (isLanguage (Lang_java) || isLanguage (Lang_csharp))
		qualifyFunctionTag (st, nameToken);
	else if (st->scope == SCOPE_TYPEDEF)
		makeTag (nameToken, st, TRUE, TAG_TYPEDEF);
	else if (isValidTypeSpecifier (st->declaration) && ! isLanguage (Lang_csharp))
		makeTag (nameToken, st, TRUE, TAG_PROTOTYPE);
}

static void qualifyCompoundTag (const statementInfo *const st,
								const tokenInfo *const nameToken)
{
	if (isType (nameToken, TOKEN_NAME))
	{
		const tagType type = declToTagType (st->declaration);
		const boolean fileScoped = (boolean)
				(!(isLanguage (Lang_java) ||
				   isLanguage (Lang_csharp) ||
				   isLanguage (Lang_vera)));

		if (type != TAG_UNDEFINED)
			makeTag (nameToken, st, fileScoped, type);
	}
}

static void qualifyBlockTag (statementInfo *const st,
							 const tokenInfo *const nameToken)
{
	switch (st->declaration)
	{
		case DECL_CLASS:
		case DECL_ENUM:
		case DECL_INTERFACE:
		case DECL_NAMESPACE:
		case DECL_PROGRAM:
		case DECL_STRUCT:
		case DECL_UNION:
			qualifyCompoundTag (st, nameToken);
			break;
		default: break;
	}
}

static void qualifyVariableTag (const statementInfo *const st,
								const tokenInfo *const nameToken)
{
	/*	We have to watch that we do not interpret a declaration of the
	 *	form "struct tag;" as a variable definition. In such a case, the
	 *	token preceding the name will be a keyword.
	 */
	if (! isType (nameToken, TOKEN_NAME))
		;
	else if (st->scope == SCOPE_TYPEDEF)
		makeTag (nameToken, st, TRUE, TAG_TYPEDEF);
	else if (st->declaration == DECL_EVENT)
		makeTag (nameToken, st, (boolean) (st->member.access == ACCESS_PRIVATE),
				TAG_EVENT);
	else if (st->declaration == DECL_PACKAGE)
		makeTag (nameToken, st, FALSE, TAG_PACKAGE);
	else if (isValidTypeSpecifier (st->declaration))
	{
		if (st->notVariable)
			;
		else if (isMember (st))
		{
			if (isLanguage (Lang_java) || isLanguage (Lang_csharp))
				makeTag (nameToken, st,
						(boolean) (st->member.access == ACCESS_PRIVATE), TAG_FIELD);
			else if (st->scope == SCOPE_GLOBAL  ||  st->scope == SCOPE_STATIC)
				makeTag (nameToken, st, TRUE, TAG_MEMBER);
		}
		else
		{
			if (st->scope == SCOPE_EXTERN  ||  ! st->haveQualifyingName)
				makeTag (nameToken, st, FALSE, TAG_EXTERN_VAR);
			else if (st->inFunction)
				makeTag (nameToken, st, (boolean) (st->scope == SCOPE_STATIC),
						TAG_LOCAL);
			else
				makeTag (nameToken, st, (boolean) (st->scope == SCOPE_STATIC),
						TAG_VARIABLE);
		}
	}
}

/*
*   Parsing functions
*/

static int skipToOneOf (const char *const chars)
{
	int c;
	do
		c = cppGetc ();
	while (c != EOF  &&  c != '\0'  &&  strchr (chars, c) == NULL);
	return c;
}

/*  Skip to the next non-white character.
 */
static int skipToNonWhite (void)
{
	boolean found = FALSE;
	int c;

#if 0
	do
		c = cppGetc ();
	while (isspace (c));
#else
	while (1)
	{
		c = cppGetc ();
		if (isspace (c))
			found = TRUE;
		else
			break;
	}
	if (CollectingSignature && found)
		vStringPut (Signature, ' ');
#endif

	return c;
}

/*  Skips to the next brace in column 1. This is intended for cases where
 *  preprocessor constructs result in unbalanced braces.
 */
static void skipToFormattedBraceMatch (void)
{
	int c, next;

	c = cppGetc ();
	next = cppGetc ();
	while (c != EOF  &&  (c != '\n'  ||  next != '}'))
	{
		c = next;
		next = cppGetc ();
	}
}

/*  Skip to the matching character indicated by the pair string. If skipping
 *  to a matching brace and any brace is found within a different level of a
 *  #if conditional statement while brace formatting is in effect, we skip to
 *  the brace matched by its formatting. It is assumed that we have already
 *  read the character which starts the group (i.e. the first character of
 *  "pair").
 */
static void skipToMatch (const char *const pair)
{
	const boolean braceMatching = (boolean) (strcmp ("{}", pair) == 0);
	const boolean braceFormatting = (boolean) (isBraceFormat () && braceMatching);
	const unsigned int initialLevel = getDirectiveNestLevel ();
	const int begin = pair [0], end = pair [1];
	const unsigned long inputLineNumber = getInputLineNumber ();
	int matchLevel = 1;
	int c = '\0';

	while (matchLevel > 0  &&  (c = skipToNonWhite ()) != EOF)
	{
		if (CollectingSignature)
			vStringPut (Signature, c);
		if (c == begin)
		{
			++matchLevel;
			if (braceFormatting  &&  getDirectiveNestLevel () != initialLevel)
			{
				skipToFormattedBraceMatch ();
				break;
			}
		}
		else if (c == end)
		{
			--matchLevel;
			if (braceFormatting  &&  getDirectiveNestLevel () != initialLevel)
			{
				skipToFormattedBraceMatch ();
				break;
			}
		}
	}
	if (c == EOF)
	{
		verbose ("%s: failed to find match for '%c' at line %lu\n",
				getInputFileName (), begin, inputLineNumber);
		if (braceMatching)
			longjmp (Exception, (int) ExceptionBraceFormattingError);
		else
			longjmp (Exception, (int) ExceptionFormattingError);
	}
}

static void skipParens (void)
{
	const int c = skipToNonWhite ();

	if (c == '(')
		skipToMatch ("()");
	else
		cppUngetc (c);
}

static void skipBraces (void)
{
	const int c = skipToNonWhite ();

	if (c == '{')
		skipToMatch ("{}");
	else
		cppUngetc (c);
}

static keywordId analyzeKeyword (const char *const name)
{
	const keywordId id = (keywordId) lookupKeyword (name, getSourceLanguage ());
	return id;
}

static void analyzeIdentifier (tokenInfo *const token)
{
	char *const name = vStringValue (token->name);
	const char *replacement = NULL;
	boolean parensToo = FALSE;

	if (isLanguage (Lang_java)  ||
		! isIgnoreToken (name, &parensToo, &replacement))
	{
		if (replacement != NULL)
			token->keyword = analyzeKeyword (replacement);
		else
			token->keyword = analyzeKeyword (vStringValue (token->name));

		if (token->keyword == KEYWORD_NONE)
			token->type = TOKEN_NAME;
		else
			token->type = TOKEN_KEYWORD;
	}
	else
	{
		initToken (token);
		if (parensToo)
		{
			int c = skipToNonWhite ();

			if (c == '(')
				skipToMatch ("()");
		}
	}
}

static void readIdentifier (tokenInfo *const token, const int firstChar)
{
	vString *const name = token->name;
	int c = firstChar;
	boolean first = TRUE;

	initToken (token);

	/* Bug #1585745: strangely, C++ destructors allow whitespace between
	 * the ~ and the class name. */
	if (isLanguage (Lang_cpp) && firstChar == '~')
	{
		vStringPut (name, c);
		c = skipToNonWhite ();
	}

	do
	{
		vStringPut (name, c);
		if (CollectingSignature)
		{
			if (!first)
				vStringPut (Signature, c);
			first = FALSE;
		}
		c = cppGetc ();
	} while (isident (c) || (isLanguage (Lang_java) && (isHighChar (c) || c == '.')));
	vStringTerminate (name);
	cppUngetc (c);        /* unget non-identifier character */

	analyzeIdentifier (token);
}

static void readPackageName (tokenInfo *const token, const int firstChar)
{
	vString *const name = token->name;
	int c = firstChar;

	initToken (token);

	while (isident (c)  ||  c == '.')
	{
		vStringPut (name, c);
		c = cppGetc ();
	}
	vStringTerminate (name);
	cppUngetc (c);        /* unget non-package character */
}

static void readPackageOrNamespace (statementInfo *const st, const declType declaration)
{
	st->declaration = declaration;
	
	if (declaration == DECL_NAMESPACE && !isLanguage (Lang_csharp))
	{
		/* In C++ a namespace is specified one level at a time. */
		return;
	}
	else
	{
		/* In C#, a namespace can also be specified like a Java package name. */
		tokenInfo *const token = activeToken (st);
		Assert (isType (token, TOKEN_KEYWORD));
		readPackageName (token, skipToNonWhite ());
		token->type = TOKEN_NAME;
		st->gotName = TRUE;
		st->haveQualifyingName = TRUE;
	}
}

static void processName (statementInfo *const st)
{
	Assert (isType (activeToken (st), TOKEN_NAME));
	if (st->gotName  &&  st->declaration == DECL_NONE)
		st->declaration = DECL_BASE;
	st->gotName = TRUE;
	st->haveQualifyingName = TRUE;
}

static void readOperator (statementInfo *const st)
{
	const char *const acceptable = "+-*/%^&|~!=<>,[]";
	const tokenInfo* const prev = prevToken (st,1);
	tokenInfo *const token = activeToken (st);
	vString *const name = token->name;
	int c = skipToNonWhite ();

	/*  When we arrive here, we have the keyword "operator" in 'name'.
	 */
	if (isType (prev, TOKEN_KEYWORD) && (prev->keyword == KEYWORD_ENUM ||
		 prev->keyword == KEYWORD_STRUCT || prev->keyword == KEYWORD_UNION))
		;        /* ignore "operator" keyword if preceded by these keywords */
	else if (c == '(')
	{
		/*  Verify whether this is a valid function call (i.e. "()") operator.
		 */
		if (cppGetc () == ')')
		{
			vStringPut (name, ' ');  /* always separate operator from keyword */
			c = skipToNonWhite ();
			if (c == '(')
				vStringCatS (name, "()");
		}
		else
		{
			skipToMatch ("()");
			c = cppGetc ();
		}
	}
	else if (isident1 (c))
	{
		/*  Handle "new" and "delete" operators, and conversion functions
		 *  (per 13.3.1.1.2 [2] of the C++ spec).
		 */
		boolean whiteSpace = TRUE;  /* default causes insertion of space */
		do
		{
			if (isspace (c))
				whiteSpace = TRUE;
			else
			{
				if (whiteSpace)
				{
					vStringPut (name, ' ');
					whiteSpace = FALSE;
				}
				vStringPut (name, c);
			}
			c = cppGetc ();
		} while (! isOneOf (c, "(;")  &&  c != EOF);
		vStringTerminate (name);
	}
	else if (isOneOf (c, acceptable))
	{
		vStringPut (name, ' ');  /* always separate operator from keyword */
		do
		{
			vStringPut (name, c);
			c = cppGetc ();
		} while (isOneOf (c, acceptable));
		vStringTerminate (name);
	}

	cppUngetc (c);

	token->type	= TOKEN_NAME;
	token->keyword = KEYWORD_NONE;
	processName (st);
}

static void copyToken (tokenInfo *const dest, const tokenInfo *const src)
{
	dest->type         = src->type;
	dest->keyword      = src->keyword;
	dest->filePosition = src->filePosition;
	dest->lineNumber   = src->lineNumber;
	vStringCopy (dest->name, src->name);
}

static void setAccess (statementInfo *const st, const accessType access)
{
	if (isMember (st))
	{
		if (isLanguage (Lang_cpp))
		{
			int c = skipToNonWhite ();

			if (c == ':')
				reinitStatement (st, FALSE);
			else
				cppUngetc (c);

			st->member.accessDefault = access;
		}
		st->member.access = access;
	}
}

static void discardTypeList (tokenInfo *const token)
{
	int c = skipToNonWhite ();
	while (isident1 (c))
	{
		readIdentifier (token, c);
		c = skipToNonWhite ();
		if (c == '.'  ||  c == ',')
			c = skipToNonWhite ();
	}
	cppUngetc (c);
}

static void addParentClass (statementInfo *const st, tokenInfo *const token)
{
	if (vStringLength (token->name) > 0  &&
		vStringLength (st->parentClasses) > 0)
	{
		vStringPut (st->parentClasses, ',');
	}
	vStringCat (st->parentClasses, token->name);
}

static void readParents (statementInfo *const st, const int qualifier)
{
	tokenInfo *const token = newToken ();
	tokenInfo *const parent = newToken ();
	int c;

	do
	{
		c = skipToNonWhite ();
		if (isident1 (c))
		{
			readIdentifier (token, c);
			if (isType (token, TOKEN_NAME))
				vStringCat (parent->name, token->name);
			else
			{
				addParentClass (st, parent);
				initToken (parent);
			}
		}
		else if (c == qualifier)
			vStringPut (parent->name, c);
		else if (c == '<')
			skipToMatch ("<>");
		else if (isType (token, TOKEN_NAME))
		{
			addParentClass (st, parent);
			initToken (parent);
		}
	} while (c != '{'  &&  c != EOF);
	cppUngetc (c);
	deleteToken (parent);
	deleteToken (token);
}

static void skipStatement (statementInfo *const st)
{
	st->declaration = DECL_IGNORE;
	skipToOneOf (";");
}

static void processInterface (statementInfo *const st)
{
	st->declaration = DECL_INTERFACE;
}

static void processToken (tokenInfo *const token, statementInfo *const st)
{
	switch (token->keyword)        /* is it a reserved word? */
	{
		default: break;

		case KEYWORD_NONE:      processName (st);                       break;
		case KEYWORD_ABSTRACT:  st->implementation = IMP_ABSTRACT;      break;
		case KEYWORD_ATTRIBUTE: skipParens (); initToken (token);       break;
		case KEYWORD_BIND:      st->declaration = DECL_BASE;            break;
		case KEYWORD_BIT:       st->declaration = DECL_BASE;            break;
		case KEYWORD_CATCH:     skipParens (); skipBraces ();           break;
		case KEYWORD_CHAR:      st->declaration = DECL_BASE;            break;
		case KEYWORD_CLASS:     st->declaration = DECL_CLASS;           break;
		case KEYWORD_CONST:     st->declaration = DECL_BASE;            break;
		case KEYWORD_DOUBLE:    st->declaration = DECL_BASE;            break;
		case KEYWORD_ENUM:      st->declaration = DECL_ENUM;            break;
		case KEYWORD_EXTENDS:   readParents (st, '.');
		                        setToken (st, TOKEN_NONE);              break;
		case KEYWORD_FLOAT:     st->declaration = DECL_BASE;            break;
		case KEYWORD_FUNCTION:  st->declaration = DECL_BASE;            break;
		case KEYWORD_FRIEND:    st->scope       = SCOPE_FRIEND;         break;
		case KEYWORD_GOTO:      skipStatement (st);                     break;
		case KEYWORD_IMPLEMENTS:readParents (st, '.');
		                        setToken (st, TOKEN_NONE);              break;
		case KEYWORD_IMPORT:    skipStatement (st);                     break;
		case KEYWORD_INT:       st->declaration = DECL_BASE;            break;
		case KEYWORD_INTEGER:   st->declaration = DECL_BASE;            break;
		case KEYWORD_INTERFACE: processInterface (st);                  break;
		case KEYWORD_LOCAL:     setAccess (st, ACCESS_LOCAL);           break;
		case KEYWORD_LONG:      st->declaration = DECL_BASE;            break;
		case KEYWORD_OPERATOR:  readOperator (st);                      break;
		case KEYWORD_PRIVATE:   setAccess (st, ACCESS_PRIVATE);         break;
		case KEYWORD_PROGRAM:   st->declaration = DECL_PROGRAM;         break;
		case KEYWORD_PROTECTED: setAccess (st, ACCESS_PROTECTED);       break;
		case KEYWORD_PUBLIC:    setAccess (st, ACCESS_PUBLIC);          break;
		case KEYWORD_RETURN:    skipStatement (st);                     break;
		case KEYWORD_SHORT:     st->declaration = DECL_BASE;            break;
		case KEYWORD_SIGNED:    st->declaration = DECL_BASE;            break;
		case KEYWORD_STRING:    st->declaration = DECL_BASE;            break;
		case KEYWORD_STRUCT:    st->declaration = DECL_STRUCT;          break;
		case KEYWORD_TASK:      st->declaration = DECL_TASK;            break;
		case KEYWORD_THROWS:    discardTypeList (token);                break;
		case KEYWORD_UNION:     st->declaration = DECL_UNION;           break;
		case KEYWORD_UNSIGNED:  st->declaration = DECL_BASE;            break;
		case KEYWORD_USING:     skipStatement (st);                     break;
		case KEYWORD_VOID:      st->declaration = DECL_BASE;            break;
		case KEYWORD_VOLATILE:  st->declaration = DECL_BASE;            break;
		case KEYWORD_VIRTUAL:   st->implementation = IMP_VIRTUAL;       break;
		case KEYWORD_WCHAR_T:   st->declaration = DECL_BASE;            break;
		
		case KEYWORD_NAMESPACE: readPackageOrNamespace (st, DECL_NAMESPACE); break;
		case KEYWORD_PACKAGE:   readPackageOrNamespace (st, DECL_PACKAGE);   break;
		
		case KEYWORD_EVENT:
			if (isLanguage (Lang_csharp))
				st->declaration = DECL_EVENT;
			break;

		case KEYWORD_TYPEDEF:
			reinitStatement (st, FALSE);
			st->scope = SCOPE_TYPEDEF;
			break;

		case KEYWORD_EXTERN:
			if (! isLanguage (Lang_csharp) || !st->gotName)
			{
				reinitStatement (st, FALSE);
				st->scope = SCOPE_EXTERN;
				st->declaration = DECL_BASE;
			}
			break;

		case KEYWORD_STATIC:
			if (! (isLanguage (Lang_java) || isLanguage (Lang_csharp)))
			{
				reinitStatement (st, FALSE);
				st->scope = SCOPE_STATIC;
				st->declaration = DECL_BASE;
			}
			break;

		case KEYWORD_FOR:
		case KEYWORD_IF:
		case KEYWORD_SWITCH:
		case KEYWORD_WHILE:
		{
			int c = skipToNonWhite ();
			if (c == '(')
				skipToMatch ("()");
			break;
		}
	}
}

/*
*   Parenthesis handling functions
*/

static void restartStatement (statementInfo *const st)
{
	tokenInfo *const save = newToken ();
	tokenInfo *token = activeToken (st);

	copyToken (save, token);
	DebugStatement ( if (debug (DEBUG_PARSE)) printf ("<ES>");)
	reinitStatement (st, FALSE);
	token = activeToken (st);
	copyToken (token, save);
	deleteToken (save);
	processToken (token, st);
}

/*  Skips over a the mem-initializer-list of a ctor-initializer, defined as:
 *
 *  mem-initializer-list:
 *    mem-initializer, mem-initializer-list
 *
 *  mem-initializer:
 *    [::] [nested-name-spec] class-name (...)
 *    identifier
 */
static void skipMemIntializerList (tokenInfo *const token)
{
	int c;

	do
	{
		c = skipToNonWhite ();
		while (isident1 (c)  ||  c == ':')
		{
			if (c != ':')
				readIdentifier (token, c);
			c = skipToNonWhite ();
		}
		if (c == '<')
		{
			skipToMatch ("<>");
			c = skipToNonWhite ();
		}
		if (c == '(')
		{
			skipToMatch ("()");
			c = skipToNonWhite ();
		}
	} while (c == ',');
	cppUngetc (c);
}

static void skipMacro (statementInfo *const st)
{
	tokenInfo *const prev2 = prevToken (st, 2);

	if (isType (prev2, TOKEN_NAME))
		retardToken (st);
	skipToMatch ("()");
}

/*  Skips over characters following the parameter list. This will be either
 *  non-ANSI style function declarations or C++ stuff. Our choices:
 *
 *  C (K&R):
 *    int func ();
 *    int func (one, two) int one; float two; {...}
 *  C (ANSI):
 *    int func (int one, float two);
 *    int func (int one, float two) {...}
 *  C++:
 *    int foo (...) [const|volatile] [throw (...)];
 *    int foo (...) [const|volatile] [throw (...)] [ctor-initializer] {...}
 *    int foo (...) [const|volatile] [throw (...)] try [ctor-initializer] {...}
 *        catch (...) {...}
 */
static boolean skipPostArgumentStuff (
		statementInfo *const st, parenInfo *const info)
{
	tokenInfo *const token = activeToken (st);
	unsigned int parameters = info->parameterCount;
	unsigned int elementCount = 0;
	boolean restart = FALSE;
	boolean end = FALSE;
	int c = skipToNonWhite ();

	do
	{
		switch (c)
		{
		case ')':                               break;
		case ':': skipMemIntializerList (token);break;  /* ctor-initializer */
		case '[': skipToMatch ("[]");           break;
		case '=': cppUngetc (c); end = TRUE;    break;
		case '{': cppUngetc (c); end = TRUE;    break;
		case '}': cppUngetc (c); end = TRUE;    break;

		case '(':
			if (elementCount > 0)
				++elementCount;
			skipToMatch ("()");
			break;

		case ';':
			if (parameters == 0  ||  elementCount < 2)
			{
				cppUngetc (c);
				end = TRUE;
			}
			else if (--parameters == 0)
				end = TRUE;
			break;

		default:
			if (isident1 (c))
			{
				readIdentifier (token, c);
				switch (token->keyword)
				{
				case KEYWORD_ATTRIBUTE: skipParens ();  break;
				case KEYWORD_THROW:     skipParens ();  break;
				case KEYWORD_TRY:                       break;

				case KEYWORD_CONST:
				case KEYWORD_VOLATILE:
					if (vStringLength (Signature) > 0)
					{
						vStringPut (Signature, ' ');
						vStringCat (Signature, token->name);
					}
					break;

				case KEYWORD_CATCH:
				case KEYWORD_CLASS:
				case KEYWORD_EXPLICIT:
				case KEYWORD_EXTERN:
				case KEYWORD_FRIEND:
				case KEYWORD_INLINE:
				case KEYWORD_MUTABLE:
				case KEYWORD_NAMESPACE:
				case KEYWORD_NEW:
				case KEYWORD_NEWCOV:
				case KEYWORD_OPERATOR:
				case KEYWORD_OVERLOAD:
				case KEYWORD_PRIVATE:
				case KEYWORD_PROTECTED:
				case KEYWORD_PUBLIC:
				case KEYWORD_STATIC:
				case KEYWORD_TEMPLATE:
				case KEYWORD_TYPEDEF:
				case KEYWORD_TYPENAME:
				case KEYWORD_USING:
				case KEYWORD_VIRTUAL:
					/* Never allowed within parameter declarations. */
					restart = TRUE;
					end = TRUE;
					break;

				default:
					if (isType (token, TOKEN_NONE))
						;
					else if (info->isKnrParamList  &&  info->parameterCount > 0)
						++elementCount;
					else
					{
						/*  If we encounter any other identifier immediately
						 *  following an empty parameter list, this is almost
						 *  certainly one of those Microsoft macro "thingies"
						 *  that the automatic source code generation sticks
						 *  in. Terminate the current statement.
						 */
						restart = TRUE;
						end = TRUE;
					}
					break;
				}
			}
		}
		if (! end)
		{
			c = skipToNonWhite ();
			if (c == EOF)
				end = TRUE;
		}
	} while (! end);

	if (restart)
		restartStatement (st);
	else
		setToken (st, TOKEN_NONE);

	return (boolean) (c != EOF);
}

static void skipJavaThrows (statementInfo *const st)
{
	tokenInfo *const token = activeToken (st);
	int c = skipToNonWhite ();

	if (isident1 (c))
	{
		readIdentifier (token, c);
		if (token->keyword == KEYWORD_THROWS)
		{
			do
			{
				c = skipToNonWhite ();
				if (isident1 (c))
				{
					readIdentifier (token, c);
					c = skipToNonWhite ();
				}
			} while (c == '.'  ||  c == ',');
		}
	}
	cppUngetc (c);
	setToken (st, TOKEN_NONE);
}

static void analyzePostParens (statementInfo *const st, parenInfo *const info)
{
	const unsigned long inputLineNumber = getInputLineNumber ();
	int c = skipToNonWhite ();

	cppUngetc (c);
	if (isOneOf (c, "{;,="))
		;
	else if (isLanguage (Lang_java))
		skipJavaThrows (st);
	else
	{
		if (! skipPostArgumentStuff (st, info))
		{
			verbose (
				"%s: confusing argument declarations beginning at line %lu\n",
				getInputFileName (), inputLineNumber);
			longjmp (Exception, (int) ExceptionFormattingError);
		}
	}
}

static void processAngleBracket (void)
{
	int c = cppGetc ();
	if (c == '>') {
		/* already found match for template */
	} else if ((isLanguage (Lang_cpp) || isLanguage (Lang_java)) && c != '<' && c != '=') {
		/* this is a template */
		cppUngetc (c);
		skipToMatch ("<>");
	} else if (c == '<') {
		/* skip "<<" or "<<=". */
		c = cppGetc ();
		if (c != '=') {
			cppUngetc (c);
		}
	} else {
		cppUngetc (c);
	}
}

static int parseParens (statementInfo *const st, parenInfo *const info)
{
	tokenInfo *const token = activeToken (st);
	unsigned int identifierCount = 0;
	unsigned int depth = 1;
	boolean firstChar = TRUE;
	int nextChar = '\0';

	CollectingSignature = TRUE;
	vStringClear (Signature);
	vStringPut (Signature, '(');
	info->parameterCount = 1;
	do
	{
		int c = skipToNonWhite ();
		vStringPut (Signature, c);

		switch (c)
		{
			case '&':
			case '*':
				info->isPointer = TRUE;
				info->isKnrParamList = FALSE;
				if (identifierCount == 0)
					info->isParamList = FALSE;
				initToken (token);
				break;

			case ':':
				info->isKnrParamList = FALSE;
				break;

			case '.':
				info->isNameCandidate = FALSE;
				c = cppGetc ();
				if (c != '.')
				{
					cppUngetc (c);
					info->isKnrParamList = FALSE;
				}
				else
				{
					c = cppGetc ();
					if (c != '.')
					{
						cppUngetc (c);
						info->isKnrParamList = FALSE;
					}
					else
						vStringCatS (Signature, "..."); /* variable arg list */
				}
				break;

			case ',':
				info->isNameCandidate = FALSE;
				if (info->isKnrParamList)
				{
					++info->parameterCount;
					identifierCount = 0;
				}
				break;

			case '=':
				info->isKnrParamList = FALSE;
				info->isNameCandidate = FALSE;
				if (firstChar)
				{
					info->isParamList = FALSE;
					skipMacro (st);
					depth = 0;
				}
				break;

			case '[':
				info->isKnrParamList = FALSE;
				skipToMatch ("[]");
				break;

			case '<':
				info->isKnrParamList = FALSE;
				processAngleBracket ();
				break;

			case ')':
				if (firstChar)
					info->parameterCount = 0;
				--depth;
				break;

			case '(':
				info->isKnrParamList = FALSE;
				if (firstChar)
				{
					info->isNameCandidate = FALSE;
					cppUngetc (c);
					vStringClear (Signature);
					skipMacro (st);
					depth = 0;
					vStringChop (Signature);
				}
				else if (isType (token, TOKEN_PAREN_NAME))
				{
					c = skipToNonWhite ();
					if (c == '*')        /* check for function pointer */
					{
						skipToMatch ("()");
						c = skipToNonWhite ();
						if (c == '(')
							skipToMatch ("()");
						else
							cppUngetc (c);
					}
					else
					{
						cppUngetc (c);
						cppUngetc ('(');
						info->nestedArgs = TRUE;
					}
				}
				else
					++depth;
				break;

			default:
				if (isident1 (c))
				{
					if (++identifierCount > 1)
						info->isKnrParamList = FALSE;
					readIdentifier (token, c);
					if (isType (token, TOKEN_NAME)  &&  info->isNameCandidate)
						token->type = TOKEN_PAREN_NAME;
					else if (isType (token, TOKEN_KEYWORD))
					{
						if (token->keyword != KEYWORD_CONST &&
							token->keyword != KEYWORD_VOLATILE)
						{
							info->isKnrParamList = FALSE;
							info->isNameCandidate = FALSE;
						}
					}
				}
				else
				{
					info->isParamList     = FALSE;
					info->isKnrParamList  = FALSE;
					info->isNameCandidate = FALSE;
					info->invalidContents = TRUE;
				}
				break;
		}
		firstChar = FALSE;
	} while (! info->nestedArgs  &&  depth > 0  &&
			 (info->isKnrParamList  ||  info->isNameCandidate));

	if (! info->nestedArgs) while (depth > 0)
	{
		skipToMatch ("()");
		--depth;
	}

	if (! info->isNameCandidate)
		initToken (token);

	vStringTerminate (Signature);
	if (info->isKnrParamList)
		vStringClear (Signature);
	CollectingSignature = FALSE;
	return nextChar;
}

static void initParenInfo (parenInfo *const info)
{
	info->isPointer				= FALSE;
	info->isParamList			= TRUE;
	info->isKnrParamList		= isLanguage (Lang_c);
	info->isNameCandidate		= TRUE;
	info->invalidContents		= FALSE;
	info->nestedArgs			= FALSE;
	info->parameterCount		= 0;
}

static void analyzeParens (statementInfo *const st)
{
	tokenInfo *const prev = prevToken (st, 1);

	if (st->inFunction  &&  ! st->assignment)
		st->notVariable = TRUE;
	if (! isType (prev, TOKEN_NONE))  /* in case of ignored enclosing macros */
	{
		tokenInfo *const token = activeToken (st);
		parenInfo info;
		int c;

		initParenInfo (&info);
		parseParens (st, &info);
		c = skipToNonWhite ();
		cppUngetc (c);
		if (info.invalidContents)
			reinitStatement (st, FALSE);
		else if (info.isNameCandidate  &&  isType (token, TOKEN_PAREN_NAME)  &&
				 ! st->gotParenName  &&
				 (! info.isParamList || ! st->haveQualifyingName  ||
				  c == '('  ||
				  (c == '='  &&  st->implementation != IMP_VIRTUAL) ||
				  (st->declaration == DECL_NONE  &&  isOneOf (c, ",;"))))
		{
			token->type = TOKEN_NAME;
			processName (st);
			st->gotParenName = TRUE;
			if (! (c == '('  &&  info.nestedArgs))
				st->isPointer = info.isPointer;
		}
		else if (! st->gotArgs  &&  info.isParamList)
		{
			st->gotArgs = TRUE;
			setToken (st, TOKEN_ARGS);
			advanceToken (st);
			if (st->scope != SCOPE_TYPEDEF)
				analyzePostParens (st, &info);
		}
		else
			setToken (st, TOKEN_NONE);
	}
}

/*
*   Token parsing functions
*/

static void addContext (statementInfo *const st, const tokenInfo* const token)
{
	if (isType (token, TOKEN_NAME))
	{
		if (vStringLength (st->context->name) > 0)
		{
			if (isLanguage (Lang_c)  ||  isLanguage (Lang_cpp))
				vStringCatS (st->context->name, "::");
			else if (isLanguage (Lang_java) || isLanguage (Lang_csharp))
				vStringCatS (st->context->name, ".");
		}
		vStringCat (st->context->name, token->name);
		st->context->type = TOKEN_NAME;
	}
}

static boolean inheritingDeclaration (declType decl)
{
	return (boolean) (
		decl == DECL_CLASS ||
		decl == DECL_STRUCT ||
		decl == DECL_INTERFACE);
}

static void processColon (statementInfo *const st)
{
	int c = skipToNonWhite ();
	const boolean doubleColon = (boolean) (c == ':');

	if (doubleColon)
	{
		setToken (st, TOKEN_DOUBLE_COLON);
		st->haveQualifyingName = FALSE;
	}
	else
	{
		cppUngetc (c);
		if ((isLanguage (Lang_cpp) || isLanguage (Lang_csharp))  &&
			inheritingDeclaration (st->declaration))
		{
			readParents (st, ':');
		}
		else if (parentDecl (st) == DECL_STRUCT)
		{
			c = skipToOneOf (",;");
			if (c == ',')
				setToken (st, TOKEN_COMMA);
			else if (c == ';')
				setToken (st, TOKEN_SEMICOLON);
		}
		else
		{
			const tokenInfo *const prev  = prevToken (st, 1);
			const tokenInfo *const prev2 = prevToken (st, 2);
			if (prev->keyword == KEYWORD_DEFAULT ||
				prev2->keyword == KEYWORD_CASE ||
				st->parent != NULL)
			{
				reinitStatement (st, FALSE);
			}
		}
	}
}

/*  Skips over any initializing value which may follow an '=' character in a
 *  variable definition.
 */
static int skipInitializer (statementInfo *const st)
{
	boolean done = FALSE;
	int c;

	while (! done)
	{
		c = skipToNonWhite ();

		if (c == EOF)
			longjmp (Exception, (int) ExceptionFormattingError);
		else switch (c)
		{
			case ',':
			case ';': done = TRUE; break;

			case '0':
				if (st->implementation == IMP_VIRTUAL)
					st->implementation = IMP_PURE_VIRTUAL;
				break;

			case '[': skipToMatch ("[]"); break;
			case '(': skipToMatch ("()"); break;
			case '{': skipToMatch ("{}"); break;
			case '<': processAngleBracket(); break;

			case '}':
				if (insideEnumBody (st))
					done = TRUE;
				else if (! isBraceFormat ())
				{
					verbose ("%s: unexpected closing brace at line %lu\n",
							getInputFileName (), getInputLineNumber ());
					longjmp (Exception, (int) ExceptionBraceFormattingError);
				}
				break;

			default: break;
		}
	}
	return c;
}

static void processInitializer (statementInfo *const st)
{
	const boolean inEnumBody = insideEnumBody (st);
	int c = cppGetc ();

	if (c != '=')
	{
		cppUngetc (c);
		c = skipInitializer (st);
		st->assignment = TRUE;
		if (c == ';')
			setToken (st, TOKEN_SEMICOLON);
		else if (c == ',')
			setToken (st, TOKEN_COMMA);
		else if (c == '}'  &&  inEnumBody)
		{
			cppUngetc (c);
			setToken (st, TOKEN_COMMA);
		}
		if (st->scope == SCOPE_EXTERN)
			st->scope = SCOPE_GLOBAL;
	}
}

static void parseIdentifier (statementInfo *const st, const int c)
{
	tokenInfo *const token = activeToken (st);

	readIdentifier (token, c);
	if (! isType (token, TOKEN_NONE))
		processToken (token, st);
}

static void parseJavaAnnotation (statementInfo *const st)
{
	/*
	 * @Override
	 * @Target(ElementType.METHOD)
	 * @SuppressWarnings(value = "unchecked")
	 *
	 * But watch out for "@interface"!
	 */
	tokenInfo *const token = activeToken (st);
	
	int c = skipToNonWhite ();
	readIdentifier (token, c);
	if (token->keyword == KEYWORD_INTERFACE)
	{
		/* Oops. This was actually "@interface" defining a new annotation. */
		processInterface (st);
	}
	else
	{
		/* Bug #1691412: skip any annotation arguments. */
		skipParens ();
	}
}

static void parseGeneralToken (statementInfo *const st, const int c)
{
	const tokenInfo *const prev = prevToken (st, 1);
	
	if (isident1 (c) || (isLanguage (Lang_java) && isHighChar (c)))
	{
		parseIdentifier (st, c);
		if (isType (st->context, TOKEN_NAME) &&
			isType (activeToken (st), TOKEN_NAME) && isType (prev, TOKEN_NAME))
		{
			initToken (st->context);
		}
	}
	else if (c == '.' || c == '-')
	{
		if (! st->assignment)
			st->notVariable = TRUE;
		if (c == '-')
		{
			int c2 = cppGetc ();
			if (c2 != '>')
				cppUngetc (c2);
		}
	}
	else if (c == '!' || c == '>')
	{
		int c2 = cppGetc ();
		if (c2 != '=')
			cppUngetc (c2);
	}
	else if (c == '@' && isLanguage (Lang_java))
	{
		parseJavaAnnotation (st);
	}
	else if (isExternCDecl (st, c))
	{
		st->declaration = DECL_NOMANGLE;
		st->scope = SCOPE_GLOBAL;
	}
}

/*  Reads characters from the pre-processor and assembles tokens, setting
 *  the current statement state.
 */
static void nextToken (statementInfo *const st)
{
	tokenInfo *token;
	do
	{
		int c = skipToNonWhite ();
		switch (c)
		{
			case EOF: longjmp (Exception, (int) ExceptionEOF);  break;
			case '(': analyzeParens (st);                       break;
			case '<': processAngleBracket ();                   break;
			case '*': st->haveQualifyingName = FALSE;           break;
			case ',': setToken (st, TOKEN_COMMA);               break;
			case ':': processColon (st);                        break;
			case ';': setToken (st, TOKEN_SEMICOLON);           break;
			case '=': processInitializer (st);                  break;
			case '[': skipToMatch ("[]");                       break;
			case '{': setToken (st, TOKEN_BRACE_OPEN);          break;
			case '}': setToken (st, TOKEN_BRACE_CLOSE);         break;
			default:  parseGeneralToken (st, c);                break;
		}
		token = activeToken (st);
	} while (isType (token, TOKEN_NONE));
}

/*
*   Scanning support functions
*/

static statementInfo *CurrentStatement = NULL;

static statementInfo *newStatement (statementInfo *const parent)
{
	statementInfo *const st = xMalloc (1, statementInfo);
	unsigned int i;

	for (i = 0  ;  i < (unsigned int) NumTokens  ;  ++i)
		st->token [i] = newToken ();

	st->context = newToken ();
	st->blockName = newToken ();
	st->parentClasses = vStringNew ();

	initStatement (st, parent);
	CurrentStatement = st;

	return st;
}

static void deleteStatement (void)
{
	statementInfo *const st = CurrentStatement;
	statementInfo *const parent = st->parent;
	unsigned int i;

	for (i = 0  ;  i < (unsigned int) NumTokens  ;  ++i)
	{
		deleteToken (st->token [i]);       st->token [i] = NULL;
	}
	deleteToken (st->blockName);           st->blockName = NULL;
	deleteToken (st->context);             st->context = NULL;
	vStringDelete (st->parentClasses);     st->parentClasses = NULL;
	eFree (st);
	CurrentStatement = parent;
}

static void deleteAllStatements (void)
{
	while (CurrentStatement != NULL)
		deleteStatement ();
}

static boolean isStatementEnd (const statementInfo *const st)
{
	const tokenInfo *const token = activeToken (st);
	boolean isEnd;

	if (isType (token, TOKEN_SEMICOLON))
		isEnd = TRUE;
	else if (isType (token, TOKEN_BRACE_CLOSE))
		/* Java and C# do not require semicolons to end a block. Neither do C++
		 * namespaces. All other blocks require a semicolon to terminate them.
		 */
		isEnd = (boolean) (isLanguage (Lang_java) || isLanguage (Lang_csharp) ||
				! isContextualStatement (st));
	else
		isEnd = FALSE;

	return isEnd;
}

static void checkStatementEnd (statementInfo *const st)
{
	const tokenInfo *const token = activeToken (st);

	if (isType (token, TOKEN_COMMA))
		reinitStatement (st, TRUE);
	else if (isStatementEnd (st))
	{
		DebugStatement ( if (debug (DEBUG_PARSE)) printf ("<ES>"); )
		reinitStatement (st, FALSE);
		cppEndStatement ();
	}
	else
	{
		cppBeginStatement ();
		advanceToken (st);
	}
}

static void nest (statementInfo *const st, const unsigned int nestLevel)
{
	switch (st->declaration)
	{
		case DECL_CLASS:
		case DECL_ENUM:
		case DECL_INTERFACE:
		case DECL_NAMESPACE:
		case DECL_NOMANGLE:
		case DECL_STRUCT:
		case DECL_UNION:
			createTags (nestLevel, st);
			break;

		case DECL_FUNCTION:
		case DECL_TASK:
			st->inFunction = TRUE;
			/* fall through */
		default:
			if (includeTag (TAG_LOCAL, FALSE))
				createTags (nestLevel, st);
			else
				skipToMatch ("{}");
			break;
	}
	advanceToken (st);
	setToken (st, TOKEN_BRACE_CLOSE);
}

static void tagCheck (statementInfo *const st)
{
	const tokenInfo *const token = activeToken (st);
	const tokenInfo *const prev  = prevToken (st, 1);
	const tokenInfo *const prev2 = prevToken (st, 2);

	switch (token->type)
	{
		case TOKEN_NAME:
			if (insideEnumBody (st))
				qualifyEnumeratorTag (st, token);
			break;
#if 0
		case TOKEN_PACKAGE:
			if (st->haveQualifyingName)
				makeTag (token, st, FALSE, TAG_PACKAGE);
			break;
#endif
		case TOKEN_BRACE_OPEN:
			if (isType (prev, TOKEN_ARGS))
			{
				if (st->haveQualifyingName)
				{
					if (! isLanguage (Lang_vera))
						st->declaration = DECL_FUNCTION;
					if (isType (prev2, TOKEN_NAME))
						copyToken (st->blockName, prev2);
					qualifyFunctionTag (st, prev2);
				}
			}
			else if (isContextualStatement (st) ||
					st->declaration == DECL_NAMESPACE ||
					st->declaration == DECL_PROGRAM)
			{
				if (isType (prev, TOKEN_NAME))
					copyToken (st->blockName, prev);
				else
				{
					/*  For an anonymous struct or union we use a unique ID
					 *  a number, so that the members can be found.
					 */
					char buf [20];  /* length of "_anon" + digits  + null */
					sprintf (buf, "__anon%d", ++AnonymousID);
					vStringCopyS (st->blockName->name, buf);
					st->blockName->type = TOKEN_NAME;
					st->blockName->keyword = KEYWORD_NONE;
				}
				qualifyBlockTag (st, prev);
			}
			else if (isLanguage (Lang_csharp))
				makeTag (prev, st, FALSE, TAG_PROPERTY);
			break;

		case TOKEN_SEMICOLON:
		case TOKEN_COMMA:
			if (insideEnumBody (st))
				;
			else if (isType (prev, TOKEN_NAME))
			{
				if (isContextualKeyword (prev2))
					makeTag (prev, st, TRUE, TAG_EXTERN_VAR);
				else
					qualifyVariableTag (st, prev);
			}
			else if (isType (prev, TOKEN_ARGS)  &&  isType (prev2, TOKEN_NAME))
			{
				if (st->isPointer)
					qualifyVariableTag (st, prev2);
				else
					qualifyFunctionDeclTag (st, prev2);
			}
			if (isLanguage (Lang_java) && token->type == TOKEN_SEMICOLON && insideEnumBody (st))
			{
				/* In Java, after an initial enum-like part,
				 * a semicolon introduces a class-like part.
				 * See Bug #1730485 for the full rationale. */
				st->parent->declaration = DECL_CLASS;
			}
			break;

		default: break;
	}
}

/*  Parses the current file and decides whether to write out and tags that
 *  are discovered.
 */
static void createTags (const unsigned int nestLevel,
						statementInfo *const parent)
{
	statementInfo *const st = newStatement (parent);

	DebugStatement ( if (nestLevel > 0) debugParseNest (TRUE, nestLevel); )
	while (TRUE)
	{
		tokenInfo *token;

		nextToken (st);
		token = activeToken (st);
		if (isType (token, TOKEN_BRACE_CLOSE))
		{
			if (nestLevel > 0)
				break;
			else
			{
				verbose ("%s: unexpected closing brace at line %lu\n",
						getInputFileName (), getInputLineNumber ());
				longjmp (Exception, (int) ExceptionBraceFormattingError);
			}
		}
		else if (isType (token, TOKEN_DOUBLE_COLON))
		{
			addContext (st, prevToken (st, 1));
			advanceToken (st);
		}
		else
		{
			tagCheck (st);
			if (isType (token, TOKEN_BRACE_OPEN))
				nest (st, nestLevel + 1);
			checkStatementEnd (st);
		}
	}
	deleteStatement ();
	DebugStatement ( if (nestLevel > 0) debugParseNest (FALSE, nestLevel - 1); )
}

static boolean findCTags (const unsigned int passCount)
{
	exception_t exception;
	boolean retry;

	Assert (passCount < 3);
	cppInit ((boolean) (passCount > 1), isLanguage (Lang_csharp));
	Signature = vStringNew ();

	exception = (exception_t) setjmp (Exception);
	retry = FALSE;
	if (exception == ExceptionNone)
		createTags (0, NULL);
	else
	{
		deleteAllStatements ();
		if (exception == ExceptionBraceFormattingError  &&  passCount == 1)
		{
			retry = TRUE;
		   verbose ("%s: retrying file with fallback brace matching algorithm\n",
					getInputFileName ());
		}
	}
	vStringDelete (Signature);
	cppTerminate ();
	return retry;
}

static void buildKeywordHash (const langType language, unsigned int idx)
{
	const size_t count = sizeof (KeywordTable) / sizeof (KeywordTable [0]);
	size_t i;
	for (i = 0  ;  i < count  ;  ++i)
	{
		const keywordDesc* const p = &KeywordTable [i];
		if (p->isValid [idx])
			addKeyword (p->name, language, (int) p->id);
	}
}

static void initializeCParser (const langType language)
{
	Lang_c = language;
	buildKeywordHash (language, 0);
}

static void initializeCppParser (const langType language)
{
	Lang_cpp = language;
	buildKeywordHash (language, 1);
}

static void initializeCsharpParser (const langType language)
{
	Lang_csharp = language;
	buildKeywordHash (language, 2);
}

static void initializeJavaParser (const langType language)
{
	Lang_java = language;
	buildKeywordHash (language, 3);
}

static void initializeVeraParser (const langType language)
{
	Lang_vera = language;
	buildKeywordHash (language, 4);
}

extern parserDefinition* CParser (void)
{
	static const char *const extensions [] = { "c", NULL };
	parserDefinition* def = parserNew ("C");
	def->kinds      = CKinds;
	def->kindCount  = KIND_COUNT (CKinds);
	def->extensions = extensions;
	def->parser2    = findCTags;
	def->initialize = initializeCParser;
	return def;
}

extern parserDefinition* CppParser (void)
{
	static const char *const extensions [] = {
		"c++", "cc", "cp", "cpp", "cxx", "h", "h++", "hh", "hp", "hpp", "hxx",
#ifndef CASE_INSENSITIVE_FILENAMES
		"C", "H",
#endif
		NULL
	};
	parserDefinition* def = parserNew ("C++");
	def->kinds      = CKinds;
	def->kindCount  = KIND_COUNT (CKinds);
	def->extensions = extensions;
	def->parser2    = findCTags;
	def->initialize = initializeCppParser;
	return def;
}

extern parserDefinition* CsharpParser (void)
{
	static const char *const extensions [] = { "cs", NULL };
	parserDefinition* def = parserNew ("C#");
	def->kinds      = CsharpKinds;
	def->kindCount  = KIND_COUNT (CsharpKinds);
	def->extensions = extensions;
	def->parser2    = findCTags;
	def->initialize = initializeCsharpParser;
	return def;
}

extern parserDefinition* JavaParser (void)
{
	static const char *const extensions [] = { "java", NULL };
	parserDefinition* def = parserNew ("Java");
	def->kinds      = JavaKinds;
	def->kindCount  = KIND_COUNT (JavaKinds);
	def->extensions = extensions;
	def->parser2    = findCTags;
	def->initialize = initializeJavaParser;
	return def;
}

extern parserDefinition* VeraParser (void)
{
	static const char *const extensions [] = { "vr", "vri", "vrh", NULL };
	parserDefinition* def = parserNew ("Vera");
	def->kinds      = VeraKinds;
	def->kindCount  = KIND_COUNT (VeraKinds);
	def->extensions = extensions;
	def->parser2    = findCTags;
	def->initialize = initializeVeraParser;
	return def;
}

/* vi:set tabstop=4 shiftwidth=4 noexpandtab: */

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.