/*
 * asm.c
 * Mainline.
 *
 * Revisions:
 *
 * ------------------------------------------------------------------------
 * Simple changes by Gordon P. Vickers @ Sigfac on 3 April '86:
 *        edited as83.c so '@' will be allowed in label, variable fields.
 *        edited as85.c, add 'rim' and 'sim' to keyword list.
 *        edited as81.c, asm.c: add variable 'mkobj' to allow source file
 *        contents to turn on/off hex file generation.
 * NOTE: for specific changes use the search key: gpv
 * Further changes by Daryl Tester, Willard Goosey.  See Changes.1 for
 * details.
 * -----------------------------------------------------------------------
 *
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "asm.h"

#ifndef lint
static char
# ifdef __GNUC__
    __attribute__ ((unused))
# endif
*sccsid = "@(#)asmain.c        1.4";
#endif

/*unlike the rest of the world, Acorn RISC OS uses / as a filename
 *extension seperator, ie files are foo/asm not foo.asm
 */

#ifdef __riscos
#define FILESEP '/'
#else
#define FILESEP '.'
#endif

/*
 * This is the mainline.
 * It scans the command line,
 * collects up a source file,
 * sets option flags and calls
 * the assembler proper.
 */
int mkobj = 1;				/* hex file generation switch. - gpv */
static int cflag = 0;			/* cross reference on flag        */
static FILE *lst;			/* Listing */
static struct sym *hashtbl[HASH];
static void usage(void);
static void assemble(char *file);
static void name(char *fn, char *file, char *type, int mode);
static void ohb(FILE *filep, int byte);
static void table(void);
static void outlisting(void);
static void outerrors(void);
static void top(int mode);
static int hash(char *id);
static struct sym *lkup(char *id , char howu);

int
main(int argc, char *argv[])
{
	register int i, c;
	register char *p;
	char *file;

	file = NULL;
	for (i = 1; i < argc; ++i) {
		p = argv[i];
		if (*p++ == '-') {
			while ((c = *p++) != 0)
				switch (c) {
				case 'l':
				case 'L':
					++lflag;
					break;

				case 'c':
				case 'C':
					++lflag;
					++cflag;
					break;

				case 'n':
				case 'N':
					++nflag;
					break;

				case 's':
				case 'S':
					++lflag;
					++sflag;
					break;

				default:
					usage();
				}
		} else {
			file = argv[i];
			if (file == NULL)
				usage();
			assemble(file);
		}
	}
	/* Exit 2 if error count > 0 */
	exit(errcnt > 0 ? 2 : 0);
}

/*
 * Assemble a file.
 */

static void
assemble(char *file)
{
	char fn[40];
	int index;
	char *p;

	strncpy(title, file, 20);
	for (index = 0; index < HASH; index ++)
		hashtbl[index] = NULL;
	name(fn, file, "asm", 0);
	if ((src = fopen(fn, "r")) == NULL) {
		fprintf(stderr, "%s: cannot open\n", fn);
		exit(1);
	}
	if (lflag) {
		name(fn, file, "lst", 1);
		lst = fopen(fn, "w");
		if (lst == NULL) {
			fprintf(stderr, "%s: cannot create\n", fn);
			exit(1);
		}
	}
	if (mkobj) {  /* mkobj - 3April'86 -- gpv */
		name(fn, file, "hex", 1);
		obj = fopen(fn, "w");
		if (obj == NULL) {
			fprintf(stderr, "%s: cannot create\n", fn);
			exit(1);
		}
	}
	page = 1;
	for (pass = 0; pass < 2; ++pass) {
		skip = 0;			/* not currently skipping*/
		rewind(src);
		errcnt = 0;
		lineno = 0;
		dot->s_type = S_ABS;
		dot->s_flag = SF_ASG;
		dot->s_value = 0;
		if (pass && lflag)
			top(0);

		while (fgets(sbuf, SRCMAX , src) != NULL) {
			++lineno;
			/* Cross platform shenanigans.  Remove all line endings,
			   particularly DOS under Unix.  --dt@hcc */
			for (p = sbuf; *p != '\0'; p++)
				;
			p--;
			while (p >= sbuf && (*p == '\r' || *p == '\n')) {
				*p = '\0';
				p--;
			}
			sptr = sbuf;
			cptr = cbuf;
			eptr = ebuf;
			asmline();
			if (pass) {
				outerrors();
				if (lflag)
					outlisting();
			}
		}
	}
	if (errcnt)
		printf(" Total errors = %5d\n", errcnt);
	if (lflag) {
		if (errcnt) {
			fprintf(lst, "\n Total number of errors = %5d \n",
				errcnt);
		}
		table();
		fclose(lst);
	}
	if (mkobj) { /* mkobj 3 April'86 -- gpv */
		cflush(1);
		fclose(obj);
	}
	printf(" Total bytes assembled = %04X\n", dot->s_value);
	fclose(src);
}

/*
 * If the user screws up, put out
 * a usage message.
 * Then quit.
 * Not much sense staying around.
 */
