[mod.sources] v06i003: new calls; shows function call flow

sources-request@mirror.UUCP (06/17/86)

Submitted by: Wombat <cca!caip!pur-ee!pucc-j.Purdue.EDU!rsk>
Mod.sources: Volume 6, Issue 3
Archive-name: calls.new

[ I collapsed the handling of the -D -I and -U flags, and changed the
  lines that used sprintf's return value.  Other than index/strchr, it
  should work on any Unix.  --R$]

This is a massively revised, bug-fixed, and hacked version of "calls",
which has been bouncing around the net for some time.  Comments and
bugs and whatnot to Kevin Braunsdorf at pucc-j!ksb or ksb@j.cc.purdue.edu.

It's also available via anonymous ftp on j.cc.purdue.edu.

-- 
Rich Kulawiec, pucc-j!rsk, rsk@j.cc.purdue.edu

#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	README
#	Makefile
#	calls.1u
#	getopt.h
#	main.h
#	scan.h
#	getopt.c
#	main.c
#	scan.c
# This archive created: Thu Jun 12 22:46:39 1986
# By:	Wombat (Purdue University)
cat << \SHAR_EOF > README
Calls is a program I use a lot.  It came off the net last year;
since then I have been beefing it up to take on bigger and better
things (static functions are the biggest).  The only thing calls
would like to know to run okay is if calloc() really does zero out
all of the memory it returns: if not, use the -DBADCALLOC in the
Makefile.

					Kevin S Braunsdorf
					ksb@j.cc.purdue.edu

P.S. to see the neat stuff you might try
	./calls -v -F newHASH/scan.c -f level1 -f dostdin *.c
SHAR_EOF
cat << \SHAR_EOF > Makefile
# Makefile for calls, Kevin Braunsdorf PUCC

BIN =	/usr/local/bin
#DEFS= -DBADCALLOC
DEFS=
CFLAGS = -g ${DEFS}

SRCm=	Makefile
HDR =	main.h getopt.h scan.h
SRCc=	main.c getopt.c scan.c
OBJ =	main.o getopt.o scan.o
SRC =	${SRCm} ${HDR} ${SRCc}

all: calls

calls: ${OBJ}
	${CC} ${CFLAGS} ${OBJ} -o $@

install: calls
	install -s -m 751 -o binary -g system calls ${BIN}

list:
	lpr ${SRC}

lint:
	lint -hnx ${SRCc}

clean:
	rm -f ${OBJ}

spotless: clean
	ci ${SRC}

${SRC}:
	co -l $@

depend: ${SRCc}
	maketd ${SRCc}

# DO NOT DELETE THIS LINE - make depend DEPENDS ON IT

main.o: getopt.h main.c main.h scan.h

getopt.o: getopt.c getopt.h

scan.o: main.h scan.c scan.h

# *** Do not add anything here - It will go away. ***
SHAR_EOF
cat << \SHAR_EOF > calls.1u
.TH CALLS 1 UNSUP
.SH NAME
calls \- print out calling pattern of a C program
.SH SYNOPSIS
calls [-aeitv] [-w n] [-f function] [-F function[/file.c]] [-D name[=def]] [-U name] [-I dir] [filenames]
.SH DESCRIPTION
.I Calls
is intended to help analyze the flow of a program by laying out the
functions called in a hierarchical manner.
.I Calls
invokes the C preprocessor on the named C source files, and outputs
the analyzed calling pattern to standard output.
All filenames given will have their calling sequences combined into
one hierarchy.
If a filename of \- is seen, standard input will be read.
.P
Functions called but not defined within the source file are shown as:
.br
.RS
function
.RE
.P
While functions defined in the source files are listed with the file they
are declared in in brackets, as shown:
.br
.RS
function [main.c] , or
.br
function [static in main.c]
.RE
or if the function is not being described
.RS
function [see also %d] , or
.br
function [see below]
.RE
.P
Recursive references are shown as:
.br
.RS
function <<< recursive >>>
.RE
.P
For example, given the file
.B prog.c
.br
.RS
.nf
main() {
	abc();
	def();
}
abc() {
	ghi();
	jkl();
}
static mno() { }
ghi() {
	abc();
	def();
	mno();
}
.fi
.RE
.sp
Executing "calls prog.c" will produce:
.sp
.RS
.nf
    1	main [prog.c]
    2		abc [prog.c]
    3			ghi [prog.c]
    4				abc <<< recursive >>>
    5				def
    6				mno [static in prog.c]
    7			jkl
    8		def
