2 * $Id: readtags.c 592 2007-07-31 03:30:41Z dhiebert $
4 * Copyright (c) 1996-2003, Darren Hiebert
6 * This source code is released into the public domain.
8 * This module contains functions for reading tag files.
19 #include <sys/types.h> /* to declare off_t */
37 /* Information about current tag file */
39 /* has the file been opened and this structure initialized? */
41 /* format of tag file */
43 /* how is the tag file sorted? */
45 /* pointer to file structure */
47 /* file position of first character of `line' */
49 /* size of tag file in seekable positions */
53 /* name of tag in last line read */
55 /* defines tag search state */
57 /* file position of last match for tag */
59 /* name of tag last searched for */
61 /* length of name for partial matches */
63 /* peforming partial match */
68 /* miscellaneous extension fields */
70 /* number of entries in `list' */
72 /* list of key value pairs */
73 tagExtensionField
*list
;
75 /* buffers to be freed at close */
77 /* name of program author */
81 /* URL of distribution */
91 const char *const EmptyString
= "";
92 const char *const PseudoTagPrefix
= "!_";
95 * FUNCTION DEFINITIONS
99 * Compare two strings, ignoring case.
100 * Return 0 for match, < 0 for smaller, > 0 for bigger
101 * Make sure case is folded to uppercase in comparison (like for 'sort -f')
102 * This makes a difference when one of the chars lies between upper and lower
103 * ie. one of the chars [ \ ] ^ _ ` for ascii. (The '_' in particular !)
105 static int struppercmp (const char *s1
, const char *s2
)
110 result
= toupper ((int) *s1
) - toupper ((int) *s2
);
111 } while (result
== 0 && *s1
++ != '\0' && *s2
++ != '\0');
115 static int strnuppercmp (const char *s1
, const char *s2
, size_t n
)
120 result
= toupper ((int) *s1
) - toupper ((int) *s2
);
121 } while (result
== 0 && --n
> 0 && *s1
++ != '\0' && *s2
++ != '\0');
125 static int growString (vstring
*s
)
133 newLine
= (char*) malloc (newLength
);
138 newLength
= 2 * s
->size
;
139 newLine
= (char*) realloc (s
->buffer
, newLength
);
142 perror ("string too large");
152 /* Copy name of tag out of tag line */
153 static void copyName (tagFile
*const file
)
156 const char *end
= strchr (file
->line
.buffer
, '\t');
159 end
= strchr (file
->line
.buffer
, '\n');
161 end
= strchr (file
->line
.buffer
, '\r');
164 length
= end
- file
->line
.buffer
;
166 length
= strlen (file
->line
.buffer
);
167 while (length
>= file
->name
.size
)
168 growString (&file
->name
);
169 strncpy (file
->name
.buffer
, file
->line
.buffer
, length
);
170 file
->name
.buffer
[length
] = '\0';
173 static int readTagLineRaw (tagFile
*const file
)
178 /* If reading the line places any character other than a null or a
179 * newline at the last character position in the buffer (one less than
180 * the buffer size), then we must resize the buffer and reattempt to read
185 char *const pLastChar
= file
->line
.buffer
+ file
->line
.size
- 2;
188 file
->pos
= ftell (file
->fp
);
191 line
= fgets (file
->line
.buffer
, (int) file
->line
.size
, file
->fp
);
195 if (! feof (file
->fp
))
196 perror ("readTagLine");
199 else if (*pLastChar
!= '\0' &&
200 *pLastChar
!= '\n' && *pLastChar
!= '\r')
202 /* buffer overflow */
203 growString (&file
->line
);
204 fseek (file
->fp
, file
->pos
, SEEK_SET
);
209 size_t i
= strlen (file
->line
.buffer
);
211 (file
->line
.buffer
[i
- 1] == '\n' || file
->line
.buffer
[i
- 1] == '\r'))
213 file
->line
.buffer
[i
- 1] = '\0';
217 } while (reReadLine
&& result
);
223 static int readTagLine (tagFile
*const file
)
228 result
= readTagLineRaw (file
);
229 } while (result
&& *file
->name
.buffer
== '\0');
233 static tagResult
growFields (tagFile
*const file
)
235 tagResult result
= TagFailure
;
236 unsigned short newCount
= (unsigned short) 2 * file
->fields
.max
;
237 tagExtensionField
*newFields
= (tagExtensionField
*)
238 realloc (file
->fields
.list
, newCount
* sizeof (tagExtensionField
));
239 if (newFields
== NULL
)
240 perror ("too many extension fields");
243 file
->fields
.list
= newFields
;
244 file
->fields
.max
= newCount
;
250 static void parseExtensionFields (tagFile
*const file
, tagEntry
*const entry
,
254 while (p
!= NULL
&& *p
!= '\0')
265 colon
= strchr (field
, ':');
270 const char *key
= field
;
271 const char *value
= colon
+ 1;
273 if (strcmp (key
, "kind") == 0)
275 else if (strcmp (key
, "file") == 0)
276 entry
->fileScope
= 1;
277 else if (strcmp (key
, "line") == 0)
278 entry
->address
.lineNumber
= atol (value
);
281 if (entry
->fields
.count
== file
->fields
.max
)
283 file
->fields
.list
[entry
->fields
.count
].key
= key
;
284 file
->fields
.list
[entry
->fields
.count
].value
= value
;
285 ++entry
->fields
.count
;
292 static void parseTagLine (tagFile
*file
, tagEntry
*const entry
)
295 char *p
= file
->line
.buffer
;
296 char *tab
= strchr (p
, TAB
);
298 entry
->fields
.list
= NULL
;
299 entry
->fields
.count
= 0;
301 entry
->fileScope
= 0;
309 tab
= strchr (p
, TAB
);
315 if (*p
== '/' || *p
== '?')
318 int delimiter
= *(unsigned char*) p
;
319 entry
->address
.lineNumber
= 0;
320 entry
->address
.pattern
= p
;
323 p
= strchr (p
+ 1, delimiter
);
324 } while (p
!= NULL
&& *(p
- 1) == '\\');
327 /* invalid pattern */
332 else if (isdigit ((int) *(unsigned char*) p
))
334 /* parse line number */
335 entry
->address
.pattern
= p
;
336 entry
->address
.lineNumber
= atol (p
);
337 while (isdigit ((int) *(unsigned char*) p
))
342 /* invalid pattern */
344 fieldsPresent
= (strncmp (p
, ";\"", 2) == 0);
347 parseExtensionFields (file
, entry
, p
+ 2);
350 if (entry
->fields
.count
> 0)
351 entry
->fields
.list
= file
->fields
.list
;
352 for (i
= entry
->fields
.count
; i
< file
->fields
.max
; ++i
)
354 file
->fields
.list
[i
].key
= NULL
;
355 file
->fields
.list
[i
].value
= NULL
;
359 static char *duplicate (const char *str
)
364 result
= strdup (str
);
371 static void readPseudoTags (tagFile
*const file
, tagFileInfo
*const info
)
374 const size_t prefixLength
= strlen (PseudoTagPrefix
);
377 info
->file
.format
= 1;
378 info
->file
.sort
= TAG_UNSORTED
;
379 info
->program
.author
= NULL
;
380 info
->program
.name
= NULL
;
381 info
->program
.url
= NULL
;
382 info
->program
.version
= NULL
;
386 fgetpos (file
->fp
, &startOfLine
);
387 if (! readTagLine (file
))
389 if (strncmp (file
->line
.buffer
, PseudoTagPrefix
, prefixLength
) != 0)
394 const char *key
, *value
;
395 parseTagLine (file
, &entry
);
396 key
= entry
.name
+ prefixLength
;
398 if (strcmp (key
, "TAG_FILE_SORTED") == 0)
399 file
->sortMethod
= (sortType
) atoi (value
);
400 else if (strcmp (key
, "TAG_FILE_FORMAT") == 0)
401 file
->format
= (short) atoi (value
);
402 else if (strcmp (key
, "TAG_PROGRAM_AUTHOR") == 0)
403 file
->program
.author
= duplicate (value
);
404 else if (strcmp (key
, "TAG_PROGRAM_NAME") == 0)
405 file
->program
.name
= duplicate (value
);
406 else if (strcmp (key
, "TAG_PROGRAM_URL") == 0)
407 file
->program
.url
= duplicate (value
);
408 else if (strcmp (key
, "TAG_PROGRAM_VERSION") == 0)
409 file
->program
.version
= duplicate (value
);
412 info
->file
.format
= file
->format
;
413 info
->file
.sort
= file
->sortMethod
;
414 info
->program
.author
= file
->program
.author
;
415 info
->program
.name
= file
->program
.name
;
416 info
->program
.url
= file
->program
.url
;
417 info
->program
.version
= file
->program
.version
;
421 fsetpos (file
->fp
, &startOfLine
);
424 static void gotoFirstLogicalTag (tagFile
*const file
)
427 const size_t prefixLength
= strlen (PseudoTagPrefix
);
431 fgetpos (file
->fp
, &startOfLine
);
432 if (! readTagLine (file
))
434 if (strncmp (file
->line
.buffer
, PseudoTagPrefix
, prefixLength
) != 0)
437 fsetpos (file
->fp
, &startOfLine
);
440 static tagFile
*initialize (const char *const filePath
, tagFileInfo
*const info
)
442 tagFile
*result
= (tagFile
*) calloc ((size_t) 1, sizeof (tagFile
));
445 growString (&result
->line
);
446 growString (&result
->name
);
447 result
->fields
.max
= 20;
448 result
->fields
.list
= (tagExtensionField
*) calloc (
449 result
->fields
.max
, sizeof (tagExtensionField
));
450 result
->fp
= fopen (filePath
, "r");
451 if (result
->fp
== NULL
)
453 /* free the result struct */
454 if (result
->fields
.list
)
455 free (result
->fields
.list
);
456 if (result
->line
.buffer
)
457 free (result
->line
.buffer
);
458 if (result
->name
.buffer
)
459 free (result
->name
.buffer
);
463 info
->status
.error_number
= errno
;
467 fseek (result
->fp
, 0, SEEK_END
);
468 result
->size
= ftell (result
->fp
);
470 readPseudoTags (result
, info
);
471 info
->status
.opened
= 1;
472 result
->initialized
= 1;
478 static tagFile
*initialize_1 (const FILE* fd
, tagFileInfo
*const info
)
480 tagFile
*result
= (tagFile
*) malloc (sizeof (tagFile
));
483 memset (result
, 0, sizeof (tagFile
));
484 growString (&result
->line
);
485 growString (&result
->name
);
486 result
->fields
.max
= 20;
487 result
->fields
.list
= (tagExtensionField
*) malloc (
488 result
->fields
.max
* sizeof (tagExtensionField
));
489 result
->fp
= (FILE*)fd
;
490 if (result
->fp
== NULL
)
492 /* free the result struct */
493 if (result
->fields
.list
)
494 free (result
->fields
.list
);
495 if (result
->line
.buffer
)
496 free (result
->line
.buffer
);
497 if (result
->name
.buffer
)
498 free (result
->name
.buffer
);
502 info
->status
.error_number
= errno
;
506 fseek (result
->fp
, 0, SEEK_END
);
507 result
->size
= ftell (result
->fp
);
509 readPseudoTags (result
, info
);
510 info
->status
.opened
= 1;
511 result
->initialized
= 1;
517 static void terminate (tagFile
*const file
)
521 free (file
->line
.buffer
);
522 free (file
->name
.buffer
);
523 free (file
->fields
.list
);
525 if (file
->program
.author
!= NULL
)
526 free (file
->program
.author
);
527 if (file
->program
.name
!= NULL
)
528 free (file
->program
.name
);
529 if (file
->program
.url
!= NULL
)
530 free (file
->program
.url
);
531 if (file
->program
.version
!= NULL
)
532 free (file
->program
.version
);
533 if (file
->search
.name
!= NULL
)
534 free (file
->search
.name
);
536 memset (file
, 0, sizeof (tagFile
));
541 static tagResult
readNext (tagFile
*const file
, tagEntry
*const entry
)
544 if (file
== NULL
|| ! file
->initialized
)
546 else if (! readTagLine (file
))
551 parseTagLine (file
, entry
);
557 static const char *readFieldValue (
558 const tagEntry
*const entry
, const char *const key
)
560 const char *result
= NULL
;
562 if (strcmp (key
, "kind") == 0)
563 result
= entry
->kind
;
564 else if (strcmp (key
, "file") == 0)
565 result
= EmptyString
;
566 else for (i
= 0 ; i
< entry
->fields
.count
&& result
== NULL
; ++i
)
567 if (strcmp (entry
->fields
.list
[i
].key
, key
) == 0)
568 result
= entry
->fields
.list
[i
].value
;
572 static int readTagLineSeek (tagFile
*const file
, const off_t pos
)
575 if (fseek (file
->fp
, pos
, SEEK_SET
) == 0)
577 result
= readTagLine (file
); /* read probable partial line */
578 if (pos
> 0 && result
)
579 result
= readTagLine (file
); /* read complete line */
584 static int nameComparison (tagFile
*const file
)
587 if (file
->search
.ignorecase
)
589 if (file
->search
.partial
)
590 result
= strnuppercmp (file
->search
.name
, file
->name
.buffer
,
591 file
->search
.nameLength
);
593 result
= struppercmp (file
->search
.name
, file
->name
.buffer
);
597 if (file
->search
.partial
)
598 result
= strncmp (file
->search
.name
, file
->name
.buffer
,
599 file
->search
.nameLength
);
601 result
= strcmp (file
->search
.name
, file
->name
.buffer
);
606 static void findFirstNonMatchBefore (tagFile
*const file
)
608 #define JUMP_BACK 512
611 off_t start
= file
->pos
;
615 if (pos
< (off_t
) JUMP_BACK
)
618 pos
= pos
- JUMP_BACK
;
619 more_lines
= readTagLineSeek (file
, pos
);
620 comp
= nameComparison (file
);
621 } while (more_lines
&& comp
== 0 && pos
> 0 && pos
< start
);
624 static tagResult
findFirstMatchBefore (tagFile
*const file
)
626 tagResult result
= TagFailure
;
628 off_t start
= file
->pos
;
629 findFirstNonMatchBefore (file
);
632 more_lines
= readTagLine (file
);
633 if (nameComparison (file
) == 0)
635 } while (more_lines
&& result
!= TagSuccess
&& file
->pos
< start
);
639 static tagResult
findBinary (tagFile
*const file
)
641 tagResult result
= TagFailure
;
642 off_t lower_limit
= 0;
643 off_t upper_limit
= file
->size
;
645 off_t pos
= upper_limit
/ 2;
646 while (result
!= TagSuccess
)
648 if (! readTagLineSeek (file
, pos
))
650 /* in case we fell off end of file */
651 result
= findFirstMatchBefore (file
);
654 else if (pos
== last_pos
)
656 /* prevent infinite loop if we backed up to beginning of file */
661 const int comp
= nameComparison (file
);
666 pos
= lower_limit
+ ((upper_limit
- lower_limit
) / 2);
671 pos
= lower_limit
+ ((upper_limit
- lower_limit
) / 2);
676 result
= findFirstMatchBefore (file
);
682 static tagResult
findSequential (tagFile
*const file
)
684 tagResult result
= TagFailure
;
685 if (file
->initialized
)
687 while (result
== TagFailure
&& readTagLine (file
))
689 if (nameComparison (file
) == 0)
696 static tagResult
find (tagFile
*const file
, tagEntry
*const entry
,
697 const char *const name
, const int options
)
700 if (file
->search
.name
!= NULL
)
701 free (file
->search
.name
);
702 file
->search
.name
= duplicate (name
);
703 file
->search
.nameLength
= strlen (name
);
704 file
->search
.partial
= (options
& TAG_PARTIALMATCH
) != 0;
705 file
->search
.ignorecase
= (options
& TAG_IGNORECASE
) != 0;
706 fseek (file
->fp
, 0, SEEK_END
);
707 file
->size
= ftell (file
->fp
);
709 if ((file
->sortMethod
== TAG_SORTED
&& !file
->search
.ignorecase
) ||
710 (file
->sortMethod
== TAG_FOLDSORTED
&& file
->search
.ignorecase
))
713 printf ("<performing binary search>\n");
715 result
= findBinary (file
);
720 printf ("<performing sequential search>\n");
722 result
= findSequential (file
);
725 if (result
!= TagSuccess
)
726 file
->search
.pos
= file
->size
;
729 file
->search
.pos
= file
->pos
;
731 parseTagLine (file
, entry
);
736 static tagResult
findNext (tagFile
*const file
, tagEntry
*const entry
)
739 if ((file
->sortMethod
== TAG_SORTED
&& !file
->search
.ignorecase
) ||
740 (file
->sortMethod
== TAG_FOLDSORTED
&& file
->search
.ignorecase
))
742 result
= tagsNext (file
, entry
);
743 if (result
== TagSuccess
&& nameComparison (file
) != 0)
748 result
= findSequential (file
);
749 if (result
== TagSuccess
&& entry
!= NULL
)
750 parseTagLine (file
, entry
);
759 extern tagFile
*tagsOpen (const char *const filePath
, tagFileInfo
*const info
)
761 return initialize (filePath
, info
);
764 extern tagFile
*tagsOpen_1 (const FILE *fd
, tagFileInfo
*const info
)
766 return initialize_1 (fd
, info
);
769 extern tagResult
tagsSetSortType (tagFile
*const file
, const sortType type
)
771 tagResult result
= TagFailure
;
772 if (file
!= NULL
&& file
->initialized
)
774 file
->sortMethod
= type
;
780 extern tagResult
tagsFirst (tagFile
*const file
, tagEntry
*const entry
)
782 tagResult result
= TagFailure
;
783 if (file
!= NULL
&& file
->initialized
)
785 gotoFirstLogicalTag (file
);
786 result
= readNext (file
, entry
);
791 extern tagResult
tagsNext (tagFile
*const file
, tagEntry
*const entry
)
793 tagResult result
= TagFailure
;
794 if (file
!= NULL
&& file
->initialized
)
795 result
= readNext (file
, entry
);
799 extern const char *tagsField (const tagEntry
*const entry
, const char *const key
)
801 const char *result
= NULL
;
803 result
= readFieldValue (entry
, key
);
807 extern tagResult
tagsFind (tagFile
*const file
, tagEntry
*const entry
,
808 const char *const name
, const int options
)
810 tagResult result
= TagFailure
;
811 if (file
!= NULL
&& file
->initialized
)
812 result
= find (file
, entry
, name
, options
);
816 extern tagResult
tagsFindNext (tagFile
*const file
, tagEntry
*const entry
)
818 tagResult result
= TagFailure
;
819 if (file
!= NULL
&& file
->initialized
)
820 result
= findNext (file
, entry
);
824 extern tagResult
tagsClose (tagFile
*const file
)
826 tagResult result
= TagFailure
;
827 if (file
!= NULL
&& file
->initialized
)
841 static const char *TagFileName
= "tags";
842 static const char *ProgramName
;
843 static int extensionFields
;
844 static int SortOverride
;
845 static sortType SortMethod
;
847 static void printTag (const tagEntry
*entry
)
851 const char* separator
= ";\"";
852 const char* const empty
= "";
853 /* "sep" returns a value only the first time it is evaluated */
854 #define sep (first ? (first = 0, separator) : empty)
855 printf ("%s\t%s\t%s",
856 entry
->name
, entry
->file
, entry
->address
.pattern
);
859 if (entry
->kind
!= NULL
&& entry
->kind
[0] != '\0')
860 printf ("%s\tkind:%s", sep
, entry
->kind
);
861 if (entry
->fileScope
)
862 printf ("%s\tfile:", sep
);
864 if (entry
->address
.lineNumber
> 0)
865 printf ("%s\tline:%lu", sep
, entry
->address
.lineNumber
);
867 for (i
= 0 ; i
< entry
->fields
.count
; ++i
)
868 printf ("%s\t%s:%s", sep
, entry
->fields
.list
[i
].key
,
869 entry
->fields
.list
[i
].value
);
875 static void findTag (const char *const name
, const int options
)
879 tagFile
*const file
= tagsOpen (TagFileName
, &info
);
882 fprintf (stderr
, "%s: cannot open tag file: %s: %s\n",
883 ProgramName
, strerror (info
.status
.error_number
), name
);
889 tagsSetSortType (file
, SortMethod
);
890 if (tagsFind (file
, &entry
, name
, options
) == TagSuccess
)
895 } while (tagsFindNext (file
, &entry
) == TagSuccess
);
901 static void listTags (void)
905 tagFile
*const file
= tagsOpen (TagFileName
, &info
);
908 fprintf (stderr
, "%s: cannot open tag file: %s: %s\n",
909 ProgramName
, strerror (info
.status
.error_number
), TagFileName
);
914 while (tagsNext (file
, &entry
) == TagSuccess
)
920 const char *const Usage
=
921 "Find tag file entries matching specified names.\n\n"
922 "Usage: %s [-ilp] [-s[0|1]] [-t file] [name(s)]\n\n"
924 " -e Include extension fields in output.\n"
925 " -i Perform case-insensitive matching.\n"
926 " -l List all tags.\n"
927 " -p Perform partial matching.\n"
928 " -s[0|1|2] Override sort detection of tag file.\n"
929 " -t file Use specified tag file (default: \"tags\").\n"
930 "Note that options are acted upon as encountered, so order is significant.\n";
932 extern int main (int argc
, char **argv
)
935 int actionSupplied
= 0;
937 ProgramName
= argv
[0];
940 fprintf (stderr
, Usage
, ProgramName
);
943 for (i
= 1 ; i
< argc
; ++i
)
945 const char *const arg
= argv
[i
];
948 findTag (arg
, options
);
954 for (j
= 1 ; arg
[j
] != '\0' ; ++j
)
958 case 'e': extensionFields
= 1; break;
959 case 'i': options
|= TAG_IGNORECASE
; break;
960 case 'p': options
|= TAG_PARTIALMATCH
; break;
961 case 'l': listTags (); actionSupplied
= 1; break;
964 if (arg
[j
+1] != '\0')
966 TagFileName
= arg
+ j
+ 1;
967 j
+= strlen (TagFileName
);
969 else if (i
+ 1 < argc
)
970 TagFileName
= argv
[++i
];
973 fprintf (stderr
, Usage
, ProgramName
);
981 SortMethod
= TAG_SORTED
;
982 else if (strchr ("012", arg
[j
]) != NULL
)
983 SortMethod
= (sortType
) (arg
[j
] - '0');
986 fprintf (stderr
, Usage
, ProgramName
);
991 fprintf (stderr
, "%s: unknown option: %c\n",
992 ProgramName
, arg
[j
]);
999 if (! actionSupplied
)
1002 "%s: no action specified: specify tag name(s) or -l option\n",
1011 /* vi:set tabstop=4 shiftwidth=4: */