/* * Copyright (c) 2019, Jiri Techet * * This source code is released for free distribution under the terms of the * GNU General Public License version 2 or (at your option) any later version. * * Provides a simple application using ctags as a library and using the same * set of ctags functions as the Geany editor */ #include "general.h" /* must always come first */ #include "types.h" #include "routines.h" #include "mio.h" #include "error_p.h" #include "writer_p.h" #include "parse_p.h" #include "options_p.h" #include "trashbox_p.h" #include "field_p.h" #include "xtag_p.h" #include "entry_p.h" #include "param_p.h" #include #include #include static int writeEntry (tagWriter *writer, MIO * mio, const tagEntryInfo *const tag, void *clientData); static void rescanFailed (tagWriter *writer, unsigned long validTagNum, void *clientData); /* we need to be able to provide a custom writer using which we collect the tags */ tagWriter customWriter = { .writeEntry = writeEntry, .writePtagEntry = NULL, .preWriteEntry = NULL, .postWriteEntry = NULL, .rescanFailedEntry = rescanFailed, .treatFieldAsFixed = NULL, .defaultFileName = "tags_file_which_should_never_appear_anywhere", .private = NULL, }; /* we need to be able to provide an error printer which doesn't crash Geany on error */ static bool nofatalErrorPrinter (const errorSelection selection, const char *const format, va_list ap, void *data CTAGS_ATTR_UNUSED) { fprintf (stderr, "%s: ", (selection & WARNING) ? "Warning: " : "Error"); vfprintf (stderr, format, ap); if (selection & PERROR) #ifdef HAVE_STRERROR fprintf (stderr, " : %s", strerror (errno)); #else perror (" "); #endif fputs ("\n", stderr); return false; } static void enableRoles(unsigned int lang, unsigned int kind) { unsigned int c = countLanguageRoles(lang, kind); for (unsigned int i = 0; i < c; i++) { roleDefinition* rdef = getLanguageRole(lang, kind, (int)i); enableRole(rdef, true); } } /* we need to be able to enable all kinds and roles for all languages (some are disabled by default) */ static void enableKindsAndRoles() { unsigned int lang; for (lang = 0; lang < countParsers(); lang++) { unsigned int kindNum = countLanguageKinds(lang); unsigned int kind; for (kind = 0; kind < kindNum; kind++) { kindDefinition *def = getLanguageKind(lang, kind); enableKind(def, true); enableRoles(lang, kind); } } } /* we need to be able to initialize ctags like in main() but skipping some things */ static void ctagsInit(void) { initDefaultTrashBox (); setErrorPrinter (nofatalErrorPrinter, NULL); setTagWriter (WRITER_CUSTOM, &customWriter); checkRegex (); initFieldObjects (); initXtagObjects (); initializeParsing (); initOptions (); initRegexOptscript (); /* make sure all parsers are initialized */ initializeParser (LANG_AUTO); /* change default value which is false */ enableXtag(XTAG_TAGS_GENERATED_BY_GUEST_PARSERS, true); enableXtag(XTAG_REFERENCE_TAGS, true); /* some kinds and roles we are interested in are disabled by default */ enableKindsAndRoles(); } /* we need to be able to get a name of a given language */ static const char *ctagsGetLangName(int lang) { return getLanguageName(lang); } /* we need to be able to get an int representing a given language */ static int ctagsGetNamedLang(const char *name) { return getNamedLanguage(name, 0); } /* we need to be able to get kind letters provided by a given language */ static const char *ctagsGetLangKinds(int lang) { unsigned int kindNum = countLanguageKinds(lang); static char kinds[257]; unsigned int i; for (i = 0; i < kindNum; i++) kinds[i] = getLanguageKind(lang, i)->letter; kinds[i] = '\0'; return kinds; } /* we need to be able to get kind name from a kind letter for a given language */ static const char *ctagsGetKindName(char kind, int lang) { kindDefinition *def = getLanguageKindForLetter (lang, kind); return def ? def->name : "unknown"; } /* we need to be able to get kind letter from a kind name for a given language */ static char ctagsGetKindFromName(const char *name, int lang) { kindDefinition *def = getLanguageKindForName (lang, name); return def ? def->letter : '-'; } /* we need to be able to get kind letter from a kind index */ static char ctagsGetKindFromIndex(int index, int lang) { return getLanguageKind(lang, index)->letter; } /* we need to be able to get the number of parsers */ static unsigned int ctagsGetLangCount(void) { return countParsers(); } void addIgnoreSymbol(const char *value) { langType lang = getNamedLanguage ("CPreProcessor", 0); applyParameter (lang, "ignore", value); } /******************************************************************************* * So let's just use what we have for our great client... ******************************************************************************/ /* our internal tag representation - this is all the tag information we use in Geany */ typedef struct { char *name; char *signature; char *scopeName; char *inheritance; char *varType; char *access; char *implementation; char kindLetter; bool isFileScope; unsigned long lineNumber; int lang; bool isAnon; } Tag; static Tag *createTag(const tagEntryInfo *info) { Tag *tag = xCalloc(1, Tag); if (info->name) tag->name = eStrdup(info->name); if (info->extensionFields.signature) tag->signature = eStrdup(info->extensionFields.signature); if (info->extensionFields.scopeName) tag->scopeName = eStrdup(info->extensionFields.scopeName); if (info->extensionFields.inheritance) tag->inheritance = eStrdup(info->extensionFields.inheritance); if (info->extensionFields.typeRef[1]) tag->varType = eStrdup(info->extensionFields.typeRef[1]); if (info->extensionFields.access) tag->access = eStrdup(info->extensionFields.access); if (info->extensionFields.implementation) tag->implementation = eStrdup(info->extensionFields.implementation); tag->kindLetter = ctagsGetKindFromIndex(info->kindIndex, info->langType); tag->isFileScope = info->isFileScope; tag->lineNumber = info->lineNumber; tag->lang = info->langType; tag->isAnon = isTagExtraBitMarked(info, XTAG_ANONYMOUS); return tag; } static void destroyTag(Tag *tag) { if (tag->name) eFree(tag->name); if (tag->signature) eFree(tag->signature); if (tag->scopeName) eFree(tag->scopeName); if (tag->inheritance) eFree(tag->inheritance); if (tag->varType) eFree(tag->varType); if (tag->access) eFree(tag->access); if (tag->implementation) eFree(tag->implementation); eFree(tag); } /* callback from ctags informing us about a new tag */ static int writeEntry (tagWriter *writer, MIO *mio, const tagEntryInfo *const tag, void *clientData) { Tag *t; /* apparently we have to call this to get the scope info - maybe we can * specify some option during initialization so we don't have to cal this * ?????? */ getTagScopeInformation((tagEntryInfo *)tag, NULL, NULL); /* convert tags into our internal representation and store them into an array */ t = createTag(tag); ptrArrayAdd((ptrArray *)clientData, t); /* output length - we don't write anything to the MIO */ return 0; } /* scan has failed so we have invalid tags in our array - validTagNum should * tell us the number of valid tags so remove all the extra tags and shrink the array */ static void rescanFailed (tagWriter *writer, unsigned long validTagNum, void *clientData) { ptrArray *tagArray = clientData; int num = ptrArrayCount(tagArray); if (num > validTagNum) { int i; for (i = validTagNum; i < num; i++) { Tag *tag = ptrArrayLast(tagArray); destroyTag(tag); ptrArrayRemoveLast(tagArray); } } } /* do whatever we want to do with the tags */ static void processCollectedTags(ptrArray *tagArray) { int i; int num = ptrArrayCount(tagArray); for (i = 0; i < num; i++) { Tag *tag = ptrArrayItem(tagArray, i); printf("%s\tline: %lu\tkind: %s\t lang: %s\n", tag->name, tag->lineNumber, ctagsGetKindName(tag->kindLetter, tag->lang), ctagsGetLangName(tag->lang)); } /* prepare for the next parsing by clearing the tag array */ ptrArrayClear(tagArray); } extern int main (int argc, char **argv) { /* called once when Geany starts */ ctagsInit(); /* create empty tag array * * this is where we store the collected tags * NOTE: Geany doesn't use the ptrArray type - it is used just for the purpose of this demo */ ptrArray *tagArray = ptrArrayNew((ptrArrayDeleteFunc)destroyTag); printf("This parser only parses C files - provide them as arguments on the " "command line or get a hard-coded buffer parsed when no arguments " "are provided\n\n"); if (argc == 1) /* parsing contents of a buffer */ { char *program = "FOO int foo() {}\n\n int bar() {}\n\n int main() {}\n"; int lang = ctagsGetNamedLang("C"); const char *kinds = ctagsGetLangKinds(lang); int i; /* we need to be able to set and clear ignore symbols */ addIgnoreSymbol("int"); /* clear */ addIgnoreSymbol(NULL); /* set to something else */ addIgnoreSymbol("FOO"); printf("Number of all parsers is: %d\n", ctagsGetLangCount()); printf("We are parsing %s which provides the following kinds:\n", ctagsGetLangName(lang)); for (i = 0; kinds[i] != '\0'; i++) { printf("%c: %s\n", /* back and forth conversion - the same like just kinds[i] */ ctagsGetKindFromName(ctagsGetKindName(kinds[i], lang), lang), ctagsGetKindName(kinds[i], lang)); } printf("\nParsing buffer:\n"); /* we always specify the language by ourselves and don't use ctags detection */ parseRawBuffer("whatever", (unsigned char *)program, strlen(program), lang, tagArray); processCollectedTags(tagArray); } else /* parsing contents of a file */ { int i; for (i = 1; i < argc; i++) { printf("\nParsing %s:\n", argv[i]); /* parseRawBuffer() is called repeatadly during Geany execution */ parseRawBuffer(argv[i], NULL, 0, getNamedLanguage("C", 0), tagArray); processCollectedTags(tagArray); } } ptrArrayDelete(tagArray); return 0; }