/*
* as81.c
* Assemble a line.
*/
#include <stdio.h>
#include "asm.h"

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

extern int mkobj, nflag;

static void ascii(char delim);
static int getreg(int type);
static void byte(int b);
static void comma(void);
static void code3(int x, int r);
static void code4(int x, int r);
static struct sym *lookup(char *id);
static struct sym *lookupreg(char *id);
static void codeb(int b);
static void codew(int w);

/*
* Assemble a line.
*/
void
asmline()
{
	register struct sym *sp, *lsp;
	register int rs, rd;
	char	*ptr;
	int	 n;
	int a, c, opcode;
	char id[NCPS];

	listaddr = dot->s_value;
	listmode = skip ? NLIST : SLIST; /* if skiping then no list */
	lsp = NULL;
	c = getnb();

	do {
		if (c == '\0' || c == ';' || c == '\n' || c == '\f')
			/* + ^L gpv */
			break;

		if (c == '-') {		/* --- gpv 3april'86 */
			mkobj = 0;
			break;
		}
		if (c == '+') {
			mkobj = nflag;
			break;
		}

		if (c == '!')
			c = getnb();
		if (! alpha(c)) {
			if (! skip)
				err('L');
			break;
		}
		getid(c, id);
		c = getnb();
		if (c == ':') {
			if (skip)
				break;
			sp = install(id, 'd');
			if (pass == 0) {
				if (sp->s_type != S_UND &&
				    (sp->s_flag & SF_ASG) == 0)
					sp->s_flag |= SF_MDF;
				sp->s_type = S_ABS;
				sp->s_value = dot->s_value;
			}
			else {
				if ((sp->s_flag & SF_MDF) != 0)
					err('L');
				if (sp->s_type != S_ABS ||
				    sp->s_value != dot->s_value)
					err('L');
			}
			listmode = ALIST;
			continue;
		}
		else
			putback(c);
		sp = lookup(id);
		if (sp == NULL) {
			if (! skip) {
				lsp = install(id, '=');
				c = getnb();
				getid(c, id);
				sp = lookup(id);
				if (sp == NULL) {
					err('O');
					break;
				}
			}
			else
				return;
		}
		else if (skip && (sp->s_type != S_ENDIF))
			return;
		else if ((skip || inif) && (sp->s_type == S_ENDIF)) {
			skip = 0;
			if (! (--inif))
				inif = 0;
			listmode = NLIST;
			return;
		}
		listmode = CLIST;
		if (lsp != NULL) {
			switch (sp->s_type) {
			case S_EQU:
			case S_SET:
				if (lsp->s_type != S_UND &&
						(lsp->s_flag & SF_ASG) == 0)
					err('L');
				lsp->s_type = S_ABS;
				lsp->s_flag |= SF_ASG;
				lsp->s_value = listaddr = expr();
				listmode = ALIST;
				lsp = NULL;
				break;
			case S_MACRO:
				lsp = NULL;
				err('M');
				break;
			}
		}
		if (lsp != NULL) {
			if (pass == 0) {
				if (lsp->s_type != S_UND &&
						(lsp->s_flag & SF_ASG) == 0)
					lsp->s_flag |= SF_MDF;
				lsp->s_type = S_ABS;
				lsp->s_value = dot->s_value;
			}
			else {
				if ((lsp->s_flag & SF_MDF) != 0)
					err('L');
				if (lsp->s_type != S_ABS ||
						lsp->s_value != dot->s_value)
					err('p');
			}
			lsp = NULL;
		}
		listmode = CLIST;
		opcode = sp->s_value;
		switch (sp->s_type) {
		case S_EQU:
		case S_SET:
		case S_MACRO:
			break;

		case S_IF:
			inif += 1;
			skip = (expr() == 0);
			listmode = NLIST;
			return;

		case S_NAME:
			n = TITLE_LN;
			c = getnb();
			ptr = title;
			while ((*ptr++ = getraw()) != c && --n > 0)
				;
			*(--ptr) = '\0';
			return;

		case S_ORG:
			listaddr = dot->s_value = expr();
			break;

		case S_ENTRY:
			entaddr = expr();
			return;

		case S_BYTE: /* DB handler. this is where the bug was*/
		/*the problem was that expressions of the form "'X' op number" failed
		 *because this would see the opening qoute and hand off the field to
		 *ascii() which runs till it finds a terminator and then be confused
		 *when it saw an operator. Worse, "+" and "-" would fail silently,
		 *because of the above code to turn on/off code generation!!!!
		 *
		 *I tried to be clever about this several times and failed. So be it,
		 *here I brute-force the correct behavior with some duplicated code,
		 *goosey 5/8/2020
		 */
			do {
				c = getnb();
				if (c == '\'')  /*starting with a char!*/
				{
				   int s1,s2; /* we need extra chars for this case*/

				   s1=getnb();
				   s2=getnb(); /*need to peek 2 chars ahead!*/
				   if(s2 == '\'') /*if a 1 char string*/
				      {              /*then its just a byte, run it through*/
				         putback(c); /*expr() so things like 'x'+80h work*/
				         putback(s1);
				         putback(s2);
				         a = expr();
				         byte(a);
				         codeb(a);
				      }
				      else    /*multi-char string, hand off to ascii() emitter*/
				      {
				        putback(s1);
				        putback(s2);
					ascii(c); /*emit chars until we find a terminating quote*/
				      }
				}
				else {   /*regular byte expression (not staring with a string!)*/
					putback(c);
					a = expr();
					byte(a);
					codeb(a);
				}
			} while ((c = getnb()) == ',');
			putback(c);
			break;

		case S_WORD:
			/* Reworked after hitting trailing , bug in M100
			   FIG-FORTH port. --dt@hcc */
			while (1) {
				c = getnb();
				if (c == '\0')
					break;
				putback(c);
				codew(expr());
				c = getnb();
				if (c != ',') {
					putback(c);
					break;
				}
			}
			break;

		case S_BLKB:
		case S_BLKZ:
			a = expr();
			if (sp->s_type == S_BLKZ) {
				/* DSZ - zero out bytes */
				while (a--)
					codeb(0);
			} else {
				/* DS - allocate without zeroing */
				dot->s_value += a;
			}
			listmode = ALIST;
			break;

		case S_OP1:
			codeb(opcode);
			break;

		case S_OP2:
			a = expr();
			byte(a);
			codeb(opcode);
			codeb(a);
			break;

		case S_OP3:
			rd = getreg(S_REG);
			comma();
			a = expr();
			byte(a);
			code3(opcode, rd);
			codeb(a);
			break;

		case S_OP4:
			rd = getreg(S_REG);
			comma();
			rs = getreg(S_REG);
			codeb(opcode | rd << 3 | rs);
			break;

		case S_OP5:
			rd = getreg(S_REG);
			if (rd != B && rd != D)
				err('a');
			code4(opcode, rd);
			break;

		case S_OP6:
			rd = getreg(S_REG);
			if (rd == PSW) {
				err('R');
			}
			code4(opcode, rd);
			break;

		case S_OP7:
			rd = getreg(S_REG);
			if (rd == SP)
				err('a');
			code4(opcode, rd);
			break;

		case S_OP8:
			a = expr();
			if (a < 0 || a > 7)
				err('t');
			code3(opcode, a & 07);
			break;

		case S_OP9:
			rs = getreg(S_REG);
			if (opcode == INR || opcode == DCR)
				code3(opcode, rs);
			else
				codeb(opcode | rs);
			break;

		case S_OP10:
			rd = getreg(S_REG);
			if (rd == PSW)
				err('a');
			comma();
			a = expr();
			code4(opcode, rd);
			codew(a);
			break;

		case S_OP11:
			a = expr();
			codeb(opcode);
			codew(a);
			break;

		default:
			err('C');
		}	/* end of case statement */
	} while ((c = getnb()) != '\0' && c != ';');
}

