/*
 * CPP main program.
 *
 * Edit history
 * 21-May-84	MM	"Field test" release
 * 23-May-84	MM	Some minor hacks.
 * 30-May-84	ARF	Didn't get enough memory for __DATE__
 *			Added code to read stdin if no input
 *			files are provided.
 * 29-Jun-84	MM	Added ARF's suggestions, Unixifying cpp.
 * 11-Jul-84	MM	"Official" first release (that's what I thought!)
 * 22-Jul-84	MM/ARF/SCK Fixed line number bugs, added cpp recognition
 *			of #line, fixed problems with #include.
 * 23-Jul-84	MM	More (minor) include hacking, some documentation.
 *			Also, redid cpp's #include files
 * 25-Jul-84	MM	#line filename isn't used for #include searchlist
 *			#line format is <number> <optional name>
 * 25-Jul-84	ARF/MM	Various bugs, mostly serious.  Removed homemade doprint
 * 01-Aug-84	MM	Fixed recursion bug, remove extra newlines and
 *			leading whitespace from cpp output.
 * 02-Aug-84	MM	Hacked (i.e. optimized) out blank lines and unneeded
 *			whitespace in general.  Cleaned up unget()'s.
 * 03-Aug-84	Keie	Several bug fixes from Ed Keizer, Vrije Universitet.
 *			-- corrected arg. count in -D and pre-defined
 *			macros.  Also, allow \n inside macro actual parameter
 *			lists.
 * 06-Aug-84	MM	If debugging, dump the preset vector at startup.
 * 12-Aug-84	MM/SCK	Some small changes from Sam Kendall
 * 15-Aug-84	Keie/MM	cerror, cwarn, etc. take a single string arg.
 *			cierror, etc. take a single int. arg.
 *			changed LINE_PREFIX slightly so it can be
 *			changed in the makefile.
 * 31-Aug-84	MM	USENET net.sources release.
 *  7-Sep-84	SCH/ado Lint complaints
 * 10-Sep-84	Keie	Char's can't be signed in some implementations
 * 11-Sep-84	ado	Added -C flag, pathological line number fix
 * 13-Sep-84	ado	Added -E flag (does nothing) and "-" file for stdin.
 * 14-Sep-84	MM	Allow # 123 as a synonym for #line 123
 * 19-Sep-84	MM	scanid always reads to token, make sure #line is
 *			written to a new line, even if -C switch given.
 *			Also, cpp - - reads stdin, writes stdout.
 * 03-Oct-84	ado/MM	Several changes to line counting and keepcomments
 *			stuff.  Also a rewritten control() hasher -- much
 *			simpler and no less "perfect". Note also changes
 *			in cpp3.c to fix numeric scanning.
 * 04-Oct-84	MM	Added recognition of macro formal parameters if
 *			they are the only thing in a string, per the
 *			draft standard.
 * 08-Oct-84	MM	One more attack on scannumber
 * 15-Oct-84	MM/ado	Added -N to disable predefined symbols.  Fixed
 *			linecount if COMMENT_INVISIBLE enabled.
 * 22-Oct-84	MM	Don't evaluate the #if/#ifdef argument if
 *			compilation is supressed.  This prevents
 *			unnecessary error messages in sequences such as
 *			    #ifdef FOO		-- undefined
 *			    #if FOO == 10	-- shouldn't print warning
 * 25-Oct-84	MM	Fixed bug in false ifdef supression.  On vms,
 *			#include <foo> should open foo.h -- this duplicates
 *			the behavior of Vax-C
 * 31-Oct-84	ado/MM	Parametized $ in indentifiers.  Added a better
 *			token concatenator and took out the trial
 *			concatenation code.  Also improved #ifdef code
 *			and cleaned up the macro recursion tester.
 *  2-Nov-84	MM/ado	Some bug fixes in token concatenation, also
 *			a variety of minor (uninteresting) hacks.
 *  6-Nov-84	MM	Happy Birthday.  Broke into 4 files and added
 *			#if sizeof (basic_types)
 *  9-Nov-84	MM	Added -S* for pointer type sizes
 * 13-Nov-84	MM	Split cpp1.c, added vms defaulting
 * 23-Nov-84	MM/ado	-E supresses error exit, added CPP_INCLUDE,
 *			fixed strncpy bug.
 *  3-Dec-84	ado/MM	Added OLD_PREPROCESSOR
 *  7-Dec-84	MM	Stuff in Nov 12 Draft Standard
 * 17-Dec-84	george	Fixed problems with recursive macros
 * 17-Dec-84	MM	Yet another attack on #if's (f/t)level removed.
 * 07-Jan-85	ado	Init defines before doing command line options
 *			so -Uunix works.
 * 11-Feb-94    MH      Changed token concatenation (hopefully) to ANSI 
 *                      standard and added token quoting.
 * 26-Nov-94    MH      -P problem of outputting a newline after leading
 *                      white space in order to correct counter if fixed 
 *                      now.  Several other fixes for -P, too.
 */

