/*


 Copyright (C) 1990 Texas Instruments Incorporated.

 Permission is granted to any individual or institution to use, copy, modify,
 and distribute this software, provided that this complete copyright and
 permission notice is maintained, intact, in all copies and supporting
 documentation.

 Texas Instruments Incorporated provides this software "as is" without
 express or implied warranty.


 *				C P P 3 . C
 *
 *		    File open and command line options
 *
 * Edit history
 * 13-Nov-84	MM	Split from cpp1.c
 * 21-Oct-85	rms	Make -g command arg not cause an error.
 * 24-Sep-89    AFM     OS2, XENIX and AIX support.
 * 19-Jan-90    DKM     MVS support.
 * 18-May-90    MBN     Conditional compilation for COOL to get "clean" cpp
 * 16-Jun-91    GPD     Added support for Sun -Y option
 * 			Added support for Sun -undef option
 * 01-Jul-91	GPD	Add __COOL__ as a predefined symbol.
 * 07-Jul-91	GPD	Fix #line nesting. Force sharp() before processing
 *			new file if wrongline.
 */


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

/* The type returned from time is double on MVS, long everywhere else.
 * MVS defines this type as time_t.   Add non MVS definition here.
 */
#if HOST != SYS_MVS
#define  time_t long
#endif

#if DEBUG 
#if (HOST == SYS_VMS || HOST == SYS_UNIX || HOST == SYS_XENIX || HOST == SYS_OS2 || HOST == SYS_AIX)
#include	<signal.h>
extern int	abort();		/* For debugging		*/
#endif
#endif

extern void expand_line();
extern void expand_file();

#ifdef COOL
extern void define_package();       /* Define a new symbol package */
#endif

int
openfile(filename)
char		*filename;
/*
 * Open a file, add it to the linked list of open files.
 * This is called only from doinclude().
 */
{
	register FILE		*fp;

	if ((fp = fopen(filename, "r")) == NULL) {
#if DEBUG
	    perror(filename);
#endif
	    return (FALSE);
	}
#if DEBUG
	if (debug)
	    fprintf(stderr, "Reading from \"%s\"\n", filename);
#endif
	if (wrongline)
	  sharp();
	addfile(fp, filename);
	return (TRUE);
}

addfile(fp, filename)
FILE		*fp;			/* Open file pointer		*/
char		*filename;		/* Name of the file		*/
/*
 * Initialize tables for this open file.  This is called from openfile()
 * above (for #include files), and from the entry to cpp to open the main
 * input file.  It calls a common routine, getfile() to build the FILEINFO
 * structure which is used to read characters.  (getfile() is also called
 * to setup a macro replacement.)
 */
{
	register FILEINFO	*file;
	extern FILEINFO		*getfile();

	file = getfile(NBUFF, filename);
	file->fp = fp;			/* Better remember FILE *	*/
	file->buffer[0] = EOS;		/* Initialize for first read	*/
	line = 1;			/* Working on line 1 now	*/
	wrongline = TRUE;		/* Force out initial #line	*/
}

