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 void terminate (tagFile
*const file
)
477 free (file
->line
.buffer
);
478 free (file
->name
.buffer
);
479 free (file
->fields
.list
);
481 if (file
->program
.author
!= NULL
)
482 free (file
->program
.author
);
483 if (file
->program
.name
!= NULL
)
484 free (file
->program
.name
);
485 if (file
->program
.url
!= NULL
)
486 free (file
->program
.url
);
487 if (file
->program
.version
!= NULL
)
488 free (file
->program
.version
);
490 memset (file
, 0, sizeof (tagFile
));
495 static tagResult
readNext (tagFile
*const file
, tagEntry
*const entry
)
497 tagResult result
= TagFailure
;
498 if (file
== NULL
|| ! file
->initialized
)
500 else if (! readTagLine (file
))
505 parseTagLine (file
, entry
);
511 static const char *readFieldValue (
512 const tagEntry
*const entry
, const char *const key
)
514 const char *result
= NULL
;
516 if (strcmp (key
, "kind") == 0)
517 result
= entry
->kind
;
518 else if (strcmp (key
, "file") == 0)
519 result
= EmptyString
;
520 else for (i
= 0 ; i
< entry
->fields
.count
&& result
== NULL
; ++i
)
521 if (strcmp (entry
->fields
.list
[i
].key
, key
) == 0)
522 result
= entry
->fields
.list
[i
].value
;
526 static int readTagLineSeek (tagFile
*const file
, const off_t pos
)
529 if (fseek (file
->fp
, pos
, SEEK_SET
) == 0)
531 result
= readTagLine (file
); /* read probable partial line */
532 if (pos
> 0 && result
)
533 result
= readTagLine (file
); /* read complete line */
538 static int nameComparison (tagFile
*const file
)
541 if (file
->search
.ignorecase
)
543 if (file
->search
.partial
)
544 result
= strnuppercmp (file
->search
.name
, file
->name
.buffer
,
545 file
->search
.nameLength
);
547 result
= struppercmp (file
->search
.name
, file
->name
.buffer
);
551 if (file
->search
.partial
)
552 result
= strncmp (file
->search
.name
, file
->name
.buffer
,
553 file
->search
.nameLength
);
555 result
= strcmp (file
->search
.name
, file
->name
.buffer
);
560 static void findFirstNonMatchBefore (tagFile
*const file
)
562 #define JUMP_BACK 512
565 off_t start
= file
->pos
;
569 if (pos
< (off_t
) JUMP_BACK
)
572 pos
= pos
- JUMP_BACK
;
573 more_lines
= readTagLineSeek (file
, pos
);
574 comp
= nameComparison (file
);
575 } while (more_lines
&& comp
== 0 && pos
> 0 && pos
< start
);
578 static tagResult
findFirstMatchBefore (tagFile
*const file
)
580 tagResult result
= TagFailure
;
582 off_t start
= file
->pos
;
583 findFirstNonMatchBefore (file
);
586 more_lines
= readTagLine (file
);
587 if (nameComparison (file
) == 0)
589 } while (more_lines
&& result
!= TagSuccess
&& file
->pos
< start
);
593 static tagResult
findBinary (tagFile
*const file
)
595 tagResult result
= TagFailure
;
596 off_t lower_limit
= 0;
597 off_t upper_limit
= file
->size
;
599 off_t pos
= upper_limit
/ 2;
600 while (result
!= TagSuccess
)
602 if (! readTagLineSeek (file
, pos
))
604 /* in case we fell off end of file */
605 result
= findFirstMatchBefore (file
);
608 else if (pos
== last_pos
)
610 /* prevent infinite loop if we backed up to beginning of file */
615 const int comp
= nameComparison (file
);
620 pos
= lower_limit
+ ((upper_limit
- lower_limit
) / 2);
625 pos
= lower_limit
+ ((upper_limit
- lower_limit
) / 2);
630 result
= findFirstMatchBefore (file
);
636 static tagResult
findSequential (tagFile
*const file
)
638 tagResult result
= TagFailure
;
639 if (file
->initialized
)
641 while (result
== TagFailure
&& readTagLine (file
))
643 if (nameComparison (file
) == 0)
650 static tagResult
find (tagFile
*const file
, tagEntry
*const entry
,
651 const char *const name
, const int options
)
653 tagResult result
= TagFailure
;
654 file
->search
.name
= name
;
655 file
->search
.nameLength
= strlen (name
);
656 file
->search
.partial
= (options
& TAG_PARTIALMATCH
) != 0;
657 file
->search
.ignorecase
= (options
& TAG_IGNORECASE
) != 0;
658 fseek (file
->fp
, 0, SEEK_END
);
659 file
->size
= ftell (file
->fp
);
661 if ((file
->sortMethod
== TAG_SORTED
&& !file
->search
.ignorecase
) ||
662 (file
->sortMethod
== TAG_FOLDSORTED
&& file
->search
.ignorecase
))
665 printf ("<performing binary search>\n");
667 result
= findBinary (file
);
672 printf ("<performing sequential search>\n");
674 result
= findSequential (file
);
677 if (result
!= TagSuccess
)
678 file
->search
.pos
= file
->size
;
681 file
->search
.pos
= file
->pos
;
683 parseTagLine (file
, entry
);
688 static tagResult
findNext (tagFile
*const file
, tagEntry
*const entry
)
690 tagResult result
= TagFailure
;
691 if ((file
->sortMethod
== TAG_SORTED
&& !file
->search
.ignorecase
) ||
692 (file
->sortMethod
== TAG_FOLDSORTED
&& file
->search
.ignorecase
))
694 result
= tagsNext (file
, entry
);
695 if (result
== TagSuccess
&& nameComparison (file
) != 0)
700 result
= findSequential (file
);
701 if (result
== TagSuccess
&& entry
!= NULL
)
702 parseTagLine (file
, entry
);
711 extern tagFile
*tagsOpen (const char *const filePath
, tagFileInfo
*const info
)
713 return initialize (filePath
, info
);
716 extern tagResult
tagsSetSortType (tagFile
*const file
, const sortType type
)
718 tagResult result
= TagFailure
;
719 if (file
!= NULL
&& file
->initialized
)
721 file
->sortMethod
= type
;
727 extern tagResult
tagsFirst (tagFile
*const file
, tagEntry
*const entry
)
729 tagResult result
= TagFailure
;
730 if (file
!= NULL
&& file
->initialized
)
732 gotoFirstLogicalTag (file
);
733 result
= readNext (file
, entry
);
738 extern tagResult
tagsNext (tagFile
*const file
, tagEntry
*const entry
)
740 tagResult result
= TagFailure
;
741 if (file
!= NULL
&& file
->initialized
)
742 result
= readNext (file
, entry
);
746 extern const char *tagsField (const tagEntry
*const entry
, const char *const key
)
748 const char *result
= NULL
;
750 result
= readFieldValue (entry
, key
);
754 extern tagResult
tagsFind (tagFile
*const file
, tagEntry
*const entry
,
755 const char *const name
, const int options
)
757 tagResult result
= TagFailure
;
758 if (file
!= NULL
&& file
->initialized
)
759 result
= find (file
, entry
, name
, options
);
763 extern tagResult
tagsFindNext (tagFile
*const file
, tagEntry
*const entry
)
765 tagResult result
= TagFailure
;
766 if (file
!= NULL
&& file
->initialized
)
767 result
= findNext (file
, entry
);
771 extern tagResult
tagsClose (tagFile
*const file
)
773 tagResult result
= TagFailure
;
774 if (file
!= NULL
&& file
->initialized
)
788 static const char *TagFileName
= "tags";
789 static const char *ProgramName
;
790 static int extensionFields
;
791 static int SortOverride
;
792 static sortType SortMethod
;
794 static void printTag (const tagEntry
*entry
)
798 const char* separator
= ";\"";
799 const char* const empty
= "";
800 /* "sep" returns a value only the first time it is evaluated */
801 #define sep (first ? (first = 0, separator) : empty)
802 printf ("%s\t%s\t%s",
803 entry
->name
, entry
->file
, entry
->address
.pattern
);
806 if (entry
->kind
!= NULL
&& entry
->kind
[0] != '\0')
807 printf ("%s\tkind:%s", sep
, entry
->kind
);
808 if (entry
->fileScope
)
809 printf ("%s\tfile:", sep
);
811 if (entry
->address
.lineNumber
> 0)
812 printf ("%s\tline:%lu", sep
, entry
->address
.lineNumber
);
814 for (i
= 0 ; i
< entry
->fields
.count
; ++i
)
815 printf ("%s\t%s:%s", sep
, entry
->fields
.list
[i
].key
,
816 entry
->fields
.list
[i
].value
);
822 static void findTag (const char *const name
, const int options
)
826 tagFile
*const file
= tagsOpen (TagFileName
, &info
);
829 fprintf (stderr
, "%s: cannot open tag file: %s: %s\n",
830 ProgramName
, strerror (info
.status
.error_number
), name
);
836 tagsSetSortType (file
, SortMethod
);
837 if (tagsFind (file
, &entry
, name
, options
) == TagSuccess
)
842 } while (tagsFindNext (file
, &entry
) == TagSuccess
);
848 static void listTags (void)
852 tagFile
*const file
= tagsOpen (TagFileName
, &info
);
855 fprintf (stderr
, "%s: cannot open tag file: %s: %s\n",
856 ProgramName
, strerror (info
.status
.error_number
), TagFileName
);
861 while (tagsNext (file
, &entry
) == TagSuccess
)
867 const char *const Usage
=
868 "Find tag file entries matching specified names.\n\n"
869 "Usage: %s [-ilp] [-s[0|1]] [-t file] [name(s)]\n\n"
871 " -e Include extension fields in output.\n"
872 " -i Perform case-insensitive matching.\n"
873 " -l List all tags.\n"
874 " -p Perform partial matching.\n"
875 " -s[0|1|2] Override sort detection of tag file.\n"
876 " -t file Use specified tag file (default: \"tags\").\n"
877 "Note that options are acted upon as encountered, so order is significant.\n";
879 extern int main (int argc
, char **argv
)
882 int actionSupplied
= 0;
884 ProgramName
= argv
[0];
887 fprintf (stderr
, Usage
, ProgramName
);
890 for (i
= 1 ; i
< argc
; ++i
)
892 const char *const arg
= argv
[i
];
895 findTag (arg
, options
);
901 for (j
= 1 ; arg
[j
] != '\0' ; ++j
)
905 case 'e': extensionFields
= 1; break;
906 case 'i': options
|= TAG_IGNORECASE
; break;
907 case 'p': options
|= TAG_PARTIALMATCH
; break;
908 case 'l': listTags (); actionSupplied
= 1; break;
911 if (arg
[j
+1] != '\0')
913 TagFileName
= arg
+ j
+ 1;
914 j
+= strlen (TagFileName
);
916 else if (i
+ 1 < argc
)
917 TagFileName
= argv
[++i
];
920 fprintf (stderr
, Usage
, ProgramName
);
928 SortMethod
= TAG_SORTED
;
929 else if (strchr ("012", arg
[j
]) != NULL
)
930 SortMethod
= (sortType
) (arg
[j
] - '0');
933 fprintf (stderr
, Usage
, ProgramName
);
938 fprintf (stderr
, "%s: unknown option: %c\n",
939 ProgramName
, arg
[j
]);
946 if (! actionSupplied
)
949 "%s: no action specified: specify tag name(s) or -l option\n",
958 /* vi:set tabstop=4 shiftwidth=4: */