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
)
455 info
->status
.error_number
= errno
;
459 fseek (result
->fp
, 0, SEEK_END
);
460 result
->size
= ftell (result
->fp
);
462 readPseudoTags (result
, info
);
463 info
->status
.opened
= 1;
464 result
->initialized
= 1;
470 static void terminate (tagFile
*const file
)
474 free (file
->line
.buffer
);
475 free (file
->name
.buffer
);
476 free (file
->fields
.list
);
478 if (file
->program
.author
!= NULL
)
479 free (file
->program
.author
);
480 if (file
->program
.name
!= NULL
)
481 free (file
->program
.name
);
482 if (file
->program
.url
!= NULL
)
483 free (file
->program
.url
);
484 if (file
->program
.version
!= NULL
)
485 free (file
->program
.version
);
486 if (file
->search
.name
!= NULL
)
487 free (file
->search
.name
);
489 memset (file
, 0, sizeof (tagFile
));
494 static tagResult
readNext (tagFile
*const file
, tagEntry
*const entry
)
497 if (file
== NULL
|| ! file
->initialized
)
499 else if (! readTagLine (file
))
504 parseTagLine (file
, entry
);
510 static const char *readFieldValue (
511 const tagEntry
*const entry
, const char *const key
)
513 const char *result
= NULL
;
515 if (strcmp (key
, "kind") == 0)
516 result
= entry
->kind
;
517 else if (strcmp (key
, "file") == 0)
518 result
= EmptyString
;
519 else for (i
= 0 ; i
< entry
->fields
.count
&& result
== NULL
; ++i
)
520 if (strcmp (entry
->fields
.list
[i
].key
, key
) == 0)
521 result
= entry
->fields
.list
[i
].value
;
525 static int readTagLineSeek (tagFile
*const file
, const off_t pos
)
528 if (fseek (file
->fp
, pos
, SEEK_SET
) == 0)
530 result
= readTagLine (file
); /* read probable partial line */
531 if (pos
> 0 && result
)
532 result
= readTagLine (file
); /* read complete line */
537 static int nameComparison (tagFile
*const file
)
540 if (file
->search
.ignorecase
)
542 if (file
->search
.partial
)
543 result
= strnuppercmp (file
->search
.name
, file
->name
.buffer
,
544 file
->search
.nameLength
);
546 result
= struppercmp (file
->search
.name
, file
->name
.buffer
);
550 if (file
->search
.partial
)
551 result
= strncmp (file
->search
.name
, file
->name
.buffer
,
552 file
->search
.nameLength
);
554 result
= strcmp (file
->search
.name
, file
->name
.buffer
);
559 static void findFirstNonMatchBefore (tagFile
*const file
)
561 #define JUMP_BACK 512
564 off_t start
= file
->pos
;
568 if (pos
< (off_t
) JUMP_BACK
)
571 pos
= pos
- JUMP_BACK
;
572 more_lines
= readTagLineSeek (file
, pos
);
573 comp
= nameComparison (file
);
574 } while (more_lines
&& comp
== 0 && pos
> 0 && pos
< start
);
577 static tagResult
findFirstMatchBefore (tagFile
*const file
)
579 tagResult result
= TagFailure
;
581 off_t start
= file
->pos
;
582 findFirstNonMatchBefore (file
);
585 more_lines
= readTagLine (file
);
586 if (nameComparison (file
) == 0)
588 } while (more_lines
&& result
!= TagSuccess
&& file
->pos
< start
);
592 static tagResult
findBinary (tagFile
*const file
)
594 tagResult result
= TagFailure
;
595 off_t lower_limit
= 0;
596 off_t upper_limit
= file
->size
;
598 off_t pos
= upper_limit
/ 2;
599 while (result
!= TagSuccess
)
601 if (! readTagLineSeek (file
, pos
))
603 /* in case we fell off end of file */
604 result
= findFirstMatchBefore (file
);
607 else if (pos
== last_pos
)
609 /* prevent infinite loop if we backed up to beginning of file */
614 const int comp
= nameComparison (file
);
619 pos
= lower_limit
+ ((upper_limit
- lower_limit
) / 2);
624 pos
= lower_limit
+ ((upper_limit
- lower_limit
) / 2);
629 result
= findFirstMatchBefore (file
);
635 static tagResult
findSequential (tagFile
*const file
)
637 tagResult result
= TagFailure
;
638 if (file
->initialized
)
640 while (result
== TagFailure
&& readTagLine (file
))
642 if (nameComparison (file
) == 0)
649 static tagResult
find (tagFile
*const file
, tagEntry
*const entry
,
650 const char *const name
, const int options
)
653 if (file
->search
.name
!= NULL
)
654 free (file
->search
.name
);
655 file
->search
.name
= duplicate (name
);
656 file
->search
.nameLength
= strlen (name
);
657 file
->search
.partial
= (options
& TAG_PARTIALMATCH
) != 0;
658 file
->search
.ignorecase
= (options
& TAG_IGNORECASE
) != 0;
659 fseek (file
->fp
, 0, SEEK_END
);
660 file
->size
= ftell (file
->fp
);
662 if ((file
->sortMethod
== TAG_SORTED
&& !file
->search
.ignorecase
) ||
663 (file
->sortMethod
== TAG_FOLDSORTED
&& file
->search
.ignorecase
))
666 printf ("<performing binary search>\n");
668 result
= findBinary (file
);
673 printf ("<performing sequential search>\n");
675 result
= findSequential (file
);
678 if (result
!= TagSuccess
)
679 file
->search
.pos
= file
->size
;
682 file
->search
.pos
= file
->pos
;
684 parseTagLine (file
, entry
);
689 static tagResult
findNext (tagFile
*const file
, tagEntry
*const entry
)
692 if ((file
->sortMethod
== TAG_SORTED
&& !file
->search
.ignorecase
) ||
693 (file
->sortMethod
== TAG_FOLDSORTED
&& file
->search
.ignorecase
))
695 result
= tagsNext (file
, entry
);
696 if (result
== TagSuccess
&& nameComparison (file
) != 0)
701 result
= findSequential (file
);
702 if (result
== TagSuccess
&& entry
!= NULL
)
703 parseTagLine (file
, entry
);
712 extern tagFile
*tagsOpen (const char *const filePath
, tagFileInfo
*const info
)
714 return initialize (filePath
, info
);
717 extern tagResult
tagsSetSortType (tagFile
*const file
, const sortType type
)
719 tagResult result
= TagFailure
;
720 if (file
!= NULL
&& file
->initialized
)
722 file
->sortMethod
= type
;
728 extern tagResult
tagsFirst (tagFile
*const file
, tagEntry
*const entry
)
730 tagResult result
= TagFailure
;
731 if (file
!= NULL
&& file
->initialized
)
733 gotoFirstLogicalTag (file
);
734 result
= readNext (file
, entry
);
739 extern tagResult
tagsNext (tagFile
*const file
, tagEntry
*const entry
)
741 tagResult result
= TagFailure
;
742 if (file
!= NULL
&& file
->initialized
)
743 result
= readNext (file
, entry
);
747 extern const char *tagsField (const tagEntry
*const entry
, const char *const key
)
749 const char *result
= NULL
;
751 result
= readFieldValue (entry
, key
);
755 extern tagResult
tagsFind (tagFile
*const file
, tagEntry
*const entry
,
756 const char *const name
, const int options
)
758 tagResult result
= TagFailure
;
759 if (file
!= NULL
&& file
->initialized
)
760 result
= find (file
, entry
, name
, options
);
764 extern tagResult
tagsFindNext (tagFile
*const file
, tagEntry
*const entry
)
766 tagResult result
= TagFailure
;
767 if (file
!= NULL
&& file
->initialized
)
768 result
= findNext (file
, entry
);
772 extern tagResult
tagsClose (tagFile
*const file
)
774 tagResult result
= TagFailure
;
775 if (file
!= NULL
&& file
->initialized
)
789 static const char *TagFileName
= "tags";
790 static const char *ProgramName
;
791 static int extensionFields
;
792 static int SortOverride
;
793 static sortType SortMethod
;
795 static void printTag (const tagEntry
*entry
)
799 const char* separator
= ";\"";
800 const char* const empty
= "";
801 /* "sep" returns a value only the first time it is evaluated */
802 #define sep (first ? (first = 0, separator) : empty)
803 printf ("%s\t%s\t%s",
804 entry
->name
, entry
->file
, entry
->address
.pattern
);
807 if (entry
->kind
!= NULL
&& entry
->kind
[0] != '\0')
808 printf ("%s\tkind:%s", sep
, entry
->kind
);
809 if (entry
->fileScope
)
810 printf ("%s\tfile:", sep
);
812 if (entry
->address
.lineNumber
> 0)
813 printf ("%s\tline:%lu", sep
, entry
->address
.lineNumber
);
815 for (i
= 0 ; i
< entry
->fields
.count
; ++i
)
816 printf ("%s\t%s:%s", sep
, entry
->fields
.list
[i
].key
,
817 entry
->fields
.list
[i
].value
);
823 static void findTag (const char *const name
, const int options
)
827 tagFile
*const file
= tagsOpen (TagFileName
, &info
);
830 fprintf (stderr
, "%s: cannot open tag file: %s: %s\n",
831 ProgramName
, strerror (info
.status
.error_number
), name
);
837 tagsSetSortType (file
, SortMethod
);
838 if (tagsFind (file
, &entry
, name
, options
) == TagSuccess
)
843 } while (tagsFindNext (file
, &entry
) == TagSuccess
);
849 static void listTags (void)
853 tagFile
*const file
= tagsOpen (TagFileName
, &info
);
856 fprintf (stderr
, "%s: cannot open tag file: %s: %s\n",
857 ProgramName
, strerror (info
.status
.error_number
), TagFileName
);
862 while (tagsNext (file
, &entry
) == TagSuccess
)
868 const char *const Usage
=
869 "Find tag file entries matching specified names.\n\n"
870 "Usage: %s [-ilp] [-s[0|1]] [-t file] [name(s)]\n\n"
872 " -e Include extension fields in output.\n"
873 " -i Perform case-insensitive matching.\n"
874 " -l List all tags.\n"
875 " -p Perform partial matching.\n"
876 " -s[0|1|2] Override sort detection of tag file.\n"
877 " -t file Use specified tag file (default: \"tags\").\n"
878 "Note that options are acted upon as encountered, so order is significant.\n";
880 extern int main (int argc
, char **argv
)
883 int actionSupplied
= 0;
885 ProgramName
= argv
[0];
888 fprintf (stderr
, Usage
, ProgramName
);
891 for (i
= 1 ; i
< argc
; ++i
)
893 const char *const arg
= argv
[i
];
896 findTag (arg
, options
);
902 for (j
= 1 ; arg
[j
] != '\0' ; ++j
)
906 case 'e': extensionFields
= 1; break;
907 case 'i': options
|= TAG_IGNORECASE
; break;
908 case 'p': options
|= TAG_PARTIALMATCH
; break;
909 case 'l': listTags (); actionSupplied
= 1; break;
912 if (arg
[j
+1] != '\0')
914 TagFileName
= arg
+ j
+ 1;
915 j
+= strlen (TagFileName
);
917 else if (i
+ 1 < argc
)
918 TagFileName
= argv
[++i
];
921 fprintf (stderr
, Usage
, ProgramName
);
929 SortMethod
= TAG_SORTED
;
930 else if (strchr ("012", arg
[j
]) != NULL
)
931 SortMethod
= (sortType
) (arg
[j
] - '0');
934 fprintf (stderr
, Usage
, ProgramName
);
939 fprintf (stderr
, "%s: unknown option: %c\n",
940 ProgramName
, arg
[j
]);
947 if (! actionSupplied
)
950 "%s: no action specified: specify tag name(s) or -l option\n",
959 /* vi:set tabstop=4 shiftwidth=4: */