// clang-format off
/*
*   $Id: eiffel.c 748 2009-11-06 02:44:42Z dhiebert $
*
*   Copyright (c) 1998-2002, Darren Hiebert
*
*   This source code is released for free distribution under the terms of the
*   GNU General Public License.
*
*   This module contains functions for generating tags for Eiffel language
*   files.
*/

/*
*   INCLUDE FILES
*/
#include "libc/fmt/conv.h"
#include "third_party/ctags/general.h"  /* must always come first */

#ifdef TYPE_REFERENCE_TOOL
#include "libc/calls/calls.h"
#include "libc/calls/dprintf.h"
#include "libc/calls/weirdtypes.h"
#include "libc/fmt/fmt.h"
#include "libc/mem/fmt.h"
#include "libc/stdio/stdio.h"
#include "libc/stdio/temp.h"
#include "third_party/musl/tempnam.h"
#endif
#include "libc/mem/alg.h"
#include "libc/str/str.h"
#include "libc/limits.h"
#include "libc/sysv/consts/_posix.h"
#include "libc/sysv/consts/iov.h"
#include "libc/sysv/consts/limits.h"
#include "libc/sysv/consts/xopen.h"
#include "libc/thread/thread.h"
#include "libc/str/str.h"  /* to define tolower () */
#include "libc/runtime/runtime.h"

#include "third_party/ctags/debug.h"
#include "third_party/ctags/keyword.h"
#include "third_party/ctags/routines.h"
#include "third_party/ctags/vstring.h"
#ifndef TYPE_REFERENCE_TOOL
#include "third_party/ctags/entry.h"
#include "third_party/ctags/options.h"
#include "third_party/ctags/parse.h"
#include "third_party/ctags/read.h"
#endif

/*
*   MACROS
*/
#define isident(c)            (isalnum(c) || (c) == '_')
#define isFreeOperatorChar(c) ((c) == '@' || (c) == '#' || \
                               (c) == '|' || (c) == '&')
#define isType(token,t)       (boolean) ((token)->type == (t))
#define isKeyword(token,k)    (boolean) ((token)->keyword == (k))

/*
*   DATA DECLARATIONS
*/

typedef enum eException { ExceptionNone, ExceptionEOF } exception_t;

/*  Used to specify type of keyword.
 */
typedef enum eKeywordId {
	KEYWORD_NONE = -1,
	KEYWORD_alias, KEYWORD_all, KEYWORD_and,
	KEYWORD_as, KEYWORD_assign, KEYWORD_attached,
	KEYWORD_check, KEYWORD_class, KEYWORD_convert, KEYWORD_create,
	KEYWORD_creation, KEYWORD_Current,
	KEYWORD_debug, KEYWORD_deferred, KEYWORD_detachable, KEYWORD_do,
	KEYWORD_else, KEYWORD_elseif, KEYWORD_end, KEYWORD_ensure,
	KEYWORD_expanded, KEYWORD_export, KEYWORD_external,
	KEYWORD_false, KEYWORD_feature, KEYWORD_from, KEYWORD_frozen,
	KEYWORD_if, KEYWORD_implies,
	KEYWORD_indexing, KEYWORD_infix, KEYWORD_inherit, KEYWORD_inspect,
	KEYWORD_invariant, KEYWORD_is, KEYWORD_like, KEYWORD_local,
	KEYWORD_loop, KEYWORD_not, KEYWORD_obsolete, KEYWORD_old, KEYWORD_once,
	KEYWORD_or, KEYWORD_prefix, KEYWORD_redefine, KEYWORD_rename,
	KEYWORD_require, KEYWORD_rescue, KEYWORD_Result, KEYWORD_retry,
	KEYWORD_select, KEYWORD_separate, KEYWORD_strip, KEYWORD_then,
	KEYWORD_true, KEYWORD_undefine, KEYWORD_unique, KEYWORD_until,
	KEYWORD_variant, KEYWORD_when, KEYWORD_xor
} keywordId;

/*  Used to determine whether keyword is valid for the token language and
 *  what its ID is.
 */
typedef struct sKeywordDesc {
	const char *name;
	keywordId id;
} keywordDesc;

typedef enum eTokenType {
	TOKEN_UNDEFINED,
	TOKEN_BANG,
	TOKEN_CHARACTER,
	TOKEN_CLOSE_BRACE,
	TOKEN_CLOSE_BRACKET,
	TOKEN_CLOSE_PAREN,
	TOKEN_COLON,
	TOKEN_COMMA,
	TOKEN_CONSTRAINT,
	TOKEN_DOT,
	TOKEN_DOLLAR,
	TOKEN_IDENTIFIER,
	TOKEN_KEYWORD,
	TOKEN_NUMERIC,
	TOKEN_OPEN_BRACE,
	TOKEN_OPEN_BRACKET,
	TOKEN_OPEN_PAREN,
	TOKEN_OPERATOR,
	TOKEN_OTHER,
	TOKEN_QUESTION,
	TOKEN_SEMICOLON,
	TOKEN_SEPARATOR,
	TOKEN_STRING,
	TOKEN_TILDE
} tokenType;