.fi
.RE
.SH FLAGS
.TP
.BI -a
Normally only the first call to a function is recorded for any
given function, under this option all calls are recorded. This may
make the output for some large programs very verbose and these are
normally not needed to show the calling structure of a program.
.TP
.BI -e
Normally an index listing (-i below) does not contain the external
functions called in the program, under this option these are also listed.
Note this option also turns on the indexing option, -i.
.TP
.BI -f function
The named function will be printed as the root of a calling tree.
.TP
.BI -F function\[/file\]
The named static function (in the given file) is used as the base of a
calling tree, as above.  This allows closer examination of sources such
as that of dbx(1) that have many functions with the same name.
.TP
.BI -h
Display a brief help message.
.TP
.BI -i
This option produces an index of all the functions declared in the
processed files. Optionally all functions mentioned can be output;
see -e above.
.TP
.BI -t
This option instructs
.I calls
not to display calling trees that were
not explicitly asked for on the command line. Using this option as
well as the index option one can produce just a list of the functions
declared in a file.
.TP
.BI -v
Be less verbose in the index output, do not output any defined functions
that were not present in any of the output trees.
Note this also turns on the index option.
For a list of all functions called
by 'missle' one might examine the index output of "calls -vt -f missle *.c".
.TP
.BI -w n
Set the max indentation width to n.  The default is 96 columns.
.TP
.BI -D name
.TP
.BI -D name=def
Define the
.I name
for the preprocessor, as if by #define.
If no definition is given, the name is defined as 1.
.TP
.BI -U name
Remove any initial definition of
.I name
in the preprocessor.
.TP
.BI -I dir
Change the path for searching for #include files whose names do not
begin with / to look in
.I dir
before looking in the directories on the standard list.
.br
.RE
.SH BUGS
Static functions must be declared (in full) 
.I before
used to work properly.
.br
Output width checking is only done on the first character on a new line.
.SH AUTHOR
Originally from the net. Major revisions by Kevin Braunsdorf, PUCC.
.SH SEE ALSO
cpp(1), cc(1), ctags(1)
SHAR_EOF
cat << \SHAR_EOF > getopt.h
/*
 * get option letter from argument vector
 */
extern int
	optind,			/* index into parent argv vector */
	optopt;			/* character checked for validity */
extern char
	*optarg;		/* argument associated with option */

#define BADCH	((int)'?')
#define EMSG	""
#define tell(s)	{fputs(*nargv,stderr);fputs((s),stderr); \
		fputc(optopt,stderr);fputc('\n',stderr);return(BADCH);}

extern int getopt(), getarg();
SHAR_EOF
cat << \SHAR_EOF > main.h
/*
 * calls   -- print out the calling struture of a C program.
 *
 *  takes these options:
 *	-a		show all calls (even duplicates)
 *	-e		include externals in index
 *	-i		normal index
 *	-t		terse form, no extra trees output
 *	-v		less verbose index
 *	-w nn		paper width  (default 96)
 *	-f name		function to start printing from
 *	-F name[/file]	static function to start printing from
 *
 *  arguments passed on to CPP:
 *	-D name		#define def
 *	-U name		#undef def
 *	-I file		#include path modifier
 */

#define MAXDEPTH	99		/* max output depth level	*/
#define PAPERWIDTH	96		/* limits tabbing		*/
#define TABWIDTH	8		/* width of a \t		*/

