NetBSD/usr.bin/ref/ref.c

551 lines
12 KiB
C

/* ref2.c */
/* This is a totally rewritten version of ref. This version looks for the
* desired function name in the "tags" file, and then reads the header out
* from the source file. There is no longer any need for a "refs" file.
*
* Usage: ref [-t] [-f file] [-c class] tag
* Options: -t output tag info, not the description
* -f file default filename for static functions
* -c class default class names for class functions
*/
#ifdef __STDC__
# include <string.h>
# include <stdlib.h>
#endif
#include <stdio.h>
#include "config.h"
extern char *cktagdir P_((char *, char *));
extern int getline P_((char *, int, FILE *));
extern int lookup P_((char *, char *));
extern int find P_((char *));
extern void usage P_((void));
extern int countcolons P_((char *));
extern void main P_((int, char **));
/* This is the default path that is searched for tags */
#if OSK
# define DEFTAGPATH ".:/dd/defs:/dd/defs/sys:/dd/usr/src/lib:../lib:/dd/usr/lib"
#else
# if ANY_UNIX
# define DEFTAGPATH ".:/usr/include:/usr/include/sys:/usr/src/lib:../lib:/usr/local/lib"
# else
# if MSDOS || TOS
# define DEFTAGPATH ".;C:\\include;C:\\include\\sys;C:\\lib;..\\lib"
# define SEP ';'
# else
# if AMIGA
# define DEFTAGPATH ".;Include:;Include:sys"
# define SEP ';'
# else /* any other OS */
# define DEFTAGPATH "."
# endif
# endif
# endif
#endif
#ifndef SEP
# define SEP ':'
#endif
/* These variables reflect the command-line options given by the user. */
int taginfo; /* boolean: give only the tag info? (not header?) */
char *def_file; /* default filename for static functions */
char *def_class; /* default classname for class members */
int colons; /* #colons in tag: 0=normal, 1=static, 2=member */
/* This function checks for a tag in the "tags" file of given directory.
* If the tag is found, then it returns a pointer to a static buffer which
* contains the filename, a tab character, and a linespec for finding the
* the tag. If the tag is not found in the "tags" file, or if the "tags"
* file cannot be opened or doesn't exist, then this function returns NULL.
*/
char *cktagdir(tag, dir)
char *tag; /* name of the tag to look for */
char *dir; /* name of the directory to check */
{
char buf[BLKSIZE];
static char found[BLKSIZE];
FILE *tfile;
int len;
#if AMIGA
if (dir[strlen(dir) - 1] == COLON)
sprintf(buf, "%s%s", dir, TAGS); /* no slash after colon. */
else
#endif
/* construct the name of the "tags" file in this directory */
sprintf(buf, "%s%c%s", dir, SLASH, TAGS);
/* Try to open the tags file. Return NULL if can't open */
#if AMIGA
if (buf[0] == '.' && buf[1] == SLASH)
tfile = fopen(&buf[2], "r");
else
#endif
tfile = fopen(buf, "r");
if (!tfile)
{
return (char *)0;
}
/* compute the length of the tagname once */
len = strlen(tag);
/* read lines until we get the one for this tag */
found[0] = '\0';
while (fgets(buf, sizeof buf, tfile))
{
/* is this the one we want? */
if (!strncmp(buf, tag, len) && buf[len] == '\t')
{
/* we've found a match -- remember it */
strcpy(found, buf);
/* if there is no default file, or this match is in
* the default file, then we've definitely found the
* one we want. Break out of the loop now.
*/
if (!def_file || !strncmp(&buf[len + 1], def_file, strlen(def_file)))
{
break;
}
}
}
/* we're through reading */
fclose(tfile);
/* if there's anything in found[], use it */
if (found[0])
{
return &found[len + 1];
}
/* else we didn't find it */
return (char *)0;
}
/* This function reads a single textline from a binary file. It returns
* the number of bytes read, or 0 at EOF.
*/
int getline(buf, limit, fp)
char *buf; /* buffer to read into */
int limit; /* maximum characters to read */
FILE *fp; /* binary stream to read from */
{
int bytes; /* number of bytes read so far */
int ch; /* single character from file */
for (bytes = 0, ch = 0; ch != '\n' && --limit > 0 && (ch = getc(fp)) != EOF; bytes++)
{
#if MSDOS || TOS
/* since this is a binary file, we'll need to manually strip CR's */
if (ch == '\r')
{
continue;
}
#endif
*buf++ = ch;
}
*buf = '\0';
return bytes;
}
/* This function reads a source file, looking for a given tag. If it finds
* the tag, then it displays it and returns TRUE. Otherwise it returns FALSE.
* To display the tag, it attempts to output any introductory comment, the
* tag line itself, and any arguments. Arguments are assumed to immediately
* follow the tag line, and start with whitespace. Comments are assumed to
* start with lines that begin with "/*", "//", "(*", or "--", and end at the
* tag line or at a blank line.
*/
int lookup(dir, entry)
char *dir; /* name of the directory that contains the source */
char *entry; /* source filename, <Tab>, linespec */
{
char *name; /* basename of source file */
char buf[BLKSIZE]; /* pathname of source file */
long lnum; /* desired line number */
long thislnum; /* current line number */
long here; /* seek position where current line began */
long comment; /* seek position of introductory comment, or -1L */
FILE *sfile; /* used for reading the source file */
int len; /* length of string */
int noargs = 0; /* boolean: don't show lines after tag line? */
char *ptr;
/* construct the pathname of the source file */
name = entry;
strcpy(buf, dir);
ptr = buf + strlen(buf);
#if AMIGA
if (ptr[-1] != COLON)
#endif
*ptr++ = SLASH;
while (*entry != '\t')
{
*ptr++ = *entry++;
}
*entry++ = *ptr = '\0';
/* searching for string or number? */
if (*entry >= '0' && *entry <= '9')
{
/* given a specific line number */
lnum = atol(entry);
entry = (char *)0;
noargs = 1;
}
else
{
/* given a string -- strip off "/^" and "$/\n" */
entry += 2;
len = strlen(entry) - 2;
if (entry[len - 1] == '$')
{
entry[len - 1] = '\n';
}
if (!strchr(entry, '('))
{
noargs = 1;
}
lnum = 0L;
}
/* Open the file. Note that we open the file in binary mode even
* though we know it is a text file, because ftell() and fseek()
* don't work on text files.
*/
#if MSDOS || TOS
sfile = fopen(buf, "rb");
#else
# if AMIGA
if (buf[0] == '.' && buf[1] == SLASH)
sfile = fopen(&buf[2], "r");
else
# endif
sfile = fopen(buf, "r");
#endif
if (!sfile)
{
/* can't open the real source file. Try "refs" instead */
#if AMIGA
if (dir[strlen(dir) - 1] == COLON)
sprintf(buf, "%srefs", dir);
else
#endif
sprintf(buf, "%s%crefs", dir, SLASH);
#if MSDOS || TOS
sfile = fopen(buf, "rb");
#else
# if AMIGA
if (buf[0] == '.' && buf[1] == SLASH)
sfile = fopen(&buf[2], "r");
else
# endif
sfile = fopen(buf, "r");
#endif
if (!sfile)
{
/* failed! */
return 0;
}
name = "refs";
}
/* search the file */
for (comment = -1L, thislnum = 0; here = ftell(sfile), thislnum++, getline(buf, BLKSIZE, sfile) > 0; )
{
/* Is this the start/end of a comment? */
if (comment == -1L)
{
/* starting a comment? */
if (buf[0] == '/' && buf[1] == '*'
|| buf[0] == '/' && buf[1] == '/'
|| buf[0] == '(' && buf[1] == '*'
|| buf[0] == '-' && buf[1] == '-')
{
comment = here;
}
}
else
{
/* ending a comment? */
if (buf[0] == '\n' || buf[0] == '#')
{
comment = -1L;
}
}
/* is this the tag line? */
if (lnum == thislnum || (entry && !strncmp(buf, entry, len)))
{
/* display the filename & line number where found */
if (strcmp(dir, "."))
printf("%s%c%s, line %ld:\n", dir, SLASH, name, thislnum);
else
printf("%s, line %ld:\n", name, thislnum);
/* if there were introductory comments, show them */
if (comment != -1L)
{
fseek(sfile, comment, 0);
while (comment != here)
{
getline(buf, BLKSIZE, sfile);
fputs(buf, stdout);
comment = ftell(sfile);
}
/* re-fetch the tag line */
fgets(buf, BLKSIZE, sfile);
}
/* show the tag line */
fputs(buf, stdout);
/* are we expected to show argument lines? */
if (!noargs)
{
/* show any argument lines */
while (getline(buf, BLKSIZE, sfile) > 0
&& buf[0] != '#'
&& strchr(buf, '{') == (char *)0)
{
fputs(buf, stdout);
}
}
/* Done! Close the file, and return TRUE */
fclose(sfile);
return 1;
}
}
/* not found -- return FALSE */
return 0;
}
/* This function searches through the entire search path for a given tag.
* If it finds the tag, then it displays the info and returns TRUE;
* otherwise it returns FALSE.
*/
int find(tag)
char *tag; /* the tag to look up */
{
char *tagpath;
char dir[80];
char *ptr;
int len;
if (colons == 1)
{
/* looking for static function -- only look in current dir */
tagpath = ".";
}
else
{
/* get the tagpath from the environment. Default to DEFTAGPATH */
tagpath = getenv("TAGPATH");
if (!tagpath)
{
tagpath = DEFTAGPATH;
}
}
/* for each entry in the path... */
while (*tagpath)
{
/* Copy the entry into the dir[] buffer */
for (ptr = dir; *tagpath && *tagpath != SEP; tagpath++)
{
*ptr++ = *tagpath;
}
if (*tagpath == SEP)
{
tagpath++;
}
/* if the entry ended with "/tags", then strip that off */
len = strlen(TAGS);
if (&dir[len] < ptr && ptr[-len - 1] == SLASH && !strncmp(&ptr[-len], TAGS, len))
{
ptr -= len + 1;
}
/* if the entry is now an empty string, then assume "." */
if (ptr == dir)
{
*ptr++ = '.';
}
*ptr = '\0';
/* look for the tag in this path. If found, then display it
* and exit.
*/
ptr = cktagdir(tag, dir);
if (ptr)
{
/* just supposed to display tag info? */
if (taginfo)
{
/* then do only that! */
if (strcmp(dir, "."))
{
printf("%s%c%s", dir, SLASH, ptr);
}
else
{
/* avoid leading "./" if possible */
fputs(ptr, stdout);
}
return 1;
}
else
{
/* else look up the declaration of the thing */
return lookup(dir, ptr);
}
}
}
/* if we get here, then the tag wasn't found anywhere */
return 0;
}
void usage()
{
fputs("usage: ref [-t] [-c class] [-f file] tag\n", stderr);
fputs(" -t output tag info, instead of the function header\n", stderr);
fputs(" -f File tag might be a static function in File\n", stderr);
fputs(" -c Class tag might be a member of class Class\n", stderr);
exit(2);
}
int countcolons(str)
char *str;
{
while (*str != ':' && *str)
{
str++;
}
if (str[0] != ':')
{
return 0;
}
else if (str[1] != ':')
{
return 1;
}
return 2;
}
void main(argc, argv)
int argc;
char **argv;
{
char def_tag[100]; /* used to build tag name with default file/class */
int i;
/* parse flags */
for (i = 1; i < argc && argv[i][0] == '-'; i++)
{
switch (argv[i][1])
{
case 't':
taginfo = 1;
break;
case 'f':
if (argv[i][2])
{
def_file = &argv[i][2];
}
else if (++i < argc)
{
def_file = argv[i];
}
else
{
usage();
}
break;
case 'c':
if (argv[i][2])
{
def_class = &argv[i][2];
}
else if (++i < argc)
{
def_class = argv[i];
}
else
{
usage();
}
break;
default:
usage();
}
}
/* if no tag was given, complain */
if (i + 1 != argc)
{
usage();
}
/* does the tag have an explicit class or file? */
colons = countcolons(argv[i]);
/* if not, then maybe try some defaults */
if (colons == 0)
{
/* try a static function in the file first */
if (def_file)
{
sprintf(def_tag, "%s:%s", def_file, argv[i]);
colons = 1;
if (find(def_tag))
{
exit(0);
}
}
/* try a member function for a class */
if (def_class)
{
sprintf(def_tag, "%s::%s", def_class, argv[i]);
colons = 2;
if (find(def_tag))
{
exit(0);
}
}
/* oh, well */
colons = 0;
}
/* find the tag */
if (find(argv[i]))
{
exit(0);
}
/* Give up. If doing tag lookup then exit(0), else exit(1) */
exit(!taginfo);
/*NOTREACHED*/
}