/**********************************************************************/
/*                                                                    */
/*	CRISP - Programmable editor                                   */
/*	===========================                                   */
/*                                                                    */
/*  File:          crmain.c                                           */
/*  Author:        P. D. Fox                                          */
/*  Created:       12 Jun 1990                     		      */
/*                                                                    */
/*  Copyright (c) 1990 Paul Fox                                       */
/*                All Rights Reserved.                                */
/*                                                                    */
/*                                                                    */
/*--------------------------------------------------------------------*/
/*  Description:  Command line decoder for crunch.                    */
/*                                                                    */
/**********************************************************************/

/*static char sccs_id[] = "%Z% %M% %R%.%L%";*/


# include	"crunch.h"
# include	<sys/types.h>
# include	<sys/stat.h>

# define	CC_BUF	1024
# define	TMPFILE	"/tmp/chXXXXXX"

static char *version = "v1.4";

extern FILE *lex_fp;
extern int line_no;
extern int generate_ascii;

FILE	*errfp;			/* Error file.				*/
FILE	*ofp;			/* Output file pointer.			*/
int	crunch_lang = TRUE;	/* Set to FALSE to parse 'C'.		*/
int	sym_dump = FALSE;	/* Dump symbol table.			*/
int	flush_flag = FALSE;	/* TRUE for debugging so we can see output */
int	c_flag = FALSE;		/* TRUE to leave .m files intact.	*/
int	g_flag = FALSE;		/* If TRUE, compile with debug trace.	*/
char	*filename;		/* Name of current source file.		*/
int	watch_flag = FALSE;	/* TRUE if we wanted to see command 	*/
				/* line passed to 'cm'.		    	*/
char	*output_file;
char	cm_buf[BUFSIZ];		/* Buffer for doing a system() on cm. 	*/
char	path[BUFSIZ];		/* Directory where executable came from */
int	quiet_flag = FALSE;	/* Dont print filenames during compilation.*/
int	no_warnings = FALSE;
int	make_flag = FALSE;	/* If set (-m) only compile if out of date. */
int	n_flag = FALSE;		/* Like 'make -n'.			*/
int	verbose = FALSE;
long	crunch_mtime;		/* Modification time of ourselves.	*/
				/* If crunch is newer than .cm file we  */
				/* need to make.			*/
int	abort_flag = FALSE;	/* Cause core dump on exit.		*/
				
# if defined(MSDOS)
#	define	OPEN_MODE	"wb"
	char	cc_buf[CC_BUF] = {"cl -nologo -E -DCRUNCH "};
	char	*space_arg = " > ";
# else
#	define	OPEN_MODE	"w"
#	if CR_AIX_3

	/* Under AIX, /lib/cpp produces _many_ bogus warning messages	*/
	/* for hex constants longer than 2 characters when it sees	*/
	/* something like "\x1Ba".  Using "/bin/cc -w -E" instead	*/
	/* avoids these warnings (though it may suppress a warning you	*/
	/* really would like to see).					*/

		char	cc_buf[CC_BUF] = { "/bin/cc -w -E -DCRUNCH "};
#	else
		char	cc_buf[CC_BUF] = {"/lib/cpp -DCRUNCH "};
#	endif
# if CR_ACORN || CR_AIX_3
	char	*space_arg = " > ";
# else
	char	*space_arg = " ";
# endif
# endif

char	*cmd_file;
char	temp_file[64];
stat_t	stats;
int	struct_flag = FALSE;

struct extra_sw {
	char	*name;
	int	*flag;
	char	*help;
	} extra_sw[] = {
	{"struct",	&struct_flag,	"Pretty print structure offsets."},
	{NULL,		NULL,		NULL},
	};
/**********************************************************************/
/*   Prototypes.						      */
/**********************************************************************/
int	do_switches PROTO((int, char **));
void	dump_syms PROTO((void));
int	unlink();
void	map_sw PROTO((char *));
void	find_executable PROTO((char *));
int	check_path PROTO((char *));
char	*strrchr();
void	usage PROTO((void));
char *getenv();
char	*mktemp();
int	crunch_file PROTO((char *));
# if defined(MSDOS)
void	backslash PROTO((char *));
# endif

