/*
 *   Copyright (C) 1988-2006 Lucent Technologies Inc. and others.
 *   All Rights Reserved.
 */

/*	cscope - interactive C symbol cross-reference
 *
 *	build cross-reference file
 */

#include "global.h"

/* convert long to a string */
#define	ltobase(value)	n = value; \
			s = buf + (sizeof(buf) - 1); \
			*s = '\0'; \
			digits = 1; \
			while (n >= BASE) { \
				++digits; \
				i = n; \
				n /= BASE; \
				*--s = i - n * BASE + '!'; \
			} \
			*--s = n + '!';

#define	SYMBOLINC	20	/* symbol list size increment */
#if defined(__BORLANDC__) || defined(FOR_WIN32)
#define	FREAD	"rb"		/* fopen for reading */
#else
#define	FREAD	"r"		/* fopen for reading */
#endif

long	dboffset;		/* new database offset */
BOOL	errorsfound;		/* prompt before clearing messages */
long	fileindex;		/* source file name index */
long	lineoffset;		/* source line database offset */
long	npostings;		/* number of postings */
int	nsrcoffset;		/* number of file name database offsets */
long	*srcoffset;		/* source file name database offsets */
int	symbols;		/* number of symbols */

static	char	*filename;	/* file name for warning messages */
static	long	fcnoffset;	/* function name database offset */
static	long	macrooffset;	/* macro name database offset */
static	int	msymbols = SYMBOLINC;	/* maximum number of symbols */
static	struct	symbol {	/* symbol data */
	int	type;		/* type */
	int	first;		/* index of first character in text */
	int	last;		/* index of last+1 character in text */
	int	length;		/* symbol length */
} *symbol;

#if defined( __STDC__ ) || defined( __BORLANDC__ )
void	crossref(char *srcfile);
void	savesymbol(int token);
void	putfilename(char *srcfile);
void	putcrossref(void);
void	putposting(char *term, int type);
void	putstring(char *s);
void	warning(char *text);
#else
void	putcrossref();
#endif

void
crossref(srcfile)
char	*srcfile;
{
	register int	i;
	register int	length;		/* symbol length */
	int	token;			/* current token */

	/* open the source file */
	if ((yyin = vpfopen(srcfile, FREAD)) == NULL) {
		cannotopen(srcfile);
		errorsfound = YES;
		return;
	}
	filename = srcfile;	/* save the file name for warning messages */
	putfilename(srcfile);	/* output the file name */
	dbputc('\n');
	dbputc('\n');

	/* read the source file */
	initscanner(srcfile);
	fcnoffset = macrooffset = 0;
	symbols = 0;
	if (symbol == NULL) {
		symbol = (struct symbol *) mymalloc(msymbols * sizeof(struct symbol));
	}
	for (;;) {
		
		/* get the next token */
		switch (token = yylex()) {
		default:
			/* if requested, truncate C symbols */
			length = last - first;
			if (truncateto8 && length > 8 &&
			    token != INCLUDE && token != NEWFILE) {
				length = 8;
				last = first + 8;
			}
			/* see if the token has a symbol */
			if (length == 0) {
				savesymbol(token);
				break;
			}
			/* see if the symbol is already in the list */
			for (i = 0; i < symbols; ++i) {
				if (length == symbol[i].length &&
				    strncmp(yytext + first, yytext +
					symbol[i].first, length) == 0 &&
				    (token == IDENT || token == symbol[i].type)) {
					first = yyleng;
					break;
				}
			}
			if (i == symbols) {	/* if not already in list */
				savesymbol(token);
			}
			break;

		case NEWLINE:	/* end of line containing symbols */
			--yyleng;	/* remove the newline */
			putcrossref();	/* output the symbols and source line */
			lineno = yylineno;	/* save the symbol line number */
			break;
			
		case LEXEOF:	/* end of file; last line may not have \n */
			
			/* if there were symbols, output them and the source line */
			if (symbols > 0) {
				putcrossref();
			}
			(void) fclose(yyin);	/* close the source file */

			/* output the leading tab expected by the next call */
			dbputc('\t');
			return;
		}
	}
}

/* save the symbol in the list */

void
savesymbol(token)
int	token;
{
	/* make sure there is room for the symbol */
	if (symbols == msymbols) {
		msymbols += SYMBOLINC;
		symbol = (struct symbol *) myrealloc((char *) symbol,
		    msymbols * sizeof(struct symbol));
	}
	/* save the symbol */
	symbol[symbols].type = token;
	symbol[symbols].first = first;
	symbol[symbols].last = last;
	symbol[symbols].length = last - first;
	++symbols;
	first = yyleng;
}

