/* app.c - 68000 assembly pre-processor
 *
 * written by Karl Lehenbauer (uunet!sugar!karl or karl@sugar.uu.net) 12-8-88
 * PUBLIC DOMAIN -- no warranties of usefulness, suitabilitiy, correctness
 *
 * There're README and make files that go along with this.  If you don't 
 * have them, you've been ripped off.
 */

#include <stdio.h>
#include <assert.h>

#define YES 1
#define NO 0

#define MATCH 0

int current_nest_level = -1;
int master_label_number = 0;
unsigned int line_number = 0;

char *label;
char *opcode;
char *operands;
char *comment;

char *input_filename, *input_extension, *output_filename;

panic(s)
char *s;
{
	fprintf(stderr,"app: %s at line %d\n",s,line_number);
	fclose(stdout);
	unlink(output_filename);
	exit(1);
}

/* given a pointer to a line, make the global character pointers "label",
 * "opcode", "operands" and "comment" point to the various fields within
 * the line, or NULL for any that aren't present.  Note that crackline
 * butchers the line up by writing null bytes into it.
 */
crackline(s)
register char *s;
{
	register char *p = s;

	label = NULL;
	opcode = NULL;
	operands = NULL;
	comment = NULL;

	/* suck up leading blanks */
	while ((*p == ' ') || (*p == '\t'))
		p++;

	/* if end of line, return -- it's an empty line */
	if (*p == '\0')
		return;

	/* if the first nonblank char is a semicolon, it's a comment */
	if (*p == ';')
	{
		comment = s;
		return;
	}

	/* if the very first char isn't blank (and we already know it's
	   not a semicolon), it's a label
	 */
	if ((*s != ' ') && (*s != '\t'))
	{
		label = s;
		p = s + 1;
		while (*p != ' ' && *p != '\t' && *p != '\0') p++;
		if ((*p == ' ') || (*p == '\t'))
		{
			*p = '\0';
			p++;
		}
		else
			return;
	}
	else	/* there isn't a label, suck up spaces to next parm */
	{
		p = s;
		while ((*p == ' ' || *p == '\t')) p++;
		if (*p == '\0')
			return;
	}

	/* if the next parm is a comment, assign and we're done */
	if (*p == ';')
	{
		comment = p;
		return;
	}

	/* we're at the opcode, assign it and terminate with \0 if spaces
	 * follow, else we're done */
	opcode = p;
	while (*p != ' ' && *p != '\t' && *p != '\0') p++;
	if ((*p == ' ') || (*p == '\t'))
	{
		*p = '\0';
		p++;
	}
	else
		return;

	/* if the next parm is a comment, assign and we're done */
	if (*p == ';')
	{
		comment = p;
		return;
	}

	operands = p;
	while (*p != ' ' && *p != '\t' && *p != '\0') p++;
	if ((*p == ' ') || (*p == '\t'))
	{
		*p = '\0';
		p++;
	}
	else
		return;

	comment = p;

}

#ifdef DEBUG
dumpit()
{
	printf("label: %s, opcode %s, operands %s, comment %s\n",label,opcode,operands,comment);
}
#endif

char s[255], ssave[255];

#define IF_STATEMENT_TYPE 1
#define ELSE_STATEMENT_TYPE 2
#define DO_STATEMENT_TYPE 3

#define MAX_NESTING_LEVELS 32

struct nesting_context
{
	int construct_type;
	int label_number;
	int second_label_number;
};

struct nesting_context nesting_data[MAX_NESTING_LEVELS];

/* push - push a nesting construct context, executed on .if and .do */
push(new_construct_type,label,second_label)
int new_construct_type, label,second_label;
{
	struct nesting_context *np;

	if (++current_nest_level >= MAX_NESTING_LEVELS)
		panic("too many nesting levels");

	np = &nesting_data[current_nest_level];
	np->construct_type = new_construct_type;
	np->label_number = label;
	np->second_label_number = second_label;
}

/* pop - discard the top nesting context, checking for underflow
 *  called when conditionals have been successfully closed
 */
pop()
{
	if (current_nest_level >= 0)
		--current_nest_level;
	else
		panic("'endif' or 'enddo' without a matching 'if' or 'do'");
}

/* generate and return new label number */
newlabel()
{
	return(master_label_number++);
}

/* structure in support of reversing the sense of conditionals */
struct condition_code_struct
{
	char *condition;
	char *reverse_condition;
};

#define N_CONDITION_TYPES 14

struct condition_code_struct condition_code_array[N_CONDITION_TYPES] =
{
	{"ne", "eq"},
	{"eq", "ne"},
	{"lt", "ge"},
	{"ge", "lt"},
	{"le", "gt"},
	{"gt", "le"},
	{"cc", "cs"},
	{"cs", "cc"},
	{"vc", "vs"},
	{"vs", "vc"},
	{"hi", "ls"},
	{"ls", "hi"},
	{"pl", "mi"},
	{"mi", "pl"},
};

/* given a pointer to text containing a condition code, returns the 
 * a pointer to text containing a condition with reverse sense of
 * the one passed as an argument. Bombs if the sense doesn't make sense
 */
