/*
 * (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands.
 * See the copyright notice in the ACK home directory, in the file "Copyright".
 *
 * Author: Hans van Staveren
 */
#include <assert.h>
#include <stdlib.h>
#include "param.h"
#include "types.h"
#include "tes.h"
#include <em_spec.h>
#include <em_pseu.h>
#include <em_mnem.h>
#include <em_flag.h>
#include "alloc.h"
#include "line.h"
#include "putline.h"
#include "util.h"
#include "lookup.h"
#include "proinf.h"
#include "optim.h"
#include "ext.h"

#define outbyte(b) putc(b,outfile)

/* Forward declarations. */
static void numlab(register num_p);
static void putargs(register arg_p);
static void putstr(register argb_p);

void putlines(register line_p lnp)
{
	register arg_p ap;
	line_p temp;
	register int instr;
	short curlin = -2;
	short thislin;

	while (lnp != (line_p) 0)
	{
		instr = lnp->l_instr & BMASK;
		switch (lnp->l_optyp)
		{
			case OPSYMBOL:
				if ((lnp->l_instr & BMASK) == ps_sym)
					outdef(lnp->l_a.la_sp);
				else
					outocc(lnp->l_a.la_sp);
				break;
			case OPSVAL:
				outocc(lnp->l_a.la_sval.lasv_sp);
				break;
#ifdef LONGOFF
			case OPLVAL:
				outocc(lnp->l_a.la_lval.lalv_sp);
				break;
#endif
			case OPLIST:
				ap = lnp->l_a.la_arg;
				while (ap != (arg_p) 0)
				{
					switch (ap->a_typ)
					{
						case ARGSYM:
							outocc(ap->a_a.a_sp);
							break;
						case ARGVAL:
							outocc(ap->a_a.a_val.av_sp);
							break;
					}
					ap = ap->a_next;
				}
				break;
		}

		/*
		 * global symbols now taken care of
		 */

		switch (instr)
		{
			case ps_sym:
				break;
			case op_lni:
				if (curlin != -2)
					curlin++;
				outinst(instr);
				break;
			case op_lin:
				switch (lnp->l_optyp)
				{
					case OPNO:
					case OPOFFSET:
					case OPNUMLAB:
					case OPSYMBOL:
					case OPSVAL:
					case OPLVAL:
					case OPLIST:
						outinst(instr);
						goto processoperand;
					case OPSHORT:
						thislin = lnp->l_a.la_short;
						break;
					default:
						thislin = (lnp->l_optyp & BMASK) - Z_OPMINI;
						break;
				}
				if (thislin == curlin && !nflag)
				{
					temp = lnp->l_next;
					oldline(lnp);
					lnp = temp;
					OPTIM(O_LINGONE);
					continue;
				}
				else if (thislin == curlin + 1 && !nflag)
				{
					instr = op_lni;
					outinst(instr);
					temp = lnp->l_next;
					oldline(lnp);
					OPTIM(O_LINLNI);
					lnp = newline(OPNO);
					lnp->l_next = temp;
					lnp->l_instr = instr;
				}
				else
				{
					outinst(instr);
				}
				curlin = thislin;
				break;
			case op_lab:
				curlin = -2;
				break;
			default:
				if ((em_flag[instr - sp_fmnem] & EM_FLO) == FLO_P)
					curlin = -2;
				outinst(instr);
		}
		processoperand: switch (lnp->l_optyp)
		{
			case OPNO:
				if ((em_flag[instr - sp_fmnem] & EM_PAR) != PAR_NO)
					outbyte((byte) sp_cend);
				break;
			default:
				outint((lnp->l_optyp & BMASK) - Z_OPMINI);
				break;
			case OPSHORT:
				outint(lnp->l_a.la_short);
				break;
#ifdef LONGOFF
			case OPOFFSET:
				outoff(lnp->l_a.la_offset);
				break;
#endif
			case OPNUMLAB:
				if (instr == op_lab)
					numlab(lnp->l_a.la_np->n_repl);
				else if (instr < sp_fpseu) /* plain instruction */
					outint((short) lnp->l_a.la_np->n_repl->n_number);
				else
					outnum(lnp->l_a.la_np->n_repl);
				break;
			case OPSYMBOL:
				outsym(lnp->l_a.la_sp);
				break;
			case OPSVAL:
				outbyte((byte) sp_doff);
				outsym(lnp->l_a.la_sval.lasv_sp);
				outint(lnp->l_a.la_sval.lasv_short);
				break;
#ifdef LONGOFF
			case OPLVAL:
				outbyte((byte) sp_doff);
				outsym(lnp->l_a.la_lval.lalv_sp);
				outoff(lnp->l_a.la_lval.lalv_offset);
				break;
#endif
			case OPLIST:
				putargs(lnp->l_a.la_arg);
				switch (instr)
				{
					case ps_con:
					case ps_rom:
					case ps_mes:
						outbyte((byte) sp_cend);
				}
		}
		/*
		 * instruction is output now.
		 * remove its useless body
		 */

		temp = lnp->l_next;
		oldline(lnp);
		lnp = temp;
		if (ferror(outfile))
			error("write error");
	}
}

