/*
 *	mc.c
 */

#include <signal.h>
#include <varargs.h>
#include <pwd.h>
#include <unistd.h>

#include "os.h"
#include "mctypes.h"
#include "mcfktns.h"
#include "symbtbl.h"
#include "mc.h"

#ifdef MSDOS
# include "lex_yy.h"
# include "y_tab.h"
#endif
#ifndef MSDOS
# include "lex.yy.h"
# include "y.tab.h"
#endif
#include "expr.h"

char	*out;
char	*p_out;
char	*o_out;
char	*e_out;
char	*pgname;
char	*actual_filename;
int	parser_errno = 0;
int	c_lineno = -1;
int	tabcnt = 0;
int	mc_rule = 0;
int	mc_init = 0;
int	mc_warn = 0;
int	mc_list_lineno = 0;
int	mc_list_level = 0;
unsigned long	mc_shmsize = 64 * 1024;
FILE	*outfile, *tempfile, *initfile, *listfile = NULL;

/* variables for environment settings */

char	*mc_libmclocal = "-lmclocal";
char	*mc_libmcremote = "-lmcremote";
char	*mc_include = NULL;
char	*mc_cclocal = "";
char	*mc_ldlocal = "";
char	*mc_remote = "";

char	cwd[260];

/* variables for IMPORT/EXPORT-Control */

char	*import_filename = NULL;
char	*import_filename_without_path = NULL;
/* export gibt an, ob eine Deklaration in einem Export-Block steht oder nicht */
int	export = 0;

/* moduletype gibt die Modulart (Processor, Hihglevel, Lowlevel)
 * des aktuellen Moduls an */
ModuleType	moduletype;

extern int	yyparse();



int	output_flag = 1;

#define	BUFLEN		256
#define	MAX_EXBUF	2048

#define	outtst()	{\
	if (output_flag == 0) {\
		return;\
	}\
}


void
c_out(fmt, va_alist)
	char	*fmt;
	va_dcl
{
	va_list	pvar;
	
	outtst ();

	va_start (pvar);
	vfprintf (outfile, fmt, pvar);
	va_end (pvar);

	return;
}


void
c_out_lf()
{
	outtst ();

	fputc ('\n', outfile);
	c_lineno++;
}


void
c_out_lineno()			/*
				 * aktuelle Modula-P-Zeilennummer ausgeben und
				 * interner C-Zeilennummer-Zaehler angleichen
				 */
{
	outtst ();

	fprintf (outfile, "\n#line %d \"%s\"\n", yylineno, actual_filename);
	c_lineno = yylineno;
}


void
c_out_linebegin(fmt, va_alist)
	char	*fmt;
	va_dcl
{
	va_list	pvar;
	int	i;

	outtst ();

	if (yylineno < c_lineno || c_lineno == -1) {
		c_out_lineno ();
	}

	while (yylineno > c_lineno) {
		fputc ('\n', outfile);
		c_lineno++;
	}

	if (tabcnt <= 0) {
		tabcnt = 0;
	} else {
		for (i = tabcnt; i; i--) {
			fputc ('\t', outfile);		
		}
	}

	va_start (pvar);
	vfprintf (outfile, fmt, pvar);
	va_end (pvar);

	return;
}


void
c_out_id(entry)
	SymbTblEntry	*entry;
{
	outtst ();

	if (entry->status.is_external || export || import_filename != NULL) {
		c_out ("m0_%s ", entry->id);
		entry->status.is_external = 1;
	} else {
		c_out ("m%d_%s ", entry->ofblock->nr, entry->id);
	}
	return;
}


void
c_init(fmt, va_alist)
	char	*fmt;
	va_dcl
{
	va_list	pvar;
	static int	save_lineno = -1;

	outtst ();

	if (save_lineno != yylineno) {
		fprintf (initfile, "\n#line %d \"%s\"\n", yylineno, actual_filename);
		save_lineno = yylineno;
	}

	
	va_start (pvar);
	vfprintf (initfile, fmt, pvar);
	va_end (pvar);

	return;
}


