/*
 *				C R E F . C
 *
 * This program was distributed to the Unix UUCP network by "cca!z" on
 * Wed Jun 23 14:19:25 1982.  It has not been converted for Decus C
 */

/* cref - cross reference program */

#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <stdio.h>
#include <vadvise.h>

#define	HRATIO	30
#define	LBUFSIZ	2048
#define	LREFS	15
#define	NKEYS	(sizeof(keytab)/sizeof(struct key))
#define	RSEGSIZ	20
#define	SYMSIZ	64
#define	Perror()	perror("cref"),exit(1)
#define	issym(c)	(isalnum(c) || c=='_' || c=='#')

char	tflag;				/* Produce tags style output */
char	*lastext;
char	*calloc();
char	prcom[80] = "pr -h ";
char	linebuf[LBUFSIZ], *nextsym(), *putline(), *strcpy();
int	file1 = 1, hconst = 1, lastline, line, nsyms;
short	braces, bracks, parens;
FILE	*fopen(), *fp, *popen(), *pfp;

struct	{
	unsigned blank1	  : 1;		/* First column is a blank */
	unsigned casef	  : 1;		/* Case keyword */
	unsigned comp	  : 1;		/* Compound statement */
	unsigned define	  : 1;		/* "Define" type statement */
	unsigned indent	  : 1;		/* Statement indented */
	unsigned qmark	  : 1;		/* Question mark encountered */
	unsigned semic	  : 1;		/* Semicolon encountered */
	unsigned preproc  : 1;		/* Preprocessor keyword */
} flags;

struct	key	{
	char	*keyword;
	unsigned cflag	: 1;		/* Compound statement flag */
	unsigned dflag	: 1;		/* Defining flag */
} keytab[] =	{
	"#define",	0,1,
	"#else",	0,0,
	"#endif",	0,0,
	"#if",		0,0,
	"#ifdef",	0,0,
	"#ifndef",	0,0,
	"#include",	0,0,
	"#line",	0,1,
	"#undef",	0,0,
	"asm",		0,0,
	"auto",		0,1,
	"break",	0,0,
	"case",		1,0,
	"char",		0,1,
	"continue",	0,0,
	"default",	0,0,
	"do",		1,0,
	"double",	0,1,
	"else",		1,0,
	"entry",	0,0,
	"extern",	0,1,
	"float",	0,1,
	"for",		1,0,
	"goto",		0,0,
	"if",		1,0,
	"int",		0,1,
	"long",		0,1,
	"register",	0,1,
	"return",	0,0,
	"short",	0,1,
	"sizeof",	0,0,
	"static",	0,1,
	"struct",	0,1,
	"switch",	1,0,
	"typedef",	0,1,
	"union",	0,1,
	"unsigned",	0,1,
	"void",		0,1,
	"while",	1,0
};

struct	refseg	{
	struct refseg	*nseg;		/* Pointer to next segment */
	struct	{
		int	ref;		/* Line number of reference */
		char	*text;		/* Text of line containing reference */
		short	file;		/* File in which reference occurred */
		char	deflag;		/* Define flag */
	} refs[RSEGSIZ];
} *pseg;

struct	symbol	{
	char	name[SYMSIZ+1];
	int	nref;
	struct	refseg	*firstseg;
	struct	refseg	*curseg;
} *htab;

