/*
 * scan.c -- a simple scanner for C, pulls out the function
 *	calling pattern	(all by KSB)
 */

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

#include "debug.h"

#ifdef MCH_AMIGA
extern int strlen();
extern char *strcpy();
extern char *malloc();
#else
#include <strings.h>
#endif


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

extern int debug;

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;

DEBUG0(1, "newHASH entered\n");

	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;

DEBUG1(1, "search entered: name(%s) \n", name);

	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 = (char *) strcpy((char *)malloc(strlen((name))+1), (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;
{

DEBUG1(1, "eatwhite entered: f(%d) \n", 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;

	DEBUG0(1, "level2 entered: \n" );

	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;
	int i;

	DEBUG1(3, "level1 entered: filename(%s)\n", filename);

	c = ' ';

	do {		/* looking to a function decl	*/

		DEBUG1(5, "level1: Top of loop c(%c)\n", c);

		if (isalpha(c) || '_' == c) {
			i =  getid(buffer, & pchToken);

			DEBUG1(5, "level1: getid returned (%d)\n", getid);

			switch (i) {
			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);
}