#include	<stdio.h>
#include	<ctype.h>
#include	"cppdef.h"
#include	"cpp.h"

/*
 * Commonly used global variables:
 * line		is the current input line number.
 * wrongline	is set in many places when the actual output
 *		line is out of sync with the numbering, e.g,
 *		when expanding a macro with an embedded newline.
 *
 * token	holds the last identifier scanned (which might
 *		be a candidate for macro expansion).
 * errors	is the running cpp error counter.
 * infile	is the head of a linked list of input files (extended by
 *		#include and macros being expanded).  infile always points
 *		to the current file/macro.  infile->parent to the includer,
 *		etc.  infile->fd is NULL if this input stream is a macro.
 */
int		line;			/* Current line number		*/
int		wrongline;		/* Force #line to compiler	*/
char		token[IDMAX + 1];	/* Current input token		*/
int		errors;			/* cpp error counter		*/
FILEINFO	*infile = NULL;		/* Current input file		*/
#if DEBUG
int		debug;			/* TRUE if debugging now	*/
#endif
/*
 * This counter is incremented when a macro expansion is initiated.
 * If it exceeds a built-in value, the expansion stops -- this tests
 * for a runaway condition:
 *	#define X Y
 *	#define Y X
 *	X
 * This can be disabled by falsifying rec_recover.  (Nothing does this
 * currently: it is a hook for an eventual invocation flag.)
 */
int		recursion;		/* Infinite recursion counter	*/
int		rec_recover = TRUE;	/* Unwind recursive macros	*/

/*
 * instring is set TRUE when a string is scanned.  It modifies the
 * behavior of the "get next character" routine, causing all characters
 * to be passed to the caller (except <DEF_MAGIC>).  Note especially that
 * comments and \<newline> are not removed from the source.  (This
 * prevents cpp output lines from being arbitrarily long).
 *
 * inmacro is set by #define -- it absorbs comments and converts
 * form-feed and vertical-tab to space, but returns \<newline>
 * to the caller.  Strictly speaking, this is a bug as \<newline>
 * shouldn't delimit tokens, but we'll worry about that some other
 * time -- it is more important to prevent infinitly long output lines.
 *
 * instring and inmarcor are parameters to the get() routine which
 * were made global for speed.
 */
int		instring = FALSE;	/* TRUE if scanning string	*/
int		inmacro = FALSE;	/* TRUE if #defining a macro	*/

/*
 * work[] and workp are used to store one piece of text in a temporay
 * buffer.  To initialize storage, set workp = work.  To store one
 * character, call save(c);  (This will fatally exit if there isn't
 * room.)  To terminate the string, call save(EOS).  Note that
 * the work buffer is used by several subroutines -- be sure your
 * data won't be overwritten.  The extra byte in the allocation is
 * needed for string formal replacement.
 */
char		work[NWORK + 1];	/* Work buffer			*/
char		*workp;			/* Work buffer pointer		*/