main(argc, argv)
int	argc;
char	*argv[];
{
	char	*p, symbol[SYMSIZ+1];
	int	hcode, i, j, k, nslots, t, tchars;
	short	nfile, nkey, rnum;
	struct	stat statb;

	tchars = 0;
	if (argc < 2)
		exit(1);		/* No filenames */
	while (*argv[file1] == '-') {
		if (argv[file1][1] == 't')
			tflag++;
		else if (!(hconst = atoi(&argv[file1][1]))) {
			fprintf(stderr, "cref: Bad option");
			exit(1);
		}
		file1++;
	}
	for (nfile = file1; nfile < argc; nfile++) {
		if (stat(argv[nfile], &statb))
			Perror();	/* File doesn't exist */
		tchars += statb.st_size;
	}
	vadvise(VA_ANOM);
	if (!(htab = (struct symbol *)calloc(nslots = hconst*tchars/HRATIO, sizeof(struct symbol)))) {
		fprintf(stderr, "cref: Not enough memory.\n");
		exit(1);
	}

/* Main loop for crunching down files */

	for (nfile = file1; nfile < argc; nfile++) {
		if (!(fp = fopen(argv[nfile], "r")))	/* Open input file */
			fprintf(stderr, "cref: Can't open %s\n", argv[nfile]);
		strcpy(&prcom[6], argv[nfile]);	/* Put name in header */
		if (!tflag)
			pfp = popen(prcom, "w"); /* Pipe output through "pr" */
		while (fgets(linebuf, LBUFSIZ, fp)) {	/* Statement loop */
			line++;
			if (!tflag) {
				fprintf(pfp, "%6d  ", line);
				fputs(linebuf, pfp);
			}
			flags.blank1 = flags.define = flags.casef = flags.comp = flags.indent
				= flags.semic = flags.qmark = flags.preproc = 0;
			p = linebuf;
			flags.blank1 = *p==' ' || *p=='\t';
			while (p = nextsym(p)) {
				for (i = 0; i < SYMSIZ && issym(*p); i++)
					symbol[i] = *p++;
				symbol[i] = '\0';
				if ((nkey = binary(symbol, keytab, NKEYS)) >= 0) {
					if (!parens)	/* So casts don't count as definitions */
						flags.define |= keytab[nkey].dflag;
					if (!strcmp(keytab[nkey].keyword, "#include")) {
						while (*p != '\n')
							p++;
						p++;
						break;
					}
					if (!strcmp(keytab[nkey].keyword, "case"))
						flags.casef = 1;
				} else {
					if (!(braces|flags.blank1))
						flags.define = 1;
					flags.define |= !flags.indent;
					if (flags.define)
						parens = 0;
					for (j = 0, hcode = 1; j < strlen(symbol); hcode *= symbol[j++]);
				scan:	for (hcode = abs(hcode) % nslots; hcode >= 0 && *htab[hcode].name &&
						strcmp(htab[hcode].name, symbol); hcode--);
					if (hcode < 0) {
						hcode = nslots -1;
						goto scan;
					}
					if (!*htab[hcode].name) {
						strcpy(htab[hcode].name, symbol);
						if (!(htab[hcode].firstseg = htab[hcode].curseg =
							(struct refseg *)calloc(1, sizeof(struct refseg)))) {
							fprintf(stderr, "cref: Out of memory!\n");
							exit(1);
						}
						htab[hcode].curseg->refs[0].ref = line;
						if (!bracks && !parens || !flags.indent)
							if (htab[hcode].curseg->refs[0].deflag = flags.define |
								(*p == ':' && !flags.qmark && !flags.casef))
								ctags(hcode, 0, nfile);
						htab[hcode].nref++;
						nsyms++;
					} else {
						if (!(htab[hcode].nref % RSEGSIZ)) {
							if (!(htab[hcode].curseg->nseg = (struct refseg *)
								calloc(1, sizeof(struct refseg)))) {
								fprintf(stderr, "cref: Out of memory!\n");
								exit(1);
							}
							htab[hcode].curseg = htab[hcode].curseg->nseg;
						}
						htab[hcode].curseg->refs[rnum = htab[hcode].nref++ % RSEGSIZ].ref = line;
						if (!bracks && !parens || !flags.indent)
							if (htab[hcode].curseg->refs[rnum].deflag = flags.define |
								(*p == ':' && !flags.qmark && !flags.casef))
								ctags(hcode, rnum, nfile);
					}
					if (nsyms == nslots) {
						fprintf(stderr, "cref: Hash table overflowed!\n");
						exit(1);
					}
					if (*p == ':')	/* End of label */
						flags.define = 0;
				}
				flags.indent = 1;
			}
		}
		fclose(fp);
		if (!tflag)
			pclose(pfp);
	}

/* Now print the cref table */

	if (tflag) {
		pfp = stdout;
		sort(htab, nslots);
		for (i = nslots - nsyms; i < nslots; i++) {
			pseg = htab[i].firstseg;
			for (j = 0, k = 0, t = 0; j < htab[i].nref; j++) {
				if (pseg->refs[k].ref != t && pseg->refs[k].deflag) {
					t = pseg->refs[k].ref;
					fprintf(pfp, "%s	%s	?^", htab[i].name, argv[pseg->refs[k].file]);
					fputs(pseg->refs[k].text, pfp);
					fprintf(pfp, "$?\n");
				}
				if (++k == RSEGSIZ) {
					pseg = pseg->nseg;	/* Print next segment */
					k = 0;
				}
			}
		}
		exit(0);
	}
	pfp = popen("pr -h 'Cref listing'", "w");
	sort(htab, nslots);
	for (i = nslots - nsyms; i < nslots; i++) {
		char lrefs;

		lrefs = LREFS;		/* References per line */
		fprintf(pfp, "%s", 	mKuname);
		if ((t = strlen(htab[i].name)) > 12) {
			lrefs = LREFS - (t-5)/8;
			for (j = 0; j < 7 - (t-5)%8; j++)
				putc(' ', pfp);	/* Space after symbol */
		} else
			for (j = 0; j < 12-t; j++)
				putc(' ', pfp);	/* Space after symbol */
		pseg = htab[i].firstseg;
		for (j=0, k=0, t=0; j < htab[i].nref; j++) {
			if (pseg->refs[k].ref != t) {
				if (!lrefs--) {
					fprintf(pfp, "\n	    ");
					lrefs = LREFS;
				}
				fprintf(pfp, "%7d", t = pseg->refs[k].ref);
				if (pseg->refs[k].deflag)
					putc('#', pfp);
				else
					putc(' ', pfp);
			}
			if (++k == RSEGSIZ) {
				pseg = pseg->nseg;	/* Print next segment */
				k = 0;
			}
		}
		putc('\n', pfp);
	}
	fprintf(pfp, "\nSymbols = %d		Hash table size = %d		Density = %f\n",
		nsyms, nslots, (double)nsyms/(double)nslots);
	pclose(pfp);
}



