1 | /* ctags.c */
|
---|
2 |
|
---|
3 | /* This is a reimplementation of the ctags(1) program. It supports ANSI C,
|
---|
4 | * and has heaps o' flags. It is meant to be distributed with elvis.
|
---|
5 | */
|
---|
6 |
|
---|
7 | #include <stdio.h>
|
---|
8 | #include "config.h"
|
---|
9 | #ifndef FALSE
|
---|
10 | # define FALSE 0
|
---|
11 | # define TRUE 1
|
---|
12 | #endif
|
---|
13 | #ifndef TAGS
|
---|
14 | # define TAGS "tags"
|
---|
15 | #endif
|
---|
16 | #ifndef REFS
|
---|
17 | # define REFS "refs"
|
---|
18 | #endif
|
---|
19 | #ifndef BLKSIZE
|
---|
20 | # define BLKSIZE 1024
|
---|
21 | #endif
|
---|
22 |
|
---|
23 | #include "ctype.c" /* yes, that really is the .c file, not the .h one. */
|
---|
24 |
|
---|
25 | /* -------------------------------------------------------------------------- */
|
---|
26 | /* Some global variables */
|
---|
27 |
|
---|
28 | /* The following boolean variables are set according to command line flags */
|
---|
29 | int incl_static; /* -s include static tags */
|
---|
30 | int incl_types; /* -t include typedefs and structs */
|
---|
31 | int incl_vars; /* -v include variables */
|
---|
32 | int make_refs; /* -r generate a "refs" file */
|
---|
33 | int append_files; /* -a append to "tags" [and "refs"] files */
|
---|
34 |
|
---|
35 | /* The following are used for outputting to the "tags" and "refs" files */
|
---|
36 | FILE *tags; /* used for writing to the "tags" file */
|
---|
37 | FILE *refs; /* used for writing to the "refs" file */
|
---|
38 |
|
---|
39 | /* -------------------------------------------------------------------------- */
|
---|
40 | /* These are used for reading a source file. It keeps track of line numbers */
|
---|
41 | char *file_name; /* name of the current file */
|
---|
42 | FILE *file_fp; /* stream used for reading the file */
|
---|
43 | long file_lnum; /* line number in the current file */
|
---|
44 | long file_seek; /* fseek() offset to the start of current line */
|
---|
45 | int file_afternl; /* boolean: was previous character a newline? */
|
---|
46 | int file_prevch; /* a single character that was ungotten */
|
---|
47 | int file_header; /* boolean: is the current file a header file? */
|
---|
48 |
|
---|
49 | /* This function opens a file, and resets the line counter. If it fails, it
|
---|
50 | * it will display an error message and leave the file_fp set to NULL.
|
---|
51 | */
|
---|
52 | void file_open(name)
|
---|
53 | char *name; /* name of file to be opened */
|
---|
54 | {
|
---|
55 | /* if another file was already open, then close it */
|
---|
56 | if (file_fp)
|
---|
57 | {
|
---|
58 | fclose(file_fp);
|
---|
59 | }
|
---|
60 |
|
---|
61 | /* try to open the file for reading. The file must be opened in
|
---|
62 | * "binary" mode because otherwise fseek() would misbehave under DOS.
|
---|
63 | */
|
---|
64 | #if MSDOS || TOS
|
---|
65 | file_fp = fopen(name, "rb");
|
---|
66 | #else
|
---|
67 | file_fp = fopen(name, "r");
|
---|
68 | #endif
|
---|
69 | if (!file_fp)
|
---|
70 | {
|
---|
71 | perror(name);
|
---|
72 | }
|
---|
73 |
|
---|
74 | /* reset the name & line number */
|
---|
75 | file_name = name;
|
---|
76 | file_lnum = 0L;
|
---|
77 | file_seek = 0L;
|
---|
78 | file_afternl = TRUE;
|
---|
79 |
|
---|
80 | /* determine whether this is a header file */
|
---|
81 | file_header = FALSE;
|
---|
82 | name += strlen(name) - 2;
|
---|
83 | if (name >= file_name && name[0] == '.' && (name[1] == 'h' || name[1] == 'H'))
|
---|
84 | {
|
---|
85 | file_header = TRUE;
|
---|
86 | }
|
---|
87 | }
|
---|
88 |
|
---|
89 | /* This function reads a single character from the stream. If the *previous*
|
---|
90 | * character was a newline, then it also increments file_lnum and sets
|
---|
91 | * file_offset.
|
---|
92 | */
|
---|
93 | int file_getc()
|
---|
94 | {
|
---|
95 | int ch;
|
---|
96 |
|
---|
97 | /* if there is an ungotten character, then return it. Don't do any
|
---|
98 | * other processing on it, though, because we already did that the
|
---|
99 | * first time it was read.
|
---|
100 | */
|
---|
101 | if (file_prevch)
|
---|
102 | {
|
---|
103 | ch = file_prevch;
|
---|
104 | file_prevch = 0;
|
---|
105 | return ch;
|
---|
106 | }
|
---|
107 |
|
---|
108 | /* if previous character was a newline, then we're starting a line */
|
---|
109 | if (file_afternl)
|
---|
110 | {
|
---|
111 | file_afternl = FALSE;
|
---|
112 | file_seek = ftell(file_fp);
|
---|
113 | file_lnum++;
|
---|
114 | }
|
---|
115 |
|
---|
116 | /* Get a character. If no file is open, then return EOF */
|
---|
117 | ch = (file_fp ? getc(file_fp) : EOF);
|
---|
118 |
|
---|
119 | /* if it is a newline, then remember that fact */
|
---|
120 | if (ch == '\n')
|
---|
121 | {
|
---|
122 | file_afternl = TRUE;
|
---|
123 | }
|
---|
124 |
|
---|
125 | /* return the character */
|
---|
126 | return ch;
|
---|
127 | }
|
---|
128 |
|
---|
129 | /* This function ungets a character from the current source file */
|
---|
130 | void file_ungetc(ch)
|
---|
131 | int ch; /* character to be ungotten */
|
---|
132 | {
|
---|
133 | file_prevch = ch;
|
---|
134 | }
|
---|
135 |
|
---|
136 | /* This function copies the current line out some other fp. It has no effect
|
---|
137 | * on the file_getc() function. During copying, any '\' characters are doubled
|
---|
138 | * and a leading '^' or trailing '$' is also quoted. The newline character is
|
---|
139 | * not copied.
|
---|
140 | *
|
---|
141 | * This is meant to be used when generating a tag line.
|
---|
142 | */
|
---|
143 | void file_copyline(seek, fp)
|
---|
144 | long seek; /* where the lines starts in the source file */
|
---|
145 | FILE *fp; /* the output stream to copy it to */
|
---|
146 | {
|
---|
147 | long oldseek;/* where the file's pointer was before we messed it up */
|
---|
148 | char ch; /* a single character from the file */
|
---|
149 | char next; /* the next character from this file */
|
---|
150 |
|
---|
151 | /* go to the start of the line */
|
---|
152 | oldseek = ftell(file_fp);
|
---|
153 | fseek(file_fp, seek, 0);
|
---|
154 |
|
---|
155 | /* if first character is '^', then emit \^ */
|
---|
156 | ch = getc(file_fp);
|
---|
157 | if (ch == '^')
|
---|
158 | {
|
---|
159 | putc('\\', fp);
|
---|
160 | putc('^', fp);
|
---|
161 | ch = getc(file_fp);
|
---|
162 | }
|
---|
163 |
|
---|
164 | /* write everything up to, but not including, the newline */
|
---|
165 | while (ch != '\n')
|
---|
166 | {
|
---|
167 | /* preread the next character from this file */
|
---|
168 | next = getc(file_fp);
|
---|
169 |
|
---|
170 | /* if character is '\', or a terminal '$', then quote it */
|
---|
171 | if (ch == '\\' || (ch == '$' && next == '\n'))
|
---|
172 | {
|
---|
173 | putc('\\', fp);
|
---|
174 | }
|
---|
175 | putc(ch, fp);
|
---|
176 |
|
---|
177 | /* next character... */
|
---|
178 | ch = next;
|
---|
179 | }
|
---|
180 |
|
---|
181 | /* seek back to the old position */
|
---|
182 | fseek(file_fp, oldseek, 0);
|
---|
183 | }
|
---|
184 |
|
---|
185 | /* -------------------------------------------------------------------------- */
|
---|
186 | /* This section handles preprocessor directives. It strips out all of the
|
---|
187 | * directives, and may emit a tag for #define directives.
|
---|
188 | */
|
---|
189 |
|
---|
190 | int cpp_afternl; /* boolean: look for '#' character? */
|
---|
191 | int cpp_prevch; /* an ungotten character, if any */
|
---|
192 | int cpp_refsok; /* boolean: can we echo characters out to "refs"? */
|
---|
193 |
|
---|
194 | /* This function opens the file & resets variables */
|
---|
195 | void cpp_open(name)
|
---|
196 | char *name; /* name of source file to be opened */
|
---|
197 | {
|
---|
198 | /* use the lower-level file_open function to open the file */
|
---|
199 | file_open(name);
|
---|
200 |
|
---|
201 | /* reset variables */
|
---|
202 | cpp_afternl = TRUE;
|
---|
203 | cpp_refsok = TRUE;
|
---|
204 | }
|
---|
205 |
|
---|
206 | /* This function copies a character from the source file to the "refs" file */
|
---|
207 | void cpp_echo(ch)
|
---|
208 | int ch; /* the character to copy */
|
---|
209 | {
|
---|
210 | static wasnl;
|
---|
211 |
|
---|
212 | /* echo non-EOF chars, unless not making "ref", or echo turned off */
|
---|
213 | if (ch != EOF && make_refs && cpp_refsok && !file_header)
|
---|
214 | {
|
---|
215 | /* try to avoid blank lines */
|
---|
216 | if (ch == '\n')
|
---|
217 | {
|
---|
218 | if (wasnl)
|
---|
219 | {
|
---|
220 | return;
|
---|
221 | }
|
---|
222 | wasnl = TRUE;
|
---|
223 | }
|
---|
224 | else
|
---|
225 | {
|
---|
226 | wasnl = FALSE;
|
---|
227 | }
|
---|
228 |
|
---|
229 | /* add the character */
|
---|
230 | putc(ch, refs);
|
---|
231 | }
|
---|
232 | }
|
---|
233 |
|
---|
234 | /* This function returns the next character which isn't part of a directive */
|
---|
235 | int cpp_getc()
|
---|
236 | {
|
---|
237 | static
|
---|
238 | int ch; /* the next input character */
|
---|
239 | char *scan;
|
---|
240 |
|
---|
241 | /* if we have an ungotten character, then return it */
|
---|
242 | if (cpp_prevch)
|
---|
243 | {
|
---|
244 | ch = cpp_prevch;
|
---|
245 | cpp_prevch = 0;
|
---|
246 | return ch;
|
---|
247 | }
|
---|
248 |
|
---|
249 | /* Get a character from the file. Return it if not special '#' */
|
---|
250 | ch = file_getc();
|
---|
251 | if (ch == '\n')
|
---|
252 | {
|
---|
253 | cpp_afternl = TRUE;
|
---|
254 | cpp_echo(ch);
|
---|
255 | return ch;
|
---|
256 | }
|
---|
257 | else if (ch != '#' || !cpp_afternl)
|
---|
258 | {
|
---|
259 | /* normal character. Any non-whitespace should turn off afternl */
|
---|
260 | if (ch != ' ' && ch != '\t')
|
---|
261 | {
|
---|
262 | cpp_afternl = FALSE;
|
---|
263 | }
|
---|
264 | cpp_echo(ch);
|
---|
265 | return ch;
|
---|
266 | }
|
---|
267 |
|
---|
268 | /* Yikes! We found a directive */
|
---|
269 |
|
---|
270 | /* see whether this is a #define line */
|
---|
271 | scan = " define ";
|
---|
272 | while (*scan)
|
---|
273 | {
|
---|
274 | if (*scan == ' ')
|
---|
275 | {
|
---|
276 | /* space character matches any whitespace */
|
---|
277 | do
|
---|
278 | {
|
---|
279 | ch = file_getc();
|
---|
280 | } while (ch == ' ' || ch == '\t');
|
---|
281 | file_ungetc(ch);
|
---|
282 | }
|
---|
283 | else
|
---|
284 | {
|
---|
285 | /* other characters should match exactly */
|
---|
286 | ch = file_getc();
|
---|
287 | if (ch != *scan)
|
---|
288 | {
|
---|
289 | file_ungetc(ch);
|
---|
290 | break;
|
---|
291 | }
|
---|
292 | }
|
---|
293 | scan++;
|
---|
294 | }
|
---|
295 |
|
---|
296 | /* is this a #define line? and should we generate a tag for it? */
|
---|
297 | if (!*scan && (file_header || incl_static))
|
---|
298 | {
|
---|
299 | /* if not a header, then this will be a static tag */
|
---|
300 | if (!file_header)
|
---|
301 | {
|
---|
302 | fputs(file_name, tags);
|
---|
303 | putc(':', tags);
|
---|
304 | }
|
---|
305 |
|
---|
306 | /* output the tag name */
|
---|
307 | for (ch = file_getc(); isalnum(ch) || ch == '_'; ch = file_getc())
|
---|
308 | {
|
---|
309 | putc(ch, tags);
|
---|
310 | }
|
---|
311 |
|
---|
312 | /* output a tab, the filename, another tab, and the line number */
|
---|
313 | fprintf(tags, "\t%s\t%ld\n", file_name, file_lnum);
|
---|
314 | }
|
---|
315 |
|
---|
316 | /* skip to the end of the directive -- a newline that isn't preceded
|
---|
317 | * by a '\' character.
|
---|
318 | */
|
---|
319 | while (ch != EOF && ch != '\n')
|
---|
320 | {
|
---|
321 | if (ch == '\\')
|
---|
322 | {
|
---|
323 | ch = file_getc();
|
---|
324 | }
|
---|
325 | ch = file_getc();
|
---|
326 | }
|
---|
327 |
|
---|
328 | /* return the newline that we found at the end of the directive */
|
---|
329 | cpp_echo(ch);
|
---|
330 | return ch;
|
---|
331 | }
|
---|
332 |
|
---|
333 | /* This puts a character back into the input queue for the source file */
|
---|
334 | cpp_ungetc(ch)
|
---|
335 | int ch; /* a character to be ungotten */
|
---|
336 | {
|
---|
337 | cpp_prevch = ch;
|
---|
338 | }
|
---|
339 |
|
---|
340 |
|
---|
341 | /* -------------------------------------------------------------------------- */
|
---|
342 | /* This is the lexical analyser. It gets characters from the preprocessor,
|
---|
343 | * and gives tokens to the parser. Some special codes are...
|
---|
344 | * (deleted) /*...* / (comments)
|
---|
345 | * (deleted) //...\n (comments)
|
---|
346 | * (deleted) (* (parens used in complex declaration)
|
---|
347 | * (deleted) [...] (array subscript, when ... contains no ])
|
---|
348 | * (deleted) struct (intro to structure declaration)
|
---|
349 | * BODY {...} ('{' can occur anywhere, '}' only at BOW if ... has '{')
|
---|
350 | * ARGS (...{ (args of function, not extern or forward)
|
---|
351 | * ARGS (...); (args of an extern/forward function declaration)
|
---|
352 | * COMMA , (separate declarations that have same scope)
|
---|
353 | * SEMICOLON ; (separate declarations that have different scope)
|
---|
354 | * SEMICOLON =...; (initializer)
|
---|
355 | * TYPEDEF typedef (the "typedef" keyword)
|
---|
356 | * STATIC static (the "static" keyword)
|
---|
357 | * STATIC private (the "static" keyword)
|
---|
358 | * STATIC PRIVATE (the "static" keyword)
|
---|
359 | * NAME [a-z]+ (really any valid name that isn't reserved word)
|
---|
360 | */
|
---|
361 |
|
---|
362 | /* #define EOF -1 */
|
---|
363 | #define DELETED 0
|
---|
364 | #define BODY 1
|
---|
365 | #define ARGS 2
|
---|
366 | #define COMMA 3
|
---|
367 | #define SEMICOLON 4
|
---|
368 | #define TYPEDEF 5
|
---|
369 | #define STATIC 6
|
---|
370 | #define EXTERN 7
|
---|
371 | #define NAME 8
|
---|
372 |
|
---|
373 | char lex_name[BLKSIZE]; /* the name of a "NAME" token */
|
---|
374 | long lex_seek; /* start of line that contains lex_name */
|
---|
375 |
|
---|
376 | lex_gettoken()
|
---|
377 | {
|
---|
378 | int ch; /* a character from the preprocessor */
|
---|
379 | int next; /* the next character */
|
---|
380 | int token; /* the token that we'll return */
|
---|
381 | int i;
|
---|
382 |
|
---|
383 | /* loop until we get a token that isn't "DELETED" */
|
---|
384 | do
|
---|
385 | {
|
---|
386 | /* get the next character */
|
---|
387 | ch = cpp_getc();
|
---|
388 |
|
---|
389 | /* process the character */
|
---|
390 | switch (ch)
|
---|
391 | {
|
---|
392 | case ',':
|
---|
393 | token = COMMA;
|
---|
394 | break;
|
---|
395 |
|
---|
396 | case ';':
|
---|
397 | token = SEMICOLON;
|
---|
398 | break;
|
---|
399 |
|
---|
400 | case '/':
|
---|
401 | /* get the next character */
|
---|
402 | ch = cpp_getc();
|
---|
403 | switch (ch)
|
---|
404 | {
|
---|
405 | case '*': /* start of C comment */
|
---|
406 | ch = cpp_getc();
|
---|
407 | next = cpp_getc();
|
---|
408 | while (next != EOF && (ch != '*' || next != '/'))
|
---|
409 | {
|
---|
410 | ch = next;
|
---|
411 | next = cpp_getc();
|
---|
412 | }
|
---|
413 | break;
|
---|
414 |
|
---|
415 | case '/': /* start of a C++ comment */
|
---|
416 | do
|
---|
417 | {
|
---|
418 | ch = cpp_getc();
|
---|
419 | } while (ch != '\n' && ch != EOF);
|
---|
420 | break;
|
---|
421 |
|
---|
422 | default: /* some other slash */
|
---|
423 | cpp_ungetc(ch);
|
---|
424 | }
|
---|
425 | token = DELETED;
|
---|
426 | break;
|
---|
427 |
|
---|
428 | case '(':
|
---|
429 | ch = cpp_getc();
|
---|
430 | if (ch == '*')
|
---|
431 | {
|
---|
432 | token = DELETED;
|
---|
433 | }
|
---|
434 | else
|
---|
435 | {
|
---|
436 | next = cpp_getc();
|
---|
437 | while (ch != '{' && ch != EOF && (ch != ')' || next != ';'))/*}*/
|
---|
438 | {
|
---|
439 | ch = next;
|
---|
440 | next = cpp_getc();
|
---|
441 | }
|
---|
442 | if (ch == '{')/*}*/
|
---|
443 | {
|
---|
444 | cpp_ungetc(ch);
|
---|
445 | }
|
---|
446 | else if (next == ';')
|
---|
447 | {
|
---|
448 | cpp_ungetc(next);
|
---|
449 | }
|
---|
450 | token = ARGS;
|
---|
451 | }
|
---|
452 | break;
|
---|
453 |
|
---|
454 | case '{':/*}*/
|
---|
455 | /* don't send the next characters to "refs" */
|
---|
456 | cpp_refsok = FALSE;
|
---|
457 |
|
---|
458 | /* skip ahead to closing '}', or to embedded '{' */
|
---|
459 | do
|
---|
460 | {
|
---|
461 | ch = cpp_getc();
|
---|
462 | } while (ch != '{' && ch != '}' && ch != EOF);
|
---|
463 |
|
---|
464 | /* if has embedded '{', then skip to '}' in column 1 */
|
---|
465 | if (ch == '{') /*}*/
|
---|
466 | {
|
---|
467 | ch = cpp_getc();
|
---|
468 | next = cpp_getc();
|
---|
469 | while (ch != EOF && (ch != '\n' || next != '}'))/*{*/
|
---|
470 | {
|
---|
471 | ch = next;
|
---|
472 | next = cpp_getc();
|
---|
473 | }
|
---|
474 | }
|
---|
475 |
|
---|
476 | /* resume "refs" processing */
|
---|
477 | cpp_refsok = TRUE;
|
---|
478 | cpp_echo('}');
|
---|
479 |
|
---|
480 | token = BODY;
|
---|
481 | break;
|
---|
482 |
|
---|
483 | case '[':
|
---|
484 | /* skip to matching ']' */
|
---|
485 | do
|
---|
486 | {
|
---|
487 | ch = cpp_getc();
|
---|
488 | } while (ch != ']' && ch != EOF);
|
---|
489 | token = DELETED;
|
---|
490 | break;
|
---|
491 |
|
---|
492 | case '=':
|
---|
493 | /* skip to next ';' */
|
---|
494 | do
|
---|
495 | {
|
---|
496 | ch = cpp_getc();
|
---|
497 |
|
---|
498 | /* leave array initializers out of "refs" */
|
---|
499 | if (ch == '{')
|
---|
500 | {
|
---|
501 | cpp_refsok = FALSE;
|
---|
502 | }
|
---|
503 | } while (ch != ';' && ch != EOF);
|
---|
504 |
|
---|
505 | /* resume echoing to "refs" */
|
---|
506 | if (!cpp_refsok)
|
---|
507 | {
|
---|
508 | cpp_refsok = TRUE;
|
---|
509 | cpp_echo('}');
|
---|
510 | cpp_echo(';');
|
---|
511 | }
|
---|
512 | token = SEMICOLON;
|
---|
513 | break;
|
---|
514 |
|
---|
515 | case EOF:
|
---|
516 | token = EOF;
|
---|
517 | break;
|
---|
518 |
|
---|
519 | default:
|
---|
520 | /* is this the start of a name/keyword? */
|
---|
521 | if (isalpha(ch) || ch == '_')
|
---|
522 | {
|
---|
523 | /* collect the whole word */
|
---|
524 | lex_name[0] = ch;
|
---|
525 | for (i = 1, ch = cpp_getc();
|
---|
526 | i < BLKSIZE - 1 && (isalnum(ch) || ch == '_');
|
---|
527 | i++, ch = cpp_getc())
|
---|
528 | {
|
---|
529 | lex_name[i] = ch;
|
---|
530 | }
|
---|
531 | lex_name[i] = '\0';
|
---|
532 | cpp_ungetc(ch);
|
---|
533 |
|
---|
534 | /* is it a reserved word? */
|
---|
535 | if (!strcmp(lex_name, "typedef"))
|
---|
536 | {
|
---|
537 | token = TYPEDEF;
|
---|
538 | lex_seek = -1L;
|
---|
539 | }
|
---|
540 | else if (!strcmp(lex_name, "static")
|
---|
541 | || !strcmp(lex_name, "private")
|
---|
542 | || !strcmp(lex_name, "PRIVATE"))
|
---|
543 | {
|
---|
544 | token = STATIC;
|
---|
545 | lex_seek = -1L;
|
---|
546 | }
|
---|
547 | else if (!strcmp(lex_name, "extern")
|
---|
548 | || !strcmp(lex_name, "EXTERN")
|
---|
549 | || !strcmp(lex_name, "FORWARD"))
|
---|
550 | {
|
---|
551 | token = EXTERN;
|
---|
552 | lex_seek = -1L;
|
---|
553 | }
|
---|
554 | else
|
---|
555 | {
|
---|
556 | token = NAME;
|
---|
557 | lex_seek = file_seek;
|
---|
558 | }
|
---|
559 | }
|
---|
560 | else /* not part of a name/keyword */
|
---|
561 | {
|
---|
562 | token = DELETED;
|
---|
563 | }
|
---|
564 |
|
---|
565 | } /* end switch(ch) */
|
---|
566 |
|
---|
567 | } while (token == DELETED);
|
---|
568 |
|
---|
569 | return token;
|
---|
570 | }
|
---|
571 |
|
---|
572 | /* -------------------------------------------------------------------------- */
|
---|
573 | /* This is the parser. It locates tag candidates, and then decides whether to
|
---|
574 | * generate a tag for them.
|
---|
575 | */
|
---|
576 |
|
---|
577 | /* This function generates a tag for the object in lex_name, whose tag line is
|
---|
578 | * located at a given seek offset.
|
---|
579 | */
|
---|
580 | void maketag(scope, seek)
|
---|
581 | int scope; /* 0 if global, or STATIC if static */
|
---|
582 | long seek; /* the seek offset of the line */
|
---|
583 | {
|
---|
584 | /* output the tagname and filename fields */
|
---|
585 | if (scope == EXTERN)
|
---|
586 | {
|
---|
587 | /* whoa! we should *never* output a tag for "extern" decl */
|
---|
588 | return;
|
---|
589 | }
|
---|
590 | else if (scope == STATIC)
|
---|
591 | {
|
---|
592 | fprintf(tags, "%s:%s\t%s\t", file_name, lex_name, file_name);
|
---|
593 | }
|
---|
594 | else
|
---|
595 | {
|
---|
596 | fprintf(tags, "%s\t%s\t", lex_name, file_name);
|
---|
597 | }
|
---|
598 |
|
---|
599 | /* output the target line */
|
---|
600 | putc('/', tags);
|
---|
601 | putc('^', tags);
|
---|
602 | file_copyline(seek, tags);
|
---|
603 | putc('$', tags);
|
---|
604 | putc('/', tags);
|
---|
605 | putc('\n', tags);
|
---|
606 | }
|
---|
607 |
|
---|
608 |
|
---|
609 | /* This function parses a source file, adding any tags that it finds */
|
---|
610 | void ctags(name)
|
---|
611 | char *name; /* the name of a source file to be checked */
|
---|
612 | {
|
---|
613 | int prev; /* the previous token from the source file */
|
---|
614 | int token; /* the current token from the source file */
|
---|
615 | int scope; /* normally 0, but could be a TYPEDEF or STATIC token */
|
---|
616 | int gotname;/* boolean: does lex_name contain a tag candidate? */
|
---|
617 | long tagseek;/* start of line that contains lex_name */
|
---|
618 |
|
---|
619 | /* open the file */
|
---|
620 | cpp_open(name);
|
---|
621 |
|
---|
622 | /* reset */
|
---|
623 | scope = 0;
|
---|
624 | gotname = FALSE;
|
---|
625 | token = SEMICOLON;
|
---|
626 |
|
---|
627 | /* parse until the end of the file */
|
---|
628 | while (prev = token, (token = lex_gettoken()) != EOF)
|
---|
629 | {
|
---|
630 | /* scope keyword? */
|
---|
631 | if (token == TYPEDEF || token == STATIC || token == EXTERN)
|
---|
632 | {
|
---|
633 | scope = token;
|
---|
634 | gotname = FALSE;
|
---|
635 | continue;
|
---|
636 | }
|
---|
637 |
|
---|
638 | /* name of a possible tag candidate? */
|
---|
639 | if (token == NAME)
|
---|
640 | {
|
---|
641 | tagseek = file_seek;
|
---|
642 | gotname = TRUE;
|
---|
643 | continue;
|
---|
644 | }
|
---|
645 |
|
---|
646 | /* if NAME BODY, without ARGS, then NAME is a struct tag */
|
---|
647 | if (gotname && token == BODY && prev != ARGS)
|
---|
648 | {
|
---|
649 | gotname = FALSE;
|
---|
650 |
|
---|
651 | /* ignore if in typedef -- better name is coming soon */
|
---|
652 | if (scope == TYPEDEF)
|
---|
653 | {
|
---|
654 | continue;
|
---|
655 | }
|
---|
656 |
|
---|
657 | /* generate a tag, if -t and maybe -s */
|
---|
658 | if (incl_types && (file_header || incl_static))
|
---|
659 | {
|
---|
660 | maketag(file_header ? 0 : STATIC, tagseek);
|
---|
661 | }
|
---|
662 | }
|
---|
663 |
|
---|
664 | /* If NAME ARGS BODY, then NAME is a function */
|
---|
665 | if (gotname && prev == ARGS && token == BODY)
|
---|
666 | {
|
---|
667 | gotname = FALSE;
|
---|
668 |
|
---|
669 | /* generate a tag, maybe checking -s */
|
---|
670 | if (scope != STATIC || incl_static)
|
---|
671 | {
|
---|
672 | maketag(scope, tagseek);
|
---|
673 | }
|
---|
674 | }
|
---|
675 |
|
---|
676 | /* If NAME SEMICOLON or NAME COMMA, then NAME is var/typedef */
|
---|
677 | if (gotname && (token == SEMICOLON || token == COMMA))
|
---|
678 | {
|
---|
679 | gotname = FALSE;
|
---|
680 |
|
---|
681 | /* generate a tag, if -v/-t and maybe -s */
|
---|
682 | if (scope == TYPEDEF && incl_types && (file_header || incl_static)
|
---|
683 | || scope == STATIC && incl_vars && incl_static
|
---|
684 | || incl_vars)
|
---|
685 | {
|
---|
686 | /* a TYPEDEF outside of a header is STATIC */
|
---|
687 | if (scope == TYPEDEF && !file_header)
|
---|
688 | {
|
---|
689 | maketag(STATIC, tagseek);
|
---|
690 | }
|
---|
691 | else /* use whatever scope was declared */
|
---|
692 | {
|
---|
693 | maketag(scope, tagseek);
|
---|
694 | }
|
---|
695 | }
|
---|
696 | }
|
---|
697 |
|
---|
698 | /* reset after a semicolon or ARGS BODY pair */
|
---|
699 | if (token == SEMICOLON || (prev == ARGS && token == BODY))
|
---|
700 | {
|
---|
701 | scope = 0;
|
---|
702 | gotname = FALSE;
|
---|
703 | }
|
---|
704 | }
|
---|
705 |
|
---|
706 | /* The source file will be automatically closed */
|
---|
707 | }
|
---|
708 |
|
---|
709 | /* -------------------------------------------------------------------------- */
|
---|
710 |
|
---|
711 | void usage()
|
---|
712 | {
|
---|
713 | fprintf(stderr, "usage: ctags [flags] filenames...\n");
|
---|
714 | fprintf(stderr, "\t-s include static functions\n");
|
---|
715 | fprintf(stderr, "\t-t include typedefs\n");
|
---|
716 | fprintf(stderr, "\t-v include variable declarations\n");
|
---|
717 | fprintf(stderr, "\t-r generate a \"refs\" file, too\n");
|
---|
718 | fprintf(stderr, "\t-a append to \"tags\", instead of overwriting\n");
|
---|
719 | exit(2);
|
---|
720 | }
|
---|
721 |
|
---|
722 |
|
---|
723 |
|
---|
724 | #if AMIGA
|
---|
725 | # include "amiwild.c"
|
---|
726 | #endif
|
---|
727 |
|
---|
728 | #if VMS
|
---|
729 | # include "vmswild.c"
|
---|
730 | #endif
|
---|
731 |
|
---|
732 | main(argc, argv)
|
---|
733 | int argc;
|
---|
734 | char **argv;
|
---|
735 | {
|
---|
736 | int i, j;
|
---|
737 |
|
---|
738 | #if MSDOS || TOS
|
---|
739 | char **wildexpand();
|
---|
740 | argv = wildexpand(&argc, argv);
|
---|
741 | #endif
|
---|
742 |
|
---|
743 | /* build the tables used by the ctype macros */
|
---|
744 | _ct_init("");
|
---|
745 |
|
---|
746 | /* parse the option flags */
|
---|
747 | for (i = 1; i < argc && argv[i][0] == '-'; i++)
|
---|
748 | {
|
---|
749 | for (j = 1; argv[i][j]; j++)
|
---|
750 | {
|
---|
751 | switch (argv[i][j])
|
---|
752 | {
|
---|
753 | case 's': incl_static = TRUE; break;
|
---|
754 | case 't': incl_types = TRUE; break;
|
---|
755 | case 'v': incl_vars = TRUE; break;
|
---|
756 | case 'r': make_refs = TRUE; break;
|
---|
757 | case 'a': append_files = TRUE; break;
|
---|
758 | default: usage();
|
---|
759 | }
|
---|
760 | }
|
---|
761 | }
|
---|
762 |
|
---|
763 | /* There should always be at least one source file named in args */
|
---|
764 | if (i == argc)
|
---|
765 | {
|
---|
766 | usage();
|
---|
767 | }
|
---|
768 |
|
---|
769 | /* open the "tags" and maybe "refs" files */
|
---|
770 | tags = fopen(TAGS, append_files ? "a" : "w");
|
---|
771 | if (!tags)
|
---|
772 | {
|
---|
773 | perror(TAGS);
|
---|
774 | exit(3);
|
---|
775 | }
|
---|
776 | if (make_refs)
|
---|
777 | {
|
---|
778 | refs = fopen(REFS, append_files ? "a" : "w");
|
---|
779 | if (!refs)
|
---|
780 | {
|
---|
781 | perror(REFS);
|
---|
782 | exit(4);
|
---|
783 | }
|
---|
784 | }
|
---|
785 |
|
---|
786 | /* parse each source file */
|
---|
787 | for (; i < argc; i++)
|
---|
788 | {
|
---|
789 | ctags(argv[i]);
|
---|
790 | }
|
---|
791 |
|
---|
792 | /* close "tags" and maybe "refs" */
|
---|
793 | fclose(tags);
|
---|
794 | if (make_refs)
|
---|
795 | {
|
---|
796 | fclose(refs);
|
---|
797 | }
|
---|
798 |
|
---|
799 | #ifdef SORT
|
---|
800 | /* This is a hack which will sort the tags list. It should
|
---|
801 | * on UNIX and OS-9. You may have trouble with csh. Note
|
---|
802 | * that the tags list only has to be sorted if you intend to
|
---|
803 | * use it with the real vi; elvis permits unsorted tags.
|
---|
804 | */
|
---|
805 | # if OSK
|
---|
806 | system("qsort tags >-_tags; -nx; del tags; rename _tags tags");
|
---|
807 | # else
|
---|
808 | system("sort tags >_tags$$; mv _tags$$ tags");
|
---|
809 | # endif
|
---|
810 | #endif
|
---|
811 |
|
---|
812 | exit(0);
|
---|
813 | /*NOTREACHED*/
|
---|
814 | }
|
---|
815 |
|
---|
816 | #if MSDOS || TOS
|
---|
817 | # define WILDCARD_NO_MAIN
|
---|
818 | # include "wildcard.c"
|
---|
819 | #endif
|
---|