/* $Id$ */
/*
 * (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands.
 * See the copyright notice in the ACK home directory, in the file "Copyright".
 */
#include <stdio.h>
#include <em_spec.h>
#include <em_pseu.h>
#include <em_mnem.h>
#include <em_flag.h>
#include <em_mes.h>
#include "../share/types.h"
#include "ca.h"
#include "ca_put.h"
#include "../share/debug.h"
#include "../share/def.h"
#include "../share/map.h"
#include "../share/alloc.h"

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

FILE *outfile;

STATIC proc_p thispro;

STATIC void outinst(int m) {

	outbyte( (byte) m );
}

STATIC void coutshort(short i) {

	outbyte( (byte) (i&BMASK) );
	outbyte( (byte) (i>>8) );
}

STATIC void coutint(short i) {

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

STATIC void coutoff(offset off) {

	if ((short) off == off)
		coutint((short) off);
	else {
		outbyte( (byte) sp_cst4) ;
		coutshort( (short) (off&0177777L) );
		coutshort( (short) (off>>16) );
	}
}


STATIC void outsym(const char *s, int t)
{
	register byte *p;
	register unsigned num;

	if (s[0] == '.') {
		num = atoi(&s[1]);
		if (num < 256) {
			outbyte( (byte) sp_dlb1) ;
			outbyte( (byte) (num) );
		} else {
			outbyte( (byte) sp_dlb2) ;
			coutshort((short) num);
		}
	} else {
		p= (byte*) s;
		while (*p && p < &s[IDL])
			p++;
		num = p - s;
		outbyte( (byte) t);
		coutint((short) num);
		p = (byte*) s;
		while (num--)
			outbyte( (byte) *p++ );
	}
}


STATIC void outdsym(dblock_p dbl)
{
	if (dnames[dbl->d_id]) outsym(dnames[dbl->d_id],sp_dnam);
}


STATIC void outpsym(proc_p p)
{
	outsym(pnames[p->p_id],sp_pnam);
}


STATIC void outddef(short id) {

	dblock_p dbl;

	dbl = dmap[id];
	dbl->d_flags2 |= DF_SYMOUT;
	if (dbl->d_flags1 & DF_EXTERNAL) {
		outinst(ps_exa);
		outdsym(dbl);
	}
}

STATIC void outpdef(proc_p p) {
	p->p_flags2 |= PF_SYMOUT;
	if (p->p_flags1 & PF_EXTERNAL) {
		outinst(ps_exp);
		outpsym(p);
	}
}


STATIC void outdocc(obj_p obj) {
	dblock_p dbl;

	dbl = obj->o_dblock;
	if ((dbl->d_flags2 & DF_SYMOUT) == 0) {
		dbl->d_flags2 |= DF_SYMOUT;
		if (dnames[dbl->d_id] != 0 && 
		    (dbl->d_flags1 & DF_EXTERNAL) == 0) {
			outinst(ps_ina);
			outdsym(dbl);
		}
	}
}


STATIC void outpocc(proc_p p) {
	if ((p->p_flags2 & PF_SYMOUT) == 0) {
		p->p_flags2 |= PF_SYMOUT;
		if ((p->p_flags1 & PF_EXTERNAL) == 0) {
			outinst(ps_inp);
			outpsym(p);
		}
	}
}


STATIC void coutobject(obj_p obj)
{
	/* In general, an object is defined by a global data
	 * label and an offset. There are two special cases:
	 * the label is omitted if the object is part of the current
	 * hol block; the offset is omitted if it is 0 and the label
	 * was not omitted.
	 */
	if (dnames[obj->o_dblock->d_id] == 0) {
		coutoff(obj->o_off);
	} else {
		if (obj->o_off == 0) {
			outdsym(obj->o_dblock);
		} else {
			outbyte((byte) sp_doff);
			outdsym(obj->o_dblock);
			coutoff(obj->o_off);
		}
	}
}


STATIC void cputstr(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;
	}
	coutint(length);
	while (abp != (argb_p) 0) {
		for (length=0;length<abp->ab_index;length++)
			outbyte( (byte) abp->ab_contents[length] );
		abp = abp->ab_next;
	}
}


STATIC void outnum(int n)
{
	if (n < 256) {
		outbyte((byte) sp_ilb1);
		outbyte((byte) n);
	} else {
		outbyte((byte) sp_ilb2);
		coutshort((short) n);
	}
}


STATIC void numlab(int n)
{
	if (n < sp_nilb0) {
		outbyte((byte) (n + sp_filb0));
	} else {
		outnum(n);
	}
}


