
/*
   This software is copyright 1989 by John Dunning.  See the file
   'COPYLEFT.JRD' for the full copyright notice.
*/


/* code generator */

#include "parse.h"
#include "global.h"
#include "symtab.h"
#include "eval.h"
#include "obj.h"

#include <file.h>
#include <stdio.h>

/* fragment types */
#define FRAG_LITERAL	0
#define FRAG_SYMDEF	1
#define FRAG_WORD	2
#define FRAG_BYTE_HI	3
#define FRAG_BYTE_LO	4
#define FRAG_BR_OFFSET	5
#define FRAG_LONG_BR	6

/* zzz */

/* when assembling, we collect a bunch of the following structs */
struct frag_literal
	{
	int nbytes;
	char bytes[32];
	};
	
struct frag
	{
	struct frag * f_next;		/* next frag */
#ifdef DEBUG
	int seg_pc;			/* debug only */
#endif
	char type;			/* type this frag */
/* not used?	int f_pc;				/* pc this frag */
	int f_value;			/* value.  if literal, points to 
					   a frag_literal.  */
	struct sym * f_sym;		/* sym defined, or sym rel to */
	};

char outname_buf[32];
int output_fd;

struct frag * all_frags;
struct frag * last_frag;

struct relfile rf;

/* data accumulated for header */
int seg_max_pc;			/* nbytes this seg takes when linked */
int seg_nbytes;			/* nbytes data this seg in obj file */
int seg_nsyms;			/* number of (external) syms this seg */
int seg_nsbytes;		/* number of bytes of symtab this file */

/* */

char two_bytes[2];

char lit_buf[32];		/* buffer for literals */
int litidx;			/* nbytes segment */
int sym_num;

out16(i)
int i;
{
  two_bytes[0] = i & 0xFF;
  two_bytes[1] = i >> 8;
  write(output_fd, two_bytes, 2);
}

out8(i)
char i;
{
  two_bytes[0] = i & 0xFF;
  write(output_fd, two_bytes, 1);
}

outsym(sym)
struct sym * sym;
{
  int i;
  
  out8(strlen(sym->name));		/* output length */
  for (i = 0 ; i < strlen(sym->name) ; i++)
	out8(sym->name[i]);
  out16(sym->value);
  out16(sym->flags);
}

/*
 * the next section of code is dealing with frags
 */

struct frag * add_frag(type, value, sym)
int type;
int value;
struct sym * sym;
{
  struct frag * this_frag;
  
  this_frag = malloc(sizeof(struct frag));
  if (!all_frags)
	all_frags = this_frag;
    else
	last_frag->f_next = this_frag;
  last_frag = this_frag;
  this_frag->f_next = NULL;
  this_frag->type = type;
  this_frag->f_sym = sym;
  if (type == FRAG_LITERAL)
	{
	struct frag_literal * lit;
	lit = malloc(sizeof(struct frag_literal));
  	this_frag->f_value = lit;
	lit->nbytes = 0;
	}
    else
	this_frag->f_value = value;
  return(this_frag);
}

/* push a literal byte, maybe add new frag first */
litbyte(b)
{
  struct frag_literal * litbuf;
  
  if (last_frag && (last_frag->type == FRAG_LITERAL))
  	litbuf = last_frag->f_value;
    else
	litbuf = 0;
  if (!litbuf || litbuf->nbytes >= 32)
	{
	add_frag(FRAG_LITERAL, 0, NULL);
	litbuf = last_frag->f_value;
	}
  litbuf->bytes[litbuf->nbytes++] = b;
}
 
/* write a literal buffer */
writelit(l)
struct frag_literal * l;
{
  if ((l->nbytes <= 0) || (l->nbytes > 32))
  	barf ("internal error: bogus literal segment size");
  out8(OP_LIT | (l->nbytes & 0x1F));
  write(output_fd, l->bytes, l->nbytes);
  litidx = 0;
}

/*
 * The three entry points used by the rest of the assembler 
 */

/* generate a literal byte */
genlit(byte)
char byte;
{
  litbyte(byte);
}

/* generate a byte value */
genbyte(byte, flags, sym)
int byte;
int flags;
struct sym * sym;
{
  int type;

  if (!(flags & E_REL))		/* relative? */
	{
	genlit(byte);
	}
    else
	{
	if (flags & E_HI_BYTE)
		type = FRAG_BYTE_HI;
	    else
	if (flags & E_LO_BYTE)
		type = FRAG_BYTE_LO;
	    else
	barf ("internal error, byte not hi or low?");
	add_frag(type, byte, sym);
	}
}