typedef struct sTokenInfo {
	tokenType type;
	keywordId keyword;
	boolean   isExported;
	vString*  string;
	vString*  className;
	vString*  featureName;
} tokenInfo;

/*
*   DATA DEFINITIONS
*/

static langType Lang_eiffel;

#ifdef TYPE_REFERENCE_TOOL

static const char *FileName;
static FILE *File;
static int PrintClass;
static int PrintReferences;
static int SelfReferences;
static int Debug;
static stringList *GenericNames;
static stringList *ReferencedTypes;

#else

typedef enum {
	EKIND_CLASS, EKIND_FEATURE, EKIND_LOCAL, EKIND_QUALIFIED_TAGS
} eiffelKind;

static kindOption EiffelKinds [] = {
	{ TRUE,  'c', "class",   "classes"},
	{ TRUE,  'f', "feature", "features"},
	{ FALSE, 'l', "local",   "local entities"}
};

#endif

static jmp_buf Exception;

static const keywordDesc EiffelKeywordTable [] = {
	/* keyword          keyword ID */
	{ "alias",          KEYWORD_alias      },
	{ "all",            KEYWORD_all        },
	{ "and",            KEYWORD_and        },
	{ "as",             KEYWORD_as         },
	{ "assign",         KEYWORD_assign     },
	{ "attached",       KEYWORD_attached   },
	{ "check",          KEYWORD_check      },
	{ "class",          KEYWORD_class      },
	{ "convert",        KEYWORD_convert    },
	{ "create",         KEYWORD_create     },
	{ "creation",       KEYWORD_creation   },
	{ "current",        KEYWORD_Current    },
	{ "debug",          KEYWORD_debug      },
	{ "deferred",       KEYWORD_deferred   },
	{ "detachable",     KEYWORD_detachable },
	{ "do",             KEYWORD_do         },
	{ "else",           KEYWORD_else       },
	{ "elseif",         KEYWORD_elseif     },
	{ "end",            KEYWORD_end        },
	{ "ensure",         KEYWORD_ensure     },
	{ "expanded",       KEYWORD_expanded   },
	{ "export",         KEYWORD_export     },
	{ "external",       KEYWORD_external   },
	{ "false",          KEYWORD_false      },
	{ "feature",        KEYWORD_feature    },
	{ "from",           KEYWORD_from       },
	{ "frozen",         KEYWORD_frozen     },
	{ "if",             KEYWORD_if         },
	{ "implies",        KEYWORD_implies    },
	{ "indexing",       KEYWORD_indexing   },
	{ "infix",          KEYWORD_infix      },
	{ "inherit",        KEYWORD_inherit    },
	{ "inspect",        KEYWORD_inspect    },
	{ "invariant",      KEYWORD_invariant  },
	{ "is",             KEYWORD_is         },
	{ "like",           KEYWORD_like       },
	{ "local",          KEYWORD_local      },
	{ "loop",           KEYWORD_loop       },
	{ "not",            KEYWORD_not        },
	{ "obsolete",       KEYWORD_obsolete   },
	{ "old",            KEYWORD_old        },
	{ "once",           KEYWORD_once       },
	{ "or",             KEYWORD_or         },
	{ "prefix",         KEYWORD_prefix     },
	{ "redefine",       KEYWORD_redefine   },
	{ "rename",         KEYWORD_rename     },
	{ "require",        KEYWORD_require    },
	{ "rescue",         KEYWORD_rescue     },
	{ "result",         KEYWORD_Result     },
	{ "retry",          KEYWORD_retry      },
	{ "select",         KEYWORD_select     },
	{ "separate",       KEYWORD_separate   },
	{ "strip",          KEYWORD_strip      },
	{ "then",           KEYWORD_then       },
	{ "true",           KEYWORD_true       },
	{ "undefine",       KEYWORD_undefine   },
	{ "unique",         KEYWORD_unique     },
	{ "until",          KEYWORD_until      },
	{ "variant",        KEYWORD_variant    },
	{ "when",           KEYWORD_when       },
	{ "xor",            KEYWORD_xor        }
};

/*
*   FUNCTION DEFINITIONS
*/

static void buildEiffelKeywordHash (void)
{
	const size_t count = sizeof (EiffelKeywordTable) /
						 sizeof (EiffelKeywordTable [0]);
	size_t i;
	for (i = 0  ;  i < count  ;  ++i)
	{
		const keywordDesc* const p = &EiffelKeywordTable [i];
		addKeyword (p->name, Lang_eiffel, (int) p->id);
	}
}

#ifdef TYPE_REFERENCE_TOOL