/*
 * keepcomments is set TRUE by the -C option.  If TRUE, comments
 * are written directly to the output stream.  This is needed if
 * the output from cpp is to be passed to lint (which uses commands
 * embedded in comments).  cflag contains the permanent state of the
 * -C flag.  keepcomments is always falsified when processing #control
 * commands and when compilation is supressed by a false #if
 *
 * If eflag is set, CPP returns "success" even if non-fatal errors
 * were detected.
 *
 * If nflag is non-zero, no symbols are predefined except __LINE__.
 * __FILE__, and __DATE__.  If nflag > 1, absolutely no symbols
 * are predefined.
 */
int		keepcomments = FALSE;	/* Write out comments flag	*/
int		cflag = FALSE;		/* -C option (keep comments)	*/
int		eflag = FALSE;		/* -E option (never fail)	*/
int		nflag = 0;		/* -N option (no predefines)	*/
int		pflag = 0;		/* -P option (no #line)		*/

/*
 * ifstack[] holds information about nested #if's.  It is always
 * accessed via *ifptr.  The information is as follows:
 *	WAS_COMPILING	state of compiling flag at outer level.
 *	ELSE_SEEN	set TRUE when #else seen to prevent 2nd #else.
 *	TRUE_SEEN	set TRUE when #if or #elif succeeds
 * ifstack[0] holds the compiling flag.  It is TRUE if compilation
 * is currently enabled.  Note that this must be initialized TRUE.
 */
char		ifstack[BLK_NEST] = { TRUE };	/* #if information	*/
char		*ifptr = ifstack;		/* -> current ifstack[] */

/*
 * incdir[] stores the -i directories (and the system-specific
 * #include <...> directories.
 */
char	*incdir[NINCLUDE];		/* -i directories		*/
char	**incend = incdir;		/* -> free space in incdir[]	*/

/*
 * This is the table used to predefine target machine and operating
 * system designators.  It may need hacking for specific circumstances.
 * Note: it is not clear that this is part of the Ansi Standard.
 * The -N option supresses preset definitions.
 */
char	*preset[] = {			/* names defined at cpp start	*/
#ifdef	MACHINE
	MACHINE,
#endif
#ifdef	SYSTEM
	SYSTEM,
#endif
#ifdef	COMPILER
	COMPILER,
#endif
#if	DEBUG
	"decus_cpp",			/* Ourselves!			*/
#endif
	NULL				/* Must be last			*/
};

/*
 * The value of these predefined symbols must be recomputed whenever
 * they are evaluated.  The order must not be changed.
 */
char	*magic[] = {			/* Note: order is important	*/
	"__LINE__",
	"__FILE__",
	NULL				/* Must be last			*/
};

