/* 
	qfcc.c

	QuakeForge Code Compiler (main program)

	Copyright (C) 1996-1997 id Software, Inc.
	Copyright (C) 2001 Jeff Teunissen <deek@dusknet.dhs.org>

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License as
	published by the Free Software Foundation; either version 2 of
	the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

	See the GNU General Public License for more details.

	You should have received a copy of the GNU General Public
	License along with this program; if not, write to:

		Free Software Foundation, Inc.
		59 Temple Place - Suite 330
		Boston, MA  02111-1307, USA

	$Id$
*/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif

#include <QF/crc.h>
#include <QF/hash.h>
#include <QF/qendian.h>
#include <QF/sys.h>

#include "qfcc.h"

options_t   options;

char        sourcedir[1024];
char        destfile[1024];
char        debugfile[1024];

float       pr_globals[MAX_REGS];
int         numpr_globals;

char        strings[MAX_STRINGS];
int         strofs;

dstatement_t statements[MAX_STATEMENTS];
int         numstatements;
int         statement_linenums[MAX_STATEMENTS];

dfunction_t functions[MAX_FUNCTIONS];
int         numfunctions;

ddef_t      globals[MAX_GLOBALS];
int         numglobaldefs;
int			num_localdefs;
const char *big_function = 0;

ddef_t      fields[MAX_FIELDS];
int         numfielddefs;

char        precache_sounds[MAX_SOUNDS][MAX_DATA_PATH];
int         precache_sounds_block[MAX_SOUNDS];
int         numsounds;

char        precache_models[MAX_MODELS][MAX_DATA_PATH];
int         precache_models_block[MAX_SOUNDS];
int         nummodels;

char        precache_files[MAX_FILES][MAX_DATA_PATH];
int         precache_files_block[MAX_SOUNDS];
int         numfiles;

/*
	WriteFiles

	Generates files.dat, which contains all of the data files actually used by
	the game, to be processed by qfiles
*/
void
WriteFiles (void)
{
	FILE	*f;
	int 	i;
	char	filename[1024];

	sprintf (filename, "%s%cfiles.dat", sourcedir, PATH_SEPARATOR);
	f = fopen (filename, "w");
	if (!f)
		Error ("Couldn't open %s", filename);

	fprintf (f, "%i\n", numsounds);
	for (i = 0; i < numsounds; i++)
		fprintf (f, "%i %s\n", precache_sounds_block[i], precache_sounds[i]);

	fprintf (f, "%i\n", nummodels);
	for (i = 0; i < nummodels; i++)
		fprintf (f, "%i %s\n", precache_models_block[i], precache_models[i]);

	fprintf (f, "%i\n", numfiles);
	for (i = 0; i < numfiles; i++)
		fprintf (f, "%i %s\n", precache_files_block[i], precache_files[i]);

	fclose (f);
}

/*
	CopyString

	Return an offset from the string heap
*/
static hashtab_t *strings_tab;

static const char *
stings_get_key (void *_str, void *unsued)
{
	return (char*)_str;
}

int
CopyString (const char *str)
{
	int 	old;

	if (!strings_tab) {
		strings_tab = Hash_NewTable (16381, stings_get_key, 0, 0);
	}
	old = strofs;
	strcpy (strings + strofs, str);
	strofs += strlen (str) + 1;
	Hash_Add (strings_tab, strings + old);
	return old;
}

int
ReuseString (const char *str)
{
	char *s;

	if (!strings_tab)
		return CopyString (str);
	s = Hash_Find (strings_tab, str);
	if (s)
		return s - strings;
	return CopyString (str);
}

void
PrintStrings (void)
{
	int 	i, l, j;

	for (i = 0; i < strofs; i += l) {
		l = strlen (strings + i) + 1;
		printf ("%5i : ", i);
		for (j = 0; j < l; j++) {
			if (strings[i + j] == '\n') {
				putchar ('\\');
				putchar ('n');
			} else {
				putchar (strings[i + j]);
			}
		}
		printf ("\n");
	}
}

void
PrintFunctions (void)
{
	int 		i, j;
	dfunction_t *d;

	for (i = 0; i < numfunctions; i++) {
		d = &functions[i];
		printf ("%s : %s : %i %i (", strings + d->s_file, strings + d->s_name,
				d->first_statement, d->parm_start);
		for (j = 0; j < d->numparms; j++)
			printf ("%i ", d->parm_size[j]);
		printf (")\n");
	}
}