static void addGenericName (tokenInfo *const token)
{
	vStringUpper (token->string);
	if (vStringLength (token->string) > 0)
		stringListAdd (GenericNames, vStringNewCopy (token->string));
}

static boolean isGeneric (tokenInfo *const token)
{
	return (boolean) stringListHas (GenericNames, vStringValue (token->string));
}

static void reportType (tokenInfo *const token)
{
	vStringUpper (token->string);
	if (vStringLength (token->string) > 0  && ! isGeneric (token)  &&
		(SelfReferences || strcmp (vStringValue (
			token->string), vStringValue (token->className)) != 0) &&
		! stringListHas (ReferencedTypes, vStringValue (token->string)))
	{
		printf ("%s\n", vStringValue (token->string));
		stringListAdd (ReferencedTypes, vStringNewCopy (token->string));
	}
}

static int fileGetc (void)
{
	int c = getc (File);
	if (c == '\r')
	{
		c = getc (File);
		if (c != '\n')
		{
			ungetc (c, File);
			c = '\n';
		}
	}
	if (Debug > 0  &&  c != EOF)
		putc (c, errout);
	return c;
}

static int fileUngetc (c)
{
	return ungetc (c, File);
}

extern char *readLine (vString *const vLine, FILE *const fp)
{
	return NULL;
}

#else

/*
*   Tag generation functions
*/

static void makeEiffelClassTag (tokenInfo *const token)
{
	if (EiffelKinds [EKIND_CLASS].enabled)
	{
		const char *const name = vStringValue (token->string);
		tagEntryInfo e;

		initTagEntry (&e, name);

		e.kindName = EiffelKinds [EKIND_CLASS].name;
		e.kind     = EiffelKinds [EKIND_CLASS].letter;

		makeTagEntry (&e);
	}
	vStringCopy (token->className, token->string);
}

static void makeEiffelFeatureTag (tokenInfo *const token)
{
	if (EiffelKinds [EKIND_FEATURE].enabled  &&
		(token->isExported  ||  Option.include.fileScope))
	{
		const char *const name = vStringValue (token->string);
		tagEntryInfo e;

		initTagEntry (&e, name);

		e.isFileScope = (boolean) (! token->isExported);
		e.kindName    = EiffelKinds [EKIND_FEATURE].name;
		e.kind        = EiffelKinds [EKIND_FEATURE].letter;
		e.extensionFields.scope [0] = EiffelKinds [EKIND_CLASS].name;
		e.extensionFields.scope [1] = vStringValue (token->className);

		makeTagEntry (&e);

		if (Option.include.qualifiedTags)
		{
			vString* qualified = vStringNewInit (vStringValue (token->className));
			vStringPut (qualified, '.');
			vStringCat (qualified, token->string);
			e.name = vStringValue (qualified);
			makeTagEntry (&e);
			vStringDelete (qualified);
		}
	}
	vStringCopy (token->featureName, token->string);
}

static void makeEiffelLocalTag (tokenInfo *const token)
{
	if (EiffelKinds [EKIND_LOCAL].enabled && Option.include.fileScope)
	{
		const char *const name = vStringValue (token->string);
		vString* scope = vStringNew ();
		tagEntryInfo e;

		initTagEntry (&e, name);

		e.isFileScope = TRUE;
		e.kindName    = EiffelKinds [EKIND_LOCAL].name;
		e.kind        = EiffelKinds [EKIND_LOCAL].letter;

		vStringCopy (scope, token->className);
		vStringPut (scope, '.');
		vStringCat (scope, token->featureName);

		e.extensionFields.scope [0] = EiffelKinds [EKIND_FEATURE].name;
		e.extensionFields.scope [1] = vStringValue (scope);

		makeTagEntry (&e);
		vStringDelete (scope);
	}
}

#endif

/*
*   Parsing functions
*/

static int skipToCharacter (const int c)
{
	int d;

	do
	{
		d = fileGetc ();
	} while (d != EOF  &&  d != c);

	return d;
}

/*  If a numeric is passed in 'c', this is used as the first digit of the
 *  numeric being parsed.
 */
static vString *parseInteger (int c)
{
	vString *string = vStringNew ();

	if (c == '\0')
		c = fileGetc ();
	if (c == '-')
	{
		vStringPut (string, c);
		c = fileGetc ();
	}
	else if (! isdigit (c))
		c = fileGetc ();
	while (c != EOF  &&  (isdigit (c)  ||  c == '_'))
	{
		vStringPut (string, c);
		c = fileGetc ();
	}
	vStringTerminate (string);
	fileUngetc (c);

	return string;
}

