
/*
 * emit.c
 *
 * (C) Copyright 1995 Archie L. Cobbs
 */

#include <stdio.h>
#include <assert.h>
#include "util.h"
#include "error.h"
#include "names.h"
#include "obj.h"
#include "asm.h"

/*
 * INTERNAL FUNCTIONS
 */

	static	void		EmitRel(int ins, IValue ivalue);

/*
 * INTERNAL VARIABLES
 */

	static int			gGaveBssError = FALSE;

/*
 * EmitByte()
 *
 * Emit a byte into the current segment
 */

void
Emit(Byte byte)
{

/* Don't emit if not assembling */

	if (!gAssembleOn)
		return;

/* Debuggetry */

	if (gDebug)
		fprintf(stderr, "Emit: [%s]:$%04X: $%02X\n",
			gSegment == CodeSegment ? "CODE" :
			gSegment == DataSegment ? "DATA" :
			"BSS ", gAddress[gSegment], byte);

/* What segment are we in? */

	switch (gSegment)
	{
		case CodeSegment:
		case DataSegment:
			if (gPass == 2)
				gSegBuffer[gSegment][gAddress[gSegment]] = byte;
			gAddress[gSegment]++;
			gGaveBssError = FALSE;
			break;
		case BssSegment:
			if (!gGaveBssError)
				uerror("opcode not allowed in BSS segment");
			gAddress[gSegment]++;
			gGaveBssError = TRUE;
			break;
		default:
			assert(0);
	}
}

/*
 * EmitIvalue()
 *
 * Emit an IValue into current segment, making relocation
 * entries as necessary.
 */

void
EmitIvalue(IValue ivalue, int bigendian, int bytes)
{
	int		bit, value;

/* Don't emit if not assembling */

	if (!gAssembleOn)
		return;

/* Can't emit into BSS segment */

	switch (gSegment)
	{
		case CodeSegment:
		case DataSegment:
			gGaveBssError = FALSE;
			break;
		case BssSegment:
			if (!gGaveBssError)
				uerror("opcode not allowed in BSS segment");
			gGaveBssError = TRUE;
			return;
		default:
			assert(0);
	}

/* Get value/offset and make relocation entry if necessary */

	switch (ivalue->type)
	{
		case iSymOff:
			value = 0;
			RelocSym(&ivalue->u.symoff, bigendian, bytes);
			break;
		case iSegRel:
			value = ivalue->u.segrel.offset;
			if (ivalue->u.segrel.segment != AbsSegment)
				RelocSeg(&ivalue->u.segrel, bigendian, bytes);
			break;
		case iUnknown:
			value = 0;
			break;
		default:
			value = 0;
			assert(0);
	}

/* Emit bytes as specified endianness and bytes */

	if (bigendian)
	{
		for (bit = 0x08; bit; bit >>= 1, value <<= 8)
			if (bytes & bit)
				Emit((Byte) (value >> 24));
	}
	else
		for (bit = 1; bit <= 0x08; bit <<= 1, value >>= 8)
			if (bytes & bit)
				Emit((Byte) value);
}

/*
 * EmitImm()
 *
 * Emit an immediate mode instruction
 */

void
EmitImm(unsigned ins, IValue ivalue, int hibyte)
{

/* Emit high or low byte into operand */

	Emit(GetOp(ins, mImmediate));
	EmitIvalue(ivalue, FALSE, hibyte ? 0x02 : 0x01);
}

/*
 * EmitIns()
 *
 * Emit an instruction using zmode if possible, else mode.
 * Complete instruction is 1, 2, or 3 bytes.
 */

void
EmitIns(unsigned ins, IValue ivalue, int mode, int zmode)
{

/* Is this an implicit mode instruction? */

	if (mode == mImplied)
	{
		assert(ivalue == NULL);
		if (CheckOp(ins, mImplied) >= 0)
			Emit(GetOp(ins, mImplied));
		else
		{
			uerror("implied addressing mode invalid");
			Emit(0);
		}
		return;
	}

/* Special case: is this a branch instruction? If so, ignore mode */

	if (CheckOp(ins, mRelative) >= 0)
	{
		EmitRel(ins, ivalue);
		return;
	}

/* Try to do zero page */

	if (zmode >= 0 && CheckOp(ins, zmode) >= 0)
		if (ivalue->type == iSegRel
				&& ivalue->u.segrel.segment == AbsSegment
				&& (ivalue->u.segrel.offset & ~0xFF) == 0)
		{
			Emit(GetOp(ins, zmode));
			Emit(ivalue->u.segrel.offset);
			return;
		}
		else if (ivalue->type == iSymOff
				&& IsZpImport(ivalue->u.symoff.id))
		{
			Emit(GetOp(ins, zmode));
			EmitIvalue(ivalue, FALSE, 0x01);
			return;
		}

/* Can we ONLY do zero page (but didn't)? */

	if (mode < 0)
	{
		uerror("zero-page address required");
		return;
	}

/* Generic three-byte instruction */

	Emit(GetOp(ins, mode));
	EmitIvalue(ivalue, FALSE, 0x03);
}

/*
 * EmitRel()
 *
 * Emit a relative branch instruction
 */

static	void
EmitRel(int ins, IValue ivalue)
{
	int		disp = 0;

/* Emit opcode */

	Emit(GetOp(ins, mRelative));

/* Compute displacement to target address */

	if (gPass == 2)
		if (ivalue->type != iSegRel || ivalue->u.segrel.segment != gSegment)
			uerror("illegal branch address");
		else
		{
			disp = ivalue->u.segrel.offset - (gAddress[gSegment] + 1);
			if (disp < -128 || disp > 127)
				uerror("branch of %d bytes is out of range", disp);
		}

/* Done */

	Emit(disp);
}

/*
 * EmitDump()
 */

unsigned
EmitSegDump(FILE *fp, int segment)
{
	if (fwrite(gSegBuffer[segment], 1,
		gAddress[segment], fp) != gAddress[segment])
	{
		perror("fwrite");
		cerror("can't write segment");
	}
	return(gAddress[segment]);
}