static void putargs(register arg_p ap)
{

	while (ap != (arg_p) 0)
	{
		switch (ap->a_typ)
		{
			default:
				assert(FALSE);
				break;
			case ARGOFF:
				outoff(ap->a_a.a_offset);
				break;
			case ARGNUM:
				outnum(ap->a_a.a_np->n_repl);
				break;
			case ARGSYM:
				outsym(ap->a_a.a_sp);
				break;
			case ARGVAL:
				outbyte((byte) sp_doff);
				outsym(ap->a_a.a_val.av_sp);
				outoff(ap->a_a.a_val.av_offset);
				break;
			case ARGSTR:
				outbyte((byte) sp_scon);
				putstr(&ap->a_a.a_string);
				break;
			case ARGICN:
				outbyte((byte) sp_icon);
				goto casecon;
			case ARGUCN:
				outbyte((byte) sp_ucon);
				goto casecon;
			case ARGFCN:
				outbyte((byte) sp_fcon);
				casecon: outint(ap->a_a.a_con.ac_length);
				putstr(&ap->a_a.a_con.ac_con);
				break;
		}
		ap = ap->a_next;
	}
}

static void putstr(register argb_p abp)
{
	register argb_p tbp;
	register int length;

	length = 0;
	tbp = abp;
	while (tbp != (argb_p) 0)
	{
		length += tbp->ab_index;
		tbp = tbp->ab_next;
	}
	outint(length);
	while (abp != (argb_p) 0)
	{
		for (length = 0; length < abp->ab_index; length++)
			outbyte((byte ) abp->ab_contents[length]);
		abp = abp->ab_next;
	}
}

void outdef(register sym_p sp)
{

	/*
	 * The surrounding If statement is removed to be friendly
	 * to Backend writers having to deal with assemblers
	 * not following our conventions.
	 if ((sp->s_flags&SYMOUT)==0) {
	 */
	sp->s_flags |= SYMOUT;
	if (sp->s_flags & SYMGLOBAL)
	{
		outinst(sp->s_flags & SYMPRO ? ps_exp : ps_exa);
		outsym(sp);
	}
	/*
	 }
	 */
}

void outocc(register sym_p sp)
{

	if ((sp->s_flags & SYMOUT) == 0)
	{
		sp->s_flags |= SYMOUT;
		if ((sp->s_flags & SYMGLOBAL) == 0)
		{
			outinst(sp->s_flags & SYMPRO ? ps_inp : ps_ina);
			outsym(sp);
		}
	}
}

void outpro(void)
{
	outdef(curpro.symbol);
	outinst(ps_pro);
	outsym(curpro.symbol);
	outoff(curpro.localbytes);
}

void outend(void)
{
	outinst(ps_end);
	outoff(curpro.localbytes);
}

void outinst(int m)
{
	outbyte((byte ) m);
}

void outoff(offset off)
{
#ifdef LONGOFF
	if ((short) off == off)
#endif
		outint((short) off);
#ifdef LONGOFF
	else
	{
		outbyte((byte) sp_cst4);
		outshort((short) (off & 0177777L));
		outshort((short) (off >> 16));
	}
#endif
}

void outint(short i)
{

	if (i >= -sp_zcst0 && i < sp_ncst0 - sp_zcst0)
		outbyte((byte) (i+sp_zcst0+sp_fcst0));
	else
	{
		outbyte((byte) sp_cst2);
		outshort(i);
	}
}

void outshort(short i)
{
	outbyte((byte) (i&BMASK));
	outbyte((byte ) (i >> 8));
}

static void numlab(register num_p np)
{
	if (np->n_number < sp_nilb0)
		outbyte((byte) (np->n_number + sp_filb0));
	else
		outnum(np);
}

void outnum(register num_p np)
{
	if (np->n_number < 256)
	{
		outbyte((byte) sp_ilb1);
		outbyte((byte ) (np->n_number));
	}
	else
	{
		outbyte((byte) sp_ilb2);
		outshort((short) np->n_number);
	}
}

void outsym(register sym_p sp)
{
	register char *p;
	register unsigned num;

	if (sp->s_name[0] == '.')
	{
		num = atoi(&sp->s_name[1]);
		if (num < 256)
		{
			outbyte((byte) sp_dlb1);
			outbyte((byte ) (num));
		}
		else
		{
			outbyte((byte) sp_dlb2);
			outshort((short) num);
		}
	}
	else
	{
		p = sp->s_name;
		/* This is not a real warning, because s_name is dynamically
		 * allocated as necessary.
		 */
		while (*p && p < &sp->s_name[IDL])
			p++;
		num = p - sp->s_name;
		outbyte((byte) (sp->s_flags&SYMPRO ? sp_pnam : sp_dnam));
		outint((short) num);
		p = sp->s_name;
		while (num--)
			outbyte((byte ) *p++);
	}
}