static vString *parseNumeric (int c)
{
	vString *string = vStringNew ();
	vString *integer = parseInteger (c);
	vStringCopy (string, integer);
	vStringDelete (integer);

	c = fileGetc ();
	if (c == '.')
	{
		integer = parseInteger ('\0');
		vStringPut (string, c);
		vStringCat (string, integer);
		vStringDelete (integer);
		c = fileGetc ();
	}
	if (tolower (c) == 'e')
	{
		integer = parseInteger ('\0');
		vStringPut (string, c);
		vStringCat (string, integer);
		vStringDelete (integer);
	}
	else if (!isspace (c))
		fileUngetc (c);

	vStringTerminate (string);

	return string;
}

static int parseEscapedCharacter (void)
{
	int d = '\0';
	int c = fileGetc ();

	switch (c)
	{
		case 'A':  d = '@';   break;
		case 'B':  d = '\b';  break;
		case 'C':  d = '^';   break;
		case 'D':  d = '$';   break;
		case 'F':  d = '\f';  break;
		case 'H':  d = '\\';  break;
		case 'L':  d = '~';   break;
		case 'N':  d = '\n';  break;
#ifdef QDOS
		case 'Q':  d = 0x9F;  break;
#else
		case 'Q':  d = '`';   break;
#endif
		case 'R':  d = '\r';  break;
		case 'S':  d = '#';   break;
		case 'T':  d = '\t';  break;
		case 'U':  d = '\0';  break;
		case 'V':  d = '|';   break;
		case '%':  d = '%';   break;
		case '\'': d = '\'';  break;
		case '"':  d = '"';   break;
		case '(':  d = '[';   break;
		case ')':  d = ']';   break;
		case '<':  d = '{';   break;
		case '>':  d = '}';   break;

		case '\n': skipToCharacter ('%'); break;

		case '/':
		{
			vString *string = parseInteger ('\0');
			const char *value = vStringValue (string);
			const unsigned long ascii = atol (value);
			vStringDelete (string);

			c = fileGetc ();
			if (c == '/'  &&  ascii < 256)
				d = ascii;
			break;
		}

		default: break;
	}
	return d;
}

static int parseCharacter (void)
{
	int c = fileGetc ();
	int result = c;

	if (c == '%')
		result = parseEscapedCharacter ();

	c = fileGetc ();
	if (c != '\'')
		skipToCharacter ('\n');

	return result;
}

static void parseString (vString *const string)
{
	boolean verbatim = FALSE;
	boolean align = FALSE;
	boolean end = FALSE;
	vString *verbatimCloser = vStringNew ();
	vString *lastLine = vStringNew ();
	int prev = '\0';
	int c;

	while (! end)
	{
		c = fileGetc ();
		if (c == EOF)
			end = TRUE;
		else if (c == '"')
		{
			if (! verbatim)
				end = TRUE;
			else
				end = (boolean) (strcmp (vStringValue (lastLine),
				                         vStringValue (verbatimCloser)) == 0);
		}
		else if (c == '\n')
		{
			if (verbatim)
				vStringClear (lastLine);
			if (prev == '[' /* ||  prev == '{' */)
			{
				verbatim = TRUE;
				vStringClear (verbatimCloser);
				vStringClear (lastLine);
				if (prev == '{')
					vStringPut (verbatimCloser, '}');
				else
				{
					vStringPut (verbatimCloser, ']');
					align = TRUE;
				}
				vStringNCat (verbatimCloser, string, vStringLength (string) - 1);
				vStringClear (string);
			}
			if (verbatim && align)
			{
				do
					c = fileGetc ();
				while (isspace (c));
			}
		}
		else if (c == '%')
			c = parseEscapedCharacter ();
		if (! end)
		{
			vStringPut (string, c);
			if (verbatim)
			{
				vStringPut (lastLine, c);
				vStringTerminate (lastLine);
			}
			prev = c;
		}
	}
	vStringTerminate (string);
	vStringDelete (lastLine);
	vStringDelete (verbatimCloser);
}

/*  Read a C identifier beginning with "firstChar" and places it into "name".
 */
static void parseIdentifier (vString *const string, const int firstChar)
{
	int c = firstChar;

	do
	{
		vStringPut (string, c);
		c = fileGetc ();
	} while (isident (c));

	vStringTerminate (string);
	if (!isspace (c))
		fileUngetc (c);  /* unget non-identifier character */
}

static void parseFreeOperator (vString *const string, const int firstChar)
{
	int c = firstChar;

	do
	{
		vStringPut (string, c);
		c = fileGetc ();
	} while (c > ' ');

	vStringTerminate (string);
	if (!isspace (c))
		fileUngetc (c);  /* unget non-identifier character */
}

static void copyToken (tokenInfo* dst, const tokenInfo *src)
{
	dst->type       = src->type;
	dst->keyword    = src->keyword;
	dst->isExported = src->isExported;

	vStringCopy (dst->string, src->string);
	vStringCopy (dst->className, src->className);
	vStringCopy (dst->featureName, src->featureName);
}