void
PrintFields (void)
{
	int 	i;
	ddef_t	*d;

	for (i = 0; i < numfielddefs; i++) {
		d = &fields[i];
		printf ("%5i : (%i) %s\n", d->ofs, d->type, strings + d->s_name);
	}
}

void
PrintGlobals (void)
{
	int 	i;
	ddef_t	*d;

	for (i = 0; i < numglobaldefs; i++) {
		d = &globals[i];
		printf ("%5i : (%i) %s\n", d->ofs, d->type, strings + d->s_name);
	}
}


void
InitData (void)
{
	int 	i;

	numstatements = 1;
	strofs = 1;
	numfunctions = 1;
	numglobaldefs = 1;
	numfielddefs = 1;

	def_ret.ofs = OFS_RETURN;
	for (i = 0; i < MAX_PARMS; i++)
		def_parms[i].ofs = OFS_PARM0 + 3 * i;
}


void
WriteData (int crc)
{
	def_t		*def;
	ddef_t		*dd;
	dprograms_t progs;
	pr_debug_header_t debug;
	FILE		*h;
	int 		i;

	for (def = pr.def_head.def_next; def; def = def->def_next) {
		if (def->scope)
			continue;
		if (def->type->type == ev_func) {
//			df = &functions[numfunctions];
//			numfunctions++;
		} else if (def->type->type == ev_field) {
			dd = &fields[numfielddefs];
			numfielddefs++;
			dd->type = def->type->aux_type->type;
			dd->s_name = ReuseString (def->name);
			dd->ofs = G_INT (def->ofs);
		}

		dd = &globals[numglobaldefs];
		numglobaldefs++;
		dd->type = def->type->type;

		if (!def->initialized
			&& def->type->type != ev_func
			&& def->type->type != ev_field && def->scope == NULL)
			dd->type |= DEF_SAVEGLOBAL;
		dd->s_name = ReuseString (def->name);
		dd->ofs = def->ofs;
	}

//	PrintStrings ();
//	PrintFunctions ();
//	PrintFields ();
//	PrintGlobals ();
	strofs = (strofs + 3) & ~3;

	if (options.quiet < 2) {
		printf ("%6i strofs\n", strofs);
		printf ("%6i statements\n", numstatements);
		printf ("%6i functions\n", numfunctions);
		printf ("%6i globaldefs\n", numglobaldefs);
		printf ("%6i locals size (%s)\n", num_localdefs, big_function);
		printf ("%6i fielddefs\n", numfielddefs);
		printf ("%6i pr_globals\n", numpr_globals);
	}

	h = SafeOpenWrite (destfile);
	SafeWrite (h, &progs, sizeof (progs));

	progs.ofs_strings = ftell (h);
	progs.numstrings = strofs;
	SafeWrite (h, strings, strofs);

	progs.ofs_statements = ftell (h);
	progs.numstatements = numstatements;
	for (i = 0; i < numstatements; i++) {
		statements[i].op = LittleShort (statements[i].op);
		statements[i].a = LittleShort (statements[i].a);
		statements[i].b = LittleShort (statements[i].b);
		statements[i].c = LittleShort (statements[i].c);
	}
	SafeWrite (h, statements, numstatements * sizeof (dstatement_t));

	progs.ofs_functions = ftell (h);
	progs.numfunctions = numfunctions;
	for (i = 0; i < numfunctions; i++) {
		functions[i].first_statement = LittleLong (functions[i].first_statement);
		functions[i].parm_start = LittleLong (functions[i].parm_start);
		functions[i].s_name = LittleLong (functions[i].s_name);
		functions[i].s_file = LittleLong (functions[i].s_file);
		functions[i].numparms = LittleLong (functions[i].numparms);
		functions[i].locals = LittleLong (functions[i].locals);
	}
	SafeWrite (h, functions, numfunctions * sizeof (dfunction_t));

	progs.ofs_globaldefs = ftell (h);
	progs.numglobaldefs = numglobaldefs;
	for (i = 0; i < numglobaldefs; i++) {
		globals[i].type = LittleShort (globals[i].type);
		globals[i].ofs = LittleShort (globals[i].ofs);
		globals[i].s_name = LittleLong (globals[i].s_name);
	}
	SafeWrite (h, globals, numglobaldefs * sizeof (ddef_t));

	progs.ofs_fielddefs = ftell (h);
	progs.numfielddefs = numfielddefs;
	for (i = 0; i < numfielddefs; i++) {
		fields[i].type = LittleShort (fields[i].type);
		fields[i].ofs = LittleShort (fields[i].ofs);
		fields[i].s_name = LittleLong (fields[i].s_name);
	}
	SafeWrite (h, fields, numfielddefs * sizeof (ddef_t));

	progs.ofs_globals = ftell (h);
	progs.numglobals = numpr_globals;

	for (i = 0; i < numpr_globals; i++)
		((int *) pr_globals)[i] = LittleLong (((int *) pr_globals)[i]);

	SafeWrite (h, pr_globals, numpr_globals * 4);

	if (options.quiet < 2)
		printf ("%6i TOTAL SIZE\n", (int) ftell (h));

	progs.entityfields = pr.size_fields;

	progs.version = options.version;
	progs.crc = crc;

	// byte swap the header and write it out
	for (i = 0; i < sizeof (progs) / 4; i++)
		((int *) &progs)[i] = LittleLong (((int *) &progs)[i]);

	fseek (h, 0, SEEK_SET);
	SafeWrite (h, &progs, sizeof (progs));
	fclose (h);

	if (!options.debug) {
		return;
	}

	h = SafeOpenRead (destfile);

	debug.version = LittleLong (PROG_DEBUG_VERSION);
	CRC_Init (&debug.crc);
	while ((i = fgetc (h)) != EOF)
		CRC_ProcessByte (&debug.crc, i);
	fclose (h);
	debug.crc = LittleShort (debug.crc);
	debug.you_tell_me_and_we_will_both_know = 0;
	
	h = SafeOpenWrite (debugfile);
	SafeWrite (h, &debug, sizeof (debug));

	debug.auxfunctions = LittleLong (ftell (h));
	debug.num_auxfunctions = LittleLong (num_auxfunctions);
	for (i = 0; i < num_auxfunctions; i++) {
		auxfunctions[i].function = LittleLong (auxfunctions[i].function);
		auxfunctions[i].source_line = LittleLong (auxfunctions[i].source_line);
		auxfunctions[i].line_info = LittleLong (auxfunctions[i].line_info);
		auxfunctions[i].local_defs = LittleLong (auxfunctions[i].local_defs);
		auxfunctions[i].num_locals = LittleLong (auxfunctions[i].num_locals);
	}
	SafeWrite (h, auxfunctions, num_auxfunctions * sizeof (auxfunctions[0]));

	debug.linenos = LittleLong (ftell (h));
	debug.num_linenos = LittleLong (num_linenos);
	for (i = 0; i < num_linenos; i++) {
		linenos[i].fa.addr = LittleLong (linenos[i].fa.addr);
		linenos[i].line = LittleLong (linenos[i].line);
	}
	SafeWrite (h, linenos, num_linenos * sizeof (linenos[0]));

	debug.locals = LittleLong (ftell (h));
	debug.num_locals = LittleLong (num_locals);
	for (i = 0; i < num_locals; i++) {
		locals[i].type = LittleShort (locals[i].type);
		locals[i].ofs = LittleShort (locals[i].ofs);
		locals[i].s_name = LittleLong (locals[i].s_name);
	}
	SafeWrite (h, locals, num_locals * sizeof (locals[0]));

	fseek (h, 0, SEEK_SET);
	SafeWrite (h, &debug, sizeof (debug));
	fclose (h);
}