setincdirs()
/*
 * Append system-specific directories to the include directory list.
 * Called only when cpp is started.
 */
{

#ifdef	CPP_INCLUDE
	*incend++ = CPP_INCLUDE;
#define	IS_INCLUDE	1
#else
#define	IS_INCLUDE	0
#endif

#if (HOST == SYS_UNIX || HOST == SYS_XENIX || HOST == SYS_AIX)
	if (yflag && altincl)
	    *incend++ = altincl;
	else
	    *incend++ = "/usr/include";
#define	MAXINCLUDE	(NINCLUDE - 1 - IS_INCLUDE)
#endif

#if HOST == SYS_OS2
	*incend++ = "\\ibmc2\\include";
#define	MAXINCLUDE	(NINCLUDE - 1 - IS_INCLUDE)
#endif

#if HOST == SYS_MVS
/* MVS will not use the include list via the -I option.  Instead
 * logical ddnames are allocated to a concatenation of include
 * libraries.  Specifying -I for mvs will be an error.
 */
#define	MAXINCLUDE	0
#endif

#if HOST == SYS_VMS
	extern char	*getenv();

	if (getenv("C$LIBRARY") != NULL)
	    *incend++ = "C$LIBRARY:";
	*incend++ = "SYS$LIBRARY:";
#define	MAXINCLUDE	(NINCLUDE - 2 - IS_INCLUDE)
#endif

#if HOST == SYS_RSX
	extern int	$$rsts;			/* TRUE on RSTS/E	*/
	extern int	$$pos;			/* TRUE on PRO-350 P/OS	*/
	extern int	$$vms;			/* TRUE on VMS compat.	*/

	if ($$pos) {				/* P/OS?		*/
	    *incend++ = "SY:[ZZDECUSC]";	/* C #includes		*/
	    *incend++ = "LB:[1,5]";		/* RSX library		*/
	}
	else if ($$rsts) {			/* RSTS/E?		*/
	    *incend++ = "SY:@";			/* User-defined account	*/
	    *incend++ = "C:";			/* Decus-C library	*/
	    *incend++ = "LB:[1,1]";		/* RSX library		*/
	}
	else if ($$vms) {			/* VMS compatibility?	*/
	    *incend++ = "C:";
	}
	else {					/* Plain old RSX/IAS	*/
	    *incend++ = "LB:[1,1]";
	}
#define	MAXINCLUDE	(NINCLUDE - 3 - IS_INCLUDE)
#endif

#if HOST == SYS_RT11
	extern int	$$rsts;			/* RSTS/E emulation?	*/

	if ($$rsts)
	    *incend++ = "SY:@";			/* User-defined account	*/
	*incend++ = "C:";			/* Decus-C library disk	*/
	*incend++ = "SY:";			/* System (boot) disk	*/
#define	MAXINCLUDE	(NINCLUDE - 3 - IS_INCLUDE)
#endif
}

int
dooptions(argc, argv)
int		argc;
char		*argv[];
/*
 * dooptions is called to process command line arguments (-Detc).
 * It is called only at cpp startup.
 */
{
	register char		*ap;
	register DEFBUF		*dp;
	register int		c;
	int			i, j;
	char			*arg;
	SIZES			*sizp;		/* For -S		*/
	int			size;		/* For -S		*/
	int			isdatum;	/* FALSE for -S*	*/
	int			endtest;	/* For -S		*/

	for (i = j = 1; i < argc; i++) {
	    arg = ap = argv[i];
	    if (*ap++ != '-' || *ap == EOS)
		argv[j++] = argv[i];
	    else {
		c = *ap++;			/* Option byte		*/
		if (islower(c))			/* Normalize case	*/
		    c = toupper(c);
		switch (c) {			/* Command character	*/
		case 'C':			/* Keep comments	*/
		    cflag = TRUE;
		    keepcomments = TRUE;
		    break;

		case 'D':			/* Define symbol	*/
#if (HOST != SYS_UNIX && HOST != SYS_XENIX && HOST != SYS_OS2 && HOST != SYS_MVS && HOST != SYS_AIX)
		    zap_uc(ap);			/* Force define to U.C.	*/
#endif
		    /*
		     * If the option is just "-Dfoo", make it -Dfoo=1
		     */
		    while (*ap != EOS && *ap != '=')
			ap++;
		    if (*ap == EOS)
			ap = "1";
		    else
			*ap++ = EOS;
		    /*
		     * Now, save the word and its definition.
		     */
		    dp = defendel(argv[i] + 2, FALSE);
		    dp->repl = savestring(ap);
		    dp->nargs = DEF_NOARGS;
		    break;

		case 'E':			/* Ignore non-fatal	*/
		    eflag = TRUE;		/* errors.		*/
		    break;

		case 'G':			/* Cmpiler's debug switch */
		    break;

		case 'B':			/* Sun's ignore // comments */
		    break;

		case 'I':			/* Include directory	*/
		    if (incend >= &incdir[MAXINCLUDE])
			cfatal("Too many include directories", NULLST);
		    *incend++ = ap;
		    break;

		case 'N':			/* No predefineds	*/
		    nflag++;			/* Repeat to undefine	*/
		    break;			/* __LINE__, etc.	*/

		case 'S':
		    sizp = size_table;
		    if (isdatum = (*ap != '*'))	/* If it's just -S,	*/
			endtest = T_FPTR;	/* Stop here		*/
		    else {			/* But if it's -S*	*/
			ap++;			/* Step over '*'	*/
			endtest = 0;		/* Stop at end marker	*/
		    }
		    while (sizp->bits != endtest && *ap != EOS) {
			if (!isdigit(*ap)) {	/* Skip to next digit	*/
			    ap++;
			    continue;
			}
			size = 0;		/* Compile the value	*/
			while (isdigit(*ap)) {
			    size *= 10;
			    size += (*ap++ - '0');
			}
			if (isdatum)
			    sizp->size = size;	/* Datum size		*/
			else
			    sizp->psize = size;	/* Pointer size		*/
			sizp++;
		    }
		    if (sizp->bits != endtest)
			cwarn("-S, too few values specified in %s", argv[i]);
		    else if (*ap != EOS)
			cwarn("-S, too many values, \"%s\" unused", ap);
		    break;

		case 'X':			/* Debug		*/
		    debug = (isdigit(*ap)) ? atoi(ap) : 1;
#if DEBUG
#if (HOST == SYS_VMS || HOST == SYS_UNIX || HOST == SYS_XENIX || HOST == SYS_OS2 || HOST == SYS_AIX)
		    signal(SIGINT, abort);	/* Trap "interrupt"	*/
#endif
#endif
		    fprintf(stderr, "Debug set to %d\n", debug);
		    break;

		case 'Y':
		    altincl = ap;
		    yflag = TRUE;
		    break;

		case 'U':			/* Undefine symbol	*/
#if (HOST != SYS_UNIX && HOST != SYS_XENIX && HOST != SYS_OS2 && HOST != SYS_MVS && HOST != SYS_AIX)
		    zap_uc(ap);
#endif
		    if (strcmp(ap-1,"undef") == 0)
		    {
			if (nflag == 0)
			    nflag = 1;
			break;
		    }
		    if (defendel(ap, TRUE) == NULL)
			cwarn("\"%s\" wasn't defined", ap);
		    break;

		default:			/* What is this one?	*/
		    cwarn("Unknown option \"%s\"", arg);
		    fprintf(stderr, "The following options are valid:\n\
  -C\t\t\tWrite source file comments to output\n\
  -Dsymbol=value\tDefine a symbol with the given (optional) value\n\
  -Idirectory\t\tAdd a directory to the #include search list\n\
  -N\t\t\tDon't predefine target-specific names\n\
  -Stext\t\tSpecify sizes for #if sizeof\n\
  -Usymbol\t\tUndefine symbol\n\
  -Xvalue\t\tSet internal debug flag\n");
		    break;
		}			/* Switch on all options	*/
	    }				/* If it's a -option		*/
	}				/* For all arguments		*/
	if (j > 3) {
	    cerror(
		"Too many file arguments.  Usage: cpp [input [output]]",
		NULLST);
	}
	return (j);			/* Return new argc		*/
}