typedef struct CLnode {
	struct CLnode *pCLnext;
	struct HTnode *pHTlist;
} LIST;
#define nilCL	((LIST *) 0)
#define newCL()	((LIST *)malloc(sizeof(LIST)))

extern char sbCmd[];
extern int Allp;
SHAR_EOF
cat << \SHAR_EOF > scan.h
/*
 * scan.h -- scanner for calls
 *	<stdio> must be included before this file, and
 *	"main.h" is assumed the main.h defines BUCKET, also included before
 */

#define LCURLY	  '{'	        /*}*/	/* messes with vi's mind 	*/
#define RCURLY	 /*{*/ 	 	 '}'	/* to have curly in text	*/
#define LPAREN	  '('		/*)*/	/* same mess			*/
#define RPAREN   /*(*/		 ')'	/* as above			*/
#define LBRACK	  '['		/*]*/	/* more mess implies		*/
#define RBRACK	 /*[*/		 ']'	/* more mass			*/
#define BUCKET		100		/* number of objects to alloc	*/
#define MAXCHARS	80		/* max number of chars in ident	*/

typedef struct INnode {
	struct HTnode *pHTname;		/* namep;			*/
	struct INnode *pINnext;		/* pnext			*/
} INST;
#define nilINST	((INST *) 0)

typedef struct HTnode {
	char *pchname, *pchfile;	/* name & file declared		*/
	struct HTnode *pHTnext;		/* next in table (list)		*/
	struct INnode *pINcalls;	/* list of calls		*/
	short int
		listp,			/* 0 = don't, 1 = do, 2 = done	*/
		calledp,		/* have we ever been called	*/
		iline,			/* line output on		*/
		localp;			/* crude static function flag	*/
} HASH, *PHT;
#define nilHASH	((HASH *) 0)

extern void level1();
extern FILE *input;
extern HASH *newHASH(), *search(), *pHTRoot[2];
extern INST *newINST();
SHAR_EOF
cat << \SHAR_EOF > getopt.c
/* @(#)getopt.c */

#include <stdio.h>

#include "getopt.h"

/*
 * get option letter from argument vector
 */
int
	optind = 1,		/* index into parent argv vector */
	optopt;			/* character checked for validity */
char	*optarg;		/* argument associated with option */

int
getopt(nargc, nargv, ostr)
int nargc;
char **nargv, *ostr;
{
	extern char	*index();
	register char	*oli;		/* option letter list index */
	static char	*place = EMSG;	/* option letter processing */

	if(!*place) {			/* update scanning pointer */
		if(optind >= nargc || *(place = nargv[optind]) != '-' || !*++place) return(EOF);
		if (*place == '-') {	/* found "--" */
			++optind;
			return EOF;
		}
	}				/* option letter okay? */
	if ((optopt = (int)*place++) == (int)':' || !(oli = index(ostr,optopt))) {
		if(!*place) ++optind;
		tell(": illegal option -- ");
	}
	if (*++oli != ':') {		/* don't need argument */
		optarg = NULL;
		if (!*place)
			++optind;
	} else {				/* need an argument */
		if (*place) {			/* no white space */
			optarg = place;
		} else if (nargc <= ++optind) {	/* no arg */
			place = EMSG;
			tell(": option requires an argument -- ");
		} else {
			optarg = nargv[optind];	/* white space */
		}
		place = EMSG;
		++optind;
	}
	return optopt;			/* dump back option letter */
}

int
getarg(nargc, nargv)
int nargc;
char **nargv;
{
	if (nargc <= optind) {
		optarg = (char *) 0;
		return EOF;
	} else {
		optarg = nargv[optind++];
		return 0;
	}
}
SHAR_EOF
cat << \SHAR_EOF > main.c
/*
 * main.c -- calls mainline, trace calling sequences of C programs
 */

#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <strings.h>

#include "scan.h"
#include "getopt.h"
#include "main.h"

/* globals */
char
	sbCmd[] = "cmd line";	/* kludge to notify user of error	*/