/*
 * Process the body of .ascii
 * and .asciz pseudo ops.
 * The z flag is true for a
 * .asciz
 */
static void
ascii(char delim)
{
	register int c;

	while ((c = getmap()) != '\0' && c != delim)
		codeb(c);
	if (c == '\0')
		err('S');
}

/*
 * Get a register.
 * Type is either S_REG or S_REGP.
 */
static int
getreg(int type)
{
	register struct sym *sp;
	register int c;
	char id[NCPS];

	c = getnb();
	if (! alpha(c)) {
		err('R');
		putback(c);
		return 0;
	}
	getid(c, id);
	sp = lookupreg(id);
	if (sp == NULL || type != sp->s_type) {
		err('R');
		return 0;
	}
	return sp->s_value;
}

/*
 * Insure that the value of
 * an expression is a legal
 * byte value.
 * Error if not.
 */
static void
byte(int b)
{
	if ((b & 0200) != 0)
		b |= ~0377;
	if (b > 127 || b < -128)
		err('R');
}

/*
 * Check for `,'.
 */
static void
comma()
{
	if (getnb() != ',')
		err('a');
}

/*
 * Build instructions of the
 * general form xxrrrxxx.
 */
static void
code3(int x, int r)
{
	codeb(x | r << 3);
}

/*
 * Build instructions of the
 * general form xxrrxxxx.
 */
static void
code4(int x, int r)
{
	codeb(x | (r & 6) << 3);
}

/*
 * Lookup id in pst.
 */
static struct sym *
lookup(char *id)
{
	int   i, lo, hi, mid;
	char *p, *name;

	for (lo = 0, hi = pstsiz - 1; lo <= hi; )
	{
		mid = (lo + hi) / 2;
		p = id;
		name = pst[mid].s_name;
		while (*p != '\0' && *p == *name)
			++p, ++name;
		if ((i = *p - *name) == 0)
			break;
		else if (i < 0)
			hi = mid - 1;
		else
			lo = mid + 1;
	}
	if (lo > hi)
		return NULL;
	else
		return &pst[mid];
}

/*
 * Lookup id in rst.
 */
static struct sym *
lookupreg(char *id)
{
	int   i, lo, hi, mid;
	char *p, *name;

	for (lo = 0, hi = rstsiz - 1; lo <= hi; )
	{
		mid = (lo + hi) / 2;
		p = id;
		name = rst[mid].s_name;
		while (*p != '\0' && *p == *name)
			++p, ++name;
		if ((i = *p - *name) == 0)
			break;
		else if (i < 0)
			hi = mid - 1;
		else
			lo = mid + 1;
	}
	if (lo > hi)
		return NULL;
	else
		return &rst[mid];
}

/*
 * Output code byte.
 * Save it in the per line buffer for outlisting.
 * Update dot.
 */
static void
codeb(int b)
{
	b &= 0377;
	if (cptr < &cbuf[CLMAX])
		*cptr++ = b;
	if (pass && mkobj) {  /* mkobj 3 April'86 -- gpv */
		if (crec >= CBMAX || cadr + crec != dot->s_value) {
			cflush(0);
			cadr = dot->s_value;
		}
		crbf[crec++] = b;
	}
	++dot->s_value;
}

/*
 * Output a word.
 * Low then high.
 */
static void
codew(int w)
{
	if (BSWAP) codeb(w >> 8);
	codeb(w);
	if (! BSWAP) codeb(w >> 8);
}