char *reverse_sense_of(s)
char *s;
{
	struct condition_code_struct *ccp;
	int i;

	for (i = 0, ccp = condition_code_array; i < N_CONDITION_TYPES; i++, ccp++)

		if (strcmp(s,ccp->condition) == MATCH)
			return(ccp->reverse_condition);

	panic("invalid condition code in 'if', 'elseif', 'while' or 'until'");
}

/* print the label name corresponding to the number specified, should be
 * called in more places in the program where printf is used instead, so
 * you have to change it in several places if you want to change what the
 * labels look like
 */
print_label(i)
int i;
{
	printf("L%d\n",i);
}

/* to prevent parsing every line, looks_promising is a quick hack to see
 * if its a line we're interested in or not.  If the line doesn't have
 * a period as the first non-space or tab char, it's not promising, so
 * we don't crack the line with the full blown line parses.  This is a
 * performance hack.
 */
looks_promising(s)
char *s;
{
	while (*s != '\0')
	{
		if (*s == '.')
			return(YES);

		if (*s != ' ' && *s != '\t')
			return(NO);

		s++;
	}
	return(NO);
}

usage()
{
	fprintf(stderr,"usage: app filename.app\n");
	exit(1);
}

main(argc,argv)
int argc;
char *argv[];
{
	fprintf(stderr,"Hackercorp public domain 68000 assembly preprocessor 0.0 12-9-88\n");

	if (argc != 2)
		usage();

	input_filename = argv[1];
	input_extension = input_filename + strlen(input_filename) - 4;
	if (strcmp(".app",input_extension) != MATCH)
		usage();

	if (freopen(input_filename,"r",stdin) == NULL)
	{
		perror(input_filename);
		exit(5);
	}

	/* kludgily create output filename */
	output_filename = input_filename;
	strcpy(input_extension,".asm");

	if (freopen(input_filename,"w",stdout) == NULL)
	{
		perror(input_filename);
		exit(5);
	}

	preprocess_file();
}

preprocess_file()
{
	struct nesting_context *np;
	int i;

	/* for all lines in the file */
	while (gets(s) != NULL)
	{
		line_number++;	/* count the line */

		/* if it's not promising, copy it to output and go on */
		if (!looks_promising(s))
		{
			printf("%s\n",s);
			goto more;
		}

		strcpy(ssave,s);
		crackline(s);

		if (strcmp(opcode,".if") == MATCH)
		{
			printf("\tb%s\tL%d\n",reverse_sense_of(operands),i = newlabel());
			push(IF_STATEMENT_TYPE,i,-1);
		}
		else if (strcmp(opcode,".else") == MATCH)
		{
			np = &nesting_data[current_nest_level];

			if (np->construct_type != IF_STATEMENT_TYPE)
				panic("'else' without 'if'");

			printf("\tbra\tL%d\n",i = newlabel());
			/* print the label from the top context */
			print_label(np->label_number);
			np->label_number = i;
			np->construct_type = ELSE_STATEMENT_TYPE;
		}
		else if (strcmp(opcode,".endif") == MATCH)
		{
			np = &nesting_data[current_nest_level];

			if ((np->construct_type != IF_STATEMENT_TYPE)
			  && (np->construct_type != ELSE_STATEMENT_TYPE))
				panic("'endif' without 'if' or 'else'");

			print_label(np->label_number);
			pop();
		}
		else if (strcmp(opcode,".elseif") == MATCH)
		{
			np = &nesting_data[current_nest_level];

			if (np->construct_type != IF_STATEMENT_TYPE)
				panic("'else' without 'if'");

			printf("\tbra\tL%d\n",i = newlabel());
			/* print the label from the top context */
			print_label(np->label_number);
			np->label_number = i;
			printf("\tb%s\tL%d\n",reverse_sense_of(operands),i);
		}
		else if (strcmp(opcode,".do") == MATCH)
		{
			print_label(i = newlabel());
			push(DO_STATEMENT_TYPE,i,newlabel());
		}
		else if (strcmp(opcode,".while") == MATCH)
		{
			np = &nesting_data[current_nest_level];

			if (np->construct_type != DO_STATEMENT_TYPE)
				panic("'while' without 'do'");

			printf("\tb%s\tL%d\n",reverse_sense_of(operands),np->second_label_number);
		}
		else if (strcmp(opcode,".enddo") == MATCH)
		{
			np = &nesting_data[current_nest_level];

			if (np->construct_type != DO_STATEMENT_TYPE)
				panic("'enddo' without 'do'");

			printf("\tbra\tL%d\n",np->label_number);
			print_label(np->second_label_number);
			pop();

		}
		else if (strcmp(opcode,".until") == MATCH)
		{
			np = &nesting_data[current_nest_level];

			if (np->construct_type != DO_STATEMENT_TYPE)
				panic("'while' without 'do'");

			printf("\tb%s\tL%d\n",operands,np->second_label_number);
		}
		else
			printf("%s\n",ssave);
	more: ;
	}
	if (current_nest_level >= 0)
		panic("didn't close all your control structures");
}