int
	Allp = 0,		/* flag to show *all* function calls	*/
	Tersep = 0,		/* only requested trees			*/
	Externp = 0,		/* include externs in index		*/
	Indexp = 0,		/* output functions index		*/
	Verbosep = 1;		/* index functions not output		*/

/* locals */
static int
	linect = 0,		/* line number				*/
	activep = 0,		/* current function being output	*/
	iWidth = PAPERWIDTH;	/* default paper width			*/
static char
	*pchProg,		/* argv[0]				*/
	cppcommand[1024] =	/* cpp command string			*/
		"/lib/cpp -P ";
static HASH
	*activelist[MAXDEPTH];	/* list of current output names		*/


char *
syserrlist()	/*  the routine returns a string for a system error	*/
{
	extern char *sys_errlist[];
	extern int sys_nerr;
	extern int errno;
	register char *pchErr =
	    errno == 0 ? "errno = 0" :
		errno < sys_nerr ? sys_errlist[errno] : "errno out of range";

	errno = 0;
	return pchErr;
}

void
process(filename, outname)	/* invoke cpp on file, call level1	*/
register char *filename, *outname;
{
	extern FILE *popen();
	register char *sbNull;
	register int ret;

	if (access(filename, 04) != 0) {
		(void) fprintf(stderr, "%s: cannot open file '%s' (%s).\n", pchProg, filename, syserrlist());
		return;
	}
	sbNull = cppcommand + strlen(cppcommand);
	strcpy(sbNull, filename);
	if (NULL == (input = popen(cppcommand, "r"))) {
		(void) fprintf(stderr, "%s: fork of CPP command '%s' failed on file '%s' (%s).\n", pchProg, cppcommand, filename, syserrlist());
	} else {
		level1(outname);
		if (0 != (ret = pclose(input)))
			(void) fprintf(stderr, "%s: CPP command '%s' failed on file '%s' with return code %d.\n", pchProg, cppcommand, filename, ret);
	}
	*sbNull = '\0';
	return;
}

void
dostdin()	/* copy stdin to temp file, call process on file	*/
{
	extern char *mktemp();
	register int cc;
	register char *filename = mktemp("/tmp/callsXXXXXX");
	register FILE *ofileptr = fopen(filename, "w");
	register char *sbNull;

	if (NULL == ofileptr) {
		(void) fprintf(stderr, "%s: cannot open tempfile '%s' for writing (%s).\n", pchProg, filename, syserrlist());
	} else {
		while (EOF != (cc = getchar()))
			putc(cc, ofileptr);
		fclose(ofileptr);
		sbNull = cppcommand + strlen(cppcommand);
		strcpy(sbNull, "-I. ");
		process(filename, "stdin");
		*sbNull = '\000';
		unlink(filename);
	}
}

int
active(func)	/* check for recursive calls, prevents endless output	*/
register HASH *func;
{
	register int i;

	for (i = 0; i < activep-1; i++)
		if (func == activelist[i])
			return 1;
	return 0;
}

void
output(pHTFunc, tabc)	/* output a (sub)tree in pretty form		*/
register HASH *pHTFunc;
register int tabc;
{
	static char dashes[] = "\n----------";
	register INST *pINTemp;
	register int i;

	++linect;
	(void) printf("\n%5d\t", linect);
	if (activep < MAXDEPTH) {
		activelist[activep++] = pHTFunc;
	} else {
		(void) printf("   * nesting is too deep");
		return;
	}

	for (i = 0; i < tabc; i++ )
		putchar('\t');
	printf("%s", pHTFunc->pchname);

	if (active(pHTFunc)) {
		(void) printf(" <<< recursive >>>");
	} else if (pHTFunc->pchfile) {
		pINTemp = pHTFunc->pINcalls;
		if (pHTFunc->listp && tabc && 0 == pHTFunc->iline) {
			(void) printf(" [%s] [see below]", pHTFunc->pchfile);
		} else if (! pHTFunc->iline) {
			(void) printf(pHTFunc->localp ? " [static in %s]" : " [%s]", pHTFunc->pchfile);
			pHTFunc->iline = linect;
			if ((++tabc) * TABWIDTH >= iWidth) {
				(void) printf(dashes);
				tabc = 0;
			}
			while (pINTemp) {
				output(pINTemp->pHTname, tabc);
				pINTemp = pINTemp->pINnext;
			}
			if (! tabc)
				(void) printf(dashes);
		} else if (pINTemp || pHTFunc->localp) {
			(void) printf(" [see line %d]", pHTFunc->iline);
		}
	}
	activelist[activep--] = nilHASH;
}