void
c_init_out()				/* Kopieren des initfile's ins outfile */
{
	static char	buf[BUFLEN + 1];

	outtst ();

	buf[BUFLEN] = '\0';
	
	fclose (initfile);

	if ((initfile = fopen ("!premod!.ini", "r")) == NULL) {
		message (D_INTERNAL, "c_init_out: Cannot open initialization file '!premod!.ini'");
	}

	while (fgets (buf, BUFLEN, initfile)) {
		fputs (buf, outfile);
	}
	c_out_lineno ();

	fclose (initfile);

	unlink ("!premod!.ini");
	
	return;
}


void
c_save(fmt, va_alist)
	char	*fmt;
	va_dcl
{
	va_list	pvar;

	outtst ();

	va_start (pvar);
	vfprintf (tempfile, fmt, pvar);
	va_end (pvar);

	return;
}


void
c_saved_out()				/* Kopieren des tempfile's ins outfile */
{
	static char	buf[BUFLEN + 1];

	outtst ();

	buf[BUFLEN] = '\0';
	
	fclose (tempfile);

	if ((tempfile = fopen ("!premod!.tmp", "r")) == NULL) {
		message (D_INTERNAL, "c_saved_out: Cannot open temporary file '!premod!.tmp'");
	}

	while (fgets (buf, BUFLEN, tempfile)) {
		fputs (buf, outfile);
	}
	c_out_lineno ();

	fclose (tempfile);

	if ((tempfile = fopen ("!premod!.tmp", "w")) == NULL) {
		message (D_INTERNAL, "c_saved_out: Cannot open temporary file '!premod!.tmp'");
	}
	setlinebuf (tempfile);
	
	return;
}


void
c_saved_kill()				/* Loeschen des tempfile's */
{
	outtst ();

	fclose (tempfile);

	if ((tempfile = fopen ("!premod!.tmp", "w")) == NULL) {
		message (D_INTERNAL, "c_saved_kill: Cannot open temporary file '!premod!.tmp'");
	}
	
	return;
}


char	expression_buffer[MAX_EXBUF +1];
char	*exbuf_err = "c_exbuf: Expression buffer length exceeded! Split Expression";

void
c_exbuf(fmt, va_alist)
	char	*fmt;
	va_dcl
{
	va_list	pvar;
	char	*pc;

	outtst ();

	pc = expression_buffer + strlen (expression_buffer);

	va_start (pvar);
	vsprintf (pc, fmt, pvar);
	va_end (pvar);

	if (strlen (expression_buffer) > MAX_EXBUF) {
		message (D_INTERNAL, exbuf_err);
	}
	
	return;
}


void
c_exbuf_id(entry)
	SymbTblEntry	*entry;
{
	outtst ();

	if (entry->status.is_external) {
		c_exbuf ("m0_%s ", entry->id);
	} else {
		c_exbuf ("m%d_%s ", entry->ofblock->nr, entry->id);
	}
	return;
}


void
insert_exbuf(exp, fmt, va_alist)
	char	*exp;
	char	*fmt;
	va_dcl
{
	va_list	pvar;
	char	save[sizeof (expression_buffer)];
	char	s[sizeof (expression_buffer)];

	outtst ();

	va_start (pvar);
	vsprintf (s, fmt, pvar);
	va_end (pvar);
	
	if ((unsigned long) exp < (unsigned long) expression_buffer || (unsigned long) exp >= ((unsigned long) expression_buffer) + sizeof (expression_buffer)) {
		message (D_INTERNAL, "insert_exbuf: Expression-Buffer-Pointer out of Position");
	}

	strncpy (save, exp, sizeof (expression_buffer) - 1);

	exp [0] = '\0';
	c_exbuf ("%s", s);
	c_exbuf ("%s", save);

	return;
}


void
yyerror(s)
	char	*s;
{
	switch (parser_errno) {
	default:
		if (strncmp (s, "syntax error", 12) == 0) {
			message (D_ERROR, "yyerror: %s at token '%.80s'", s, yytext);
		} else {
			message (D_ERROR, "yyerror: %s", s);
		}
		break;
	case 1:
		message (D_WARN, "yyerror (yyparse (module)): '%s' received after end of Module; rest of file ignored.", yytext);
		statistics (0);
		break;
	}

	return;
}