/*
	PR_String

	Returns a string suitable for printing (no newlines, max 60 chars length)
*/
char *
PR_String (char *string)
{
	static char buf[80];
	char		*s;

	s = buf;
	*s++ = '"';
	while (string && *string) {

		if (s == buf + sizeof (buf) - 2)
			break;

		if (*string == '\n') {
			*s++ = '\\';
			*s++ = 'n';
		} else if (*string == '"') {
			*s++ = '\\';
			*s++ = '"';
		} else {
			*s++ = *string;
		}
		string++;

		if (s - buf > 60) {
			*s++ = '.';
			*s++ = '.';
			*s++ = '.';
			break;
		}
	}
	*s++ = '"';
	*s++ = 0;

	return buf;
}



def_t *
PR_DefForFieldOfs (gofs_t ofs)
{
	def_t	*d;

	for (d = pr.def_head.def_next; d; d = d->def_next) {
		if (d->type->type != ev_field)
			continue;
		if (*((int *) &pr_globals[d->ofs]) == ofs)
			return d;
	}

	Error ("PR_DefForFieldOfs: couldn't find %i", ofs);
	return NULL;
}

/*
	PR_ValueString

	Return a string describing *data in a type-specific manner
*/
char *
PR_ValueString (etype_t type, void *val)
{
	static char line[256];
	def_t		*def;
	dfunction_t *f;

	switch (type) {
		case ev_string:
			sprintf (line, "%s", PR_String (strings + *(int *) val));
			break;
		case ev_entity:
			sprintf (line, "entity %i", *(int *) val);
			break;
		case ev_func:
			if (!(f = functions + *(int *) val))
				sprintf (line, "undefined function");
			else
				sprintf (line, "%s()", strings + f->s_name);
			break;
		case ev_field:
			def = PR_DefForFieldOfs (*(int *) val);
			sprintf (line, ".%s", def->name);
			break;
		case ev_void:
			sprintf (line, "void");
			break;
		case ev_float:
			sprintf (line, "%5.1f", *(float *) val);
			break;
		case ev_vector:
			sprintf (line, "'%5.1f %5.1f %5.1f'", ((float *) val)[0],
					 ((float *) val)[1], ((float *) val)[2]);
			break;
		case ev_pointer:
			sprintf (line, "pointer");
			break;
		default:
			sprintf (line, "bad type %i", type);
			break;
	}

	return line;
}