int
main(argc, argv)	/* parse args, add files, call output		*/
int argc;
char *argv[];
{
	extern int atoi();
	extern char *index();
	static char sbOpts[] = "aehitvw:D:f:F:U:I:"; /* valid options	*/
	static char sbTemp[200];
	static LIST *pCLRoot;
	register HASH *pHTList;
	register int cOption;
	register LIST **ppCL;
	register char *pchSplit;

	pchProg = argv[0];
	ppCL = & pCLRoot;

	while (EOF != (cOption = getopt(argc, argv, sbOpts))) {
		switch (cOption) {
		case 'a':
			Allp = 1;
			break;
		case 'e':
			Externp = 1;
			Indexp = 1;
			break;
		case 'F':
			if (0 != (pchSplit = index(optarg, '/'))) {
				*pchSplit++ = '\000';
			} else {
		case 'f':	
				pchSplit = sbCmd;
			}
			pHTList = search(optarg, 'F' == cOption, pchSplit);
			pHTList->listp = 1;
			pHTList->pchfile = pchSplit;
			*ppCL = newCL();
			(*ppCL)->pCLnext = pCLRoot;
			(*ppCL)->pHTlist = pHTList;
			ppCL = & (*ppCL)->pCLnext;
			break;
		case 't':	
			Tersep = 1;
			break;
		case 'v':
			Verbosep = 0;
			/*fallthrough*/
		case 'i':
			Indexp = 1;
			break;
		case 'w':
			if (0 >= (iWidth = atoi(optarg)))
				iWidth = PAPERWIDTH;
			break;
		case 'D':
		case 'I':
		case 'U':
			sprintf(sbTemp, "-%c%s ", cOption, optarg));
			strcat(cppcommand, sbTemp);
			break;
		case '?':
		case 'h':
			(void) fprintf(stderr, "usage: %s [-aehitv] [-f function] [-F function[/file.c]] [-w width]\n\
    [-D define] [-U undefine] [-I include-dir] [filename|-]*\n\
\ta\tprint all calls in every function body\n\
\te\tindex external functions too\n\
\tf,F\tstart calling trace at given function\n\
\th\tprint this message\n\
\ti\tprint an index of defined functions\n\
\tv\tlist only called functions in index output\n\
\tt\tterse, list only trees that are requested\n\
\tw\tset ouptut width\n\
\tD,U,I\tas in cpp\n", pchProg);
			exit('h' != cOption);
		}
	}
	*ppCL = nilCL;

	while (EOF != getarg(argc, argv)) {
		if ('-' == optarg[0] && '\000' == optarg[1])
			dostdin();
		else
			process(optarg, optarg);
	}

	while (pCLRoot) {		/* print requested trees	*/
		output(pCLRoot->pHTlist, 0);
		putchar('\n');
		pCLRoot = pCLRoot->pCLnext;
	}

	if (!Tersep) {			/* print other trees		*/
		for (cOption = 0; cOption < 2; ++cOption) {
			for (pHTList = pHTRoot[cOption]; pHTList; pHTList = pHTList->pHTnext) {
				if (!pHTList->calledp && NULL != pHTList->pchfile) {
					output(pHTList, 0);
					putchar('\n');
				}
			}
		}
	}

	if (Indexp) {			/* print index			*/
		printf("\fIndex:\n");
		while (nilHASH != pHTRoot[0] || nilHASH != pHTRoot[1]) {
			
			if (nilHASH == pHTRoot[0] || (nilHASH != pHTRoot[1] && strcmp(pHTRoot[0]->pchname, pHTRoot[1]->pchname) >= 0)) {
				pHTList = pHTRoot[1];
				pHTRoot[1] = pHTRoot[1]->pHTnext;
			} else {
				pHTList = pHTRoot[0];
				pHTRoot[0] = pHTRoot[0]->pHTnext;
			}
			if (!Externp && NULL == pHTList->pchfile)
				continue;
			if (!Verbosep && 0 == pHTList->iline)
				continue;
			putchar('\t');
			fputs(pHTList->pchname, stdout);
			if (pHTList->localp) {
				printf(" [static in %s]", pHTList->pchfile);
			} else if (((char *) 0) != pHTList->pchfile) {
				printf(" [%s]", pHTList->pchfile);
			}
			if (0 != pHTList->iline) {
				printf(" [see line %d]", pHTList->iline);
			}
			putchar('\n');
		}
	}
	exit(0);
}
SHAR_EOF
cat << \SHAR_EOF > scan.c
/*
 * scan.c -- a simple scanner for C, pulls out the function
 *	calling pattern	(all by KSB)
 */