int
main(argc, argv)
int	argc;
char **argv;
{	int	arg_index;
	int	exit_status = 0;
	char	*cp;

	errfp = stderr;
	find_executable(argv[0]);
	cp = getenv("CRUNCHCPP");
	if (cp) {
		strcpy(cc_buf, cp);
		strcat(cc_buf, " ");
		}
	arg_index = do_switches(argc, argv);
	if (struct_flag)
		sym_dump = TRUE;
	cmd_file = cc_buf + strlen(cc_buf);
	if (!c_flag)
		generate_ascii = FALSE;	
	/***********************************************/
	/*   If  no  output  file  specified, default  */
	/*   it to the current directory.	       */
	/***********************************************/
	if (output_file == NULL || output_file[0] == 0)
		output_file = ".";

	if (argc - arg_index > 1 && !quiet_flag)
		verbose = TRUE;
	for (; arg_index < argc; arg_index++) {
		exit_status |= crunch_file(argv[arg_index]);
		}
	/***********************************************/
	/*   Cause  a  core dump if user wants one on  */
	/*   exit -- used for debugging.	       */
	/***********************************************/
	if (abort_flag)
		abort();
	exit(exit_status);
	return 0;
}

/**********************************************************************/
/*   Process a file on the command line.			      */
/**********************************************************************/
int
crunch_file(name)
char	*name;
{
	int	len;
	extern int fatals;
	int	exit_status = 0;
	int	unlink_flag;
	int	doit;
	int	ext_len = 0;
	char	pass2_file[BUFSIZ];
	struct stat sbuf, sbuf1;

	fatals = 0;
	filename = name;
# if	defined(MSDOS)
	backslash(filename);
# endif
	len = strlen(filename);
	strcpy(cmd_file, filename);
	strcpy(pass2_file, filename);
	unlink_flag = FALSE;
	doit = FALSE;
	if (len > 2 && strcmp(filename + len - 2, ".c") == 0) {
		doit = TRUE;
		crunch_lang = FALSE;
		ext_len = 2;
		}
	if (len > 3 && strcmp(filename + len - 3, ".cr") == 0) {
		doit = TRUE;
		crunch_lang = TRUE;
		ext_len = 3;
		}
	if (doit) {
		unlink_flag = TRUE;
		if (c_flag)
			strcpy(pass2_file + len - ext_len, ".m");
		else if (output_file && filename[0] != '/') {
			strcpy(pass2_file, output_file);
			if (stat(pass2_file, &sbuf) >= 0 && (sbuf.st_mode & S_IFMT) == S_IFDIR) {
				strcat(pass2_file, "/");
				strcat(pass2_file, filename);
				strcpy(pass2_file + strlen(pass2_file) - ext_len, ".cm");
				}
			}
		else
			strcpy(pass2_file + len - ext_len, ".cm");
			
		/***********************************************/
		/*   If  make  flag  set then only compile if  */
		/*   out of date.			       */
		/***********************************************/
		if (make_flag && stat(pass2_file, &sbuf) >= 0 && 
		    stat(name, &sbuf1) >= 0 &&
		    sbuf.st_mtime >= sbuf1.st_mtime && 
		    sbuf.st_mtime >= crunch_mtime) {
		    	return 0;
			}
		/***********************************************/
		/*   If  -n  flag specified then tell us what  */
		/*   would have happened.		       */
		/***********************************************/
		if (n_flag) {
			printf("%s out of date.\n", name);
			return 0;
			}
		if (verbose)
			printf("%s\n", name);
		strcpy(temp_file, TMPFILE);
		strcat(cc_buf, space_arg);
		strcat(cc_buf, mktemp(temp_file));
		if (watch_flag)
			printf("%s\n", cc_buf);
		if (system(cc_buf) != 0)
			exit(1);
		if (flush_flag)
			ofp = stdout;
		else if ((ofp = fopen(pass2_file, OPEN_MODE)) == NULL) {
			perror(pass2_file);
			exit(1);
			}
		lex_fp = fopen(temp_file, "r");
		if (lex_fp == NULL) {
			perror(filename);
			exit(1);
			}
		init_parser();
		
		yyparse();
		if (fatals)
			exit_status = 1;

		/***********************************************/
		/*   Now   we   need  to  generate  code  for  */
		/*   function  main  --  this'll  handle  any  */
		/*   global variables declared as well.	       */
		/***********************************************/
		if (sym_dump)
			dump_syms();
		else {
			extern node_t *main_tree;
			compile_main(main_tree);
			}
		fclose(lex_fp);
		unlink(temp_file);
		if (!flush_flag)
			fclose(ofp);
		if (exit_status)
			unlink(pass2_file);
		}
	return exit_status;
}
# if defined(MSDOS)
void
backslash(filename)
char *filename;
{
	while (*filename) {
		if (*filename == '\\')
			*filename = '/';
		filename++;
		}
}
# endif
void
usage()
{
	fprintf(errfp, "Usage: crunch [-#acfgmnpqSW] [-Dvar[=value]] [-Uvar] [-Ipath]\n\t\t[-o output_file] [+struct] file-name ...\n\n");
	
	fprintf(errfp, "	-a	Create core file on exit (for debugging).\n");
	fprintf(errfp, "	-c	Leave .m file.\n");
	fprintf(errfp, "	-Dvar	Define var as in #define.\n");
	fprintf(errfp, "	-f	Flush output for debugging.\n");
	fprintf(errfp, "	-Ipath	Add 'path' to the #include search path.\n");
	fprintf(errfp, "	-g	Compile with debug on.\n");
	fprintf(errfp, "	-m 	Compile only if out of data (make option)\n");
	fprintf(errfp, "	-n 	Don't do anything but tell us what you would do.\n");
	fprintf(errfp, "	-o file	Name of compiled output file.\n");
	fprintf(errfp, "	-p cpp	Specify name of C pre-processor to use.\n");
	fprintf(errfp, "	-q	Dont print filenames during compilation.\n");
	fprintf(errfp, "	-S    	Dump symbol table. (Used for getting structure offsets).\n");
	fprintf(errfp, "	-Uvar	Undefine var as in #undef.\n");
	fprintf(errfp, "	-V	Print version of crunch.\n");
	fprintf(errfp, "	-W	Turn off warning messages.\n");
	fprintf(errfp, "	-#	Watch compiler passes.\n");
	fprintf(errfp, "	+struct	Pretty print structure offsets for easy parsing.\n");
	exit(1);
}
int
do_switches(ac, av)
int	ac;
char	**av;
{	int	c;
	extern int yydebug;
	char	*cp;

	for (c = 1; c < ac; c++) {
		if (av[c][0] == '+') {
			map_sw(av[c]+1);
			continue;
			}
		if (av[c][0] != '-')
			break;
		cp = av[c] + 1;
		while (*cp) {
			switch (*cp++) {
			  case 'a':
			  	abort_flag = TRUE;
				break;
			  case 'U':
			  case 'D':
			  case 'I':
				strcat(cc_buf, "-");
			  	strcat(cc_buf, cp-1);
				strcat(cc_buf, " ");
				cp = "";
				break;
			  case 'c':	c_flag = TRUE; break;
			  case 'd':	yydebug = TRUE;	break;
			  case 'e':
			  	errfp = fopen(av[++c], "w");
				if (errfp == NULL) {
					fprintf(stderr, "Cannot open error file.\n");
					exit(1);
					}
				break;
			  case 'f':	flush_flag = TRUE;	break;
			  case 'g':	g_flag = TRUE;	break;
			  case 'p':	
			  	if (c+1 >= ac)
					usage();
				strcpy(cc_buf, av[++c]);
		  		strcat(cc_buf, " ");
				break;
			  case 'm':
			  	make_flag = TRUE;
				break;
			  case 'n':
			  	n_flag = TRUE;
				break;
			  case 'o':
			  	output_file = av[++c];
				cp = "";
			  	break;
			  case 'S':
			  	sym_dump = TRUE;
				break;
			  case '#':
			  		watch_flag = TRUE;
			  		break;
			  case 'q':
			  	quiet_flag = TRUE;
				break;
			  case 'V': {
			  	extern char *version;
			  	printf("crunch version %s\n", version);
				exit(0);
				}
			  case 'W':
			  	no_warnings = TRUE;
				break;
			  default:
			  	usage();
			  }
			}
		}
	
	return c;
}
# if !CR_ACORN && !CR_AIX_3
void
bcopy(a,b,c)
char *a,*b;
int	c;
{
	memcpy(b,a,c);
}
# endif
void
map_sw(str)
char *str;
{	struct extra_sw *sp = extra_sw;

	while (sp->name) {
		if (strcmp(sp->name, str) == 0) {
			*sp->flag = TRUE;
			return;
			}
		sp++;
		}
	usage();
}

