cosmopolitan/third_party/ctags/eiffel.c

1305 lines
31 KiB
C

/*
* $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 "third_party/ctags/general.h"
/* must always come first */
#include "libc/calls/calls.h"
#include "libc/conv/conv.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "third_party/ctags/debug.h"
#include "third_party/ctags/entry.h"
#include "third_party/ctags/keyword.h"
#include "third_party/ctags/options.h"
#include "third_party/ctags/parse.h"
#include "third_party/ctags/read.h"
#include "third_party/ctags/routines.h"
#include "third_party/ctags/vstring.h"
/*
* 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: */