/* output the file name */

void
putfilename(srcfile)
char	*srcfile;
{
	/* check for file system out of space */
	/* note: dbputc is not used to avoid lint complaint */
	if (putc(NEWFILE, newrefs) == EOF) {
		cannotwrite(newreffile);
		/* NOTREACHED */
	}
	++dboffset;
	if (invertedindex) {
		srcoffset[nsrcoffset++] = dboffset;
	}
	dbfputs(srcfile);
	fcnoffset = macrooffset = 0;
}

/* output the symbols and source line */

void
putcrossref()
{
	register int	i, j;
	register unsigned c;
	register BOOL	blank = NO;	/* output blank */
	register BOOL	newline = NO;	/* output newline */
	register int	symput = 0;	/* symbols output */
	register struct	keystruct *p;	/* keyword */
	int	type;

	/* output the source line */
	lineoffset = dboffset;
	dbfprintf(newrefs, "%d ", lineno);
	for (i = 0; i < yyleng; ++i) {
		
		/* change a tab to a blank and compress blanks */
		if ((c = yytext[i]) == ' ' || c == '\t') {
			blank = YES;
		}
		/* look for the start of a symbol */
		else if (symput < symbols && i == symbol[symput].first) {

			/* check for compressed blanks */
			if (blank) {
				blank = NO;
				if (newline) {
					dbputc('\n');
				}
				dbputc(' ');
			}
			dbputc('\n');	/* symbols start on a new line */
			
			/* output any symbol type */
			if ((type = symbol[symput].type) != IDENT) {
				dbputc('\t');
				dbputc(type);
			}
			else {
				type = ' ';
			}
			/* output the symbol */
			j = symbol[symput].last;
			c = yytext[j];
			yytext[j] = '\0';
			if (invertedindex) {
				putposting(yytext + i, type);
			}
			putstring(yytext + i);
			newline = YES;
			yytext[j] = c;
			i = j - 1;
			++symput;
		}
		else {
			if (newline) {
				newline = NO;
				dbputc('\n');
			}
			/* check for compressed blanks */
			if (blank) {
				if (dicode2[c]) {
					c = (0200 - 2) + dicode1[' '] + dicode2[c];
				}
				else {
					dbputc(' ');
				}
			}
			/* compress digraphs */
			else if (dicode1[c] && (j = dicode2[(unsigned) yytext[i + 1]]) != 0 && 
			    symput < symbols && i + 1 != symbol[symput].first) {
				c = (0200 - 2) + dicode1[c] + j;
				++i;
			}
			/* if the last line of the file is a '}' without a
			   newline, the lex EOF code overwrites it with a 0 */
			if (c) {
				dbputc((int) c);
			}
			else {
				dbputc(' ');
			}
			blank = NO;
			
			/* skip compressed characters */
			if (c < ' ') {
				++i;
				
				/* skip blanks before a preprocesor keyword */
				/* note: don't use isspace() because \f and \v
				   are used for keywords */
				while ((j = yytext[i]) == ' ' || j == '\t') {
					++i;
				}
				/* skip the rest of the keyword */
				while (isalpha(yytext[i])) {
					++i;
				}
				/* skip space after certain keywords */
#ifdef MULTILINGUAL
				p = keyword(c);
#else
				p = &keyword[c];
#endif
				if (p->delim != '\0') {
					while ((j = yytext[i]) == ' ' || j == '\t') {
						++i;
					}
				}
				/* skip a '(' after certain keywords */
#ifdef MULTILINGUAL
				p = keyword(c);
#else
				p = &keyword[c];
#endif
				if (p->delim == '(' && yytext[i] == '(') {
					++i;
				}
				--i;	/* compensate for ++i in for() */
			}
		}
	}
	/* ignore trailing blanks */
	dbputc('\n');
	dbputc('\n');

	/* output any #define end marker */
	/* note: must not be part of #define so putsource() doesn't discard it
	   so findcalledbysub() can find it and return */
	if (symput < symbols && symbol[symput].type == DEFINEEND) {
		dbputc('\t');
		dbputc(DEFINEEND);
		dbputc('\n');
		dbputc('\n');	/* mark beginning of next source line */
		macrooffset = 0;
	}
	symbols = 0;
}