static void
usage()
{
	fprintf(stderr, "Usage:%4s  [-ln] file file ...\n",TASK);
	fprintf(stderr, "where: \n");
	fprintf(stderr, "      l = make a listing \n");
	fprintf(stderr, " and  n = don't make an object file\n");
	exit(1);
}

/*
 * Build RSX file names.
 * The mode argument is either 0
 * which means default, or 1 which
 * means replace with.
 */
static void
name(char *fn, char *file, char *type, int mode)
{
	register char *p1, *p2;
	register int c;

	p1 = fn;
	p2 = file;

	while ((c = *p2++) && c != FILESEP)
		*p1++ = c;
	if (mode == 0) {
		if (c == FILESEP) {
			do {
				*p1++ = c;
			}
			while ((c = *p2++) != 0);
		}
		else {
			*p1++ = '.';
			p2 = type;
			while ((c = *p2++) != 0)
				*p1++ = c;
		}
	}
	else {
		*p1++ = FILESEP;
		p2 = type;
		while ((c = *p2++) != 0)
			*p1++ = c;
	}
	*p1 = '\0';
}

/*
 * Signal error.
 * Add it to the error buffer
 * if not already there.
 */
void
err(char c)
{
	register char *p;

	errcnt += 1;
	p = ebuf;
	if (eptr == &ebuf[ERRMAX - 1])
		c ='*';
	while (p < eptr)
		if (*p++ == c)
			return;
	*p++ = c;
	if (p > &ebuf[ERRMAX])
		--p;
	eptr = p;
}

/*
 * Format a line for the
 * listing.
 * More work than you would
 * think.
 * Only called if -l.
 */
static void
outlisting()
{
	register int nbytes;
	register char *cp;
	int w1, w2, w3, w4;

	if (listmode == NLIST)
		return;
	for (cp = eptr; cp < &ebuf[ERRMAX - 1]; *cp++ = ' ');
	*cp = '\0';
	if (! (--pline))
		top(1);
	if (listmode == SLIST)
		fprintf(lst, "%.6s      ", ebuf);
	else
		fprintf(lst, "%.6s %04X ", ebuf, listaddr);
	if (listmode == ALIST)
		fprintf(lst, "%9s%4d %s\n", "", lineno, sbuf);
	else {
		nbytes = cptr - cbuf;
		w1 = cbuf[0] & 0377;
		w2 = cbuf[1] & 0377;
		w3 = cbuf[2] & 0377;
		w4 = cbuf[3] & 0377;
		switch (nbytes) {
		case 0:
			fprintf(lst, "%9s", "");
			break;
		case 1:
			fprintf(lst, "%02X%7s", w1, "");
			break;
		case 2:
			fprintf(lst, "%02X%02X%5s", w1, w2, "");
			break;
		case 3:
			fprintf(lst, "%02X%02X%02X%3s", w1, w2, w3,"");
			break;
		default:
			fprintf(lst, "%02X%02X%02X%02X%1s", w1, w2, w3, w4, "");
		}
		fprintf(lst, "%4d\t%s\n", lineno, sbuf);
		cp = &cbuf[4];
		while ((nbytes -= 4) > 0) {
			if (--pline < 0)
				top(1);
			listaddr += 4;
			fprintf(lst, "%5s%04X ", "", listaddr);
			switch (nbytes) {
			case 0:
				break;
			case 1:
				w1 = cp[0] & 0377;
				fprintf(lst, "%02X\n", w1);
				break;
			case 2:
				w1 = cp[0] & 0377;
				w2 = cp[1] & 0377;
				fprintf(lst, "%02X%02X\n", w1, w2);
				break;
			case 3:
				w1 = cp[0] & 0377;
				w2 = cp[1] & 0377;
				w3 = cp[2] & 0377;
				fprintf(lst, "%02X%02X%02X\n", w1, w2, w3);
				break;
			default:
				w1 = cp[0] & 0377;
				w2 = cp[1] & 0377;
				w3 = cp[2] & 0377;
				w4 = cp[3] & 0377;
				fprintf(lst, "%02X%02X%02X%02X\n", w1, w2, w3,
					w4);
			}
			cp += 4;
		}
	}
}

/*
 * Write errors to the tty.
 */
static void
outerrors()
{
	if (lflag)
		return;
	if (eptr > ebuf) {
		*eptr = '\0';
		printf("%s\t%04d\t%s\n", ebuf, lineno, sbuf);
		/*add an eol to error line goosey@sdc.org 2014*/
	}
}

/*
 * Flush the binary code buffer.
 */