/*
	PR_GlobalString

	Return a string with a description and the contents of a global, padded
	to 20 field width
*/
char *
PR_GlobalStringNoContents (gofs_t ofs)
{
	int 		i;
	def_t		*def;
	void		*val;
	static char line[128];

	val = (void *) &pr_globals[ofs];
	def = pr_global_defs[ofs];
	if (!def) {
//		Error ("PR_GlobalString: no def for %i", ofs);
		sprintf (line, "%i(\?\?\?)", ofs);
	} else {
		sprintf (line, "%i(%s)", ofs, def->name);
	}

	for (i = strlen (line); i < 16; i++) {
		strcat (line, " ");
	}
	strcat (line, " ");

	return line;
}

char *
PR_GlobalString (gofs_t ofs)
{
	char		*s;
	int 		i;
	def_t		*def;
	void		*val;
	static char line[128];

	val = (void *) &pr_globals[ofs];
	
	if (!(def = pr_global_defs[ofs]))
		return PR_GlobalStringNoContents (ofs);

	if (def->initialized && def->type->type != ev_func) {
		s = PR_ValueString (def->type->type, &pr_globals[ofs]);
		sprintf (line, "%i(%s)", ofs, s);
	} else {
		sprintf (line, "%i(%s)", ofs, def->name);
	}

	for (i = strlen (line); i < 16; i++) {
		strcat (line, " ");
	}
	strcat (line, " ");

	return line;
}

/*
============
PR_PrintOfs
============
*/
void
PR_PrintOfs (gofs_t ofs)
{
	printf ("%s\n", PR_GlobalString (ofs));
}

/*
=================
PR_PrintStatement
=================
*/
void
PR_PrintStatement (dstatement_t *s)
{
	int 	i;
	opcode_t *op;

	op = PR_Opcode (s->op);
	printf ("%4i : %4i : %s ", (int) (s - statements),
			statement_linenums[s - statements], op->opname);

	for (i = strlen (op->opname); i < 10; i++) {
		printf (" ");
	}

	if (s->op == OP_IF || s->op == OP_IFNOT) {
		printf ("%sbranch %i", PR_GlobalString (s->a), s->b);
	} else if (s->op == OP_GOTO) {
		printf ("branch %i", s->a);
	} else if ((unsigned) (s->op - OP_STORE_F) < 6) {
		printf ("%s", PR_GlobalString (s->a));
		printf ("%s", PR_GlobalStringNoContents (s->b));
	} else {
		if (s->a)
			printf ("%s", PR_GlobalString (s->a));
		if (s->b)
			printf ("%s", PR_GlobalString (s->b));
		if (s->c)
			printf ("%s", PR_GlobalStringNoContents (s->c));
	}
	printf ("\n");
}


/*
	PR_PrintDefs
*/
void
PR_PrintDefs (void)
{
	def_t	*d;

	for (d = pr.def_head.def_next; d; d = d->def_next)
		PR_PrintOfs (d->ofs);
}