/* generate a word value */
genword(word, flags, sym)
int word, flags;
struct sym * sym;
{
  if (!(flags & E_REL))		/* not relative? */
	{
	genlit(word & 0xFF);
	genlit(word >> 8);
	}
    else
    	{			/* it is relative */
	add_frag(FRAG_WORD, word, sym);
	}
}

genbranch(sym, offset)
struct sym * sym;
int offset;
{
#ifdef DEBUG
  if (sym)
	printf("  genbranch %s+%X\n", sym->name, offset);
    else
	printf("  genbranch *+%X\n", offset);
#endif
  if (!sym)		/* no sym means pc, ie "beq *+3" */
	genlit(offset - 2);
    else
	add_frag(FRAG_BR_OFFSET, offset, sym);
}

gen_lbr(basecode, sym)
int basecode;		/* base opcode */
struct sym * sym;
{
#ifdef DEBUG
  printf("  gen long branch %X %s\n", basecode, sym->name);
#endif
  add_frag(FRAG_LONG_BR, basecode, sym);
  /* we know basecode is a byte.  we'll use the top byte later,
     as a flag to say whether it's a long or short branch */
}

/* enter a label.  the label's in p.label */
gen_label()
{
  struct sym * sym;

  if (p.label)
	{
	sym = assign_sym(p.label, 0, 1);
	add_frag(FRAG_SYMDEF, 0, sym);
	}
}

#ifdef DEBUG
/* debug code */
dump_frags()
{
  struct frag * this_frag;
  struct frag_literal * l;
  int i;
  FILE * fd;

  fd = fopen("ra65.dmp", "w");
  
  for (this_frag = all_frags ; this_frag ; this_frag = this_frag->f_next)
	{
	fprintf(fd, "@%04X frag %X type %02X value %04X\n", 
		this_frag->seg_pc,
		this_frag, this_frag->type, this_frag->f_value);
	switch (this_frag->type)
		{
		case FRAG_SYMDEF:
			{
			fprintf(fd, "  sym %s\n", &(this_frag->f_sym->name));
			break;
			}
		case FRAG_LITERAL:
			{
			l = this_frag->f_value;
			fprintf (fd, "  %d bytes:\n    ", l->nbytes);
			for (i = 0 ; i < l->nbytes ; i++)
				fprintf(fd, " %02X", l->bytes[i] & 0xFF);
			fprintf(fd, "\n");
			break;
			}
		case FRAG_WORD:
			{
			fprintf(fd, "  word      %s + %04X\n", 
				&(this_frag->f_sym->name), this_frag->f_value);
			break;
			}
		case FRAG_BYTE_HI:
			{
			fprintf(fd, "  high byte %s + %04X\n", 
				&(this_frag->f_sym->name), this_frag->f_value);
			break;
			}
		case FRAG_BYTE_LO:
			{
			fprintf(fd, "  low byte  %s + %04X\n", 
				&(this_frag->f_sym->name), this_frag->f_value);
			break;
			}
		case FRAG_BR_OFFSET:
			{
			fprintf(fd, "  branch    %s + %04X\n", 
				&(this_frag->f_sym->name), this_frag->f_value);
			break;
			}
		case FRAG_LONG_BR:
			{
			fprintf(fd, "  long branch %X %s\n", 
				this_frag->f_value, &(this_frag->f_sym->name));
			break;
			}
		}
	}
  fclose(fd);
}
#endif		/* debug */

/*
 * init and util code
 */

init_gen()
{
  rf.header = OBJ_HEADER;
  rf.nb_sym = 0;
  rf.nb_seg = 0;
  rf.nb_segdata = 0;
  rf.n_sym = 0;
}

gen_o_open(name, source_name)
char * name;
char * source_name;
{
  char * ext;

printf("gen-output-open(%x, %s)\n", name, source_name);
  if (name && *name)
	strcpy(outname_buf, name);
    else
	strcpy(outname_buf, source_name);

  if (ext = (char *)strchr(outname_buf, '.'))
	*ext = '\0';
/*  if (rel_p) */
	strcat(outname_buf, ".obj");
/*    else
	strcat(outname_buf, ".com");
*/
  output_fd = open(outname_buf, O_WRONLY | O_CREAT | O_TRUNC);
  if (output_fd <= 0)
	{
	fprintf(stderr, "Can't open output file, %d\n", output_fd);
	exit(9);
	}
printf("output '%s'->%x\n", outname_buf, output_fd);
  all_frags = last_frag = 0;
}