void
cflush(int lf)
{
	int chksum;			/* checksum for hex file format */
	register int i;

	if (crec == 0)
		return;
	putc(':', obj);
	ohb(obj, crec & 0xff);
	ohb(obj, (cadr >> 8) & 0xff);
	ohb(obj, cadr & 0xff);
	putc('0', obj);
	putc('0', obj);
	chksum = 0;
	chksum += crec;
	chksum += ((cadr >> 8) & 0xFF);
	chksum += (cadr & 0xFF);
	for (i = 0; i < crec; ++i) {
		ohb(obj, crbf[i] & 0xff);
		chksum += crbf[i] & 0xFF;
	}
	ohb(obj, (0 - (chksum & 0xff)) & 0xff);
	putc('\n', obj);
	crec = 0;
	if (lf) {
		/* Write End of File termination record.  Derived from
		   patch submitted by Willard Goosey. */
		chksum = 1 + ((entaddr >> 8) & 0xff) + (entaddr & 0xff);
		chksum &= 0xff;
		fprintf(obj, ":00%04X01%02X\n", entaddr, (0 - chksum) & 0xff);
	}
}

/*
**	More efficient than using the general purpose fprintf
*/
static void
ohb(FILE *filep, int byte)
{
	static char hex[] = "0123456789ABCDEF";

	putc(hex[(byte >> 4) & 0xf], filep);
	putc(hex[byte & 0xf], filep);
}

/*
 * Print out the user symbol table
 */
static void
table()
{
	register struct sym *mine;
	register struct hu *howu;
	int line, indx, count;

	count = line = 0;
	page = 1;
	fprintf(lst, "\f\n%-40s  Symbol table dump \t\t  Page %3d\n\n\n",
		title, page++);
	for (indx = 0; indx < HASH; indx++) {
		for (mine = hashtbl[indx]; mine != NULL; mine = mine->right) {
			if (line >= mxline) {
				fprintf(lst, "\f\n Symbol table dump "
					"page %3d\n\n\n", page++);
				line = 0;
			}
			fprintf(lst, "%10s = %04X  ", mine->s_name,
				mine->s_value);
			if (cflag) {
				for (howu = mine->howused; howu != NULL;
						howu = howu->next ){
					if (count >= 10) {
						fprintf(lst,"\n           "
								"        ");
						line++;
						count = 0;
					}
					fprintf(lst,"%6d %c :", howu->line,
							howu->how);
					count++;
				}
				count = 0;
				line++;
				fprintf(lst, "\n");
			}
			else {
				if (count >= 4) {
					line++;
					count = 0;
					fprintf(lst, "\n");
				}
				count++;
			}
		}
	}
	fprintf(lst, "\f\n");
	fflush(lst);
}

/*
 *
 * Top - Print the label at the top of the page
 *
 */
static void
top(int mode)
{

	if (mode)
		putc('\f', lst);
	pline = mxline;
	fprintf(lst, "\n %-40s KSE cross assembler for the %-5s", title, MICRO);
	fprintf(lst, "      page %3d", page++);
	fprintf(lst, " \n\n\n");
}

static int
hash(char *id)
{
	register unsigned	u;
	register char		*p;

	u = 0;
	p = id;
	while (*p != '\0')
		u = (u << 1) + *p++;
	return (u % HASH);
}

static struct sym *
lkup(char *id , char howu)
{
	struct sym *np;
	struct hu  *usage;

	for (np = hashtbl[hash(id)]; np != NULL; np = np->right) {
		if (strcmp(id, np->s_name) == 0) {
			if (cflag) {
				for (usage = np->howused; usage->line != lineno
						&& usage->next != NULL;
						usage = usage->next )
					;
				if (usage->line != lineno) {
					usage->next = (struct hu *)
						malloc(sizeof(*usage));
					if (usage->next != NULL) {
						usage->next->line = lineno;
						usage->next->how = howu;
						usage->next->next = NULL;
					}
				}
			}
			return np;
		}
	}
	return NULL;
}

struct sym *
install(char *id, char howu)
{
	struct sym *np,*lp;
	int hashval;

	np = lkup(id, howu);
	if (np == NULL) {
		np = (struct sym *) malloc(sizeof(*np));
		if (np == NULL) {
			fprintf(stderr, "in line %d ", lineno);
			fprintf(stderr, "Symbol table overflow\n");
			return NULL;
		}
		np->right = NULL;
		np->left = NULL;
		np->s_type = 0;
		np->s_flag = 0;
		np->s_value = 0;
		if (cflag) {
			np->howused = (struct hu *) malloc(sizeof(*np->howused));
			if (np->howused != NULL) {
				np->howused->how = howu;
				np->howused->line = lineno;
				np->howused->next = NULL;
			}
		}
		strcpy(np->s_name, id);
		hashval = hash(id);
		if (hashtbl[hashval] == NULL)
			hashtbl[hashval] = np;
		else if (strcmp(id, hashtbl[hashval]->s_name) > 0) {
			hashtbl[hashval]->left = np;
			np->right = hashtbl[hashval];
			hashtbl[hashval] = np;
		}
		else
			for (lp = hashtbl[hashval]; lp != NULL; lp = lp->right) {
				if (lp->right == NULL) {
					lp->right = np;
					np->left = lp;
					break;
				}
				else if (strcmp(id, lp->right->s_name) < 0) {
					np->right = lp->right;
					lp->right->left = np;
					lp->right = np;
					np->left = lp;
					break;
				}
			}
	}
	return np;
}