/**********************************************************************
 * output the inverted index posting for the inverted index.
 *
 * The file will eventually be sorted using the standard /bin/sort in
 * normal ASCII sorting order (LC_COLLATE=C).
 *
 * Each line consists of the term (the string key) followed by a space
 * followed by an encoded dboffset (precisely PRECISION characters
 * wide) followed by a type (a single char as defined in constants.h).
 *
 * dboffset is the character offset into the cscope.out file.
 *
 * If there is an offset (such as with function or macro names) then
 * the line may also have an additional space, and the encoded offset
 * to the function or macro name. This encoding does not have the
 * leading spaces converted to '!' chars as it does not impact sorting
 * order.
 *
 * The encoding converts to a high-base number (depending on the way
 * that chars are treated: as signed or unsigned integers). The
 * high-base (much higher than decimal) means that the offset can be
 * compressed to fewer digits (4 or 5). However, in order to ensure
 * proper collating sequence, the leading spaces are replaced with
 * '!' characters (with a value of 041 which is the next value up
 * from a space, 040). We need to replace the leading spaces with
 * something else as spaces are used to separate tokens in each line.
 *
 * Note: with BASE=95 and PRECISION=5, the cscope.out file is limited
 * to about 7.7 gigabytes. For a BASE=223 and PRECISION=4 the database
 * is limited to only 2.4 gigabytes. Someday, we may have to boost the
 * PRECISION up but since the largest (as of this writing) databases
 * are still less than 200 megabytes, this shouldn't be for a while...
 *
 * Here is a typical subset of postings as originally output to the
 * postings file (encoded but not yet sorted). Note that the last
 * 3 lines have the optional offset to function or macro name. In
 * this example, the PRECISION is 5 and the BASE is 95.
 *
 * global.h !!!!h~
 * curses.h !!!!{~
 * setjmp.h !!!"/~
 * jmp_buf !!!"B 
 * env !!!"Bg
 * prevchar !!!"Yg
 * defined !!!"k 
 * __STDC__ !!!"k 
 * __BORLANDC__ !!!"k 
 * ungetch !!!#D 
 * catchint !!!#V 
 * sig !!!#V 
 * __BORLANDC__ !!!&)  %v
 * signal !!!&A` %v
 * SIGINT !!!&A  %v
 *
 *********************************************************************/
void
putposting(term, type)
char	*term;
int	type;
{
	register long	i, n;
	register char	*s;
	register int	digits;		/* digits output */
	register long	offset;		/* function/macro database offset */
	char	buf[11];		/* number buffer */

	/* get the function or macro name offset */
	offset = fcnoffset;
	if (macrooffset != 0) {
		offset = macrooffset;
	}
	/* then update them to avoid negative relative name offset */
	switch (type) {
	case DEFINE:
		macrooffset = dboffset;
		break;
	case DEFINEEND:
		macrooffset = 0;
		return;		/* null term */
	case FCNDEF:
		fcnoffset = dboffset;
		break;
	case FCNEND:
		fcnoffset = 0;
		return;		/* null term */
	}
	/* ignore a null term caused by a enum/struct/union without a tag */
	if (*term == '\0') {
		return;
	}
	/* skip any #include secondary type char (< or ") */
	if (type == INCLUDE) {
		++term;
	}
	/* output the posting, which should be as small as possible to reduce
	   the temp file size and sort time */
	(void) fputs(term, postings);
	(void) putc(' ', postings);

	/* the line offset is padded so postings for the same term will sort
	   in ascending line offset order to order the references as they
	   appear withing a source file */
	ltobase(lineoffset);
	for (i = PRECISION - digits; i > 0; --i) {
		(void) putc('!', postings);
	}
	do {
		(void) putc(*s, postings);
	} while (*++s != '\0');
	
	/* postings are also sorted by type */
	(void) putc(type, postings);
	
	/* function or macro name offset */
	if (offset > 0) {
		(void) putc(' ', postings);
		ltobase(offset);
		do {
			(void) putc(*s, postings);
		} while (*++s != '\0');
	}
	if (putc('\n', postings) == EOF) {
		cannotwrite(temp1);
		/* NOTREACHED */
	}
	++npostings;
}

/* put the string into the new database */

void
putstring(s)
register char	*s;
{
	register unsigned c;
	register int	i;
	
	/* compress digraphs */
	for (i = 0; (c = s[i]) != '\0'; ++i) {
		if (dicode1[c] && dicode2[(unsigned) s[i + 1]]) {
			c = (0200 - 2) + dicode1[c] + dicode2[(unsigned) s[i + 1]];
			++i;
		}
		dbputc((int) c);
	}
}

/* print a warning message with the file name and line number */

void
warning(text)
char	*text;
{
	extern	int	yylineno;
	
	(void) fprintf(stderr, "cscope: \"%s\", line %d: warning: %s\n", filename, 
		yylineno, text);
	errorsfound = YES;
}