/*
	PR_BeginCompilation

	called before compiling a batch of files, clears the pr struct
*/
void
PR_BeginCompilation (void *memory, int memsize)
{
	int 	i;

	pr.memory = memory;
	pr.max_memory = memsize;

	numpr_globals = RESERVED_OFS;
	pr.def_tail = &pr.def_head;

	for (i = 0; i < RESERVED_OFS; i++)
		pr_global_defs[i] = &def_void;

	// link the function type in so state forward declarations match proper type
	pr.types = &type_function;
	type_function.next = NULL;
	pr_error_count = 0;
}

void
PR_RelocateRefs (def_t *def)
{
	statref_t	*ref;
	for (ref = def->refs; ref; ref = ref->next) {
		switch (ref->field) {
			case 0:
				ref->statement->a = def->ofs;
				break;
			case 1:
				ref->statement->b = def->ofs;
				break;
			case 2:
				ref->statement->c = def->ofs;
				break;
			default:
				abort();
		}
	}
}

/*
	PR_FinishCompilation

	called after all files are compiled to check for errors.
	Returns false if errors were detected.
*/
qboolean
PR_FinishCompilation (void)
{
	def_t		*d;
	qboolean	errors = false;
	function_t	*f;
	def_t		*def;
	expr_t       e;

	// check to make sure all functions prototyped have code
	if (options.undefined_function_warning)
		for (d = pr.def_head.def_next; d; d = d->def_next) {
			if (d->type->type == ev_func && !d->scope) {	// function args ok
//				f = G_FUNCTION(d->ofs);
//				if (!f || (!f->code && !f->builtin))
				if (!d->initialized) {
					warning (0, "function %s was not defined\n", d->name);
				}
			}
		}

	if (errors)
		return !errors;

	if (options.debug) {
		e.type = ex_string;
		e.e.string_val = debugfile;
		PR_ReuseConstant (&e, PR_GetDef (&type_string, ".debug_file", 0,
										 &numpr_globals));
	}

	for (def = pr.def_head.def_next; def; def = def->def_next) {
		if (def->scope)
			continue;
		PR_RelocateRefs (def);
	}

	for (f = pr_functions; f; f = f->next) {
		if (f->builtin)
			continue;
		if (f->def->num_locals > num_localdefs) {
			num_localdefs = f->def->num_locals;
			big_function = f->def->name;
		}
		f->dfunc->parm_start = numpr_globals;
		for (def = f->def->scope_next; def; def = def->scope_next) {
			def->ofs += numpr_globals;
			PR_RelocateRefs (def);
		}
	}
	numpr_globals += num_localdefs;

	return !errors;
}

//=============================================================================

/*
	PR_WriteProgdefs

	Writes the global and entity structures out.
	Returns a crc of the header, to be stored in the progs file for comparison
	at load time.
*/
int
PR_WriteProgdefs (char *filename)
{
	def_t			*d;
	FILE			*f;
	unsigned short	crc;
	int 			c;

	if (!options.quiet)
		printf ("writing %s\n", filename);
	f = fopen (filename, "w");

	// print global vars until the first field is defined
	fprintf (f,	"\n/* file generated by qcc, do not modify */\n\ntypedef struct\n{\tint\tpad[%i];\n", RESERVED_OFS);

	for (d = pr.def_head.def_next; d; d = d->def_next) {
		if (!strcmp (d->name, "end_sys_globals"))
			break;

		switch (d->type->type) {
			case ev_float:
				fprintf (f, "\tfloat\t%s;\n", d->name);
				break;
			case ev_vector:
				fprintf (f, "\tvec3_t\t%s;\n", d->name);
				d = d->def_next->def_next->def_next;	// skip the elements
				break;
			case ev_string:
				fprintf (f, "\tstring_t\t%s;\n", d->name);
				break;
			case ev_func:
				fprintf (f, "\tfunc_t\t%s;\n", d->name);
				break;
			case ev_entity:
				fprintf (f, "\tint\t%s;\n", d->name);
				break;
			default:
				fprintf (f, "\tint\t%s;\n", d->name);
				break;
		}
	}
	fprintf (f, "} globalvars_t;\n\n");

	// print all fields
	fprintf (f, "typedef struct\n{\n");
	for (d = pr.def_head.def_next; d; d = d->def_next) {
		if (!strcmp (d->name, "end_sys_fields"))
			break;

		if (d->type->type != ev_field)
			continue;

		switch (d->type->aux_type->type) {
			case ev_float:
				fprintf (f, "\tfloat\t%s;\n", d->name);
				break;
			case ev_vector:
				fprintf (f, "\tvec3_t\t%s;\n", d->name);
				d = d->def_next->def_next->def_next;	// skip the elements
				break;
			case ev_string:
				fprintf (f, "\tstring_t\t%s;\n", d->name);
				break;
			case ev_func:
				fprintf (f, "\tfunc_t\t%s;\n", d->name);
				break;
			case ev_entity:
				fprintf (f, "\tint\t%s;\n", d->name);
				break;
			default:
				fprintf (f, "\tint\t%s;\n", d->name);
				break;
		}
	}
	fprintf (f, "} entvars_t;\n\n");

	fclose (f);

	// do a crc of the file
	CRC_Init (&crc);
	f = fopen (filename, "r+");
	while ((c = fgetc (f)) != EOF)
		CRC_ProcessByte (&crc, (byte) c);

	fprintf (f, "#define PROGHEADER_CRC %i\n", crc);
	fclose (f);

	return crc;
}