#include <ctype.h>
#include <stdio.h>
#include <strings.h>
#define strsave(X)	strcpy((char *)malloc(strlen((X))+1), (X))

#include "scan.h"
#include "main.h"

int c;				/* parser look ahead			*/
FILE *input;			/* our input file pointer		*/
HASH *pHTRoot[2] =	 	/* our list of idents			*/
	{nilHASH, nilHASH};
static char 
	AUTO[] = "auto",	BREAK[] = "break",	CASE[] = "case",
	CHAR[] = "char",	CONTINUE[] = "continue",DEFAULT[] = "default",
	DO[] = "do",		DOUBLE[] = "double",	ELSE[] = "else",
	ENUM[] = "enum",	EXTERN[] = "extern",	FLOAT[] = "float",
	FOR[] = "for",		FORTRAN[] = "fortran",	GOTO[] = "goto",
	IF[] = "if",		INT[] = "int",		LONG[] = "long",
	REGISTER[] = "register",RETURN[] = "return",	SHORT[] = "short",
	SIZEOF[] = "sizeof",	STATIC[] = "static",	STRUCT[] = "struct",
	SWITCH[] = "switch",	TYPEDEF[] = "typedef",	UNION[] = "union",
	UNSIGNED[] = "unsigned",VOID[] = "void",	WHILE[] = "while";

static HASH *
newHASH()	/* get a new hash node					*/
{
	extern char *calloc();
	register HASH *pHTRet;
	static HASH *pHTQueue = nilHASH;

	if (nilHASH == pHTQueue) {
		if (!(pHTRet = (HASH *)calloc(BUCKET, sizeof(HASH)))) {
			(void) fprintf(stderr, "out of mem\n");
			exit(2);
		}
		pHTQueue = (pHTRet+(BUCKET-1))->pHTnext = pHTRet;
	}
	pHTRet = pHTQueue;
	pHTQueue = pHTRet->pHTnext ? nilHASH : pHTRet+1;
	return pHTRet;
}

