3
* Copyright (c) 1996-2003, Darren Hiebert
5
* This source code is released into the public domain.
7
* This module contains functions for reading tag files.
18
#include <sys/types.h> /* to declare off_t */
36
/* Information about current tag file */
38
/* has the file been opened and this structure initialized? */
40
/* format of tag file */
42
/* how is the tag file sorted? */
44
/* pointer to file structure */
46
/* file position of first character of `line' */
48
/* size of tag file in seekable positions */
52
/* name of tag in last line read */
54
/* defines tag search state */
56
/* file position of last match for tag */
58
/* name of tag last searched for */
60
/* length of name for partial matches */
62
/* peforming partial match */
67
/* miscellaneous extension fields */
69
/* number of entries in `list' */
71
/* list of key value pairs */
72
tagExtensionField *list;
74
/* buffers to be freed at close */
76
/* name of program author */
80
/* URL of distribution */
90
const char *const EmptyString = "";
91
const char *const PseudoTagPrefix = "!_";
94
* FUNCTION DEFINITIONS
98
* Compare two strings, ignoring case.
99
* Return 0 for match, < 0 for smaller, > 0 for bigger
100
* Make sure case is folded to uppercase in comparison (like for 'sort -f')
101
* This makes a difference when one of the chars lies between upper and lower
102
* ie. one of the chars [ \ ] ^ _ ` for ascii. (The '_' in particular !)
104
static int struppercmp (const char *s1, const char *s2)
109
result = toupper ((int) *s1) - toupper ((int) *s2);
110
} while (result == 0 && *s1++ != '\0' && *s2++ != '\0');
114
static int strnuppercmp (const char *s1, const char *s2, size_t n)
119
result = toupper ((int) *s1) - toupper ((int) *s2);
120
} while (result == 0 && --n > 0 && *s1++ != '\0' && *s2++ != '\0');
124
static int growString (vstring *s)
132
newLine = (char*) malloc (newLength);
137
newLength = 2 * s->size;
138
newLine = (char*) realloc (s->buffer, newLength);
141
perror ("string too large");
151
/* Copy name of tag out of tag line */
152
static void copyName (tagFile *const file)
155
const char *end = strchr (file->line.buffer, '\t');
158
end = strchr (file->line.buffer, '\n');
160
end = strchr (file->line.buffer, '\r');
163
length = end - file->line.buffer;
165
length = strlen (file->line.buffer);
166
while (length >= file->name.size)
167
growString (&file->name);
168
strncpy (file->name.buffer, file->line.buffer, length);
169
file->name.buffer [length] = '\0';
172
static int readTagLineRaw (tagFile *const file)
177
/* If reading the line places any character other than a null or a
178
* newline at the last character position in the buffer (one less than
179
* the buffer size), then we must resize the buffer and reattempt to read
184
char *const pLastChar = file->line.buffer + file->line.size - 2;
187
file->pos = ftell (file->fp);
190
line = fgets (file->line.buffer, (int) file->line.size, file->fp);
194
if (! feof (file->fp))
195
perror ("readTagLine");
198
else if (*pLastChar != '\0' &&
199
*pLastChar != '\n' && *pLastChar != '\r')
201
/* buffer overflow */
202
growString (&file->line);
203
fseek (file->fp, file->pos, SEEK_SET);
208
size_t i = strlen (file->line.buffer);
210
(file->line.buffer [i - 1] == '\n' || file->line.buffer [i - 1] == '\r'))
212
file->line.buffer [i - 1] = '\0';
216
} while (reReadLine && result);
222
static int readTagLine (tagFile *const file)
227
result = readTagLineRaw (file);
228
} while (result && *file->name.buffer == '\0');
232
static tagResult growFields (tagFile *const file)
234
tagResult result = TagFailure;
235
unsigned short newCount = 2 * file->fields.max;
236
tagExtensionField *newFields = (tagExtensionField*)
237
realloc (file->fields.list, newCount * sizeof (tagExtensionField));
238
if (newFields == NULL)
239
perror ("too many extension fields");
242
file->fields.list = newFields;
243
file->fields.max = newCount;
249
static void parseExtensionFields (tagFile *const file, tagEntry *const entry,
253
while (p != NULL && *p != '\0')
264
colon = strchr (field, ':');
269
const char *key = field;
270
const char *value = colon + 1;
272
if (strcmp (key, "kind") == 0)
274
else if (strcmp (key, "file") == 0)
275
entry->fileScope = 1;
276
else if (strcmp (key, "line") == 0)
277
entry->address.lineNumber = atol (value);
280
if (entry->fields.count == file->fields.max)
282
file->fields.list [entry->fields.count].key = key;
283
file->fields.list [entry->fields.count].value = value;
284
++entry->fields.count;
291
static void parseTagLine (tagFile *file, tagEntry *const entry)
294
char *p = file->line.buffer;
295
char *tab = strchr (p, TAB);
296
int fieldsPresent = 0;
298
entry->fields.list = NULL;
299
entry->fields.count = 0;
301
entry->fileScope = 0;
309
tab = strchr (p, TAB);
314
if (*p == '/' || *p == '?')
317
int delimiter = *(unsigned char*) p;
318
entry->address.lineNumber = 0;
319
entry->address.pattern = p;
322
p = strchr (p + 1, delimiter);
323
} while (p != NULL && *(p - 1) == '\\');
326
/* invalid pattern */
331
else if (isdigit ((int) *(unsigned char*) p))
333
/* parse line number */
334
entry->address.pattern = p;
335
entry->address.lineNumber = atol (p);
336
while (isdigit ((int) *(unsigned char*) p))
341
/* invalid pattern */
345
fieldsPresent = (strncmp (p, ";\"", 2) == 0);
348
parseExtensionFields (file, entry, p + 2);
352
if (entry->fields.count > 0)
353
entry->fields.list = file->fields.list;
354
for (i = entry->fields.count ; i < file->fields.max ; ++i)
356
file->fields.list [i].key = NULL;
357
file->fields.list [i].value = NULL;
361
static char *duplicate (const char *str)
366
result = (char*) malloc (strlen (str) + 1);
370
strcpy (result, str);
375
static void readPseudoTags (tagFile *const file, tagFileInfo *const info)
378
const size_t prefixLength = strlen (PseudoTagPrefix);
381
info->file.format = 1;
382
info->file.sort = TAG_UNSORTED;
383
info->program.author = NULL;
384
info->program.name = NULL;
385
info->program.url = NULL;
386
info->program.version = NULL;
390
fgetpos (file->fp, &startOfLine);
391
if (! readTagLine (file))
393
if (strncmp (file->line.buffer, PseudoTagPrefix, prefixLength) != 0)
398
const char *key, *value;
399
parseTagLine (file, &entry);
400
key = entry.name + prefixLength;
402
if (strcmp (key, "TAG_FILE_SORTED") == 0)
403
file->sortMethod = (sortType) atoi (value);
404
else if (strcmp (key, "TAG_FILE_FORMAT") == 0)
405
file->format = atoi (value);
406
else if (strcmp (key, "TAG_PROGRAM_AUTHOR") == 0)
407
file->program.author = duplicate (value);
408
else if (strcmp (key, "TAG_PROGRAM_NAME") == 0)
409
file->program.name = duplicate (value);
410
else if (strcmp (key, "TAG_PROGRAM_URL") == 0)
411
file->program.url = duplicate (value);
412
else if (strcmp (key, "TAG_PROGRAM_VERSION") == 0)
413
file->program.version = duplicate (value);
416
info->file.format = file->format;
417
info->file.sort = file->sortMethod;
418
info->program.author = file->program.author;
419
info->program.name = file->program.name;
420
info->program.url = file->program.url;
421
info->program.version = file->program.version;
425
fsetpos (file->fp, &startOfLine);
428
static void gotoFirstLogicalTag (tagFile *const file)
431
const size_t prefixLength = strlen (PseudoTagPrefix);
435
fgetpos (file->fp, &startOfLine);
436
if (! readTagLine (file))
438
if (strncmp (file->line.buffer, PseudoTagPrefix, prefixLength) != 0)
441
fsetpos (file->fp, &startOfLine);
444
static tagFile *initialize (const char *const filePath, tagFileInfo *const info)
446
tagFile *result = (tagFile*) malloc (sizeof (tagFile));
449
memset (result, 0, sizeof (tagFile));
450
growString (&result->line);
451
growString (&result->name);
452
result->fields.max = 20;
453
result->fields.list = (tagExtensionField*) malloc (
454
result->fields.max * sizeof (tagExtensionField));
455
result->fp = fopen (filePath, "r");
456
if (result->fp == NULL)
460
info->status.error_number = errno;
464
fseek (result->fp, 0, SEEK_END);
465
result->size = ftell (result->fp);
467
readPseudoTags (result, info);
468
info->status.opened = 1;
469
result->initialized = 1;
475
static void terminate (tagFile *const file)
479
free (file->line.buffer);
480
free (file->name.buffer);
481
free (file->fields.list);
483
if (file->program.author != NULL)
484
free (file->program.author);
485
if (file->program.name != NULL)
486
free (file->program.name);
487
if (file->program.url != NULL)
488
free (file->program.url);
489
if (file->program.version != NULL)
490
free (file->program.version);
492
memset (file, 0, sizeof (tagFile));
497
static tagResult readNext (tagFile *const file, tagEntry *const entry)
499
tagResult result = TagFailure;
500
if (file == NULL || ! file->initialized)
502
else if (! readTagLine (file))
507
parseTagLine (file, entry);
513
static const char *readFieldValue (
514
const tagEntry *const entry, const char *const key)
516
const char *result = NULL;
518
if (strcmp (key, "kind") == 0)
519
result = entry->kind;
520
else if (strcmp (key, "file") == 0)
521
result = EmptyString;
522
else for (i = 0 ; i < entry->fields.count && result == NULL ; ++i)
523
if (strcmp (entry->fields.list [i].key, key) == 0)
524
result = entry->fields.list [i].value;
528
static int readTagLineSeek (tagFile *const file, const off_t pos)
531
if (fseek (file->fp, pos, SEEK_SET) == 0)
533
result = readTagLine (file); /* read probable partial line */
534
if (pos > 0 && result)
535
result = readTagLine (file); /* read complete line */
540
static int nameComparison (tagFile *const file)
543
if (file->search.ignorecase)
545
if (file->search.partial)
546
result = strnuppercmp (file->search.name, file->name.buffer,
547
file->search.nameLength);
549
result = struppercmp (file->search.name, file->name.buffer);
553
if (file->search.partial)
554
result = strncmp (file->search.name, file->name.buffer,
555
file->search.nameLength);
557
result = strcmp (file->search.name, file->name.buffer);
562
static void findFirstNonMatchBefore (tagFile *const file)
564
#define JUMP_BACK 512
567
off_t start = file->pos;
571
if (pos < (off_t) JUMP_BACK)
574
pos = pos - JUMP_BACK;
575
more_lines = readTagLineSeek (file, pos);
576
comp = nameComparison (file);
577
} while (more_lines && comp == 0 && pos > 0 && pos < start);
580
static tagResult findFirstMatchBefore (tagFile *const file)
582
tagResult result = TagFailure;
584
off_t start = file->pos;
585
findFirstNonMatchBefore (file);
588
more_lines = readTagLine (file);
589
if (nameComparison (file) == 0)
591
} while (more_lines && result != TagSuccess && file->pos < start);
595
static tagResult findBinary (tagFile *const file)
597
tagResult result = TagFailure;
598
off_t lower_limit = 0;
599
off_t upper_limit = file->size;
601
off_t pos = upper_limit / 2;
602
while (result != TagSuccess)
604
if (! readTagLineSeek (file, pos))
606
/* in case we fell off end of file */
607
result = findFirstMatchBefore (file);
610
else if (pos == last_pos)
612
/* prevent infinite loop if we backed up to beginning of file */
617
const int comp = nameComparison (file);
622
pos = lower_limit + ((upper_limit - lower_limit) / 2);
627
pos = lower_limit + ((upper_limit - lower_limit) / 2);
632
result = findFirstMatchBefore (file);
638
static tagResult findSequential (tagFile *const file)
640
tagResult result = TagFailure;
641
if (file->initialized)
643
while (result == TagFailure && readTagLine (file))
645
if (nameComparison (file) == 0)
652
static tagResult find (tagFile *const file, tagEntry *const entry,
653
const char *const name, const int options)
655
tagResult result = TagFailure;
656
file->search.name = name;
657
file->search.nameLength = strlen (name);
658
file->search.partial = (options & TAG_PARTIALMATCH) != 0;
659
file->search.ignorecase = (options & TAG_IGNORECASE) != 0;
660
fseek (file->fp, 0, SEEK_END);
661
file->size = ftell (file->fp);
663
if ((file->sortMethod == TAG_SORTED && !file->search.ignorecase) ||
664
(file->sortMethod == TAG_FOLDSORTED && file->search.ignorecase))
667
printf ("<performing binary search>\n");
669
result = findBinary (file);
674
printf ("<performing sequential search>\n");
676
result = findSequential (file);
679
if (result != TagSuccess)
680
file->search.pos = file->size;
683
file->search.pos = file->pos;
685
parseTagLine (file, entry);
690
static tagResult findNext (tagFile *const file, tagEntry *const entry)
692
tagResult result = TagFailure;
693
if ((file->sortMethod == TAG_SORTED && !file->search.ignorecase) ||
694
(file->sortMethod == TAG_FOLDSORTED && file->search.ignorecase))
696
result = tagsNext (file, entry);
697
if (result == TagSuccess && nameComparison (file) != 0)
702
result = findSequential (file);
703
if (result == TagSuccess && entry != NULL)
704
parseTagLine (file, entry);
713
extern tagFile *tagsOpen (const char *const filePath, tagFileInfo *const info)
715
return initialize (filePath, info);
718
extern tagResult tagsSetSortType (tagFile *const file, const sortType type)
720
tagResult result = TagFailure;
721
if (file != NULL && file->initialized)
723
file->sortMethod = type;
729
extern tagResult tagsFirst (tagFile *const file, tagEntry *const entry)
731
tagResult result = TagFailure;
732
if (file != NULL && file->initialized)
734
gotoFirstLogicalTag (file);
735
result = readNext (file, entry);
740
extern tagResult tagsNext (tagFile *const file, tagEntry *const entry)
742
tagResult result = TagFailure;
743
if (file != NULL && file->initialized)
744
result = readNext (file, entry);
748
extern const char *tagsField (const tagEntry *const entry, const char *const key)
750
const char *result = NULL;
752
result = readFieldValue (entry, key);
756
extern tagResult tagsFind (tagFile *const file, tagEntry *const entry,
757
const char *const name, const int options)
759
tagResult result = TagFailure;
760
if (file != NULL && file->initialized)
761
result = find (file, entry, name, options);
765
extern tagResult tagsFindNext (tagFile *const file, tagEntry *const entry)
767
tagResult result = TagFailure;
768
if (file != NULL && file->initialized)
769
result = findNext (file, entry);
773
extern tagResult tagsClose (tagFile *const file)
775
tagResult result = TagFailure;
776
if (file != NULL && file->initialized)
790
static const char *TagFileName = "tags";
791
static const char *ProgramName;
792
static int extensionFields;
793
static int SortOverride;
794
static sortType SortMethod;
796
static void printTag (const tagEntry *entry)
800
const char* separator = ";\"";
801
const char* const empty = "";
802
/* "sep" returns a value only the first time it is evaluated */
803
#define sep (first ? (first = 0, separator) : empty)
804
printf ("%s\t%s\t%s",
805
entry->name, entry->file, entry->address.pattern);
808
if (entry->kind != NULL && entry->kind [0] != '\0')
809
printf ("%s\tkind:%s", sep, entry->kind);
810
if (entry->fileScope)
811
printf ("%s\tfile:", sep);
813
if (entry->address.lineNumber > 0)
814
printf ("%s\tline:%lu", sep, entry->address.lineNumber);
816
for (i = 0 ; i < entry->fields.count ; ++i)
817
printf ("%s\t%s:%s", sep, entry->fields.list [i].key,
818
entry->fields.list [i].value);
824
static void findTag (const char *const name, const int options)
828
tagFile *const file = tagsOpen (TagFileName, &info);
831
fprintf (stderr, "%s: cannot open tag file: %s: %s\n",
832
ProgramName, strerror (info.status.error_number), name);
838
tagsSetSortType (file, SortMethod);
839
if (tagsFind (file, &entry, name, options) == TagSuccess)
844
} while (tagsFindNext (file, &entry) == TagSuccess);
850
static void listTags (void)
854
tagFile *const file = tagsOpen (TagFileName, &info);
857
fprintf (stderr, "%s: cannot open tag file: %s: %s\n",
858
ProgramName, strerror (info.status.error_number), TagFileName);
863
while (tagsNext (file, &entry) == TagSuccess)
869
const char *const Usage =
870
"Find tag file entries matching specified names.\n\n"
871
"Usage: %s [-ilp] [-s[0|1]] [-t file] [name(s)]\n\n"
873
" -e Include extension fields in output.\n"
874
" -i Perform case-insensitive matching.\n"
875
" -l List all tags.\n"
876
" -p Perform partial matching.\n"
877
" -s[0|1|2] Override sort detection of tag file.\n"
878
" -t file Use specified tag file (default: \"tags\").\n"
879
"Note that options are acted upon as encountered, so order is significant.\n";
881
extern int main (int argc, char **argv)
884
int actionSupplied = 0;
886
ProgramName = argv [0];
889
fprintf (stderr, Usage, ProgramName);
892
for (i = 1 ; i < argc ; ++i)
894
const char *const arg = argv [i];
897
findTag (arg, options);
903
for (j = 1 ; arg [j] != '\0' ; ++j)
907
case 'e': extensionFields = 1; break;
908
case 'i': options |= TAG_IGNORECASE; break;
909
case 'p': options |= TAG_PARTIALMATCH; break;
910
case 'l': listTags (); actionSupplied = 1; break;
913
if (arg [j+1] != '\0')
915
TagFileName = arg + j + 1;
916
j += strlen (TagFileName);
918
else if (i + 1 < argc)
919
TagFileName = argv [++i];
922
fprintf (stderr, Usage, ProgramName);
930
SortMethod = TAG_SORTED;
931
else if (strchr ("012", arg[j]) != NULL)
932
SortMethod = (sortType) (arg[j] - '0');
935
fprintf (stderr, Usage, ProgramName);
940
fprintf (stderr, "%s: unknown option: %c\n",
941
ProgramName, arg[j]);
948
if (! actionSupplied)
951
"%s: no action specified: specify tag name(s) or -l option\n",
960
/* vi:set tabstop=8 shiftwidth=4: */