/* return the size in object-bytes of a frag, ie how much of the 
   final executable this frag accounts for. */
int frag_gen_size(this_frag, frag_pc)
struct frag * this_frag;
int frag_pc;			/* module pc this frag is sized at */
{
  struct sym * sy;
  struct frag * xfrag;
  struct frag_literal * l;
  int offset, tmp;

  switch (this_frag->type)
	{
	case FRAG_SYMDEF:
		return(0);		/* generates no bytes */
	case FRAG_WORD:
		return(2);
	case FRAG_BYTE_HI: case FRAG_BYTE_LO:
		return(1);
	case FRAG_LITERAL: 
		{
		l = this_frag->f_value;	/* n bytes this literal frag */
		return(l->nbytes);
		}
	case FRAG_BR_OFFSET: 
		return(1);		/* branch offset is one byte */
	case FRAG_LONG_BR:
		{
		tmp = this_frag->f_value >> 8;	/* get top byte */
		if (tmp > 0)			/* it's already a size.*/
			{
#ifdef DEBUG
			printf("  size long branch, found size %d\n", tmp);
#endif
			return(tmp);		/* return it */
			}
		/* this one's tricky.  if the symbol's defined,
		   and in this module,
		   we're in good shape; just compute the offset
		   from the symbol to the module PC and mark the
		   frag short or long, depending on the offset.
		   if the symbol's not defined yet, scan forward
		   thru frags counting sizes until find symbol
		   or cumulative size gets too big.  If found it
		   within arbitrary offset, assume real offset
		   will be short enough to generate a short branch
		*/
		sy = this_frag->f_sym;		/* get the sym */
		if (sy->flags & THIS_SEG)	/* this sym defined here? */
			{
			/* sym's defined.  compute offset from PC. */
			offset = frag_pc - sy->value;
#ifdef DEBUG
			printf("  sym %s already defined, offset %d\n", 
				&sy->name, offset);
#endif
			if ((offset < 120) || (offset > 120))	/* slush */
				return(2);	/* can do short br */
			    else
				return(5);	/* must do long br */
			}
		/* search forward thru frag list for the ref'ed sym */
		offset = 0;
#ifdef DEBUG
		printf("  searching forward for %s\n", &sy->name);
#endif
		for (xfrag = this_frag->f_next ; xfrag ; xfrag = xfrag->f_next)
			{
			if (offset > 120)	/* too big? */
				return(5);	/* yes, assume long br */
			if ((xfrag->type == FRAG_SYMDEF) &&
			    (xfrag->f_sym == sy))
				return(2);	/* found it.  short br */
			if (xfrag->type == FRAG_LONG_BR)
				offset += 5;	/* don't recurse */
			    else
				offset += frag_gen_size(xfrag, frag_pc);
#ifdef DEBUG
			printf("  see frag type %X, offset now %d\n",
				xfrag->type, offset);
#endif
			}
		/* oops! fell off end of frags? ok return long */
		return(5);
		}
	default:
		barf("frag_gen_size: unknown frag type %x", this_frag->type);
	}
}

/* walk thru all frags assigning symbol offsets and long branch sizes */
ass_sym_values()
{
  struct frag * this_frag;
  int segment_pc, tmp;

  segment_pc = 0;
  for (this_frag = all_frags ; this_frag ; this_frag = this_frag->f_next)
	{
#ifdef DEBUG
	this_frag->seg_pc = segment_pc;		/* sanity check */
#endif
	switch (this_frag->type)
		{
		case FRAG_SYMDEF:
			{
/* zzz do some checking before bash sym value */
			this_frag->f_sym->value = segment_pc;
			this_frag->f_sym->flags |= THIS_SEG;
			break;
			}
		case FRAG_WORD:
		case FRAG_BYTE_HI: case FRAG_BYTE_LO:
		case FRAG_LITERAL: 
		case FRAG_BR_OFFSET: 
			{
			segment_pc += frag_gen_size(this_frag, segment_pc);
			break;
			}
		case FRAG_LONG_BR:
			{
			/* see hair in size routine, above */
			tmp = frag_gen_size(this_frag, segment_pc);
#ifdef DEBUG
			printf("@%04X: setting long branch size to %d\n",
				segment_pc, tmp);
#endif
			this_frag->f_value |= (tmp << 8);
			segment_pc += tmp;
			break;
			}
		default:
			barf("ass_sym_values: unknown frag type %x", this_frag->type);
		}
	}
  seg_max_pc = segment_pc;
/*  printf("assign sym val: max pc = %04X\n", segment_pc); */
}