static tokenInfo *newToken (void)
{
	tokenInfo *const token = xMalloc (1, tokenInfo);

	token->type			= TOKEN_UNDEFINED;
	token->keyword		= KEYWORD_NONE;
	token->isExported	= TRUE;

	token->string = vStringNew ();
	token->className = vStringNew ();
	token->featureName = vStringNew ();

	return token;
}

static void deleteToken (tokenInfo *const token)
{
	vStringDelete (token->string);
	vStringDelete (token->className);
	vStringDelete (token->featureName);

	eFree (token);
}

static void readToken (tokenInfo *const token)
{
	int c;

	token->type    = TOKEN_UNDEFINED;
	token->keyword = KEYWORD_NONE;
	vStringClear (token->string);

getNextChar:

	do
		c = fileGetc ();
	while (c == '\t'  ||  c == ' '  ||  c == '\n');

	switch (c)
	{
		case EOF:  longjmp (Exception, (int)ExceptionEOF); break;
		case ';':  token->type = TOKEN_SEMICOLON;          break;
		case '!':  token->type = TOKEN_BANG;               break;
		case '}':  token->type = TOKEN_CLOSE_BRACE;        break;
		case ']':  token->type = TOKEN_CLOSE_BRACKET;      break;
		case ')':  token->type = TOKEN_CLOSE_PAREN;        break;
		case ',':  token->type = TOKEN_COMMA;              break;
		case '$':  token->type = TOKEN_DOLLAR;             break;
		case '.':  token->type = TOKEN_DOT;                break;
		case '{':  token->type = TOKEN_OPEN_BRACE;         break;
		case '[':  token->type = TOKEN_OPEN_BRACKET;       break;
		case '(':  token->type = TOKEN_OPEN_PAREN;         break;
		case '~':  token->type = TOKEN_TILDE;              break;


		case '+':
		case '*':
		case '^':
		case '=':  token->type = TOKEN_OPERATOR;           break;

		case '-':
			c = fileGetc ();
			if (c == '>')
				token->type = TOKEN_CONSTRAINT;
			else if (c == '-')  /* is this the start of a comment? */
			{
				skipToCharacter ('\n');
				goto getNextChar;
			}
			else
			{
				if (!isspace (c))
					fileUngetc (c);
				token->type = TOKEN_OPERATOR;
			}
			break;

		case '?':
		case ':':
		{
			int c2 = fileGetc ();
			if (c2 == '=')
				token->type = TOKEN_OPERATOR;
			else
			{
				if (!isspace (c2))
					fileUngetc (c2);
				if (c == ':')
					token->type = TOKEN_COLON;
				else
					token->type = TOKEN_QUESTION;
			}
			break;
		}

		case '<':
			c = fileGetc ();
			if (c != '='  &&  c != '>'  &&  !isspace (c))
				fileUngetc (c);
			token->type = TOKEN_OPERATOR;
			break;

		case '>':
			c = fileGetc ();
			if (c != '='  &&  c != '>'  &&  !isspace (c))
				fileUngetc (c);
			token->type = TOKEN_OPERATOR;
			break;

		case '/':
			c = fileGetc ();
			if (c != '/'  &&  c != '='  &&  !isspace (c))
				fileUngetc (c);
			token->type = TOKEN_OPERATOR;
			break;

		case '\\':
			c = fileGetc ();
			if (c != '\\'  &&  !isspace (c))
				fileUngetc (c);
			token->type = TOKEN_OPERATOR;
			break;

		case '"':
			token->type = TOKEN_STRING;
			parseString (token->string);
			break;

		case '\'':
			token->type = TOKEN_CHARACTER;
			parseCharacter ();
			break;

		default:
			if (isalpha (c))
			{
				parseIdentifier (token->string, c);
				token->keyword = analyzeToken (token->string, Lang_eiffel);
				if (isKeyword (token, KEYWORD_NONE))
					token->type = TOKEN_IDENTIFIER;
				else
					token->type = TOKEN_KEYWORD;
			}
			else if (isdigit (c))
			{
				vString* numeric = parseNumeric (c);
				vStringCat (token->string, numeric);
				vStringDelete (numeric);
				token->type = TOKEN_NUMERIC;
			}
			else if (isFreeOperatorChar (c))
			{
				parseFreeOperator (token->string, c);
				token->type = TOKEN_OPERATOR;
			}
			else
			{
				token->type = TOKEN_UNDEFINED;
				Assert (! isType (token, TOKEN_UNDEFINED));
			}
			break;
	}
}

/*
*   Scanning functions
*/

static boolean isIdentifierMatch (
		const tokenInfo *const token, const char *const name)
{
	return (boolean) (isType (token, TOKEN_IDENTIFIER)  &&
		strcasecmp (vStringValue (token->string), name) == 0);
}

static void findToken (tokenInfo *const token, const tokenType type)
{
	while (! isType (token, type))
		readToken (token);
}

static void findKeyword (tokenInfo *const token, const keywordId keyword)
{
	while (! isKeyword (token, keyword))
		readToken (token);
}