#if (HOST != SYS_UNIX && HOST != SYS_XENIX && HOST != SYS_OS2 && HOST != SYS_MVS && HOST != SYS_AIX)
FILE_LOCAL
zap_uc(ap)
register char	*ap;
/*
 * Dec operating systems mangle upper-lower case in command lines.
 * This routine forces the -D and -U arguments to uppercase.
 * It is called only on cpp startup by dooptions().
 */
{
	while (*ap != EOS) {
	    /*
	     * Don't use islower() here so it works with Multinational
	     */
	    if (*ap >= 'a' && *ap <= 'z')
		*ap = toupper(*ap);
	    ap++;
	}
}
#endif

define_builtin (name, function, replacement)
     char *name;
     void (*function)();
     char *replacement;
{
  DEFBUF *dp;
  dp = defendel(name, FALSE);
  dp->repl = replacement;
  dp->expander = function;
  dp->nargs = (function == NULL) ? DEF_NOARGS : DEF_BUILTIN;
}

static char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
			   "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",};

initdefines()
/*
 * Initialize the built-in #define's.  There are two flavors:
 * 	#xomdefine decus	1		(static definitions)
 *	#define	__FILE__ ??		(dynamic, evaluated by magic)
 * Called only on cpp startup.
 *
 * Note: the built-in static definitions are supressed by the -N option.
 * __LINE__, __FILE__, and __DATE__ are always present.
 */
{
	register char		**pp;
	register char		*tp;
	register DEFBUF		*dp;
	struct tm		*tvec;
	/*
	 * Predefine the built-in symbols.  Allow the
	 * implementor to pre-define a symbol as "" to
	 * eliminate it.
	 */
	if (nflag == 0) {
	    for (pp = preset; *pp != NULL; pp++) {
		if (*pp[0] != EOS) {
		    dp = defendel(*pp, FALSE);
		    dp->repl = savestring("1");
		    dp->nargs = DEF_NOARGS;
		}
	    }
	}

	if (nflag < 2) {

	  define_builtin("__LINE__", expand_line, "");
	  define_builtin("__FILE__", expand_file, "");
	  define_builtin("__STDC__", NULL, "1");
#ifdef COOL
	  define_builtin("__COOL__", NULL, "1");
#endif
#if OK_DATE
	  /*
	   * Define __DATE__ as today's date.
	   */
	  { time_t clock = time(NULL);
	    tvec = localtime(&clock);}
	  tp = getmem(12);
	  /* strftime(tp, 12, "%b %d %Y", tp); */
	  sprintf(tp, "%s %02d %04d",
		  months[tvec->tm_mon],
		  tvec->tm_mday,
		  tvec->tm_year + 1900);
	  define_builtin( "__DATE__", NULL, tp);

	  tp = getmem(9);
	  /* strftime(tp, 9, "%H:%M:%S", tp); */
	  sprintf(tp, "%02d:%02d:%02d",
		  tvec->tm_hour,
		  tvec->tm_min,
		  tvec->tm_sec);
	  define_builtin( "__TIME__", NULL, tp);
#endif
#ifdef COOL
	  define_builtin("DEFPACKAGE", define_package, "");
#endif
	}
}