void
mc_link()
{
	FileNames	*help;
	char	*fntemp;
	int	l;
	
	exbuf_err = "c_exbuf: Too many Object Files for automatic Link";
	clr_exbuf ();
	c_exbuf ("cc -o %s %s ", e_out, o_out);
	c_exbuf ("%s ", mc_libmclocal);
	c_exbuf ("%s ", mc_ldlocal);

	help = filenames;

	if (help != NULL) {
			/* Aktueller Filename bereits ausgegeben */
		help = help->next;
	}

	while (help != NULL) {
		if ((fntemp = strrchr (help->name, '/')) == NULL) {
			fntemp = help->name;
		} else {
			fntemp += 1;
		}
		l = strlen (help->name);
		if (l < 5 || strcmp (help->name + l - 4, ".mod") != 0) {
			message (D_INTERNAL, "mc_link: Unable to link Object Code of File '%s'", help->name);
		} else {
			help->name [ l - 4 ] = '\0';
		}
		c_exbuf ("%s.m.o ", help->name);
		help = help->next;
	}
#ifdef sequent
	c_exbuf ("-lsocket -lpps -lm -lrpc -lrpcsvc -lseq -linet -lnsl -lc");
#else
	c_exbuf ("-lm");
#endif

	fprintf (stderr, "\n%s\n", expression_buffer);
	exit (system (expression_buffer));
}


void
compile()
{
	int	i;
	
#ifndef MSDOS
	exbuf_err = "c_exbuf: Filenames too long for automatic C-Compilation";
	clr_exbuf ();
	c_exbuf ("cc -c %s ", p_out);

	if (mc_include != NULL) {
		c_exbuf ("-I%s ", mc_include);
	}
	c_exbuf ("%s ", mc_cclocal);

	fprintf (stderr, "\n%s\n", expression_buffer);
	if ((i = system (expression_buffer)) == 0) {
		if (moduletype == e_PROCMOD) {
			mc_link ();
		}
	}
#endif

	exit (i);
}


void
remote_compile(ws_name, comm_name, ws_class)
	char	*ws_name, *comm_name, *ws_class;
{
	int	i;
	char	rshbuf[512];

	sprintf (rshbuf, "rsh %s \"cc -o %s/%s_%s_%s %s/%s_%s.c -I%s -I$MC_INCLUDE/. %s -lm %s",
		ws_name, cwd, module_name, comm_name, ws_class, cwd, module_name, comm_name,
		cwd, mc_libmcremote, mc_remote);
	
	strcat (rshbuf, "\"");

	fprintf (stderr, "\n%s\n", rshbuf);
	if (i = system (rshbuf)) {
		message (D_ERROR, "remote_compile: Error at Remote Compilation, Return Code '%d'", i);
	}
	
	return;
}


void
statistics(i)
	int	i;
{
	fprintf (stderr, "\nModula-P-Compiler: %d Warnings, %d Errors\n", warnings, errors);
	c_out ("/*\nModula-P-Compiler: %d Warnings, %d Errors\n*/\n", warnings, errors);
	if (listfile != NULL) {	
		fprintf (listfile, "\n\nModula-P-Compiler: %d Warnings, %d Errors\n", warnings, errors);
	}

	c_saved_out ();
	fclose (tempfile);
	unlink ("!premod!.tmp");
	c_out_lf ();
	fclose (outfile);

	if (errors || i) {
		exit (1);
	}

	compile ();
}


void
usage(fmt, va_alist)
	char	*fmt;
	va_dcl
{
	va_list	pvar;
	
	outtst ();

	va_start (pvar);
	vprintf (fmt, pvar);
	va_end (pvar);

	putchar ('\n');
	printf ("usage: %s [Options] <in_file>\n", pgname);
	printf ("\n\t<in_file[.mod]> is compiled (must be last parameter)\n\n");
	printf ("\tOptions are: -d -l -s -w\n");
	printf ("\t             -c <Compiler_out_file>\n");
	printf ("\t             -m <Shared_Memory_Size in kByte>\n");
	printf ("\t             -o <C-Linker_out_file>\n\n");
	printf ("\t-c\t<Compiler_out_file>: default is <in_file>.m.c\n");
	printf ("\t-d\terror-message includes rule- or subroutine-name\n");
	printf ("\t\twhere error occured\n");
	printf ("\t-l\tlisting is generated into file <in_file>.lis\n");
	printf ("\t-w\tsuppress warnings\n");
	printf ("\n\tOnly for Processor Modules:\n");
	printf ("\t-m\t<Shared_Memory_Size>: default is 64 (kByte)\n");
	printf ("\t-o\t<C-Linker_out_file>: default is <in_file>\n");
	printf ("\t-s\toutput of initialization sequence is shown at execution\n");
	printf ("\n\tThe following environment variables may be set:\n");
	printf ("\t\tMC_LIBMCLOCAL  full path for mc library (libmclocal.a)\n");
	printf ("\t\tMC_LIBMCREMOTE full path for mc library (libmcremote.a)\n");
	printf ("\t\tMC_INCLUDE     directory-path for mc include files\n");
	printf ("\t\t               or additional self-defined include files (*.h)\n");
	printf ("\t\tMC_CCLOCAL     additional options for compilation of the\n");
	printf ("\t\t               generated C files\n");
	printf ("\t\tMC_LDLOCAL     additional options for linking of the\n");
	printf ("\t\t               processor module\n");
	printf ("\t\tMC_REMOTE      additional options for remote compilation\n");
	printf ("\t\t               and linking of the communications\n");
	exit (1);
}