static boolean parseType (tokenInfo *const token);

static void parseGeneric (tokenInfo *const token, boolean declaration __unused)
{
	unsigned int depth = 0;
#ifdef TYPE_REFERENCE_TOOL
	boolean constraint = FALSE;
#endif
	Assert (isType (token, TOKEN_OPEN_BRACKET));
	do
	{
		if (isType (token, TOKEN_OPEN_BRACKET))
		{
			++depth;
			readToken (token);
		}
		else if (isType (token, TOKEN_CLOSE_BRACKET))
		{
			--depth;
			readToken (token);
		}
#ifdef TYPE_REFERENCE_TOOL
		else if (declaration)
		{
			boolean advanced = FALSE;
			if (depth == 1)
			{
				if (isType (token, TOKEN_CONSTRAINT))
					constraint = TRUE;
				else if (isKeyword (token, KEYWORD_create))
					findKeyword (token, KEYWORD_end);
				else if (isType (token, TOKEN_IDENTIFIER))
				{
					if (constraint)
						advanced = parseType (token);
					else
						addGenericName (token);
					constraint = FALSE;
				}
			}
			else if (isType (token, TOKEN_IDENTIFIER))
				advanced = parseType (token);
			if (! advanced)
				readToken (token);
		}
#endif
		else
			parseType (token);
	} while (depth > 0);
}

static boolean parseType (tokenInfo *const token)
{
	tokenInfo* const id = newToken ();
	copyToken (id, token);
	readToken (token);
	if (isType (token, TOKEN_COLON))  /* check for "{entity: TYPE}" */
	{
		readToken (id);
		readToken (token);
	}
	if (isKeyword (id, KEYWORD_like))
	{
		if (isType (token, TOKEN_IDENTIFIER) ||
				isKeyword (token, KEYWORD_Current))
			readToken (token);
	}
	else
	{
		if (isKeyword (id, KEYWORD_attached) ||
		    isKeyword (id, KEYWORD_detachable) ||
		    isKeyword (id, KEYWORD_expanded))
		{
			copyToken (id, token);
			readToken (token);
		}
		if (isType (id, TOKEN_IDENTIFIER))
		{
#ifdef TYPE_REFERENCE_TOOL
			reportType (id);
#endif
			if (isType (token, TOKEN_OPEN_BRACKET))
				parseGeneric (token, FALSE);
			else if ((strcmp ("BIT", vStringValue (id->string)) == 0))
				readToken (token);  /* read token after number of bits */
		}
	}
	deleteToken (id);
	return TRUE;
}

static void parseEntityType (tokenInfo *const token)
{
	Assert (isType (token, TOKEN_COLON));
	readToken (token);

	if (isType (token, TOKEN_BANG) || isType (token, TOKEN_QUESTION))
		readToken (token);  /* skip over '!' or '?' */
	parseType (token);
}

static void parseLocal (tokenInfo *const token)
{
	Assert (isKeyword (token, KEYWORD_local));
	readToken (token);

	/*  Check keyword first in case local clause is empty
	 */
	while (! isKeyword (token, KEYWORD_do)  &&
		   ! isKeyword (token, KEYWORD_once))
	{
#ifndef TYPE_REFERENCE_TOOL
		if (isType (token, TOKEN_IDENTIFIER))
			makeEiffelLocalTag (token);
#endif
		readToken (token);
		if (isType (token, TOKEN_COLON))
			parseEntityType (token);
	}
}

static void findFeatureEnd (tokenInfo *const token)
{
	boolean isFound = isKeyword (token, KEYWORD_is);
	if (isFound)
		readToken (token);
	switch (token->keyword)
	{
		case KEYWORD_deferred:
		case KEYWORD_do:
		case KEYWORD_external:
		case KEYWORD_local:
		case KEYWORD_obsolete:
		case KEYWORD_once:
		case KEYWORD_require:
		{
			int depth = 1;

			while (depth > 0)
			{
#ifdef TYPE_REFERENCE_TOOL
				if (isType (token, TOKEN_OPEN_BRACE))
				{
					readToken (token);
					if (isType (token, TOKEN_IDENTIFIER))
						parseType (token);
				}
				else if (isType (token, TOKEN_BANG))
				{
					readToken (token);
					if (isType (token, TOKEN_IDENTIFIER))
						parseType (token);
					if (isType (token, TOKEN_BANG))
						readToken (token);
				}
				else
#endif
				switch (token->keyword)
				{
					case KEYWORD_check:
					case KEYWORD_debug:
					case KEYWORD_from:
					case KEYWORD_if:
					case KEYWORD_inspect:
						++depth;
						break;

					case KEYWORD_local:
						parseLocal (token);
						break;

					case KEYWORD_end:
						--depth;
						break;

					default:
						break;
				}
				readToken (token);
			}
			break;
		}

		default:
			/* is this a manifest constant? */
			if (isFound || isType (token, TOKEN_OPERATOR)) {
				if (isType (token, TOKEN_OPERATOR))
					readToken (token);
				readToken (token);
			}
			break;
	}
}