#if HOST == SYS_VMS
/*
 * getredirection() is intended to aid in porting C programs
 * to VMS (Vax-11 C) which does not support '>' and '<'
 * I/O redirection.  With suitable modification, it may
 * useful for other portability problems as well.
 */

int
getredirection(argc, argv)
int		argc;
char		**argv;
/*
 * Process vms redirection arg's.  Exit if any error is seen.
 * If getredirection() processes an argument, it is erased
 * from the vector.  getredirection() returns a new argc value.
 *
 * Warning: do not try to simplify the code for vms.  The code
 * presupposes that getredirection() is called before any data is
 * read from stdin or written to stdout.
 *
 * Normal usage is as follows:
 *
 *	main(argc, argv)
 *	int		argc;
 *	char		*argv[];
 *	{
 *		argc = getredirection(argc, argv);
 *	}
 */
{
	register char		*ap;	/* Argument pointer	*/
	int			i;	/* argv[] index		*/
	int			j;	/* Output index		*/
	int			file;	/* File_descriptor 	*/
	extern int		errno;	/* Last vms i/o error 	*/

	for (j = i = 1; i < argc; i++) {   /* Do all arguments	*/
	    switch (*(ap = argv[i])) {
	    case '<':			/* <file		*/
		if (freopen(++ap, "r", stdin) == NULL) {
		    perror(ap);		/* Can't find file	*/
		    exit(errno);	/* Is a fatal error	*/
		}
		break;

	    case '>':			/* >file or >>file	*/
		if (*++ap == '>') {	/* >>file		*/
		    /*
		     * If the file exists, and is writable by us,
		     * call freopen to append to the file (using the
		     * file's current attributes).  Otherwise, create
		     * a new file with "vanilla" attributes as if the
		     * argument was given as ">filename".
		     * access(name, 2) returns zero if we can write on
		     * the specified file.
		     */
		    if (access(++ap, 2) == 0) {
			if (freopen(ap, "a", stdout) != NULL)
			    break;	/* Exit case statement	*/
			perror(ap);	/* Error, can't append	*/
			exit(errno);	/* After access test	*/
		    }			/* If file accessable	*/
		}
		/*
		 * On vms, we want to create the file using "standard"
		 * record attributes.  creat(...) creates the file
		 * using the caller's default protection mask and
		 * "variable length, implied carriage return"
		 * attributes. dup2() associates the file with stdout.
		 */
		if ((file = creat(ap, 0, "rat=cr", "rfm=var")) == -1
		 || dup2(file, fileno(stdout)) == -1) {
		    perror(ap);		/* Can't create file	*/
		    exit(errno);	/* is a fatal error	*/
		}			/* If '>' creation	*/
		break;			/* Exit case test	*/

	    default:
		argv[j++] = ap;		/* Not a redirector	*/
		break;			/* Exit case test	*/
	    }
	}				/* For all arguments	*/
	argv[j] = NULL;			/* Terminate argv[]	*/
	return (j);			/* Return new argc	*/
}
#endif