/* walk thru all syms assigning numbers to all those which are 
   ref'ed or def'ed in this module, and are global */
assign_next_num(sym)
struct sym * sym;
{
  if ((sym->flags & GLOBAL) || (sym->flags == 0))
  	{
	sym->nbr = sym_num++;
	seg_nsyms++;
	seg_nsbytes += strlen(sym->name) + 1 + 4;
	}
}

asgn_sym_numbers()
{
  sym_num = 0;
  map_syms(assign_next_num);
}

out_global_sym(sym)
struct sym * sym;
{
  if (sym->nbr >= 0)
	outsym(sym);
}

output_symtab()
{
  map_syms(out_global_sym);
}

out_frags()
{
  struct frag * this_frag;
  struct frag_literal * l;
  int segment_pc;
  int i;
  
  segment_pc = 0;
  for (this_frag = all_frags ; this_frag ; this_frag = this_frag->f_next)
	{
#ifdef DEBUG
	if (this_frag->seg_pc != segment_pc)
		barf("Internal error! frag %x sez it's at pc %04x, computed %04x\n",
			this_frag, this_frag->seg_pc, segment_pc);
#endif
	switch (this_frag->type)
		{
		case FRAG_SYMDEF:
			{
/* this is a no-op here.  maybe check validity? */
			break;
			}
		case FRAG_WORD:
			{
			i = this_frag->f_sym->nbr;
			if (this_frag->f_sym->flags & THIS_SEG)
				{
/* output "rel word, offset by this segment base" */
				out8(OP_REL);
				out16(this_frag->f_sym->value + this_frag->f_value);
				}
			    else
				{
/* output "rel word, offset from symbol number foo" */
				if (i < 32)
					out8(OP_SYM | i);
				    else
					{
					out8(OP_SYM | OP_SYM_EMASK | (i >> 8));
					out8(i & 0xFF);
					}
				out16(this_frag->f_value);
				}
			segment_pc += 2;
			break;
			}
		case FRAG_BYTE_HI:
			{
			i = this_frag->f_sym->nbr;
			if (this_frag->f_sym->flags & THIS_SEG)
				{
/* output "rel hibyte, offset by this segment base" */
				out8(OP_REL_HI);
				out16(this_frag->f_sym->value + this_frag->f_value);
				}
			    else
				{
/* output "rel hibyte, offset from symbol number foo" */
				if (i < 32)
					out8(OP_SYM_HI | i);
				    else
					{
					out8(OP_SYM_HI | OP_SYM_EMASK | (i >> 8));
					out8(i & 0xFF);
					}
				out16(this_frag->f_value);
				}
			segment_pc += 1;
			break;
			}
		case FRAG_BYTE_LO:
			{
			i = this_frag->f_sym->nbr;
			if (this_frag->f_sym->flags & THIS_SEG)
				{
/* output "rel lobyte, offset by this segment base" */
				out8(OP_REL_LO);
				out16(this_frag->f_sym->value + this_frag->f_value);
				}
			    else
				{
/* output "rel lobyte, offset from symbol number foo" */
				if (i < 32)
					out8(OP_SYM_LO | i);
				    else
					{
					out8(OP_SYM_LO | OP_SYM_EMASK | (i >> 8));
					out8(i & 0xFF);
					}
				out16(this_frag->f_value);
				}
			segment_pc += 1;
			break;
			}
		case FRAG_LITERAL: 
			{
			l = this_frag->f_value;
			out8(OP_LIT | (l->nbytes & 0x1F));
			for (i = 0 ; i < l->nbytes ; i++)
				out8(l->bytes[i]);
			segment_pc += l->nbytes;
			break;
			}
		case FRAG_BR_OFFSET: 
			{
/* should really collapse this frag into another literal one... */
			out8(OP_LIT | 1);	/* one literal byte */
			out8(this_frag->f_value + this_frag->f_sym->value
				- segment_pc - 1);
			segment_pc += 1;
			break;
			}
		case FRAG_LONG_BR:
			{
			i = this_frag->f_value >> 8;	/* get resolved size */
			if (!((i == 2) || (i == 5)))
				barf("bogus long branch size %d", i);
			if (i == 2)
				{
				/* do a short branch */
				out8(OP_LIT | 2);	/* 2 literal bytes */
				out8(this_frag->f_value);	/* out the branch code */
				segment_pc += 1;
				out8(this_frag->f_sym->value - segment_pc - 1);
							/* out the offset */
				segment_pc += 1;
				}
			    else
				{
				/* do a long branch */
				out8(OP_LIT | 3);	/* 3 literal bytes */
				/* out brach opcode, flipping sense */
				out8(this_frag->f_value ^ 0x20);
				out8(3);		/* branch offset, *+5 */
				out8(0x4C);		/* opcode for jmp */
				out8(OP_REL);
				out16(this_frag->f_sym->value);
				segment_pc += 5;
				}
			break;
			}
		default:
			barf("out_frags: unknown frag type %x", this_frag->type);
		}
	}
  if (segment_pc != seg_max_pc)
	barf("segment output mismatch, expected %04X, got %04X",
		seg_max_pc, segment_pc);
/*  printf("out_frags: max pc = %04X\n", segment_pc); */
}