int
no_recovery()
{
	message (D_FATAL, "no_recovery: Error Recovery impossible at Token '%s' because of a previously detected error", yytext);
}


void
main(argc, argv, envp)
	int	argc;
	char	*argv[];
	char	*envp[];
{
	char	**avp;
	char	*ap;

	char	in[64];
	char	in_p[64];
	char	in_o[64];
	char	in_e[64];
	char	out_l[64];
	char	*login_name;
	struct passwd	*ppwd;
	char	home_dir[sizeof (cwd)];

		/* Environment handling */
	avp = envp;
	while (*avp) {
		if (strncmp (*avp, "MC_", 3) == 0) {
			if (strncmp (*avp, "MC_LIBMCLOCAL", 13) == 0) {
				mc_libmclocal = (*avp) + 14;
			} else if (strncmp (*avp, "MC_LIBMCREMOTE", 14) == 0) {
				mc_libmcremote = (*avp) + 15;
				mc_libmcremote = "\\$MC_LIBMCREMOTE";
			} else if (strncmp (*avp, "MC_INCLUDE", 10) == 0) {
				mc_include = (*avp) + 11;
			} else if (strncmp (*avp, "MC_CCLOCAL", 10) == 0) {
				mc_cclocal = (*avp) + 11;
			} else if (strncmp (*avp, "MC_LDLOCAL", 10) == 0) {
				mc_ldlocal = (*avp) + 11;
			} else if (strncmp (*avp, "MC_REMOTE", 9) == 0) {
				mc_remote = (*avp) + 10;
			}
		}
		avp++;
	}
	
	if (getcwd (cwd, sizeof (cwd)) == NULL) {
		message (D_INTERNAL, "main: getcwd of current directory failed");
	}
	if ((login_name = getlogin ()) == NULL) {
		/* Name not found; alternate way over uid */
		ppwd = getpwuid (getuid ());
		if (ppwd == NULL) {
			message (D_INTERNAL, "main: getpwuid failed");
		}
	} else {
		ppwd = getpwnam (login_name);
		if (ppwd == NULL) {
			message (D_INTERNAL, "main: getpwnam failed");
		}
	}
	if (chdir (ppwd->pw_dir)) {
		message (D_INTERNAL, "main: chdir to home directory failed");
	}
	if (getcwd (home_dir, sizeof (home_dir)) == NULL) {
		message (D_INTERNAL, "main: getcwd of home directory failed");
	}
	if (   strncmp (home_dir, cwd, strlen (home_dir)) == 0
	    && (   cwd [strlen (home_dir)] == '\0'
	        || cwd [strlen (home_dir)] == '/')) {
		/* current directory is under home directory */
		char	help_dir[sizeof (cwd)];

		if (strlen (home_dir) == strlen (cwd)) {
			/* current is home directory */
			strcpy (help_dir, ".");
		} else {
			sprintf (help_dir, ".%s\0", cwd + strlen (home_dir));
		}
		strcpy (cwd, help_dir);
	}
	if (chdir (cwd)) {
		message (D_INTERNAL, "main: chdir to current directory failed");
	}

	pgname = argv [0];

	if (argc < 2) {
		usage ("");
	}

	if (strlen (argv [argc - 1]) > 55) {
		usage ("in_file-name too long");
	}

	strcpy (in, argv [argc - 1]);

	argv [argc - 1][0] = '\0';	/* letztes Argument verbergen fuer nachfolgende Optionslisten-Bearbeitung */
	
	if (strlen (in) >= 5 && strcmp (in + strlen (in) - 4, ".mod") == 0) {
		in [strlen (in) - 4] = '\0';
	}
	strcpy (in_p, in);
	strcpy (in_o, in);
	strcpy (in_e, in);
	strcpy (out_l, in);

	strcat (in, ".mod");
	strcat (out_l, ".lis");

#ifdef MSDOS
	strcat (in_p, ".c");
	strcat (in_o, ".o");
#else
	strcat (in_p, ".m.c");
	strcat (in_o, ".m.o");
#endif

	p_out = in_p;
	o_out = in_o;
	e_out = in_e;

	for (avp = argv + 1; ap = *avp; avp++) {
		if (*ap++ == '-') {
			switch (*ap) {
			default:
				usage ("invalid parameter '%s'\n", *avp);
				break;
			case 'c':
				if (ap [1])
					p_out = ap + 1;
				else {
					if (*++avp == NULL || *avp [0] == '-') {
						usage ("-o <filename> is missing\n");
					}
					p_out = *avp;
				}
				break;
			case 'd':
				mc_rule = 1;
				break;
			case 'l':
				if (listfile == NULL) {
					if ((listfile = fopen (out_l, "w")) == NULL) {
						message (D_OPEN_ERROR, "main: Cannot open output file '%s'", out_l);
					}
					fprintf (listfile, "M o d u l a - P - C o m p i l e r   V 2.1\n");
					fprintf (listfile, "(C) University Stuttgart, R. Norz, 1990-1993\n\n");
					fprintf (listfile, "Listing for File '%s'\n", in);
					fprintf (listfile, "\nline numbers followed by character 'C' show continued lines\n\n");
					fprintf (listfile, "\n%d:\t", ++mc_list_lineno);
				}
				break;
			case 'm':
				if (ap [1])
					mc_shmsize = atol (ap + 1) * 1024;
				else {
					if (*++avp == NULL || *avp [0] == '-') {
						usage ("-m <Shared_Memory_Size> is missing\n");
					}
					mc_shmsize = atol (*avp) * 1024;
				}
				break;
			case 'o':
				if (ap [1])
					e_out = ap + 1;
				else {
					if (*++avp == NULL || *avp [0] == '-') {
						usage ("-o <filename> is missing\n");
					}
					e_out = *avp;
				}
				break;
			case 's':
				mc_init = 1;
				break;
			case 'w':
				mc_warn = 1;
				break;
			}
		}
	}


	fprintf (stderr, "M o d u l a - P - C o m p i l e r   V 2.1\n");
	fprintf (stderr, "(C) University Stuttgart, R. Norz, 1990-1993\n\n");

	actual_filename = "Modula-P Compiler";

	if ((freopen (in, "r", stdin)) == NULL) {
		message (D_OPEN_ERROR, "main: Cannot open input file '%s'", in);
	}

	blocktype = e_FILE;
	newblocklist (actual_filename = add_filename (in));
	

	if ((outfile = fopen (p_out, "w")) == NULL) {
		message (D_OPEN_ERROR, "main: Cannot open output file '%s'", out);
	}
	setlinebuf (outfile);

	c_out ("/*\nM o d u l a - P - C o m p i l e r   V 2.1\n");
	c_out ("(C) University Stuttgart, R. Norz, 1990-1993\n*/\n");
#ifdef sequent
	c_out ("#define sequent\n");
#endif
	c_out ("#include <mcfktns.h>\n");
	c_out ("#include <mclocal.h>\n\n");

	if ((tempfile = fopen ("!premod!.tmp", "w")) == NULL) {
		message (D_INTERNAL, "main: Cannot open temporary file '!premod!.tmp'");
	}
	setlinebuf (tempfile);

	if ((initfile = fopen ("!premod!.ini", "w")) == NULL) {
		message (D_INTERNAL, "main: Cannot open initialization file '!premod!.ini'");
	}
	setlinebuf (initfile);

	initprecompiler ();	/* darf erst nach newblocklist laufen! */

				/* fuer error-recovery */
	signal (SIGILL, no_recovery);	/* illegal instruction */
	signal (SIGBUS, no_recovery);	/* bus error */
	signal (SIGSEGV, no_recovery);	/* segmentation violation */

	statistics (yyparse ());
}
