1 /*
2 * Copyright (c) 2019, Jiri Techet
3 *
4 * This source code is released for free distribution under the terms of the
5 * GNU General Public License version 2 or (at your option) any later version.
6 *
7 * Provides a simple application using ctags as a library and using the same
8 * set of ctags functions as the Geany editor
9 */
10
11 #include "general.h" /* must always come first */
12
13 #include "types.h"
14 #include "routines.h"
15 #include "mio.h"
16 #include "error_p.h"
17 #include "writer_p.h"
18 #include "parse_p.h"
19 #include "options_p.h"
20 #include "trashbox_p.h"
21 #include "field_p.h"
22 #include "xtag_p.h"
23 #include "entry_p.h"
24 #include "param_p.h"
25
26 #include <stdio.h>
27 #include <string.h>
28 #include <errno.h>
29
30 static int writeEntry (tagWriter *writer, MIO * mio, const tagEntryInfo *const tag, void *clientData);
31 static void rescanFailed (tagWriter *writer, unsigned long validTagNum, void *clientData);
32
33 /* we need to be able to provide a custom writer using which we collect the tags */
34 tagWriter customWriter = {
35 .writeEntry = writeEntry,
36 .writePtagEntry = NULL,
37 .preWriteEntry = NULL,
38 .postWriteEntry = NULL,
39 .rescanFailedEntry = rescanFailed,
40 .treatFieldAsFixed = NULL,
41 .defaultFileName = "tags_file_which_should_never_appear_anywhere",
42 .private = NULL,
43 };
44
45
46 /* we need to be able to provide an error printer which doesn't crash Geany on error */
nofatalErrorPrinter(const errorSelection selection,const char * const format,va_list ap,void * data CTAGS_ATTR_UNUSED)47 static bool nofatalErrorPrinter (const errorSelection selection,
48 const char *const format,
49 va_list ap, void *data CTAGS_ATTR_UNUSED)
50 {
51 fprintf (stderr, "%s: ", (selection & WARNING) ? "Warning: " : "Error");
52 vfprintf (stderr, format, ap);
53 if (selection & PERROR)
54 #ifdef HAVE_STRERROR
55 fprintf (stderr, " : %s", strerror (errno));
56 #else
57 perror (" ");
58 #endif
59 fputs ("\n", stderr);
60
61 return false;
62 }
63
64
enableRoles(unsigned int lang,unsigned int kind)65 static void enableRoles(unsigned int lang, unsigned int kind)
66 {
67 unsigned int c = countLanguageRoles(lang, kind);
68
69 for (unsigned int i = 0; i < c; i++)
70 {
71 roleDefinition* rdef = getLanguageRole(lang, kind, (int)i);
72 enableRole(rdef, true);
73 }
74 }
75
76
77 /* we need to be able to enable all kinds and roles for all languages (some are disabled by default) */
enableKindsAndRoles()78 static void enableKindsAndRoles()
79 {
80 unsigned int lang;
81
82 for (lang = 0; lang < countParsers(); lang++)
83 {
84 unsigned int kindNum = countLanguageKinds(lang);
85 unsigned int kind;
86
87 for (kind = 0; kind < kindNum; kind++)
88 {
89 kindDefinition *def = getLanguageKind(lang, kind);
90 enableKind(def, true);
91 enableRoles(lang, kind);
92 }
93 }
94 }
95
96
97 /* we need to be able to initialize ctags like in main() but skipping some things */
ctagsInit(void)98 static void ctagsInit(void)
99 {
100 initDefaultTrashBox ();
101
102 setErrorPrinter (nofatalErrorPrinter, NULL);
103 setTagWriter (WRITER_CUSTOM, &customWriter);
104
105 checkRegex ();
106 initFieldObjects ();
107 initXtagObjects ();
108
109 initializeParsing ();
110 initOptions ();
111 initRegexOptscript ();
112
113 /* make sure all parsers are initialized */
114 initializeParser (LANG_AUTO);
115
116 /* change default value which is false */
117 enableXtag(XTAG_TAGS_GENERATED_BY_GUEST_PARSERS, true);
118 enableXtag(XTAG_REFERENCE_TAGS, true);
119
120 /* some kinds and roles we are interested in are disabled by default */
121 enableKindsAndRoles();
122 }
123
124
125 /* we need to be able to get a name of a given language */
ctagsGetLangName(int lang)126 static const char *ctagsGetLangName(int lang)
127 {
128 return getLanguageName(lang);
129 }
130
131
132 /* we need to be able to get an int representing a given language */
ctagsGetNamedLang(const char * name)133 static int ctagsGetNamedLang(const char *name)
134 {
135 return getNamedLanguage(name, 0);
136 }
137
138
139 /* we need to be able to get kind letters provided by a given language */
ctagsGetLangKinds(int lang)140 static const char *ctagsGetLangKinds(int lang)
141 {
142 unsigned int kindNum = countLanguageKinds(lang);
143 static char kinds[257];
144 unsigned int i;
145
146 for (i = 0; i < kindNum; i++)
147 kinds[i] = getLanguageKind(lang, i)->letter;
148 kinds[i] = '\0';
149
150 return kinds;
151 }
152
153
154 /* we need to be able to get kind name from a kind letter for a given language */
ctagsGetKindName(char kind,int lang)155 static const char *ctagsGetKindName(char kind, int lang)
156 {
157 kindDefinition *def = getLanguageKindForLetter (lang, kind);
158 return def ? def->name : "unknown";
159 }
160
161
162 /* we need to be able to get kind letter from a kind name for a given language */
ctagsGetKindFromName(const char * name,int lang)163 static char ctagsGetKindFromName(const char *name, int lang)
164 {
165 kindDefinition *def = getLanguageKindForName (lang, name);
166 return def ? def->letter : '-';
167 }
168
169
170 /* we need to be able to get kind letter from a kind index */
ctagsGetKindFromIndex(int index,int lang)171 static char ctagsGetKindFromIndex(int index, int lang)
172 {
173 return getLanguageKind(lang, index)->letter;
174 }
175
176
177 /* we need to be able to get the number of parsers */
ctagsGetLangCount(void)178 static unsigned int ctagsGetLangCount(void)
179 {
180 return countParsers();
181 }
182
183
addIgnoreSymbol(const char * value)184 void addIgnoreSymbol(const char *value)
185 {
186 langType lang = getNamedLanguage ("CPreProcessor", 0);
187 applyParameter (lang, "ignore", value);
188 }
189
190 /*******************************************************************************
191 * So let's just use what we have for our great client...
192 ******************************************************************************/
193
194 /* our internal tag representation - this is all the tag information we use in Geany */
195 typedef struct {
196 char *name;
197 char *signature;
198 char *scopeName;
199 char *inheritance;
200 char *varType;
201 char *access;
202 char *implementation;
203 char kindLetter;
204 bool isFileScope;
205 unsigned long lineNumber;
206 int lang;
207 bool isAnon;
208 } Tag;
209
210
createTag(const tagEntryInfo * info)211 static Tag *createTag(const tagEntryInfo *info)
212 {
213 Tag *tag = xCalloc(1, Tag);
214 if (info->name)
215 tag->name = eStrdup(info->name);
216 if (info->extensionFields.signature)
217 tag->signature = eStrdup(info->extensionFields.signature);
218 if (info->extensionFields.scopeName)
219 tag->scopeName = eStrdup(info->extensionFields.scopeName);
220 if (info->extensionFields.inheritance)
221 tag->inheritance = eStrdup(info->extensionFields.inheritance);
222 if (info->extensionFields.typeRef[1])
223 tag->varType = eStrdup(info->extensionFields.typeRef[1]);
224 if (info->extensionFields.access)
225 tag->access = eStrdup(info->extensionFields.access);
226 if (info->extensionFields.implementation)
227 tag->implementation = eStrdup(info->extensionFields.implementation);
228 tag->kindLetter = ctagsGetKindFromIndex(info->kindIndex, info->langType);
229 tag->isFileScope = info->isFileScope;
230 tag->lineNumber = info->lineNumber;
231 tag->lang = info->langType;
232 tag->isAnon = isTagExtraBitMarked(info, XTAG_ANONYMOUS);
233 return tag;
234 }
235
236
destroyTag(Tag * tag)237 static void destroyTag(Tag *tag)
238 {
239 if (tag->name)
240 eFree(tag->name);
241 if (tag->signature)
242 eFree(tag->signature);
243 if (tag->scopeName)
244 eFree(tag->scopeName);
245 if (tag->inheritance)
246 eFree(tag->inheritance);
247 if (tag->varType)
248 eFree(tag->varType);
249 if (tag->access)
250 eFree(tag->access);
251 if (tag->implementation)
252 eFree(tag->implementation);
253 eFree(tag);
254 }
255
256
257 /* callback from ctags informing us about a new tag */
writeEntry(tagWriter * writer,MIO * mio,const tagEntryInfo * const tag,void * clientData)258 static int writeEntry (tagWriter *writer, MIO *mio, const tagEntryInfo *const tag, void *clientData)
259 {
260 Tag *t;
261
262 /* apparently we have to call this to get the scope info - maybe we can
263 * specify some option during initialization so we don't have to cal this
264 * ?????? */
265 getTagScopeInformation((tagEntryInfo *)tag, NULL, NULL);
266
267 /* convert tags into our internal representation and store them into an array */
268 t = createTag(tag);
269 ptrArrayAdd((ptrArray *)clientData, t);
270
271 /* output length - we don't write anything to the MIO */
272 return 0;
273 }
274
275
276 /* scan has failed so we have invalid tags in our array - validTagNum should
277 * tell us the number of valid tags so remove all the extra tags and shrink the array */
rescanFailed(tagWriter * writer,unsigned long validTagNum,void * clientData)278 static void rescanFailed (tagWriter *writer, unsigned long validTagNum, void *clientData)
279 {
280 ptrArray *tagArray = clientData;
281 int num = ptrArrayCount(tagArray);
282
283 if (num > validTagNum)
284 {
285 int i;
286 for (i = validTagNum; i < num; i++)
287 {
288 Tag *tag = ptrArrayLast(tagArray);
289 destroyTag(tag);
290 ptrArrayRemoveLast(tagArray);
291 }
292 }
293 }
294
295
296 /* do whatever we want to do with the tags */
processCollectedTags(ptrArray * tagArray)297 static void processCollectedTags(ptrArray *tagArray)
298 {
299 int i;
300 int num = ptrArrayCount(tagArray);
301
302 for (i = 0; i < num; i++)
303 {
304 Tag *tag = ptrArrayItem(tagArray, i);
305 printf("%s\tline: %lu\tkind: %s\t lang: %s\n",
306 tag->name,
307 tag->lineNumber,
308 ctagsGetKindName(tag->kindLetter, tag->lang),
309 ctagsGetLangName(tag->lang));
310 }
311
312 /* prepare for the next parsing by clearing the tag array */
313 ptrArrayClear(tagArray);
314 }
315
316
main(int argc,char ** argv)317 extern int main (int argc, char **argv)
318 {
319 /* called once when Geany starts */
320 ctagsInit();
321
322 /* create empty tag array *
323 * this is where we store the collected tags
324 * NOTE: Geany doesn't use the ptrArray type - it is used just for the purpose of this demo */
325 ptrArray *tagArray = ptrArrayNew((ptrArrayDeleteFunc)destroyTag);
326
327 printf("This parser only parses C files - provide them as arguments on the "
328 "command line or get a hard-coded buffer parsed when no arguments "
329 "are provided\n\n");
330 if (argc == 1) /* parsing contents of a buffer */
331 {
332 char *program = "FOO int foo() {}\n\n int bar() {}\n\n int main() {}\n";
333 int lang = ctagsGetNamedLang("C");
334 const char *kinds = ctagsGetLangKinds(lang);
335 int i;
336
337 /* we need to be able to set and clear ignore symbols */
338 addIgnoreSymbol("int");
339 /* clear */
340 addIgnoreSymbol(NULL);
341 /* set to something else */
342 addIgnoreSymbol("FOO");
343
344 printf("Number of all parsers is: %d\n", ctagsGetLangCount());
345 printf("We are parsing %s which provides the following kinds:\n",
346 ctagsGetLangName(lang));
347 for (i = 0; kinds[i] != '\0'; i++)
348 {
349 printf("%c: %s\n",
350 /* back and forth conversion - the same like just kinds[i] */
351 ctagsGetKindFromName(ctagsGetKindName(kinds[i], lang), lang),
352 ctagsGetKindName(kinds[i], lang));
353 }
354
355 printf("\nParsing buffer:\n");
356 /* we always specify the language by ourselves and don't use ctags detection */
357 parseRawBuffer("whatever", (unsigned char *)program, strlen(program), lang, tagArray);
358
359 processCollectedTags(tagArray);
360 }
361 else /* parsing contents of a file */
362 {
363 int i;
364 for (i = 1; i < argc; i++)
365 {
366 printf("\nParsing %s:\n", argv[i]);
367 /* parseRawBuffer() is called repeatadly during Geany execution */
368 parseRawBuffer(argv[i], NULL, 0, getNamedLanguage("C", 0), tagArray);
369
370 processCollectedTags(tagArray);
371 }
372 }
373
374 ptrArrayDelete(tagArray);
375
376 return 0;
377 }
378