/**********************************************************************/
/*   The  following  function  is  used  to  try  and find out which  */
/*   directory  the  crunch  executable was taken from. This is done  */
/*   so  that  we  can  execute  the  'cm'  compiler  from  the same  */
/*   directory.  This  facility was added because of a name conflict  */
/*   with  Sun  OpenWindows,  which  has  a utility called 'cm' (the  */
/*   calendar  manager),  and  its not convenient to change the name  */
/*   of  either  of  these  programs. So to avoid the name conflict,  */
/*   we  try  and  be  clever  and  execute the 'cm' binary from the  */
/*   same directory where crunch comes from.			      */
/**********************************************************************/
void
find_executable(name)
char	*name;
{	char	*cp;
	struct stat	sbuf;
	/***********************************************/
	/*   If  we  have an absolute directory name,  */
	/*   try that.				       */
	/***********************************************/
	if (strchr(name, '/')) {
		strcpy(path, name);
		cp = strrchr(path, '/');
		*cp = NULL;
		if (stat(name, &sbuf) >= 0)
			crunch_mtime = sbuf.st_mtime;
		return;
		}
	
	/***********************************************/
	/*   OK,   so   now   walk   down   the  PATH  */
	/*   environment variable.		       */
	/***********************************************/
	if (check_path(getenv("PATH")))
		return;
	check_path(getenv("BPATH"));
}
int
check_path(cp)
char	*cp;
{
	char	buf[BUFSIZ];
	char	buf1[BUFSIZ];
	struct stat sbuf;

	if (cp == NULL)
		return FALSE;
	strcpy(buf1, cp);
	for (cp = strtok(buf1, ":"); cp; cp = strtok((char *) NULL, ":")) {
		if (*cp == NULL)
			cp = ".";
		sprintf(buf, "%s/crunch", cp);
		if (stat(buf, &sbuf) >= 0) {
			/***********************************************/
			/*   Check   for   'cm'  executable  in  same  */
			/*   directory.				       */
			/***********************************************/
			sprintf(buf, "%s/cm", cp);
			if (stat(buf, &sbuf) >= 0 && sbuf.st_mode & S_IEXEC) {
				strcpy(path, cp);
				crunch_mtime = sbuf.st_mtime;
				return TRUE;
				}
			}
		}
	return FALSE;

}
# if defined(unix)
/**********************************************************************/
/*   Emulate  the  system()  call  bypassing  the  spawning  of  the  */
/*   shell, which is so expensive.				      */
/**********************************************************************/
int
system(str)
char	*str;
{	char	*args[128];
	char	buf[BUFSIZ];
	char	*cp;
	int	arg = 0;
	int	status;

	strcpy(buf, str);
	for (cp = strtok(buf, " "); cp; cp = strtok((char *) NULL, " ")) {
		args[arg++] = cp;
		}
	args[arg] = NULL;
	if (fork() == 0) {
		execv(args[0], args);
		_exit(1);
		}
	wait(&status);
	return status;
}
# endif