/* Create entry for tags file */

ctags(hcode, rnum, nfile)
register hcode, rnum, nfile;
{
	register len;

	if (!tflag)
		return;
	len = strlen(linebuf);
	if (lastline != line) {
		if (!(lastext = calloc(len+1, 1))) {
			fprintf(stderr, "cref: Out of memory!\n");
			exit(1);
		}
		(void) strcpy(lastext, linebuf);
		lastext[--len] = '\0';
		lastline = line;
	}
	htab[hcode].curseg->refs[rnum].text = lastext;
	htab[hcode].curseg->refs[rnum].file = nfile;
}



/* Binary search for word in tab */

binary(word, tab, n)
char	*word;
struct	key	tab[];
int	n;
{
	int	low, high, mid, cond;

	low = 0;
	high = n - 1;
	while (low <= high) {
		mid = (low+high)/2;
		if ((cond = strcmp(word, tab[mid].keyword)) < 0)
			high = mid - 1;
		else if (cond > 0)
			low = mid + 1;
		else
			return(mid);
	}
	return(-1);
}



/* Find next symbol in statement, and return a pointer to it.  If end of
 * statement is reached, return null pointer.
 */

char	*
nextsym(p)
char	*p;
{
	static symline;

	for (; !issym(*p) || isdigit(*p); p++) {
		switch (*p) {
		case '{':
			braces++;
			break;
		case '}':
			braces--;
			break;
		case '\n':
			if (!flags.semic && !flags.comp && symline == line && !flags.preproc ||
				flags.preproc && *(p-1) == '\\') {
				if (!fgets(linebuf, LBUFSIZ, fp))
					return(0);
				p = linebuf - 1;
				flags.blank1 = *(p+1)==' ' || *(p+1)=='\t';
				line++;
				if (!tflag) {
					fprintf(pfp, "%6d  ", line);
					fputs(linebuf, pfp);
				}
				break;
			} else
				return(0);
		case '?':
			flags.qmark = 1;
			break;
		case ':':
			flags.comp |= !flags.qmark;
			break;
		case ';':
			flags.semic = 1;
			break;
		case '\'':
			while (*++p != '\'' || *(p-1) == '\\' && *(p-2) != '\\');
			break;
		case '"':
			while (*++p != '"' || *(p-1) == '\\')
				if (!(p = putline(p)))
					return(0);
			break;
		case '/':
			if (*(p+1) != '*')
				break;
			p++;
			while (*++p != '*' || *(p+1) != '/')
				if (!(p = putline(p)))
					return(0);
			p++;
			break;
		case '(':
			parens++;
			flags.indent = 1;
			break;
		case ')':
			parens--;
			break;
		case '[':
			bracks++;
			break;
		case ']':
			bracks--;
			break;
		case '0':
			while (isalnum(*(p+1)))
				p++;	/* Ignore hex numbers */
			break;
		case ' ':
		case '\t':
			flags.indent = 1;
			break;
		default:
			;
		}
	}
	if (*p == '#')
		flags.preproc = 1;
	symline = line;		/* Indicate symbol found on this line */
	return(p);
}