STATIC void cputargs(line_p lnp)
{
	register arg_p ap;
	int cnt = 0;
	ap = ARG(lnp);
	while (ap != (arg_p) 0) {
		switch(ap->a_type) {
			case ARGOFF:
				coutoff(ap->a_a.a_offset);
				break;
			case ARGOBJECT:
				coutobject(ap->a_a.a_obj);
				break;
			case ARGPROC:
				outpsym(ap->a_a.a_proc);
				break;
			case ARGINSTRLAB:
				outnum(ap->a_a.a_instrlab);
				break;
			case ARGSTRING:
				outbyte((byte) sp_scon);
				cputstr(&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:
				coutint(ap->a_a.a_con.ac_length);
				cputstr(&ap->a_a.a_con.ac_con);
				break;
			default:
				assert(FALSE);
		}
		ap = ap->a_next;
		/* Avoid generating extremely long CON or ROM statements */
		if (cnt++ > 10 && ap != (arg_p) 0 && 
		    (INSTR(lnp) == ps_con || INSTR(lnp) == ps_rom)) {
			cnt = 0;
			outbyte((byte) sp_cend);
			outinst(INSTR(lnp));
		}
	}
}



STATIC void outoperand(line_p lnp)
{
	/* Output the operand of instruction lnp */

	switch(TYPE(lnp)) {
		case OPNO:
			if (INSTR(lnp) <= sp_lmnem &&
			    (em_flag[INSTR(lnp)-sp_fmnem]&EM_PAR) != PAR_NO) {
				outbyte((byte) sp_cend);
			}
			break;
		case OPSHORT:
			if (INSTR(lnp) == ps_sym) {
				outsym(dnames[SHORT(lnp)],sp_dnam);
			} else {
				coutint(SHORT(lnp));
			}
			break;
		case OPOFFSET:
			coutoff(OFFSET(lnp));
			break;
		case OPINSTRLAB:
			if (INSTR(lnp) == op_lab) {
				numlab(INSTRLAB(lnp));
			} else {
				if (INSTR(lnp) < sp_fpseu) {
					coutint(INSTRLAB(lnp));
				} else {
					numlab(INSTRLAB(lnp));
				}
			}
			break;
		case OPOBJECT:
			coutobject(OBJ(lnp));
			break;
		case OPPROC:
			outpsym(PROC(lnp));
			break;
		case OPLIST:
			cputargs(lnp);
			switch(INSTR(lnp)) {
				case ps_con:
				case ps_rom:
				case ps_mes:
					outbyte((byte) sp_cend);
					/* list terminator */
					break;
			}
			break;
		default:
			assert(FALSE);
	}
}


STATIC void outvisibility(line_p lnp)
{
	/* In EM names of datalabels and procedures can be made
	 * externally visible, so they can be used in other files.
	 * There are special EM pseudo-instructions to state
	 * explicitly that a certain identifier is externally
	 * visible (ps_exa,ps_exp) or invisible (ps_ina,ps_inp).
	 * If there is no such pseudo for a certain identifier,
	 * the identifier is external only if its first use
	 * in the current file is an applied occurrence.
	 * Unfortunately the global optimizer may change the
	 * order of defining and applied occurrences.
	 * In the first optimizer pass (ic) we record for each identifier
	 * whether it is external or not. If necessary we generate
	 * pseudo instructions here.
	 */

	 arg_p ap;
	 short instr;

	 instr = INSTR(lnp);
	 switch(TYPE(lnp)) {
		case OPOBJECT:
			outdocc(OBJ(lnp));
			/* applied occurrence of a data label */
			break;
		case OPSHORT:
			if (instr == ps_sym) {
				outddef(SHORT(lnp));
				/* defining occ. data label */
			}
			break;
		case OPPROC:
			if (instr == ps_pro) {
				outpdef(PROC(lnp));
				/* defining occ. procedure */
			} else {
				outpocc(PROC(lnp));
			}
			break;
		case OPLIST:
			for (ap =  ARG(lnp); ap != (arg_p) 0; ap = ap->a_next) {
				switch(ap->a_type) {
					case ARGOBJECT:
					   outdocc(ap->a_a.a_obj);
					   break;
					case ARGPROC:
					   outpocc(ap->a_a.a_proc);
					   break;
				}
			}
			break;
	}
}


void cputlines(line_p l, FILE *lf)
{
	/* Output the lines in Campact assembly language
	 * format.
	 */

	line_p next,lnp;

	outfile = lf;
	for (lnp = l; lnp != (line_p) 0; lnp = next) {
		next = lnp->l_next;
		outvisibility(lnp); /* take care of visibiltity rules */
		if (INSTR(lnp) != ps_sym && INSTR(lnp) != op_lab) {
			outinst(INSTR(lnp));
		}
		outoperand(lnp);
		switch(INSTR(lnp)) {
			case ps_pro:
				thispro = PROC(lnp);
				/* fall through ... */
			case ps_end:
				coutoff(thispro->p_localbytes);
		}
		oldline(lnp);
	}
	if (lmap != (line_p *) 0) {
		oldmap((void **) lmap,llength);
		lmap = (line_p *) 0;
	}
}

void cputmagic(FILE *lf)
{
	/* write the magic number */

	outfile = lf;
	coutshort(sp_magic);
}