HASH *
search(name, Localp, pchFile)	/* translate name to hash node		*/
register char *name;
int Localp;			/* -> trying to make a local def	*/
char *pchFile;
{
	register HASH **ppHT, *pHT;
	register int i = 1;

	ppHT = & pHTRoot[1];	/* first search statics	*/
	while((pHT = *ppHT) && (i = strcmp(pHT->pchname, name)) <= 0) {
		if (0 == i && 0 == strcmp(pchFile, pHT->pchfile))
			break;	/* found a visible static function	*/
		ppHT = & pHT->pHTnext;
		i = 1;
	}

	if (0 != i && ! Localp) {
		ppHT = & pHTRoot[0];
		while((pHT = *ppHT) && (i = strcmp(pHT->pchname, name)) < 0)
			ppHT = & pHT->pHTnext;
	}

	if (0 != i) {
		pHT = newHASH();
		pHT->pchname = strsave(name);
#ifdef BADCALLOC		/* calloc does not zero mem?		*/
		pHT->pchfile = (char *) 0;
		pHT->listp = 0;
		pHT->calledp = 0;
		pHT->pINcalls = nilINST;
#endif BADCALLOC
		pHT->localp = Localp;
		pHT->pHTnext = *ppHT;
		*ppHT = pHT;
	}
	return pHT;
}

/*
 * here we don't assume that cpp takes out comments, really
 * paranoid of us, but I think that way
 * f is a flag we use to make the look ahead come out right
 * in all cases
 */
void
eatwhite(f)	/* skip blanks, comments, "strings", 'chars' in input	*/
register int f;
{
	if (f)
		c = getc(input);
	for(/* void */; /* c != EOF */; c = getc(input)) {
		if (isspace(c) || c == '\b') {
			continue;
		} else if ('/' == c) {		/* start of comment? */
			if ('*' == (c = getc(input))) {
				c = getc(input);	/* eat comment */
				for(;;) {
					while (c != '*')
						c = getc(input);
					if ('/' == (c = getc(input)))
						break;
				}
			} else {
				ungetc(c, input);
				c = '/';
				break;
			}
		} else if ('\'' == c || '"' == c) {
			while(c != (f = getc(input))) {
				if ('\\' == f)
					getc(input);
			}
		} else if ('#' == c) {
			while ('\n' != getc(input))
				/* void */;
		} else {
			break;
		}
	}
}

void
balance(l, r)	/* find balancing character				*/
register int l, r;
{
	register int brace = 1;

	do
		eatwhite(1);
	while (brace += (l == c) - (r == c));
}

int
getid(sb, ppchToken)	/* return 0 = var, 1 == func, 2 == keyword	*/
register char *sb;
char **ppchToken;
{
	static char *keywords[] = {
		AUTO, BREAK, CASE, CHAR, CONTINUE, DEFAULT,
		DO, DOUBLE, ELSE, ENUM, EXTERN, FLOAT, FOR,
		FORTRAN, GOTO, IF, INT, LONG, REGISTER,
		RETURN, SHORT, SIZEOF, STATIC, STRUCT, SWITCH,
		TYPEDEF, UNION, UNSIGNED, VOID, WHILE, (char *)0
	};
	register int i = 0;
	register char **psbKey = keywords;

	do {
		if (i < MAXCHARS)
			sb[i++] = c;
		c = getc(input);
	} while (isalpha(c) || isdigit(c) || '_' == c);
	sb[i] = '\000';		/* buffer really goes to MAXCHARS+1	*/
	eatwhite(0);	/* c == next char after id */

	while (*psbKey && 0 != strcmp(*psbKey, sb))
		++psbKey;

	if (*psbKey) {
		*ppchToken = *psbKey;
		return 2;
	}

	return LPAREN == c;
}

void
eatdecl(sb)	/* eat anything that starts with any keyword listed	*/
register char *sb;
{
	static char *which[] = {	/* keywords mark a declaration	*/
		AUTO, CHAR, STATIC, DOUBLE, ENUM, EXTERN, FLOAT, INT,
		LONG, REGISTER, SHORT, STATIC, STRUCT, TYPEDEF, UNION,
		UNSIGNED, VOID, (char *) 0};
	register char **psb = which;

	while(*psb)
		if (*psb++ == sb)
			break;
	if (*psb) {
		while ('=' != c && ';' != c && RPAREN != c) {
			if (LCURLY == c)
				balance(LCURLY, RCURLY);
			else if (LPAREN == c) {
				balance(LPAREN, RPAREN);
			}
			eatwhite(1);
		}
	}
}

