2 * $Id: readtags.c 2606 2006-08-16 21:13:34Z naba $
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
= 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
);
297 int fieldsPresent
= 0;
299 entry
->fields
.list
= NULL
;
300 entry
->fields
.count
= 0;
302 entry
->fileScope
= 0;
310 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
= (char*) malloc (strlen (str
) + 1);
368 strcpy (result
, str
);
373 static void readPseudoTags (tagFile
*const file
, tagFileInfo
*const info
)
376 const size_t prefixLength
= strlen (PseudoTagPrefix
);
379 info
->file
.format
= 1;
380 info
->file
.sort
= TAG_UNSORTED
;
381 info
->program
.author
= NULL
;
382 info
->program
.name
= NULL
;
383 info
->program
.url
= NULL
;
384 info
->program
.version
= NULL
;
388 fgetpos (file
->fp
, &startOfLine
);
389 if (! readTagLine (file
))
391 if (strncmp (file
->line
.buffer
, PseudoTagPrefix
, prefixLength
) != 0)
396 const char *key
, *value
;
397 parseTagLine (file
, &entry
);
398 key
= entry
.name
+ prefixLength
;
400 if (strcmp (key
, "TAG_FILE_SORTED") == 0)
401 file
->sortMethod
= (sortType
) atoi (value
);
402 else if (strcmp (key
, "TAG_FILE_FORMAT") == 0)
403 file
->format
= atoi (value
);
404 else if (strcmp (key
, "TAG_PROGRAM_AUTHOR") == 0)
405 file
->program
.author
= duplicate (value
);
406 else if (strcmp (key
, "TAG_PROGRAM_NAME") == 0)
407 file
->program
.name
= duplicate (value
);
408 else if (strcmp (key
, "TAG_PROGRAM_URL") == 0)
409 file
->program
.url
= duplicate (value
);
410 else if (strcmp (key
, "TAG_PROGRAM_VERSION") == 0)
411 file
->program
.version
= duplicate (value
);
414 info
->file
.format
= file
->format
;
415 info
->file
.sort
= file
->sortMethod
;
416 info
->program
.author
= file
->program
.author
;
417 info
->program
.name
= file
->program
.name
;
418 info
->program
.url
= file
->program
.url
;
419 info
->program
.version
= file
->program
.version
;
423 fsetpos (file
->fp
, &startOfLine
);
426 static void gotoFirstLogicalTag (tagFile
*const file
)
429 const size_t prefixLength
= strlen (PseudoTagPrefix
);
433 fgetpos (file
->fp
, &startOfLine
);
434 if (! readTagLine (file
))
436 if (strncmp (file
->line
.buffer
, PseudoTagPrefix
, prefixLength
) != 0)
439 fsetpos (file
->fp
, &startOfLine
);
442 static tagFile
*initialize (const char *const filePath
, tagFileInfo
*const info
)
444 tagFile
*result
= (tagFile
*) malloc (sizeof (tagFile
));
447 memset (result
, 0, sizeof (tagFile
));
448 growString (&result
->line
);
449 growString (&result
->name
);
450 result
->fields
.max
= 20;
451 result
->fields
.list
= (tagExtensionField
*) malloc (
452 result
->fields
.max
* sizeof (tagExtensionField
));
453 result
->fp
= fopen (filePath
, "r");
454 if (result
->fp
== NULL
)
458 info
->status
.error_number
= errno
;
462 fseek (result
->fp
, 0, SEEK_END
);
463 result
->size
= ftell (result
->fp
);
465 readPseudoTags (result
, info
);
466 info
->status
.opened
= 1;
467 result
->initialized
= 1;
473 static tagFile
*initialize_1 (FILE* fd
, tagFileInfo
*const info
)
475 tagFile
*result
= (tagFile
*) malloc (sizeof (tagFile
));
478 memset (result
, 0, sizeof (tagFile
));
479 growString (&result
->line
);
480 growString (&result
->name
);
481 result
->fields
.max
= 20;
482 result
->fields
.list
= (tagExtensionField
*) malloc (
483 result
->fields
.max
* sizeof (tagExtensionField
));
485 if (result
->fp
== NULL
)
489 info
->status
.error_number
= errno
;
493 fseek (result
->fp
, 0, SEEK_END
);
494 result
->size
= ftell (result
->fp
);
496 readPseudoTags (result
, info
);
497 info
->status
.opened
= 1;
498 result
->initialized
= 1;
504 static void terminate (tagFile
*const file
)
508 free (file
->line
.buffer
);
509 free (file
->name
.buffer
);
510 free (file
->fields
.list
);
512 if (file
->program
.author
!= NULL
)
513 free (file
->program
.author
);
514 if (file
->program
.name
!= NULL
)
515 free (file
->program
.name
);
516 if (file
->program
.url
!= NULL
)
517 free (file
->program
.url
);
518 if (file
->program
.version
!= NULL
)
519 free (file
->program
.version
);
521 memset (file
, 0, sizeof (tagFile
));
526 static tagResult
readNext (tagFile
*const file
, tagEntry
*const entry
)
528 tagResult result
= TagFailure
;
529 if (file
== NULL
|| ! file
->initialized
)
531 else if (! readTagLine (file
))
536 parseTagLine (file
, entry
);
542 static const char *readFieldValue (
543 const tagEntry
*const entry
, const char *const key
)
545 const char *result
= NULL
;
547 if (strcmp (key
, "kind") == 0)
548 result
= entry
->kind
;
549 else if (strcmp (key
, "file") == 0)
550 result
= EmptyString
;
551 else for (i
= 0 ; i
< entry
->fields
.count
&& result
== NULL
; ++i
)
552 if (strcmp (entry
->fields
.list
[i
].key
, key
) == 0)
553 result
= entry
->fields
.list
[i
].value
;
557 static int readTagLineSeek (tagFile
*const file
, const off_t pos
)
560 if (fseek (file
->fp
, pos
, SEEK_SET
) == 0)
562 result
= readTagLine (file
); /* read probable partial line */
563 if (pos
> 0 && result
)
564 result
= readTagLine (file
); /* read complete line */
569 static int nameComparison (tagFile
*const file
)
572 if (file
->search
.ignorecase
)
574 if (file
->search
.partial
)
575 result
= strnuppercmp (file
->search
.name
, file
->name
.buffer
,
576 file
->search
.nameLength
);
578 result
= struppercmp (file
->search
.name
, file
->name
.buffer
);
582 if (file
->search
.partial
)
583 result
= strncmp (file
->search
.name
, file
->name
.buffer
,
584 file
->search
.nameLength
);
586 result
= strcmp (file
->search
.name
, file
->name
.buffer
);
591 static void findFirstNonMatchBefore (tagFile
*const file
)
593 #define JUMP_BACK 512
596 off_t start
= file
->pos
;
600 if (pos
< (off_t
) JUMP_BACK
)
603 pos
= pos
- JUMP_BACK
;
604 more_lines
= readTagLineSeek (file
, pos
);
605 comp
= nameComparison (file
);
606 } while (more_lines
&& comp
== 0 && pos
> 0 && pos
< start
);
609 static tagResult
findFirstMatchBefore (tagFile
*const file
)
611 tagResult result
= TagFailure
;
613 off_t start
= file
->pos
;
614 findFirstNonMatchBefore (file
);
617 more_lines
= readTagLine (file
);
618 if (nameComparison (file
) == 0)
620 } while (more_lines
&& result
!= TagSuccess
&& file
->pos
< start
);
624 static tagResult
findBinary (tagFile
*const file
)
626 tagResult result
= TagFailure
;
627 off_t lower_limit
= 0;
628 off_t upper_limit
= file
->size
;
630 off_t pos
= upper_limit
/ 2;
631 while (result
!= TagSuccess
)
633 if (! readTagLineSeek (file
, pos
))
635 /* in case we fell off end of file */
636 result
= findFirstMatchBefore (file
);
639 else if (pos
== last_pos
)
641 /* prevent infinite loop if we backed up to beginning of file */
646 const int comp
= nameComparison (file
);
651 pos
= lower_limit
+ ((upper_limit
- lower_limit
) / 2);
656 pos
= lower_limit
+ ((upper_limit
- lower_limit
) / 2);
661 result
= findFirstMatchBefore (file
);
667 static tagResult
findSequential (tagFile
*const file
)
669 tagResult result
= TagFailure
;
670 if (file
->initialized
)
672 while (result
== TagFailure
&& readTagLine (file
))
674 if (nameComparison (file
) == 0)
681 static tagResult
find (tagFile
*const file
, tagEntry
*const entry
,
682 const char *const name
, const int options
)
684 tagResult result
= TagFailure
;
685 file
->search
.name
= name
;
686 file
->search
.nameLength
= strlen (name
);
687 file
->search
.partial
= (options
& TAG_PARTIALMATCH
) != 0;
688 file
->search
.ignorecase
= (options
& TAG_IGNORECASE
) != 0;
689 fseek (file
->fp
, 0, SEEK_END
);
690 file
->size
= ftell (file
->fp
);
692 if ((file
->sortMethod
== TAG_SORTED
&& !file
->search
.ignorecase
) ||
693 (file
->sortMethod
== TAG_FOLDSORTED
&& file
->search
.ignorecase
))
696 printf ("<performing binary search>\n");
698 result
= findBinary (file
);
703 printf ("<performing sequential search>\n");
705 result
= findSequential (file
);
708 if (result
!= TagSuccess
)
709 file
->search
.pos
= file
->size
;
712 file
->search
.pos
= file
->pos
;
714 parseTagLine (file
, entry
);
719 static tagResult
findNext (tagFile
*const file
, tagEntry
*const entry
)
721 tagResult result
= TagFailure
;
722 if ((file
->sortMethod
== TAG_SORTED
&& !file
->search
.ignorecase
) ||
723 (file
->sortMethod
== TAG_FOLDSORTED
&& file
->search
.ignorecase
))
725 result
= tagsNext (file
, entry
);
726 if (result
== TagSuccess
&& nameComparison (file
) != 0)
731 result
= findSequential (file
);
732 if (result
== TagSuccess
&& entry
!= NULL
)
733 parseTagLine (file
, entry
);
742 extern tagFile
*tagsOpen (const char *const filePath
, tagFileInfo
*const info
)
744 return initialize (filePath
, info
);
747 extern tagFile
*tagsOpen_1 (const FILE *fd
, tagFileInfo
*const info
)
749 return initialize_1 (fd
, info
);
753 extern tagResult
tagsSetSortType (tagFile
*const file
, const sortType type
)
755 tagResult result
= TagFailure
;
756 if (file
!= NULL
&& file
->initialized
)
758 file
->sortMethod
= type
;
764 extern tagResult
tagsFirst (tagFile
*const file
, tagEntry
*const entry
)
766 tagResult result
= TagFailure
;
767 if (file
!= NULL
&& file
->initialized
)
769 gotoFirstLogicalTag (file
);
770 result
= readNext (file
, entry
);
775 extern tagResult
tagsNext (tagFile
*const file
, tagEntry
*const entry
)
777 tagResult result
= TagFailure
;
778 if (file
!= NULL
&& file
->initialized
)
779 result
= readNext (file
, entry
);
783 extern const char *tagsField (const tagEntry
*const entry
, const char *const key
)
785 const char *result
= NULL
;
787 result
= readFieldValue (entry
, key
);
791 extern tagResult
tagsFind (tagFile
*const file
, tagEntry
*const entry
,
792 const char *const name
, const int options
)
794 tagResult result
= TagFailure
;
795 if (file
!= NULL
&& file
->initialized
)
796 result
= find (file
, entry
, name
, options
);
800 extern tagResult
tagsFindNext (tagFile
*const file
, tagEntry
*const entry
)
802 tagResult result
= TagFailure
;
803 if (file
!= NULL
&& file
->initialized
)
804 result
= findNext (file
, entry
);
808 extern tagResult
tagsClose (tagFile
*const file
)
810 tagResult result
= TagFailure
;
811 if (file
!= NULL
&& file
->initialized
)
825 static const char *TagFileName
= "tags";
826 static const char *ProgramName
;
827 static int extensionFields
;
828 static int SortOverride
;
829 static sortType SortMethod
;
831 static void printTag (const tagEntry
*entry
)
835 const char* separator
= ";\"";
836 const char* const empty
= "";
837 /* "sep" returns a value only the first time it is evaluated */
838 #define sep (first ? (first = 0, separator) : empty)
839 printf ("%s\t%s\t%s",
840 entry
->name
, entry
->file
, entry
->address
.pattern
);
843 if (entry
->kind
!= NULL
&& entry
->kind
[0] != '\0')
844 printf ("%s\tkind:%s", sep
, entry
->kind
);
845 if (entry
->fileScope
)
846 printf ("%s\tfile:", sep
);
848 if (entry
->address
.lineNumber
> 0)
849 printf ("%s\tline:%lu", sep
, entry
->address
.lineNumber
);
851 for (i
= 0 ; i
< entry
->fields
.count
; ++i
)
852 printf ("%s\t%s:%s", sep
, entry
->fields
.list
[i
].key
,
853 entry
->fields
.list
[i
].value
);
859 static void findTag (const char *const name
, const int options
)
863 tagFile
*const file
= tagsOpen (TagFileName
, &info
);
866 fprintf (stderr
, "%s: cannot open tag file: %s: %s\n",
867 ProgramName
, strerror (info
.status
.error_number
), name
);
873 tagsSetSortType (file
, SortMethod
);
874 if (tagsFind (file
, &entry
, name
, options
) == TagSuccess
)
879 } while (tagsFindNext (file
, &entry
) == TagSuccess
);
885 static void listTags (void)
889 tagFile
*const file
= tagsOpen (TagFileName
, &info
);
892 fprintf (stderr
, "%s: cannot open tag file: %s: %s\n",
893 ProgramName
, strerror (info
.status
.error_number
), TagFileName
);
898 while (tagsNext (file
, &entry
) == TagSuccess
)
904 const char *const Usage
=
905 "Find tag file entries matching specified names.\n\n"
906 "Usage: %s [-ilp] [-s[0|1]] [-t file] [name(s)]\n\n"
908 " -e Include extension fields in output.\n"
909 " -i Perform case-insensitive matching.\n"
910 " -l List all tags.\n"
911 " -p Perform partial matching.\n"
912 " -s[0|1|2] Override sort detection of tag file.\n"
913 " -t file Use specified tag file (default: \"tags\").\n"
914 "Note that options are acted upon as encountered, so order is significant.\n";
916 extern int main (int argc
, char **argv
)
919 int actionSupplied
= 0;
921 ProgramName
= argv
[0];
924 fprintf (stderr
, Usage
, ProgramName
);
927 for (i
= 1 ; i
< argc
; ++i
)
929 const char *const arg
= argv
[i
];
932 findTag (arg
, options
);
938 for (j
= 1 ; arg
[j
] != '\0' ; ++j
)
942 case 'e': extensionFields
= 1; break;
943 case 'i': options
|= TAG_IGNORECASE
; break;
944 case 'p': options
|= TAG_PARTIALMATCH
; break;
945 case 'l': listTags (); actionSupplied
= 1; break;
948 if (arg
[j
+1] != '\0')
950 TagFileName
= arg
+ j
+ 1;
951 j
+= strlen (TagFileName
);
953 else if (i
+ 1 < argc
)
954 TagFileName
= argv
[++i
];
957 fprintf (stderr
, Usage
, ProgramName
);
965 SortMethod
= TAG_SORTED
;
966 else if (strchr ("012", arg
[j
]) != NULL
)
967 SortMethod
= (sortType
) (arg
[j
] - '0');
970 fprintf (stderr
, Usage
, ProgramName
);
975 fprintf (stderr
, "%s: unknown option: %c\n",
976 ProgramName
, arg
[j
]);
983 if (! actionSupplied
)
986 "%s: no action specified: specify tag name(s) or -l option\n",
995 /* vi:set tabstop=4 shiftwidth=4: */