main(argc, argv)
int		argc;
char		*argv[];
{
	register int	i;

#if HOST == SYS_VMS
	argc = getredirection(argc, argv);	/* vms >file and <file	*/
#endif
	initdefines();				/* O.S. specific def's	*/
	i = dooptions(argc, argv);		/* Command line -flags	*/
	switch (i) {
	case 3:
	    /*
	     * Get output file, "-" means use stdout.
	     */
	    if (!streq(argv[2], "-")) {
#if HOST == SYS_VMS
		/*
		 * On vms, reopen stdout with "vanilla rms" attributes.
		 */
		if ((i = creat(argv[2], 0, "rat=cr", "rfm=var")) == -1
		 || dup2(i, fileno(stdout)) == -1) {
#else
		if (freopen(argv[2], "w", stdout) == NULL) {
#endif
		    perror(argv[2]);
		    cerror("Can't open output file `%s'", argv[2]);
		    exit(IO_ERROR);
		}
	    }				/* Continue by opening input	*/
	case 2:				/* One file -> stdin		*/
	    /*
	     * Open input file, "-" means use stdin.
	     */
	    if (!streq(argv[1], "-")) {
		if (freopen(argv[1], "r", stdin) == NULL) {
		    perror(argv[1]);
		    cerror("Can't open input file `%s'", argv[1]);
		    exit(IO_ERROR);
		}
		strcpy(work, argv[1]);	/* Remember input filename	*/
		break;
	    }				/* Else, just get stdin		*/
	case 0:				/* No args?			*/
	case 1:				/* No files, stdin -> stdout	*/
#if HOST == SYS_UNIX
	    work[0] = EOS;		/* Unix can't find stdin name	*/
#else
	    fgetname(stdin, work);	/* Vax-11C, Decus C know name	*/
#endif
	    break;

	default:
	    exit(IO_ERROR);		/* Can't happen			*/
	}
	setincdirs();			/* Setup -I include directories	*/
	addfile(stdin, work);		/* "open" main input file	*/
#if DEBUG
	if (debug > 0)
	    dumpdef("preset #define symbols");
#endif
	cppmain();			/* Process main file		*/
	if ((i = (ifptr - &ifstack[0])) != 0) {
#if OLD_PREPROCESSOR
	    ciwarn("Inside #ifdef block at end of input, depth = %d", i);
#else
	    cierror("Inside #ifdef block at end of input, depth = %d", i);
#endif
	}
	fclose(stdout);
	if (errors > 0) {
	    fprintf(stderr, (errors == 1)
		? "%d error in preprocessor\n"
		: "%d errors in preprocessor\n", errors);
	    if (!eflag)
		exit(IO_ERROR);
	}
	exit(IO_NORMAL);		/* No errors or -E option set	*/
}

FILE_LOCAL
cppmain()
/*
 * Main process for cpp -- copies tokens from the current input
 * stream (main file, include file, or a macro) to the output
 * file.
 */
{
	register int		c;		/* Current character	*/
	register int		counter;	/* newlines and spaces	*/
	extern int		output();	/* Output one character	*/

	/*
	 * Explicitly output a #line at the start of cpp output so
	 * that lint (etc.) knows the name of the original source
	 * file.  If we don't do this explicitly, we may get
	 * the name of the first #include file instead.
	 * We also seem to need a blank line following that first #line.
	 */
	sharp();
	output('\n');
	/*
	 * This loop is started "from the top" at the beginning of each line
	 * wrongline is set TRUE in many places if it is necessary to write
	 * a #line record.  (But we don't write them when expanding macros.)
	 *
	 * The counter variable has two different uses:  at
	 * the start of a line, it counts the number of blank lines that
	 * have been skipped over.  These are then either output via
	 * #line records or by outputting explicit blank lines.
 	 * When expanding tokens within a line, the counter remembers
	 * whether a blank/tab has been output.  These are dropped
	 * at the end of the line, and replaced by a single blank
	 * within lines.
	 */
	for (;;) {
	    counter = 0;			/* Count empty lines	*/
	    for (;;) {				/* For each line, ...	*/
		while (type[(c = get())] == SPA)/* Skip leading blanks	*/
		    if (pflag && compiling)	/* in this line.	*/
			output(c);
		if (c == '\n')			/* If line's all blank,	*/
		    if (pflag)			/* Do nothing now	*/
			output('\n');
		    else ++counter;
		else if (c == '#') {		/* Is 1st non-space '#'	*/
		    keepcomments = FALSE;	/* Don't pass comments	*/
		    counter = control(counter);	/* Yes, do a #command	*/
		    if (pflag)			/* Output missing \n    */
			while (counter>0) { output('\n'); --counter; }
		    keepcomments = (cflag && compiling);
		}
		else if (c == EOF_CHAR)		/* At end of file?	*/
		    break;
		else if (!compiling) {		/* #ifdef false?	*/
		    skipnl();			/* Skip to newline	*/
		    if (pflag) 
			output('\n');		/* Output blank line    */
		    else counter++;		/* Count it, too.	*/
		}
		else {
		    break;			/* Actual token		*/
		}
	    }
	    if (c == EOF_CHAR)			/* Exit process at	*/
		break;				/* End of file		*/
	    /*
	     * If the loop didn't terminate because of end of file, we
	     * know there is a token to compile.  First, clean up after
	     * absorbing newlines.  counter has the number we skipped.
	     */
	    if ((wrongline && infile->fp != NULL) || counter > 4)
		sharp();			/* Output # line number	*/
	    else {				/* If just a few, stuff	*/
		while (--counter >= 0)		/* them out ourselves	*/
		    output('\n');
	    }
	    /*
	     * Process each token on this line.
	     */
	    unget();				/* Reread the char.	*/
	    for (;;) {				/* For the whole line,	*/
		do {				/* Token concat. loop	*/
		    for (counter = 0; (type[(c = get())] == SPA);) {
			if (pflag) output(c);
			else {
#if COMMENT_INVISIBLE
			    if (c != COM_SEP)
			        counter++;
#else
			    counter++;		/* Skip over blanks	*/
#endif
			}
		    }
		    if (c == EOF_CHAR || c == '\n')
			goto end_line;		/* Exit line loop	*/
		    else if (counter > 0)	/* If we got any spaces	*/
			output(' ');		/* Output one space	*/
		    c = macroid(c);		/* Grab the token	*/
		} while (type[c] == LET && catenate());
		if (c == EOF_CHAR || c == '\n')	/* From macro exp error	*/
		    goto end_line;		/* Exit line loop	*/
		switch (type[c]) {
		case LET:
		    output(*token); fputs(token+1, stdout);	/* Quite ordinary token	*/
		    break;


		case DIG:			/* Output a number	*/
		case DOT:			/* Dot may begin floats	*/
		    scannumber(c, output);
		    break;

		case QUO:			/* char or string const	*/
		    scanstring(c, output);	/* Copy it to output	*/
		    break;

		default:			/* Some other character	*/
		    output(c);			/* Just output it	*/
		    break;
		}				/* Switch ends		*/
	    }					/* Line for loop	*/
end_line:   if (c == '\n') {			/* Compiling at EOL?	*/
		output('\n');			/* Output newline, if	*/
		if (infile->fp == NULL)		/* Expanding a macro,	*/
		    wrongline = TRUE;		/* Output # line later	*/
	    }
	}					/* Continue until EOF	*/
}

int join=0;

output(c)
int		c;
/*
 * Output one character to stdout -- output() is passed as an
 * argument to scanstring()
 */
{
 	static quote_quote=0;
 	
	if (c == DBL_QUOTE)
	    { if (join) { putchar('\\'); join=0; } putchar('"'); quote_quote=!quote_quote; }
	else if (c == '"')
	    { if (join) { putchar('\\'); join=0; } if (quote_quote) putchar('\\'); putchar('"'); }
	else if (c == '\\')
	    { if (join) { putchar('\\'); putchar('\\'); join=0; } else join=1; }
	else if (c=='\n')
	    { if (join==0) putchar('\n'); else wrongline=1; join=0; }
        else
#if COMMENT_INVISIBLE
	if (c != TOK_SEP && c != COM_SEP)
#else
	if (c != TOK_SEP)
#endif
	    { if (join) putchar('\\'); join=0; putchar(c); }
}

static char	*sharpfilename = NULL;

FILE_LOCAL
sharp()
/*
 * Output a line number line.
 */
{
	register char		*name;

	if (keepcomments)			/* Make sure # comes on	*/
	    putchar('\n');			/* a fresh, new line.	*/
	if (pflag == 0) {
	    printf("#%s %d", LINE_PREFIX, line);
	    if (infile->fp != NULL) {
	        name = (infile->progname != NULL)
		    ? infile->progname : infile->filename;
	        if (sharpfilename == NULL
	        || sharpfilename != NULL && !streq(name, sharpfilename)) {
		    if (sharpfilename != NULL)
		        free(sharpfilename);
		    sharpfilename = savestring(name);
		    output(' '); output('"'); fputs(name,stdout); output('"');
	        }
	    }
	    output('\n');
	}
	wrongline = FALSE;
}
