cosmopolitan/third_party/ctags/vhdl.c

738 lines
19 KiB
C

/*
* $Id: vhdl.c 652 2008-04-18 03:51:47Z elliotth $
*
* Copyright (c) 2008, Nicolas Vincent
*
* 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 VHDL files.
*/
#include "third_party/ctags/general.h"
/* must always come first */
#include "third_party/ctags/debug.h"
#include "third_party/ctags/entry.h"
#include "third_party/ctags/keyword.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 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_ABS,
KEYWORD_ACCESS,
KEYWORD_AFTER,
KEYWORD_ALIAS,
KEYWORD_ALL,
KEYWORD_AND,
KEYWORD_ARCHITECTURE,
KEYWORD_ARRAY,
KEYWORD_ASSERT,
KEYWORD_ATTRIBUTE,
KEYWORD_BEGIN,
KEYWORD_BLOCK,
KEYWORD_BODY,
KEYWORD_BUFFER,
KEYWORD_BUS,
KEYWORD_CASE,
KEYWORD_COMPONENT,
KEYWORD_CONFIGURATION,
KEYWORD_CONSTANT,
KEYWORD_DISCONNECT,
KEYWORD_DOWNTO,
KEYWORD_ELSE,
KEYWORD_ELSIF,
KEYWORD_END,
KEYWORD_ENTITY,
KEYWORD_EXIT,
KEYWORD_FILE,
KEYWORD_FOR,
KEYWORD_FUNCTION,
KEYWORD_GENERATE,
KEYWORD_GENERIC,
KEYWORD_GROUP,
KEYWORD_GUARDED,
KEYWORD_IF,
KEYWORD_IMPURE,
KEYWORD_IN,
KEYWORD_INERTIAL,
KEYWORD_INOUT,
KEYWORD_IS,
KEYWORD_LABEL,
KEYWORD_LIBRARY,
KEYWORD_LINKAGE,
KEYWORD_LITERAL,
KEYWORD_LOOP,
KEYWORD_MAP,
KEYWORD_MOD,
KEYWORD_NAND,
KEYWORD_NEW,
KEYWORD_NEXT,
KEYWORD_NOR,
KEYWORD_NOT,
KEYWORD_NULL,
KEYWORD_OF,
KEYWORD_ON,
KEYWORD_OPEN,
KEYWORD_OR,
KEYWORD_OTHERS,
KEYWORD_OUT,
KEYWORD_PACKAGE,
KEYWORD_PORT,
KEYWORD_POSTPONED,
KEYWORD_PROCEDURE,
KEYWORD_PROCESS,
KEYWORD_PURE,
KEYWORD_RANGE,
KEYWORD_RECORD,
KEYWORD_REGISTER,
KEYWORD_REJECT,
KEYWORD_RETURN,
KEYWORD_ROL,
KEYWORD_ROR,
KEYWORD_SELECT,
KEYWORD_SEVERITY,
KEYWORD_SIGNAL,
KEYWORD_SHARED,
KEYWORD_SLA,
KEYWORD_SLI,
KEYWORD_SRA,
KEYWORD_SRL,
KEYWORD_SUBTYPE,
KEYWORD_THEN,
KEYWORD_TO,
KEYWORD_TRANSPORT,
KEYWORD_TYPE,
KEYWORD_UNAFFECTED,
KEYWORD_UNITS,
KEYWORD_UNTIL,
KEYWORD_USE,
KEYWORD_VARIABLE,
KEYWORD_WAIT,
KEYWORD_WHEN,
KEYWORD_WHILE,
KEYWORD_WITH,
KEYWORD_XNOR,
KEYWORD_XOR
} 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;
} keywordDesc;
typedef enum eTokenType {
TOKEN_NONE, /* none */
TOKEN_OPEN_PAREN, /* ( */
TOKEN_CLOSE_PAREN, /* ) */
TOKEN_COMMA, /* the comma character */
TOKEN_IDENTIFIER,
TOKEN_KEYWORD,
TOKEN_PERIOD, /* . */
TOKEN_OPERATOR,
TOKEN_SEMICOLON, /* the semicolon character */
TOKEN_STRING
} tokenType;
typedef struct sTokenInfo {
tokenType type;
keywordId keyword;
vString *string; /* the name of the token */
vString *scope;
unsigned long lineNumber; /* line number of tag */
fpos_t filePosition; /* file position of line containing name */
} tokenInfo;
/*
* DATA DEFINITIONS
*/
static int Lang_vhdl;
static jmp_buf Exception;
/* Used to index into the VhdlKinds table. */
typedef enum {
VHDLTAG_UNDEFINED = -1,
VHDLTAG_CONSTANT,
VHDLTAG_TYPE,
VHDLTAG_SUBTYPE,
VHDLTAG_RECORD,
VHDLTAG_ENTITY,
VHDLTAG_COMPONENT,
VHDLTAG_PROTOTYPE,
VHDLTAG_FUNCTION,
VHDLTAG_PROCEDURE,
VHDLTAG_PACKAGE,
VHDLTAG_LOCAL
} vhdlKind;
static kindOption VhdlKinds[] = {
{TRUE, 'c', "constant", "constant declarations"},
{TRUE, 't', "type", "type definitions"},
{TRUE, 'T', "subtype", "subtype definitions"},
{TRUE, 'r', "record", "record names"},
{TRUE, 'e', "entity", "entity declarations"},
{FALSE, 'C', "component", "component declarations"},
{FALSE, 'd', "prototype", "prototypes"},
{TRUE, 'f', "function", "function prototypes and declarations"},
{TRUE, 'p', "procedure", "procedure prototypes and declarations"},
{TRUE, 'P', "package", "package definitions"},
{FALSE, 'l', "local", "local definitions"}};
static keywordDesc VhdlKeywordTable[] = {
{"abs", KEYWORD_ABS},
{"access", KEYWORD_ACCESS},
{"after", KEYWORD_AFTER},
{"alias", KEYWORD_ALIAS},
{"all", KEYWORD_ALL},
{"and", KEYWORD_AND},
{"architecture", KEYWORD_ARCHITECTURE},
{"array", KEYWORD_ARRAY},
{"assert", KEYWORD_ASSERT},
{"attribute", KEYWORD_ATTRIBUTE},
{"begin", KEYWORD_BEGIN},
{"block", KEYWORD_BLOCK},
{"body", KEYWORD_BODY},
{"buffer", KEYWORD_BUFFER},
{"bus", KEYWORD_BUS},
{"case", KEYWORD_CASE},
{"component", KEYWORD_COMPONENT},
{"configuration", KEYWORD_CONFIGURATION},
{"constant", KEYWORD_CONSTANT},
{"disconnect", KEYWORD_DISCONNECT},
{"downto", KEYWORD_DOWNTO},
{"else", KEYWORD_ELSE},
{"elsif", KEYWORD_ELSIF},
{"end", KEYWORD_END},
{"entity", KEYWORD_ENTITY},
{"exit", KEYWORD_EXIT},
{"file", KEYWORD_FILE},
{"for", KEYWORD_FOR},
{"function", KEYWORD_FUNCTION},
{"generate", KEYWORD_GENERATE},
{"generic", KEYWORD_GENERIC},
{"group", KEYWORD_GROUP},
{"guarded", KEYWORD_GUARDED},
{"if", KEYWORD_IF},
{"impure", KEYWORD_IMPURE},
{"in", KEYWORD_IN},
{"inertial", KEYWORD_INERTIAL},
{"inout", KEYWORD_INOUT},
{"is", KEYWORD_IS},
{"label", KEYWORD_LABEL},
{"library", KEYWORD_LIBRARY},
{"linkage", KEYWORD_LINKAGE},
{"literal", KEYWORD_LITERAL},
{"loop", KEYWORD_LOOP},
{"map", KEYWORD_MAP},
{"mod", KEYWORD_MOD},
{"nand", KEYWORD_NAND},
{"new", KEYWORD_NEW},
{"next", KEYWORD_NEXT},
{"nor", KEYWORD_NOR},
{"not", KEYWORD_NOT},
{"null", KEYWORD_NULL},
{"of", KEYWORD_OF},
{"on", KEYWORD_ON},
{"open", KEYWORD_OPEN},
{"or", KEYWORD_OR},
{"others", KEYWORD_OTHERS},
{"out", KEYWORD_OUT},
{"package", KEYWORD_PACKAGE},
{"port", KEYWORD_PORT},
{"postponed", KEYWORD_POSTPONED},
{"procedure", KEYWORD_PROCEDURE},
{"process", KEYWORD_PROCESS},
{"pure", KEYWORD_PURE},
{"range", KEYWORD_RANGE},
{"record", KEYWORD_RECORD},
{"register", KEYWORD_REGISTER},
{"reject", KEYWORD_REJECT},
{"return", KEYWORD_RETURN},
{"rol", KEYWORD_ROL},
{"ror", KEYWORD_ROR},
{"select", KEYWORD_SELECT},
{"severity", KEYWORD_SEVERITY},
{"signal", KEYWORD_SIGNAL},
{"shared", KEYWORD_SHARED},
{"sla", KEYWORD_SLA},
{"sli", KEYWORD_SLI},
{"sra", KEYWORD_SRA},
{"srl", KEYWORD_SRL},
{"subtype", KEYWORD_SUBTYPE},
{"then", KEYWORD_THEN},
{"to", KEYWORD_TO},
{"transport", KEYWORD_TRANSPORT},
{"type", KEYWORD_TYPE},
{"unaffected", KEYWORD_UNAFFECTED},
{"units", KEYWORD_UNITS},
{"until", KEYWORD_UNTIL},
{"use", KEYWORD_USE},
{"variable", KEYWORD_VARIABLE},
{"wait", KEYWORD_WAIT},
{"when", KEYWORD_WHEN},
{"while", KEYWORD_WHILE},
{"with", KEYWORD_WITH},
{"xnor", KEYWORD_XNOR},
{"xor", KEYWORD_XOR}};
/*
* FUNCTION DECLARATIONS
*/
static void parseKeywords(tokenInfo *const token, boolean local);
/*
* FUNCTION DEFINITIONS
*/
static boolean isIdentChar1(const int c) {
return (boolean)(isalpha(c) || c == '_');
}
static boolean isIdentChar(const int c) {
return (boolean)(isalpha(c) || isdigit(c) || c == '_');
}
static boolean isIdentifierMatch(const tokenInfo *const token,
const vString *const name) {
return (boolean)(
isType(token, TOKEN_IDENTIFIER) &&
strcasecmp(vStringValue(token->string), vStringValue(name)) == 0);
/* XXX this is copy/paste from eiffel.c and slightly modified */
/* shouldn't we use strNcasecmp ? */
}
static boolean isKeywordOrIdent(const tokenInfo *const token,
const keywordId keyword,
const vString *const name) {
return (boolean)(isKeyword(token, keyword) || isIdentifierMatch(token, name));
}
static tokenInfo *newToken(void) {
tokenInfo *const token = xMalloc(1, tokenInfo);
token->type = TOKEN_NONE;
token->keyword = KEYWORD_NONE;
token->string = vStringNew();
token->scope = vStringNew();
token->lineNumber = getSourceLineNumber();
token->filePosition = getInputFilePosition();
return token;
}
static void deleteToken(tokenInfo *const token) {
if (token != NULL) {
vStringDelete(token->string);
vStringDelete(token->scope);
eFree(token);
}
}
/*
* Parsing functions
*/
static void parseString(vString *const string, const int delimiter) {
boolean end = FALSE;
while (!end) {
int c = fileGetc();
if (c == EOF)
end = TRUE;
else if (c == '\\') {
c = fileGetc(); /* This maybe a ' or ". */
vStringPut(string, c);
} else if (c == delimiter)
end = TRUE;
else
vStringPut(string, c);
}
vStringTerminate(string);
}
/* Read a VHDL identifier beginning with "firstChar" and place it into "name".
*/
static void parseIdentifier(vString *const string, const int firstChar) {
int c = firstChar;
Assert(isIdentChar1(c));
do {
vStringPut(string, c);
c = fileGetc();
} while (isIdentChar(c));
vStringTerminate(string);
if (!isspace(c)) fileUngetc(c); /* unget non-identifier character */
}
static void readToken(tokenInfo *const token) {
int c;
token->type = TOKEN_NONE;
token->keyword = KEYWORD_NONE;
vStringClear(token->string);
getNextChar:
do {
c = fileGetc();
token->lineNumber = getSourceLineNumber();
token->filePosition = getInputFilePosition();
} while (c == '\t' || c == ' ' || c == '\n');
switch (c) {
case EOF:
longjmp(Exception, (int)ExceptionEOF);
break;
case '(':
token->type = TOKEN_OPEN_PAREN;
break;
case ')':
token->type = TOKEN_CLOSE_PAREN;
break;
case ';':
token->type = TOKEN_SEMICOLON;
break;
case '.':
token->type = TOKEN_PERIOD;
break;
case ',':
token->type = TOKEN_COMMA;
break;
case '\'': /* only single char are inside simple quotes */
break; /* or it is for attributes so we don't care */
case '"':
token->type = TOKEN_STRING;
parseString(token->string, c);
token->lineNumber = getSourceLineNumber();
token->filePosition = getInputFilePosition();
break;
case '-':
c = fileGetc();
if (c == '-') /* start of a comment */
{
fileSkipToCharacter('\n');
goto getNextChar;
} else {
if (!isspace(c)) fileUngetc(c);
token->type = TOKEN_OPERATOR;
}
break;
default:
if (!isIdentChar1(c))
token->type = TOKEN_NONE;
else {
parseIdentifier(token->string, c);
token->lineNumber = getSourceLineNumber();
token->filePosition = getInputFilePosition();
token->keyword = analyzeToken(token->string, Lang_vhdl);
if (isKeyword(token, KEYWORD_NONE))
token->type = TOKEN_IDENTIFIER;
else
token->type = TOKEN_KEYWORD;
}
break;
}
}
static void skipToKeyword(const keywordId keyword) {
tokenInfo *const token = newToken();
do {
readToken(token);
} while (!isKeyword(token, keyword));
deleteToken(token);
}
static void skipToMatched(tokenInfo *const token) {
int nest_level = 0;
tokenType open_token;
tokenType close_token;
switch (token->type) {
case TOKEN_OPEN_PAREN:
open_token = TOKEN_OPEN_PAREN;
close_token = TOKEN_CLOSE_PAREN;
break;
default:
return;
}
/*
* This routine will skip to a matching closing token.
* It will also handle nested tokens like the (, ) below.
* ( name varchar(30), text binary(10) )
*/
if (isType(token, open_token)) {
nest_level++;
while (!(isType(token, close_token) && (nest_level == 0))) {
readToken(token);
if (isType(token, open_token)) {
nest_level++;
}
if (isType(token, close_token)) {
if (nest_level > 0) {
nest_level--;
}
}
}
readToken(token);
}
}
static void makeConstTag(tokenInfo *const token, const vhdlKind kind) {
if (VhdlKinds[kind].enabled) {
const char *const name = vStringValue(token->string);
tagEntryInfo e;
initTagEntry(&e, name);
e.lineNumber = token->lineNumber;
e.filePosition = token->filePosition;
e.kindName = VhdlKinds[kind].name;
e.kind = VhdlKinds[kind].letter;
makeTagEntry(&e);
}
}
static void makeVhdlTag(tokenInfo *const token, const vhdlKind kind) {
if (VhdlKinds[kind].enabled) {
/*
* If a scope has been added to the token, change the token
* string to include the scope when making the tag.
*/
if (vStringLength(token->scope) > 0) {
vString *fulltag = vStringNew();
vStringCopy(fulltag, token->scope);
vStringCatS(fulltag, ".");
vStringCatS(fulltag, vStringValue(token->string));
vStringTerminate(fulltag);
vStringCopy(token->string, fulltag);
vStringDelete(fulltag);
}
makeConstTag(token, kind);
}
}
static void initialize(const langType language) {
size_t i;
const size_t count = sizeof(VhdlKeywordTable) / sizeof(VhdlKeywordTable[0]);
Lang_vhdl = language;
for (i = 0; i < count; ++i) {
const keywordDesc *const p = &VhdlKeywordTable[i];
addKeyword(p->name, language, (int)p->id);
}
}
static void parsePackage(tokenInfo *const token) {
tokenInfo *const name = newToken();
Assert(isKeyword(token, KEYWORD_PACKAGE));
readToken(token);
if (isKeyword(token, KEYWORD_BODY)) {
readToken(name);
makeVhdlTag(name, VHDLTAG_PACKAGE);
} else if (isType(token, TOKEN_IDENTIFIER)) {
makeVhdlTag(token, VHDLTAG_PACKAGE);
}
deleteToken(name);
}
static void parseModule(tokenInfo *const token) {
tokenInfo *const name = newToken();
const vhdlKind kind =
isKeyword(token, KEYWORD_ENTITY) ? VHDLTAG_ENTITY : VHDLTAG_COMPONENT;
Assert(isKeyword(token, KEYWORD_ENTITY) ||
isKeyword(token, KEYWORD_COMPONENT));
readToken(name);
if (kind == VHDLTAG_COMPONENT) {
makeVhdlTag(name, VHDLTAG_COMPONENT);
skipToKeyword(KEYWORD_END);
fileSkipToCharacter(';');
} else {
readToken(token);
if (isKeyword(token, KEYWORD_IS)) {
makeVhdlTag(name, VHDLTAG_ENTITY);
skipToKeyword(KEYWORD_END);
fileSkipToCharacter(';');
}
}
deleteToken(name);
}
static void parseRecord(tokenInfo *const token) {
tokenInfo *const name = newToken();
Assert(isKeyword(token, KEYWORD_RECORD));
readToken(name);
do {
readToken(token); /* should be a colon */
fileSkipToCharacter(';');
makeVhdlTag(name, VHDLTAG_RECORD);
readToken(name);
} while (!isKeyword(name, KEYWORD_END));
fileSkipToCharacter(';');
deleteToken(name);
}
static void parseTypes(tokenInfo *const token) {
tokenInfo *const name = newToken();
const vhdlKind kind =
isKeyword(token, KEYWORD_TYPE) ? VHDLTAG_TYPE : VHDLTAG_SUBTYPE;
Assert(isKeyword(token, KEYWORD_TYPE) || isKeyword(token, KEYWORD_SUBTYPE));
readToken(name);
readToken(token);
if (isKeyword(token, KEYWORD_IS)) {
readToken(token); /* type */
if (isKeyword(token, KEYWORD_RECORD)) {
makeVhdlTag(name, kind);
/*TODO: make tags of the record's names */
parseRecord(token);
} else {
makeVhdlTag(name, kind);
}
}
deleteToken(name);
}
static void parseConstant(boolean local) {
tokenInfo *const name = newToken();
readToken(name);
if (local) {
makeVhdlTag(name, VHDLTAG_LOCAL);
} else {
makeVhdlTag(name, VHDLTAG_CONSTANT);
}
fileSkipToCharacter(';');
deleteToken(name);
}
static void parseSubProgram(tokenInfo *const token) {
tokenInfo *const name = newToken();
boolean endSubProgram = FALSE;
const vhdlKind kind =
isKeyword(token, KEYWORD_FUNCTION) ? VHDLTAG_FUNCTION : VHDLTAG_PROCEDURE;
Assert(isKeyword(token, KEYWORD_FUNCTION) ||
isKeyword(token, KEYWORD_PROCEDURE));
readToken(name); /* the name of the function or procedure */
readToken(token);
if (isType(token, TOKEN_OPEN_PAREN)) {
skipToMatched(token);
}
if (kind == VHDLTAG_FUNCTION) {
if (isKeyword(token, KEYWORD_RETURN)) {
/* Read datatype */
readToken(token);
while (!isKeyword(token, KEYWORD_IS) && !isType(token, TOKEN_SEMICOLON)) {
readToken(token);
}
}
}
if (isType(token, TOKEN_SEMICOLON)) {
makeVhdlTag(name, VHDLTAG_PROTOTYPE);
} else if (isKeyword(token, KEYWORD_IS)) {
if (kind == VHDLTAG_FUNCTION) {
makeVhdlTag(name, VHDLTAG_FUNCTION);
do {
readToken(token);
if (isKeyword(token, KEYWORD_END)) {
readToken(token);
endSubProgram =
isKeywordOrIdent(token, KEYWORD_FUNCTION, name->string);
fileSkipToCharacter(';');
} else {
parseKeywords(token, TRUE);
}
} while (!endSubProgram);
} else {
makeVhdlTag(name, VHDLTAG_PROCEDURE);
do {
readToken(token);
if (isKeyword(token, KEYWORD_END)) {
readToken(token);
endSubProgram =
isKeywordOrIdent(token, KEYWORD_PROCEDURE, name->string);
fileSkipToCharacter(';');
} else {
parseKeywords(token, TRUE);
}
} while (!endSubProgram);
}
}
deleteToken(name);
}
/* TODO */
/* records */
static void parseKeywords(tokenInfo *const token, boolean local) {
switch (token->keyword) {
case KEYWORD_END:
fileSkipToCharacter(';');
break;
case KEYWORD_CONSTANT:
parseConstant(local);
break;
case KEYWORD_TYPE:
parseTypes(token);
break;
case KEYWORD_SUBTYPE:
parseTypes(token);
break;
case KEYWORD_ENTITY:
parseModule(token);
break;
case KEYWORD_COMPONENT:
parseModule(token);
break;
case KEYWORD_FUNCTION:
parseSubProgram(token);
break;
case KEYWORD_PROCEDURE:
parseSubProgram(token);
break;
case KEYWORD_PACKAGE:
parsePackage(token);
break;
default:
break;
}
}
static void parseVhdlFile(tokenInfo *const token) {
do {
readToken(token);
parseKeywords(token, FALSE);
} while (!isKeyword(token, KEYWORD_END));
}
static void findVhdlTags(void) {
tokenInfo *const token = newToken();
exception_t exception = (exception_t)(setjmp(Exception));
while (exception == ExceptionNone) parseVhdlFile(token);
deleteToken(token);
}
extern parserDefinition *VhdlParser(void) {
static const char *const extensions[] = {"vhdl", "vhd", NULL};
parserDefinition *def = parserNew("VHDL");
def->kinds = VhdlKinds;
def->kindCount = KIND_COUNT(VhdlKinds);
def->extensions = extensions;
def->parser = findVhdlTags;
def->initialize = initialize;
return def;
}
/* vi:set tabstop=4 shiftwidth=4 noet: */