void
PrintFunction (const char *name)
{
	int 			i;
	dstatement_t	*ds;
	dfunction_t 	*df;

	for (i = 0; i < numfunctions; i++)
		if (!strcmp (name, strings + functions[i].s_name))
			break;

	if (i == numfunctions)
		Error ("No function names \"%s\"", name);

	df = functions + i;

	printf ("Statements for %s:\n", name);
	ds = statements + df->first_statement;

	while (1) {
		PR_PrintStatement (ds);
		if (!ds->op)
			break;
		ds++;
	}
}

void
PR_PrintFunction (def_t *def)
{
	def_t *d;
	statref_t *r;

	printf ("%s\n", def->name);
	for (d = def->scope_next; d; d = d->scope_next) {
		printf ("%s: %d %d %d\n",
				d->name ? d->name : "<temp>",
				d->ofs, d->type->type, type_size[d->type->type]);
		for (r = d->refs; r; r = r->next)
			printf (" %d", r->statement - statements);
		printf ("\n");
	}
}


//============================================================================

/*
	main

	The nerve center of our little operation
*/
int
main (int argc, char **argv)
{
	char	*src;
	char	filename[1024];
	int 	p, crc;
	double	start, stop;
	int		no_cpp = 0;

	start = Sys_DoubleTime ();

	myargc = argc;
	myargv = argv;

	options.version = PROG_VERSION;

	if (CheckParm ("-h") || CheckParm ("--help")) {
		printf ("%s - A compiler for the QuakeC language\n", argv[0]);
		printf ("Usage: %s [options]\n", argv[0]);
		printf (
"Options: \n"
"	-s, --source <dir>	look for progs.src in directory <dir>\n"
"	-h, --help		display this help and exit\n"
"	-V, --version		output version information and exit\n"
"	--cow			allow assignment to initialized globals\n"
"	--id			only support id (progs version 6) features\n"
"	--warn=error	treat warnings as errors\n"
"	--undefined-function-warning	warn when a function isn't defined\n"
);
		return 1;
	}

	if (CheckParm ("-V") || CheckParm ("--version")) {
		printf ("%s version %s\n", PACKAGE, VERSION);
		return 1;
	}

	if ((p = CheckParm ("--source")) && p < argc - 1) {
		strcpy (sourcedir, argv[p + 1]);
	} else {
		if ((p = CheckParm ("-s")) && p < argc - 1) {
			strcpy (sourcedir, argv[p + 1]);
		} else {
			strcpy (sourcedir, ".");
		}
	}

	if (CheckParm ("--quiet=2")) { //FIXME: getopt, need getopt </#5>
		options.quiet = 2;
	}

	if (CheckParm ("--quiet")) {
		options.quiet = 1;
	}

	if (CheckParm ("--cow")) {
		options.cow = 1;
	}

	if (CheckParm ("--id")) {
		options.version = PROG_ID_VERSION;
	}

	if (CheckParm ("--debug")) {
		options.debug = 1;
	}

	if (CheckParm ("--no-cpp")) {
		no_cpp = 1;
	}

	// FIXME eww, really must go to getopt
	if (CheckParm ("--warn=error")) {
		options.warn_error = 1;
	}

	if (CheckParm ("--undefined-function-warning")) {
		options.undefined_function_warning = 1;
	}

	if (strcmp (sourcedir, ".")) {
		printf ("Source directory: %s\n", sourcedir);
	}

	PR_Opcode_Init_Tables ();

	InitData ();

	sprintf (filename, "%s/progs.src", sourcedir);
	LoadFile (filename, (void *) &src);

	if (!(src = COM_Parse (src)))
		Error ("No destination filename.  qfcc --help for info.\n");

	strcpy (destfile, com_token);
	if (!options.quiet) {
		printf ("outputfile: %s\n", destfile);
	}
	if (options.debug) {
		char *s;
		strcpy (debugfile, com_token);

		s = debugfile + strlen (debugfile);
		while (s-- > debugfile) {
			if (*s == '.')
				break;
			if (*s == '/' || *s == '\\') {
				s = debugfile + strlen (debugfile);
				break;
			}
		}
		strcpy (s, ".sym");
		if (!options.quiet)
			printf ("debug file: %s\n", debugfile);
	}

	PR_BeginCompilation (malloc (0x100000), 0x100000);

	// compile all the files
	while ((src = COM_Parse (src))) {
#ifdef USE_CPP
		pid_t	pid;
		char	*temp1;
		char	*temp2 = strrchr (argv[0], PATH_SEPARATOR);
		char	tempname[1024];
		int 	tempfd;
#endif
		int		error;
		extern FILE *yyin;
		int yyparse(void);
		extern void clear_frame_macros (void);
		//extern int yydebug;
		//yydebug = 1;

		sprintf (filename, "%s%c%s", sourcedir, PATH_SEPARATOR, com_token);
		if (!options.quiet)
			printf ("compiling %s\n", filename);

#ifdef USE_CPP
		if (!no_cpp) {
			temp1 = getenv ("TMPDIR");
			if ((!temp1) || (!temp1[0])) {
				temp1 = getenv ("TEMP");
				if ((!temp1) || (!temp1[0])) {
					temp1 = "/tmp";
				}
			}

			snprintf (tempname, sizeof (tempname), "%s%c%sXXXXXX", temp1,
					  PATH_SEPARATOR, temp2 ? temp2 + 1 : argv[0]);
			tempfd = mkstemp (tempname);

			if ((pid = fork ()) == -1) {
				perror ("fork");
				return 1;
			}

			if (!pid) { // we're a child, check for abuse
				execlp ("cpp", "-D__QFCC__=1", "-o", tempname, filename, NULL);
				printf ("Child shouldn't reach here\n");
				exit (1);
			} else { 	// give parental guidance (or bury it in the back yard)
				int 	status;
				pid_t	rc;
					
	//			printf ("pid = %d\n", pid);
				if ((rc = waitpid (0, &status, 0 | WUNTRACED)) != pid) {
					if (rc == -1) {
						perror ("wait");
						exit (1);
					}
					printf ("*** Uhh, dude, the wrong child (%d) just died.\n"
							"*** Don't ask me, I can't figure it out either.\n",
							rc);
					exit (1);
				}
				if (WIFEXITED (status)) {
					if (WEXITSTATUS (status)) {
						printf ("cpp returned error code %d",
								WEXITSTATUS (status));
						exit (1);
					}
				} else {
					printf ("cpp returned prematurely.");
					exit (1);
				}
			}
				
			yyin = fdopen (tempfd, "r+t");
		} else {
			yyin = fopen (filename, "rt");
		}
#else
		yyin = fopen (filename, "rt");
#endif
		s_file = ReuseString (filename);
		pr_source_line = 1;
		clear_frame_macros ();
		error = yyparse () || pr_error_count;
		fclose (yyin);
#ifdef USE_CPP
		if (!no_cpp) {
			if (unlink (tempname)) {
				perror ("unlink");
				exit (1);
			}
		}
#endif
		if (error)
			return 1;
	}

	if (!PR_FinishCompilation ())
		Error ("compilation errors");

	// write progdefs.h
	crc = PR_WriteProgdefs ("progdefs.h");

	// write data file
	WriteData (crc);

	// write files.dat
	WriteFiles ();

	stop = Sys_DoubleTime ();
	if (options.quiet < 2)
		printf ("Compilation time: %.3f seconds.\n", (stop - start));
	return 0;
}