INST *
newINST()	/* get a new instaniation  node				*/
{
	extern char *calloc();
	register INST *pINRet;
	static INST *pINQueue = nilINST;

	if (nilINST == pINQueue) {
		if (!(pINRet = (INST *)calloc(BUCKET, sizeof(INST)))) {
			(void) fprintf(stderr, "out of mem\n");
			exit(2);
		}
		pINQueue = (pINRet+(BUCKET-1))->pINnext = pINRet;
	}
	pINRet = pINQueue;
	pINQueue = pINRet->pINnext ? nilINST : pINRet+1;
	return pINRet;
}

void
level2(pHTCaller, pchFile)	/* inside a function looking for calls	*/
HASH *pHTCaller;
char *pchFile;
{
	static char buffer[MAXCHARS+1];
	register struct INnode *pINLoop;
	register int brace = 0;
	register HASH *pHTFound;
	register struct INnode **ppIN = & (pHTCaller->pINcalls);
	register int declp = 1;		/* eating declarations		*/
	auto char *pchToken;

	while (brace || declp) {
		if (isalpha(c) || '_' == c) {
			switch (getid(buffer, & pchToken)) {
			case 1:
				pHTFound = search(buffer, 0, pchFile);
				if (Allp)
					goto regardless;
				for(pINLoop = pHTCaller->pINcalls;
				    pINLoop;
				    pINLoop = pINLoop->pINnext)
					if (pHTFound == pINLoop->pHTname)
						break;
				if (! pINLoop) {
			regardless:
					pINLoop = *ppIN = newINST();
					pINLoop->pHTname = pHTFound;
					ppIN = & pINLoop->pINnext;
				}
				++pHTFound->calledp;
				break;
			case 2:
				eatdecl(pchToken);
				/* fall through */
			case 0:
				break;
			}
		} else {
			if (LCURLY == c)
				declp = 0, ++brace;
			else if (RCURLY == c)
				--brace;
			eatwhite(1);
		}
	}
	*ppIN = nilINST;
}

void
level1(filename)	/* in a C source program, looking for fnx(){..}	*/
register char *filename;
{
	static char buffer[MAXCHARS+1];
	static char *pchToken;
	register HASH *pHTTemp;
	register int parens = 0;
	register int Localp = 0;

	c = ' ';

	do {		/* looking to a function decl	*/
		if (isalpha(c) || '_' == c) {
			switch (getid(buffer, & pchToken)) {
			case 1:
				while (parens += (LPAREN == c) - (RPAREN == c))
					eatwhite(1);
				for (;;) {	/* eat complex stuff	*/
					eatwhite(1);
					if (LPAREN == c) {
						balance(LPAREN, RPAREN);
						continue;
					} else if (LBRACK == c) {
						balance(LBRACK, RBRACK);
						continue;
					} else {
						break;
					}
				}
				pHTTemp = search(buffer, Localp, filename);
				if (',' == c || ';' == c) {
					Localp = 0;
					break;
				}
				if (pHTTemp->pchfile && pHTTemp->pchfile != sbCmd &&
				    (pHTTemp->pchfile == filename ||
				    0 != strcmp(pHTTemp->pchfile, filename))) {
					fprintf(stderr, "%s is multiply defined [%s, %s]\n", pHTTemp->pchname, pHTTemp->pchfile, filename);
					exit(5);
				} else {
					pHTTemp->pchfile = filename;
					Localp = 0;
					level2(pHTTemp, filename);
				}
				continue;
			case 2:
				if (STATIC == pchToken)
					Localp = 1;
			case 0:
				continue;
			}
		} else if (LCURLY == c) {
			balance(LCURLY, RCURLY);
		} else if (LPAREN == c) {
			++parens;
		} else if (RPAREN == c) {
			--parens;
		} else if ('*' != c) {
			Localp = 0;
		}
		eatwhite(1);
	} while (EOF != c);
}
SHAR_EOF
#	End of shell archive
exit 0