/* figure out how many bytes this seg takes up in the file */
compute_seg_size()
{
  struct frag * this_frag;
  struct frag_literal * l;
  int i;

/* could probly also use this routine to collapse frags */
  for (this_frag = all_frags ; this_frag ; this_frag = this_frag->f_next)
	{
	switch (this_frag->type)
		{
		case FRAG_SYMDEF:
			{
/* this is a no-op here.  maybe check validity? */
			break;
			}
		case FRAG_WORD: case FRAG_BYTE_HI: case FRAG_BYTE_LO:
			{
			i = this_frag->f_sym->nbr;
			if (this_frag->f_sym->flags & THIS_SEG)
				{
/* output "rel word, offset by this segment base" */
/*				out8(OP_REL);
				out16(this_frag->f_sym->value);
*/
				seg_nbytes += 3;
				}
			    else
				{
/* output "rel word, offset from symbol number foo" */
				if (i < 32)
/*					out8(OP_SYM | i); */
					seg_nbytes += 1;
				    else
					{
/*					out8(OP_SYM | OP_SYM_EMASK | (i >> 8));
					out8(i & 0xFF);	*/
					seg_nbytes += 2;
					}
/*				out16(this_frag->f_value);	*/
				seg_nbytes += 2;
				}
			break;
			}
		case FRAG_LITERAL: 
			{
			l = this_frag->f_value;
/*			out8(OP_LIT | (l->nbytes & 0x1F));
			for (i = 0 ; i < l->nbytes ; i++)
				out8(l->bytes[i]);	*/
			seg_nbytes += l->nbytes + 1;
			break;
			}
		case FRAG_BR_OFFSET: 
			{
			seg_nbytes += 2;
			break;
			}
		case FRAG_LONG_BR:
			{
			int tmp;

			tmp = this_frag->f_value >> 8;
			if (tmp == 2)
				seg_nbytes += 3;	/* lit + br + offset */
			    else
			if (tmp == 5)
				seg_nbytes += 7;
			    else
				barf("Bogus long branch size code %d\n", tmp);
			break;
			}
		default:
			barf("compute_seg_size: unknown frag type %x", this_frag->type);
		}
	}
}

output_header()
{
  rf.header = OBJ_HEADER;
  rf.nb_sym = seg_nsbytes;
  rf.nb_seg = seg_max_pc;
  rf.nb_segdata = seg_nbytes;
  rf.n_sym = seg_nsyms;
#ifdef M6502			/* byte order's right here; lo, hi */
  write(output_fd, rf, 10);
#else
  out16(rf.header);
  out16(rf.nb_sym);
  out16(rf.nb_seg);
  out16(rf.nb_segdata);
  out16(rf.n_sym);
#endif

  if (verbose)
	printf(" %04x segment bytes in %d object bytes\n", seg_max_pc, seg_nbytes);
}

gen_o_close()
{
  seg_nbytes = seg_max_pc = seg_nsyms = 0;
  seg_nsbytes = 2;
  ass_sym_values();
  asgn_sym_numbers();
  compute_seg_size();
#ifdef DEBUG
  dump_frags();
#endif
  output_header();
  output_symtab();
  out_frags();
  close(output_fd);
}

int gen_n_passes()
{
  return(1);
}
