// clang-format off /* * $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 FILES */ #include "third_party/ctags/general.h" /* must always come first */ #include "libc/str/str.h" /* to define isalpha () */ #include "libc/mem/alg.h" #include "libc/str/str.h" #include "libc/runtime/runtime.h" #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: */