static boolean readFeatureName (tokenInfo *const token)
{
	boolean isFeatureName = FALSE;

	if (isKeyword (token, KEYWORD_frozen))
		readToken (token);
	if (isType (token, TOKEN_IDENTIFIER))
		isFeatureName = TRUE;
	else if (isKeyword (token, KEYWORD_assign))  /* legacy code */
		isFeatureName = TRUE;
	else if (isKeyword (token, KEYWORD_infix)  ||
			isKeyword (token, KEYWORD_prefix))
	{
		readToken (token);
		if (isType (token, TOKEN_STRING))
			isFeatureName = TRUE;
	}
	return isFeatureName;
}

static void parseArguments (tokenInfo *const token)
{
#ifndef TYPE_REFERENCE_TOOL
	findToken (token, TOKEN_CLOSE_PAREN);
	readToken (token);
#else
	Assert (isType (token, TOKEN_OPEN_PAREN));
	readToken (token);
	do
	{
		if (isType (token, TOKEN_COLON))
			parseEntityType (token);
		else
			readToken (token);
	} while (! isType (token, TOKEN_CLOSE_PAREN));
	readToken (token);
#endif
}

static boolean parseFeature (tokenInfo *const token)
{
	boolean found = FALSE;
	while (readFeatureName (token))
	{
		found = TRUE;
#ifndef TYPE_REFERENCE_TOOL
		makeEiffelFeatureTag (token);
#endif
		readToken (token);
		if (isType (token, TOKEN_COMMA))
			readToken (token);
	}
	if (found)
	{
		if (isKeyword (token, KEYWORD_alias)) {
			readToken (token);
#ifndef TYPE_REFERENCE_TOOL
			if (isType (token, TOKEN_STRING))
				makeEiffelFeatureTag (token);
#endif
			readToken (token);
		}
		if (isType (token, TOKEN_OPEN_PAREN))  /* arguments? */
			parseArguments (token);
		if (isType (token, TOKEN_COLON))       /* a query? */
			parseEntityType (token);
		if (isKeyword (token, KEYWORD_assign))
		{
			readToken (token);
			readToken (token);
		}
		if (isKeyword (token, KEYWORD_obsolete))
		{
			readToken (token);
			if (isType (token, TOKEN_STRING))
				readToken (token);
		}
		findFeatureEnd (token);
	}
	return found;
}

static void parseExport (tokenInfo *const token)
{
	token->isExported = TRUE;
	readToken (token);
	if (isType (token, TOKEN_OPEN_BRACE))
	{
		token->isExported = FALSE;
		while (! isType (token, TOKEN_CLOSE_BRACE))
		{
			if (isType (token, TOKEN_IDENTIFIER))
				token->isExported |= !isIdentifierMatch (token, "NONE");
			readToken (token);
		}
		readToken (token);
	}
}

static void parseFeatureClauses (tokenInfo *const token)
{
	Assert (isKeyword (token, KEYWORD_feature));
	do
	{
		if (isKeyword (token, KEYWORD_feature))
			parseExport (token);
		if (! isKeyword (token, KEYWORD_feature) &&
			! isKeyword (token, KEYWORD_invariant) &&
			! isKeyword (token, KEYWORD_indexing))
		{
			if (! parseFeature (token))
				readToken (token);
		}
	} while (! isKeyword (token, KEYWORD_end) &&
			 ! isKeyword (token, KEYWORD_invariant) &&
			 ! isKeyword (token, KEYWORD_indexing));
}

static void parseRename (tokenInfo *const token)
{
	Assert (isKeyword (token, KEYWORD_rename));
	do {
		readToken (token);
		if (readFeatureName (token))
		{
			readToken (token);
			if (isKeyword (token, KEYWORD_as))
			{
				readToken (token);
				if (readFeatureName (token))
				{
#ifndef TYPE_REFERENCE_TOOL
					makeEiffelFeatureTag (token);  /* renamed feature */
#endif
					readToken (token);
				}
			}
		}
	} while (isType (token, TOKEN_COMMA));
}

static void parseInherit (tokenInfo *const token)
{
	Assert (isKeyword (token, KEYWORD_inherit));
	readToken (token);
	while (isType (token, TOKEN_IDENTIFIER))
	{
		parseType (token);
		if (isType (token, TOKEN_KEYWORD))
		{
			switch (token->keyword)  /* check for feature adaptation */
			{
				case KEYWORD_rename:
					parseRename (token);
				case KEYWORD_export:
				case KEYWORD_undefine:
				case KEYWORD_redefine:
				case KEYWORD_select:
					findKeyword (token, KEYWORD_end);
					readToken (token);
					break;

				case KEYWORD_end:
					readToken (token);
					break;

				default: break;
			}
		}
		if (isType (token, TOKEN_SEMICOLON))
			readToken (token);
	}
}

