/* app.c - 68000 assembly pre-processor
 *
 * written by Karl Lehenbauer (uunet!sugar!karl or karl@sugar.hackercorp.com)
 * original release Dec-08-1988
 *
 * PUBLIC DOMAIN -- no warranties of usefulness, suitabilitiy, correctness
 *
 * Jan-09-1990	Brett Bourbin (Selgus Limited) changed input/output file
 *		handling, incorrect text strings and added the dbra construct.
 *
 * April-28-1990  Release 2.0
 *  Rewrote filename-handling code to provide the -o option with backward
 *   compatibility. Updated the docs plus converted to ANSI C.
 */

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

#include "app.pro"

#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(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(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(void)
{
	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(int new_construct_type,int label,int 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(void)
{
	if (current_nest_level >= 0)
		--current_nest_level;
	else
		panic("'endif', 'enddo' or 'dbra' without a matching 'if' or 'do'");
}

/* generate and return new label number */
newlabel(void)
{
	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(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(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(char *s)
{
	while (*s != '\0')
	{
		if (*s == '.')
			return(YES);

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

		s++;
	}
	return(NO);
}

usage()
{
	fprintf(stderr,"\nThis program converts high-level structured assembly code\n");
	fprintf(stderr,"into regular assembly code.  See the README file for details.\n\n");
	fprintf(stderr,"usage:  app [ -o outputfile] filename.app\n");
	fprintf(stderr," (if -o is not specified, .app extension is required\n");
	fprintf(stderr,"  and output will have a .asm extension).\n\n");
	fprintf(stderr,"Hackercorp, 3918 Panorama, Missouri City, TX 77459 USA\n");
	fprintf(stderr,"tel# 713-438-4964, Usenet: uunet!sugar!karl\n");
	fprintf(stderr,"Internet/BITNET: karl@sugar.hackercorp.com\n\n");
	exit(1);
}

int main(int argc,char *argv[])
{
	fprintf(stderr,"Hackercorp public domain 68000 assembly preprocessor 2.0 28-April-1990\n");

	if (argc == 4)
	{
		if (strcmp(argv[1],"-o") != MATCH)
			usage();

		output_filename = argv[2];
		input_filename = argv[3];
	}
	else if (argc == 2)
	{
		input_filename = argv[1];
		output_filename = NULL;
	}
	else
		usage();

	if (!output_filename)
	{
		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);
	}

	/* create output filename if necessary, by overwriting input name */
	if (!output_filename)
	{
		strcpy(input_extension,".asm");
		output_filename = input_filename;
	}

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

	preprocess_file();
	return(0);
}

preprocess_file(void)
{
	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,".dbra") == MATCH)
		{
			np = &nesting_data[current_nest_level];

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

			printf("\tdbra\t%s,L%d\n",operands,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("'until' 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");
}