char	*
putline(p)
char	*p;
{
	if (*p == '\n') {
		if (!fgets(linebuf, LBUFSIZ, fp))
			return(0);
		p = linebuf - 1;
		line++;
		if (!tflag) {
			fprintf(pfp, "%6d  ", line);
			fputs(linebuf, pfp);
		}
	}
	if (*p == '\\' && *(p-1) == '\\')
		*p = '\0';		/* Help out parsing quoted strings */
	return(p);
}

sort(tab, n)
struct	symbol	*tab;
int	n;
{
	int	gap, i, j;
	struct	symbol	temp;

	for (gap = n/2; gap > 0; gap /= 2)
		for (i = gap; i < n; i++)
			for (j = i-gap; j >= 0; j -= gap) {
				if (strcmp(tab[j].name, tab[j+gap].name) <= 0)
					break;
				temp = tab[j];
				tab[j] = tab[j+gap];
				tab[j+gap] = temp;
			}
}



.TH CREF 1 10/30/80
.CC
.SH NAME
cref \- cross reference program
.SH SYNOPSIS
.B cref
[
.B -
.I n
]
[
.B -t
]
file...
.SH DESCRIPTION
.I Cref
generates a complete cross reference listing of one or more C programs, printing
the result on the standard output.  A listing of the programs with line numbers
is printed first, followed by the actual cross reference listing.  This latter
contains all the programs's symbols alphabetically arranged, one to a line,
with each line containing the numbers of the lines in the programs where the
symbol was referenced.  If the symbol was defined on a given line, that line
number will be followed by a `#'.  Symbols with more than approximately 15
references occupy multiple lines.  There is no limit on the number of symbols
that
.I cref
will handle, nor on the number of references per symbol.
.PP
.I Cref
stores its symbols in a hash table whose size is determined by
.I cref
based on the total number of characters in the files to be processed.  For
almost all programs, this turns out to be an excellent approximation.
However, for a few programs, generally short header files, there may be
too many symbols for the hash table, and the diagnostic "Hash table
overflowed!" will be printed out.  Since the output of
.I cref
is piped through
.I pr,
it is not really possible for cref to recover from this condition.  Instead,
.I cref
should be rerun with the
.B -n
option, where
.B n
is some number.  This will multiply the starting size of the hash table by
.B n
times.
.PP
If
.I cref
is invoked with the
.B -t
option, instead of its regular output it produces an output identical in
form to that produced by the
.I ctags(1)
program.  The advantage of the
.I cref
output over
.I ctags
is that
.I cref
will flag all variable and macro definitions as well as all function
definitions.
.SH AUTHOR
Steve Zimmerman
.SH SEE ALSO
ctags(1)
.SH BUGS
.I Cref
occasionally flags a reference as a definition when it really isn't.  This
most frequently happens after a
.B struct.