static void parseConvert (tokenInfo *const token)
{
	Assert (isKeyword (token, KEYWORD_convert));
	do
	{
		readToken (token);
		if (! isType (token, TOKEN_IDENTIFIER))
			break;
		else if (isType (token, TOKEN_OPEN_PAREN))
		{
			while (! isType (token, TOKEN_CLOSE_PAREN))
				readToken (token);
		}
		else if (isType (token, TOKEN_COLON))
		{
			readToken (token);
			if (! isType (token, TOKEN_OPEN_BRACE))
				break;
			else while (! isType (token, TOKEN_CLOSE_BRACE))
				readToken (token);
		}
	} while (isType (token, TOKEN_COMMA));
}

static void parseClass (tokenInfo *const token)
{
	Assert (isKeyword (token, KEYWORD_class));
	readToken (token);
	if (isType (token, TOKEN_IDENTIFIER))
	{
#ifndef TYPE_REFERENCE_TOOL
		makeEiffelClassTag (token);
		readToken (token);
#else
		vStringCopy (token->className, token->string);
		vStringUpper (token->className);
		if (PrintClass)
			puts (vStringValue (token->className));
		if (! PrintReferences)
			exit (0);
		readToken (token);
#endif
	}

	do
	{
		if (isType (token, TOKEN_OPEN_BRACKET))
			parseGeneric (token, TRUE);
		else if (! isType (token, TOKEN_KEYWORD))
			readToken (token);
		else switch (token->keyword)
		{
			case KEYWORD_inherit:  parseInherit (token);        break;
			case KEYWORD_feature:  parseFeatureClauses (token); break;
			case KEYWORD_convert:  parseConvert (token);        break;
			default:               readToken (token);           break;
		}
	} while (! isKeyword (token, KEYWORD_end));
}

static void initialize (const langType language)
{
	Lang_eiffel = language;
	buildEiffelKeywordHash ();
}

static void findEiffelTags (void)
{
	tokenInfo *const token = newToken ();
	exception_t exception;

	exception = (exception_t) (setjmp (Exception));
	while (exception == ExceptionNone)
	{
		findKeyword (token, KEYWORD_class);
		parseClass (token);
	}
	deleteToken (token);
}

#ifndef TYPE_REFERENCE_TOOL

extern parserDefinition* EiffelParser (void)
{
	static const char *const extensions [] = { "e", NULL };
	parserDefinition* def = parserNew ("Eiffel");
	def->kinds      = EiffelKinds;
	def->kindCount  = KIND_COUNT (EiffelKinds);
	def->extensions = extensions;
	def->parser     = findEiffelTags;
	def->initialize = initialize;
	return def;
}

#else

static void findReferences (void)
{
	ReferencedTypes = stringListNew ();
	GenericNames = stringListNew ();
	initialize (0);

	findEiffelTags ();

	stringListDelete (GenericNames);
	GenericNames = NULL;
	stringListDelete (ReferencedTypes);
	ReferencedTypes = NULL;
}

static const char *const Usage =
	"Prints names of types referenced by an Eiffel language file.\n"
	"\n"
	"Usage: %s [-cdrs] [file_name | -]\n"
	"\n"
	"Options:\n"
	"    -c    Print class name of current file (on first line of output).\n"
	"    -d    Enable debug output.\n"
	"    -r    Print types referenced by current file (default unless -c).\n"
	"    -s    Include self-references.\n"
	"\n";

extern int main (int argc, char** argv)
{
	int i;
	for (i = 1  ;  argv [i] != NULL  ;  ++i)
	{
		const char *const arg = argv [i];
		if (arg [0] == '-')
		{
			int j;
			if (arg [1] == '\0')
			{
					File = stdin;
					FileName = "stdin";
			}
			else for (j = 1  ;  arg [j] != '\0'  ;  ++j) switch (arg [j])
			{
				case 'c':  PrintClass      = 1;  break;
				case 'r':  PrintReferences = 1;  break;
				case 's':  SelfReferences  = 1;  break;
				case 'd':  Debug           = 1;  break;
				default:
					fprintf (errout, "%s: unknown option: %c\n", argv [0], arg [1]);
					fprintf (errout, Usage, argv [0]);
					exit (1);
					break;
			}
		}
		else if (File != NULL)
		{
			fprintf (errout, Usage, argv [0]);
			exit (1);
		}
		else
		{
			FileName = arg;
			File = fopen (FileName, "r");
			if (File == NULL)
			{
				perror (argv [0]);
				exit (1);
			}
		}
	}
	if (! PrintClass)
		PrintReferences = 1;
	if (File == NULL)
	{
		fprintf (errout, Usage, argv [0]);
		exit (1);
	}
	else
	{
		findReferences ();
		fclose (File);
	}
	return 0;
}

#endif

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