/*==========================================================================
 * Project: ATasm: atari cross assembler
 * File: asm.c
 *
 * Contains the actual code for assembly generation
 *==========================================================================
 * Created: 03/25/98 Mark Schmelzenbach (mws)
 * Modifications:
 *   03/26/98 mws added more things
 *   03/27/98 mws added more things - version 0.5 
 *
 *   12/17/98 mws added source comments, line number stripping, macros,
 *                put_float(), local vars, symbol table dump,
 *                if/else/endif, formatted string output
 *
 *  12/18/98 mws split up src files, full expression parsing
 *               (comparisons,.AND,.OR,.NOT), .REF, tight binary save
 * 
 *  12/19/98 mws added memory snapshot, no macro subst if line is skipped
 *  12/22/98 mws fixed macro label reference
 *  12/23/98 mws general bug fixes -- sample.m65 now compiles correctly
 *               fixed EQU=* and *=EQU, fixed macro comma handling
 *  12/28/98 mws added .DS statement, .INCBIN
 *  12/30/98 mws added .DC, .WARN, .REPT/.ENDR
 *  01/01/99 mws fixed forward reference equates, equate reassignment,
 *               fixed equates and tnasitory equates in macros,
 *               distinction between equates and transitory equates.
 *
 *               RELEASE 0.90!
 *
 *  01/11/99 mws added initial .XFD support, some additional error testing
 *               for address overwrites
 *  01/14/99 mws added undocumented opcodes
 *  10/19/99 mws fixed indirect jump size - thanks to Carsten Strotmann
 *               <cstrotm@ibm.net> for finding this one!
 *               also fixed up spurious warnings generated by .dc directive
 *               (has anyone actually used this?)
 *  11/10/99 mws fixed some more bugs:  now ';' can be embedded in strings,
 *               mapped 'illegal' sta/lda z,y => sta/lda a,y, fixed indirect
 *               jmp size (again!), fixed jmp into zero page locations;
 *               thanks go out again to Carsten Strotmann and also to
 *               Ken Siders <atari@columbus.rr.com>;
 *  01/15/01 mws fixed a bug in incbin that would result in an extra
 *               byte being stored in memory.  thanks go out to Chris
 *               Hutt <cghutt@hotmail.com> for finding this bug!
 *==========================================================================*
 * TODO
 *   indepth testing of .IF,.ELSE,.ENDIF (signal error on mismatches?)
 *   ? add e .FLOAT notation (10e10)
 *   ? distinguish between Equates and Labels for symbol dump
 *   ? allow '.' within label names
 *
 * Bugs: see kill.asm
 *   ORA #16                              (fixed 12/18/98 mws)
 *   LSR, LSR A                           (fixed 12/18/98 mws)
 *   spaces within byte statements        (fixed 12/17/98 mws)
 *   commas in strings in byte statements (fixed 12/17/98 mws)
 *   
 *==========================================================================*/
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "ops.h"
#include "directive.h"
#include "symbol.h"

unsigned short pc;   /* program counter */
int init_pc;  /* pc orig flag */
int pass; /* pass number */
int eq, verbose;  /* assignment flag, verbosity flag */
int local,warn,bsize;

file_stack *fin;
unsigned char *memmap, *bitmap;  /* memory snapshot, and bitmap */
char *outline;  /* the line of text written out in verbose mode */

/*=========================================================================*
 * function error(char *err, int tp)
 * parameters: err - the error message
 *             tp  - the error severity (0=warning, else fatal error)
 *
 * generates an error/warning message to stderr, including the position
 * of the error
 *=========================================================================*/
int error(char *err, int tp) {
  if (fin) {
    if (!invoked)
      fprintf(stderr,"\n\nIn %s, line %d--\n ",fin->name,fin->line);
    else
      fprintf(stderr,"\n\nIn %s, line %d--[while expanding macro '%s']\n ",fin->name,fin->line,invoked->orig->name);
  }
  if (tp) {
    fprintf(stderr,"Error: ");
  } else {
    fprintf(stderr,"Warning: ");
  }
  fprintf(stderr,"%s\n",err);
  if (tp)
    exit(tp);
  warn++;
  return 0;
}
/*=========================================================================*
 * function init_asm
 * initializes the assembler
 *=========================================================================*/
int init_asm(int udoc) {
  int i,ops;
  symbol *sym;
  
  pass=warn=bsize=0;
  fin=NULL;
  macro_list=NULL;
  invoked=NULL;
  
  for(i=0;i<HSIZE;i++)  /* clear symbol table */
    hash[i]=NULL;
  memmap=(unsigned char *)malloc(65536); /* memory snapshot */
  bitmap=(unsigned char *)malloc(8096);
  outline=(char *)malloc(256);
  
  if ((!memmap)||(!bitmap)||(!outline)) {
    error("Cannot alocate memory snapshot.",1);
  }
  memset(memmap,0,65536);
  memset(bitmap,0,8096);
  
  if (udoc)
    ops=NUM_OPS;
  else
    ops=LEGAL_OPS;
    
  for(i=0;i<ops;i++) {  /* insert ops into symbol table */
    sym=get_sym();
    sym->tp=OPCODE;
    sym->addr=i;
    sym->name=nmem[i];
    addsym(sym);
  }
  for(i=0;i<NUM_DIR;i++) { /* insert compiler directives into table */
    sym=get_sym();
    sym->tp=DIRECT;
    sym->addr=i;
    sym->name=direct[i];
    addsym(sym);
  }
  return 1;
}
/*=========================================================================*
 * function open_file(char *fname)
 * parameters: fname is the file name to process
 *
 * this functions adds a new file onto the current file processing stack
 * The file stack structure saves the state of each file as it is is
 * being processed.  This allows for nested .INCLUDEs
 *=========================================================================*/
int open_file(char *fname) {
  file_stack *fnew;
  char buf[80];
  
  fnew=(file_stack *)malloc(sizeof(file_stack));
  fnew->name=(char *)malloc(strlen(fname)+1);
  strcpy(fnew->name,fname);
  fnew->in=fopen(fname,"rt");
  if (!fnew->in) {
    sprintf(buf,"Cannot open file '%s'\n",fname);
    error(buf,1);
  }
  fnew->line=0;
  fnew->nxt=fin;
  fin=fnew;
  return 1;
}
/*=========================================================================*
 * function get_nxt_word(int tp)
 * parameters: tp denotes the processing mode where(?)
 *   0: normal processing, getting next line
 *   1: get rest of current line
 *   2: return entire current line
 *   3: get rest of current line, but don't advance buffer (peeking ahead)
 *   4: get next word, returning "" when eol is hit (no advance to next line)
 *   5: 'skip' mode, don't subst macro params!
 *   6: replace commas with spaces in reading buffer and return NULL
 *
 * This function return a pointer to the next 'interesting' word,
 * skipping white space, comments and empty lines
 * Strings are returned verbatim (including spaces)
 *=========================================================================*/
char *get_nxt_word(int tp) {
  static char buf[256], line[256], *fget=NULL;
  char l,*look,*walk;
  int instr,i;
  file_stack *kill;
  macro_call *mkill;
  macro_line *lkill;

  if (tp==6) {
    look=fget;
    instr=0;
    while(*look) {
      if ((*look==',')&&(!instr)&&(*(look-1)!='\''))
	*look=32;
      if (*look==34)
	instr^=1;
      look++;
    }
    return NULL;
  }
  
  look=buf;
  *look=0;
  
  if ((tp==1)||(tp==3)) {
    if (!fget)
      return buf;
    strcpy(buf,fget);
    instr=0;
    for(i=0;i<strlen(buf);i++) {
      if (buf[i]==34)    /* fix embedded ';' problem - mws 11/10/99 */
	instr^=1;
      else if ((buf[i]==';')&&(!instr))  
	buf[i]=0;
    }
    if (tp==1) {
      fget=NULL;
    }
    return buf;
  } else if (tp==2)
    return line;
  
  /* skip over empty space, blank lines and comments */
  do {  
    while ((!fget)||(!(*fget))) {
      if (tp==4) {
	buf[0]=0;
	return buf;
      }
      if (!fin) 
	error("No active filehandle.",1);
      outline[0]=0;
      memset(line,0,256);
      memset(buf,0,256);
      if (invoked) {  /* use macro table, if needed */
	strcpy(line,invoked->line->line);
	if (tp!=5)
	  macro_subst(invoked->orig->name,line,invoked->cmd,invoked->argc);
	invoked->line=invoked->line->nxt;
	if (!invoked->line) {
	  if (invoked->orig->tp==1) {
	    invoked->orig->num--;
	    if (invoked->orig->num)
	      invoked->line=invoked->orig->lines;
	    else {
	      mkill=invoked;
	      invoked=invoked->nxt;
	      del_rept(mkill);
	    }
	  } else {
	    mkill=invoked;
	    invoked=invoked->nxt;
	    if (mkill->argc) {
	      for(i=0;i<mkill->argc;i++) {
		lkill=mkill->cmd;
		mkill->cmd=mkill->cmd->nxt;
		free(lkill->line);
		free(lkill);
	      }
	    }
	    free(mkill);
	  }
	}
      } else { /* get new line from file */
	fin->line++;
	fgets(line,256,fin->in);
      }
      /* strip line number, if one exists */
      if (isdigit(line[0])) {
	i=0;
	while(isdigit(line[i]))
	  line[i++]=32;
      }
      /* Remove EOL characters */
      for(i=0;i<strlen(line);i++)
	if ((line[i]==10)||(line[i]==13))
	  line[i]=0;
      walk=line+strlen(line)-1;
      while(isspace(*walk)) {
	*walk=0;
	walk--;
      }
      fget=line;
      if (feof(fin->in)) {
	kill=fin;
	fin=fin->nxt;
	fclose(kill->in);
	free(kill);
	fget=NULL;
	if (!fin)
	  return NULL;
      }
    }
    while(isspace(*fget)) {
      fget++;
    }
    if (((fget)&&(!(*fget)))||(*fget==';')) {
      fget=NULL;
    }
  } while ((!fget)||(!(*fget)));

  instr=0;

  /* Get next token (one word or string) */
  do {
    l=*fget;
    if (l)
      fget++;
    if (l==34)
      instr^=1;
    if (!instr) {
      if (l=='=') {
	l=0;
	eq=1;
      } else if ((l=='.')&&(*fget=='=')) {
	l=0;
	eq=2;
	fget++;
      }
    }
    *look++=l;
  } while((l)&&((instr)||(!isspace(l))));

  if (l)
    *(look-1)=0;
  else
    *look=0;
  
  if (instr)
    error("Unterminated string constant.",1);

  if (l) {
    /* Skip to next token if available */
    while((fget)&&(isspace(*fget))) {
      fget++;
    }
    /* Check for '=' or '.=' */
    if (fget) {
      if (*fget=='=') {
	eq=1;
	fget++;
      } else if ((*fget=='.')&&(*(fget+1)=='=')) {
	eq=2;
	fget+=2;
      } else fget--;
    }
  }
  
  return buf;
}
/*=========================================================================*
  function put_byte(int b)
  parameters: b is the byte to store

  This function stores a byte at the current program counter (PC),
  storing the result into the memory map, writing to the output line buffer
  and the memory storage bitmap
 *=========================================================================*/
int put_byte(int b) {
  char buf[64];
  unsigned char v;
  int a;
  
  v=b&0xff;
  if (verbose) {
    sprintf(buf,"%.2X ",v);
    strcat(outline,buf);
  }
  a=pc>>3;
  if (a>8096)
    error("Address overflow",1);
  
  memmap[pc]=v;
  if (bitmap[a]&(128>>(pc&7))) {
    sprintf(buf,"Warning: Address %.4x over-written",pc);
    error(buf,0);    
  } else bsize++;
  
  bitmap[a]=bitmap[a]|(128>>(pc&7));
  pc++;
  return 0;
}
/*=========================================================================*
  funtion put_word(int b, int e)
  parameters: b - a word to store 
	      e - flag denoting storage method (0=lo/hi, 1=hi/lo)

  calls put_byte to store a bigendian/little endian word
 *=========================================================================*/
int put_word(int b,int e) {
  unsigned char v;

  if (!e) {  /* Lo Hi */
    v=b&0xff;
    put_byte(v);
    v=b>>8;
    put_byte(v);
  } else {  /* Hi Lo */
    v=b>>8;
    put_byte(v);
    v=b&0xff;
    put_byte(v);
  }
  return 0;
}

/*=========================================================================*
  function put_float(char *f)
  parameter: f is a string containing a FP constant

  calls put_word to store a floating point constant in Atari FP format
  
  the first byte contains the exponent portion of the number, in
  excess-64 notation representing power of 100.  The upper bit of the
  exponent byte designates the sign of the mantissa portion.  The
  following 5 bytes are the mantissa, in packed BCD form, normalized
  on a byte boundary (consistant with the powers-of-100 exponent).

  This has not been extensively tested!  It works for the examples in
  De Re Atari...but beyond that nothing has been verified
 *=========================================================================*/
int put_float(char *f) {
  char tmp[64],buf[64],*look,*walk;
  int n[3],i;
  int neg,d=-1;
  
  i=neg=0;
  look=f;
  walk=buf;

  /* determine sign and strip it */
  while (((*look=='+')||(*look=='-'))&&(*look)) {
    if (*look=='-')
      neg^=1;
    look++;
  }
  if (!(*look))
    error("Malformed floating point constant.",1);

  /* strip excess leading 0s */
  while((*look=='0')&&(*(look+1)=='0')&&(*look))
    look++;

  tmp[0]=0;
  
  /* Prepend leading 0 if necessary */
  if (*look=='.')
    strcpy(tmp,"0");
  strcat(tmp,look);
  
  /* Append decimal point in necessary */
  if (!strchr(tmp,'.'))
    strcat(tmp,".");
  
  /* Pad number */
  strcat(tmp,"00000000000000");
  look=tmp;
  
  while(*look) { /* strip out decimal point */
    if (*look=='.') {
      if (d!=-1)
	error("Malformed floating point constant.",1);
      d=i;
      look++;
    } else if (!isdigit(*look))
      error("Malformed floating point constant.",1);
    else if (i<16)
      *walk++=*look++;
    else
      look++;
    i++;
  }
  *walk=0;
  
  look=tmp;
  
  if (d&1) {
    strcpy(tmp,"0");
    strcat(tmp,buf);
    d=d+1;
  } else strcpy(tmp,buf);
  
  while ((*look=='0')&&(*look)) {
    look++;
  }
  if (!(*look)) {
    sprintf(buf,"0000 0000 0000");      
  } else {
    look=tmp;
    /* MSB is non-zero */
    while((*look=='0')&&(*(look+1)=='0')) {
      look++;
      d=d-1;
    }
    d=d/2-1;
    
    if ((d>64)||(d<-64)) 
      error("Floating point constant overflow.",0);
    
    d=neg*128+64+d;
    d=d&0xff;
    sprintf(buf,"%.2x",d);
    strncat(buf,look,2);
    strcat(buf," ");
    look+=2;
    for(i=0;i<2;i++) {
      strncat(buf,look,4);
      strcat(buf," ");
      look+=4;
    }
  }
  sscanf(buf,"%x %x %x",&n[0],&n[1],&n[2]);    
  put_word(n[0],1);
  put_word(n[1],1);
  put_word(n[2],1);
  return 0;
}
/*=========================================================================*
  function put_opcode(int b)
  parameter: b is the opcode to store (-1 denotes illegal addressing mode)

  calls put_byte to store an opcode
 *=========================================================================*/
int put_opcode(int b) {
  if (b==-1)
    error("Illegal addressing mode.",1);
  else
    put_byte(b);

  return 0;
}
/*=========================================================================*
  function print_pc()

  prints the current PC address to output string (or appropriate padding,
  if PC is undefined)
 *=========================================================================*/
int print_pc() {
  char buf[32];
  
  if (!init_pc)
    sprintf(buf,"      ");
  else
    sprintf(buf,"%.2X:%.2X  ",pc&0xff,pc>>8);
  strcat(outline,buf);
  return 0;
}
/*=========================================================================*
  function get_address(char *str)
  parameters: str - a string containing the address (possibly an expr)
  returns the address

  returns a numeric address from a given string
 *=========================================================================*/
unsigned short get_address(char *str) {
  short v;
  unsigned short a;
  
  v=get_expression(str,1);
  a=(unsigned short)v;
  a=a&0xffff;
  return a;
}
/*=========================================================================*
  function get_immediate(char *str)
  parameters: str - a string containing an expression to parse
  
  returns the byte value of an expression
 *=========================================================================*/
short get_immediate(char *str) {
  short v,i;
  
  v=get_expression(str,1);
  if (v<0)
    i=-v;
  else
    i=v;
  if (i>>8) {
    error("Immediate overflow",0);
  }
  v=v&0xff;
  return v;
}
/*=========================================================================*
  function add_label(char *label)
  parameter: label - a string containing a label

  inserts a label into the symbol table, also handles equates
  
  TODO add local labels
 *=========================================================================*/
int add_label(char *label) {
  symbol *sym;
  char *str,num[32];
  int v;

  if (!strcmp(label,"A")) {
    error("'A' is a reserved operand.",1);
  }
  
  if (!pass) {
    if (strchr(label,'='))
      error("Illegal label (cannot contain '='",1);
    sym=get_sym();
    if (label[0]=='?') {
      sprintf(num,"=%d=",local);
      sym->name=(char *)malloc(strlen(label)+strlen(num));
      sprintf(sym->name,"=%d=%s",local,label+1);
    } else {
      sym->name=(char *)malloc(strlen(label)+1);
      strcpy(sym->name,label);
    }
    if (invoked) {  /* We are instantiating a macro */      
      sym->mlnk=invoked->orig->mlabels;
      invoked->orig->mlabels=sym;
      sym->invoked=(unsigned short *)malloc(sizeof(unsigned short)*32);
      sym->sz=32;
      sym->invoked[0]=0;
      if (eq==1) {
	error("Defining an equate in macro",0);
	sym->tp=MACROL;
      } else if (eq==2) {
	sym->tp=MACROQ;
      } else {
	sym->tp=MACROL;
	sym->invoked[1]=sym->addr=pc;
      }
      sym->num=0;
    } else {
      if (eq==2)  /* .= is transitory equate */
	sym->tp=TEQUATE;
      else if (eq==1)
	sym->tp=EQUATE;
      else
	sym->tp=LABEL;  /* otherwise, a standard label/equate */      
      sym->addr=pc;
    }
    addsym(sym);
  }
  if (eq) {  /* an equate */
    sym=findsym(label);
    str=get_nxt_word(1);
    if (pass) {
      squeeze_str(str);
      v=get_address(str);
      sym->addr=v;
    } else {
      squeeze_str(str);
      v=get_expression(str,0);  /* allow forward reference */
      sym->addr=v&0xffff;
    }
    if ((invoked)&&(!pass))
      sym->invoked[1]=v&0xffff;
    eq=0;
  }
  return 1;
}
/*=========================================================================*
  function parse_operand(symbol *sym, char *str)
  parameters: sym - the opcode
	      str - the remaining chars on the line

  This function assembles a given opcode/operand pair, determing the
  appropriate opcode mode (immediate, indexed, indirect, etc)
 *=========================================================================*/
int parse_operand(symbol *sym, char *str) {
  short v,cmd;
  unsigned short a;
  char *idx, vidx, xtnd;
  char buf[80];
  
  idx=strchr(str,',');
  a=0;
  vidx=xtnd=0;

  if (idx) {
    *idx=0;
    idx++;
    vidx=toupper(*idx);
    if ((vidx!='X')&&(vidx!='Y'))
      error("Illegal index",1);
    if ((vidx=='X')&&(str[0]=='(')) {
      *idx=0;
      idx--;
      *idx=')'; 
    }
  }
  if (str[0]=='#') { /* Immediate mode */
    if (pass) {   /* determine value */
      v=get_immediate(str+1);
      put_opcode(imm[sym->addr]);
      put_byte(v);
    } else pc+=2;
  } else {
    if (str[0]=='(') {
      if (pass)
	a=get_address(str);
      
      if (sym->addr==30) { /* JMP indirect abs */
	if (vidx) 
	  error("Illegal indirect JMP",1);
	cmd=ind[sym->addr];
	xtnd=1;           /* fix indirect JMP size */
      } else {
	if (!vidx) {
	  error("Illegal indirect reference",1);
	} else if (vidx=='X') 
	  cmd=i_x[sym->addr];
	else 
	  cmd=i_y[sym->addr];
	if ((pass)&&(a>255))
	  error("Illegal zero page reference.",1);
      }
      if (pass) {       
	put_opcode(cmd);
	put_byte(a&0xff);
	if ((a>255)||(xtnd))
	  put_byte(a>>8);
      } else {
	if (vidx)  
	  pc+=2;
	else
	  pc+=3;
      }
    } else { /* absolute reference */
      a=get_expression(str,0);
      if (pass) {  /* determine address */
	a=get_address(str);
	if (rel[sym->addr]>=0) { /* Relative branch */
	  if (vidx)
	    error("Illegal operand for relative branch",1);
	  cmd=rel[sym->addr];
	  if (a>255) {
	    v=a-pc-2;
	    if ((v>127)||(v<-128)) {
	      sprintf(buf,"Branch overflow! Target %d bytes away.",v);
	      error(buf,1);
	    }
	    if (v<0) v+=256;
	    a=v;
	  }
	} else {
	  if (vidx) { /* Indexed mode */
	    if (vidx=='X') {
	      if (a<256)
		cmd=z_x[sym->addr];
	      else
		cmd=a_x[sym->addr];
	    } else {
	      if (a<256) {
		cmd=z_y[sym->addr];
		if ((cmd==0xB9)||(cmd==0x99))  /* pad LDA/STA ZP,y */
		  xtnd=1;
	      } else
		cmd=a_y[sym->addr];
	    }
	  } else {
	    if (a<256) {
	      cmd=zpg[sym->addr];
	      if (sym->addr==30)
		xtnd=1;          /* pad "zero-page" jump */
	    } else
	      cmd=abl[sym->addr];
	  }
	}
	put_opcode(cmd);
	put_byte(a&0xff);
	if ((a>255)||(xtnd))  
	  put_byte(a>>8);
      } else {
	if ((a<256)||(rel[sym->addr]>=0)) {
	  if ((sym->addr==30)|| /* pad a few zero-page opcodes */
	      ((vidx=='Y')&&((sym->addr==32)||(sym->addr==50))))
	    pc+=3;
	  else
	    pc+=2;
	} else
	  pc+=3;
      }
    }
  }
  return 1;
}
/*=========================================================================*
  function num_cvt(char *num)
  parameters: num - a string containing a numeric value

  this function returns the value of a hex, decimal or binary string
 *=========================================================================*/
int num_cvt(char *num) {
  int v,i,bit,tp;
  char *txt;

  if (!isdigit(num[0])) {
    txt=num+1;
    if (num[0]=='$')
      tp=1;
    else if (num[0]=='~')
      tp=2;
    else
      error("Malformed numeric constant.",1);
  } else {
    tp=0;
    txt=num;
  }
  
  switch (tp) {
  case 0:   /* decimal */
    sscanf(txt,"%d",&v);
    break;
  case 1:   /* hex */
    sscanf(txt,"%x",&v);
    break;
  case 2:   /* binary */
    v=0;
    bit=1;
    for(i=strlen(txt)-1;i>=0;i--) {
      if (txt[i]=='1')
	v+=bit;
      else if (txt[i]!='0') {
	error("Malformed binary value",0);
      }
      bit=bit<<1;
    }
    break;
  }
  return v;
}
/*=========================================================================*
  function squeeze_str(char *str)
  parameters: str - a string to compress

  this function removes white space from within a string (and surrounding
  white space)
 *=========================================================================*/
int squeeze_str(char *str) {
  char buf[256], *look, *walk;

  look=str;
  walk=buf;
  while(*look) {
    if (!isspace(*look)) {
      *walk=*look;
      walk++;  /* *walk++ */
    }
    look++;
  }
  *walk=0;
  strcpy(str,buf);
  return 1;
}
/*=========================================================================*
  function to_comma(char *in, char *out)
  parameters: in - the source string
	      out- the dest string

  This function copies the string from in to out, stopping at either the
  end of in, or at a comma -- also strips surrounding whitespace
 *=========================================================================*/
int to_comma(char *in, char *out) {
  char *look=in;
  int c,q;

  c=0;
  while ((*look)&&(*look==' ')) {
    look++;
    c++;
  }
  q=0;
  while (*look) {
    if (*look==34)
      q^=1;
    *out++=*look++;
    c++;
    if ((*look==',')&&(!q))
      break;
  }
  if (*look)
    c++;
  *out=0;
  
  out=out+strlen(out)-1;  
  while(isspace(*out)) {
    *out=0;
    out--;
  }
  return c;
}
/*=========================================================================*
 * function do_float(int tp)
 *
 * processes a .FLOAT sequence in the file stream
 *=========================================================================*/
int do_float() {
  char *str,*look;
  char buf[80];
  int d,c,p;
    
  str=get_nxt_word(1);
  while(isspace(*str))
    str++;
  look=str+strlen(str)-1;
  while(isspace(*look)) {
    *look=0;
    look--;
  }
  
  look=str;
  p=c=0;
  while(*look) {
    if ((pass)&&(verbose)) {
      if (!c) {
	print_pc();
	c++;
      }
      c++;
    }

    d=to_comma(look,buf);
    look=look+d;
    if (!pass)
      pc+=6;
    else 
      put_float(buf);
	
    if ((pass)&&(verbose)&&(c==2)) {
      if (!p) {
	printf("%s %s\n",outline,get_nxt_word(2));
	p=1;
      } else {
	printf("%s\n",outline);
      }
      outline[0]=0;
      c=0;
    }
  }
  
  if ((pass)&&(verbose)&&(c)) {
    if (!p) {
      printf("%s %s\n",outline,get_nxt_word(2));
    } else {
      printf("%s\n",outline);
    }
  }
  outline[0]=0;
  return 1;
}
/*=========================================================================*
 * function do_xword(int tp)
 * parameter tp: flag defining storage mode (0=lo/hi, 1=hi/lo)
 *
 * processes a .WORD or .DBYTE sequence in the file stream, also handles
 * additives
 *=========================================================================*/
int do_xword(int tp) {
  char *str,*look;
  char buf[80];
  unsigned short add, a;
  int d,c,p;
  
  add=0;
  tp=(tp==3);
  
  str=get_nxt_word(1);
  while(isspace(*str))
    str++;
  look=str+strlen(str)-1;
  while(isspace(*look)) {
    *look=0;
    look--;
  }
  
  look=str;
  p=c=0;
  while(*look) {
    if ((pass)&&(verbose)) {
      if (!c) {
	print_pc();
	c++;
      }
      c++;
    }
    switch(*look) {
    case 9:
    case 32:
      look++;
      break;
    case '+':
      if (look!=str) 
	error("Misplaced additive modifier.",0);
      look++;
      d=to_comma(look,buf);
      look=look+d;
      add=get_immediate(buf);
      c=1;
      if (!look)
	error("Useless statement.",0);
      break;
    case 34:
      error("String must be in xbyte format.",1);
      break;
    default:
      d=to_comma(look,buf);
      look=look+d;
      if (!pass)
	pc+=2;
      else {
	a=get_address(buf);
	put_word(a+add,tp);
      }
      break;
    }
    if ((pass)&&(verbose)&&(c==3)) {
      if (!p) {
	printf("%s %s\n",outline,get_nxt_word(2));
	p=1;
      } else {
	printf("%s\n",outline);
      }
      outline[0]=0;
      c=0;
    }
  }
  if ((pass)&&(verbose)&&(c)) {
    if (!p) {
      printf("%s %s\n",outline,get_nxt_word(2));
    } else {
      printf("%s\n",outline);
    }
  }
  outline[0]=0;
  return 1;
}
/*=========================================================================*
  function do_xbyte(int tp)
  parameter tp: indicates type
     0- .BYTE
     1- .CBYTE
     2- .SBYTE
  
  processes a .xBYTE sequence in the file stream, also handles additives
 *=========================================================================*/
int do_xbyte(int tp) {
  char *str,*look;
  char buf[80];
  unsigned char add,cb,hi;
  short a;
  int d,i,p,c;
  
  add=cb=0;
  str=get_nxt_word(1);
  while(isspace(*str))
    str++;
  look=str+strlen(str)-1;
  while(isspace(*look)) {
    *look=0;
    look--;
  }

  look=str;
  c=p=0;
  while(*look) {
    if ((pass)&&(verbose)) {
      if (!c) {
	print_pc();
	c++;
      }
      c++;
    }
    switch(*look) {
    case 9:
    case 32:
      look++;
      break;
    case '+':
      if (look!=str) 
	error("Misplaced additive modifier.",0);
      look++;
      d=to_comma(look,buf);
      look=look+d;
      add=get_immediate(buf);
      c=1;
      if (!look)
	error("Useless statement.",0);
      break;
    case 34:
      d=to_comma(look,buf);
      look=look+d;
      d=strlen(buf)-1;
      if (((d<0)||(buf[0]!=34))&&(buf[d]!=34)) {
	error("Malformed string.",0);
      } else {
	if (pass)
	  for(i=1;i<d;i++) {
	    a=buf[i];
	    if ((!*look)&&(tp==1)&&(i==d-1))
	      cb=128;
	    else if (tp==2) {
	      hi=a&128;
	      a=ascii_to_screen[a&127]|hi;
	    }
	    put_byte((a+add)^cb);
	    c++;
	    if ((pass)&&(verbose)&&(c==6)) {
	      if (!p) {
		printf("%s %s\n",outline,get_nxt_word(2));
		p=1;
	      } else {
		printf("%s\n",outline);
	      }
	      outline[0]=0;
	      print_pc();
	      c=2;
	    }
	  }
	else
	  pc+=d-1;
      }
      break;
    default:
      d=to_comma(look,buf);
      look=look+d;
      if (!pass)
	pc++;
      else {
	a=get_immediate(buf);
	if ((!*look)&&(tp==1))
	  cb=128;
	else if (tp==2) {
	  hi=a&128;
	  a=ascii_to_screen[a&127]|hi;
	}
	put_byte((a+add)^cb);
      }
      break;
    }
    
    if ((pass)&&(verbose)&&(c==5)) {
      if (!p) {
	printf("%s %s\n",outline,get_nxt_word(2));
	p=1;
      } else {
	printf("%s\n",outline);
      }
      c=0;
      outline[0]=0;
    }
  }
  if ((pass)&&(verbose)&&(c)) {
    if (!p) {
      printf("%s %s\n",outline,get_nxt_word(2));
    } else {
      printf("%s\n",outline);
    }
  }
  outline[0]=0;
  return 1;
}
/*=========================================================================*
  function get_single(symbol *sym)
  parameters: sym - the opcode to process

  this function assembles single opcodes if appropraite, or continues
  execution by passing parameters to parse_operand()
 *=========================================================================*/
int get_single(symbol *sym) {
  char *a;
  
  if (imp[sym->addr]>=0) {
    if (pass)
      put_opcode(imp[sym->addr]);
    else
      pc++;
    return 1;
  } else if (acc[sym->addr]<0) {
    error("Illegal operand",1);
  }
  a=get_nxt_word(3);
  squeeze_str(a);
  if ((!strlen(a))||((strlen(a)==1)&&(toupper(*a)=='A'))) {
    a=get_nxt_word(1);
    if (pass)
      put_opcode(acc[sym->addr]);
    else
      pc++;
  } else {
    a=get_nxt_word(1);
    squeeze_str(a);
    parse_operand(sym,a);
  }
  return 1;
}
/*=========================================================================*
 * function incbin(char *fname)
 *
 * this includes a binary file
 *=========================================================================*/
int incbin(char *fname) {
  FILE *in;
  int b,v;
  
  in=fopen(fname,"rb");
  if (!in) {
    error("Cannot open binary file",1);
  }
  v=verbose;
  verbose=0;
  while(!feof(in)) {
    b=fgetc(in);
    if (!feof(in))
      put_byte(b);
  }
  verbose=v;
  fclose(in);
  return 0;
}
/*=========================================================================*
 * function skip_if()
 *
 * this skips code until a matching .ENDIF or .ELSEIF is found
 * correctly handles nested .IFs
 *=========================================================================*/
int skip_if() {
  int i=0;
  char *str;
  
  while(i>=0) {
    str=get_nxt_word(5);
    if (!str) 
      error("Mismached .IF/.ELSE/.ENDIF statements.",1);
    if (!strcasecmp(str,".IF"))
      i++;
    if (!strcasecmp(str,".ENDIF"))
      i--;
    if ((!strcasecmp(str,".ELSE"))&&(!i))
      i--;
  }
  eq=0;
  return 1;
}
/*=========================================================================*
 * function proc_sym(symbol *sym)
 * parameter sym- the symbol to parse
 *
 * This function handles operands and system defines, farming out to the
 * appropriate functions
 *=========================================================================*/
int proc_sym(symbol *sym) {
  char *line, *str=NULL;
  short addr;
  int i,stor;
  macro_call *mc;
  symbol *mlabels;
  char buf[80];
  
  switch (sym->tp) {
  case OPCODE:  /* opcode */
    if (!init_pc)
      error("No initial address specified.",1);
    if ((verbose)&&(pass))
      print_pc();    
      
    if (num_args[sym->addr]) {
      str=get_nxt_word(1);
      squeeze_str(str);
      parse_operand(sym,str);
    } else {
      get_single(sym);
    }
    if ((verbose)&&(pass)) {
      while(strlen(outline)<16)
	strcat(outline," ");
      line=get_nxt_word(2);
      printf("%s%s\n",outline,line);
    }
    break;
  case DIRECT:  /* system def */
    switch(sym->addr) {
    case 0:  /* .BYTE */
    case 1:  /* .SBYTE */
    case 2:  /* .CBYTE */
      do_xbyte(sym->addr);
      break;
    case 3:  /* .DBYTE */
      do_xword(sym->addr);
      break;
    case 4: /* .ELSE */
      skip_if();
      break;
    case 5:  /* .END */
      break;
    case 6: /* .ENDIF */
      break;
    case 7: /* .ERROR */
      str=get_nxt_word(0);
      error(str,1);
      break;      
    case 8: /* .FLOAT */
      do_float();
      break;
    case 9: /* .IF */
      str=get_nxt_word(1);
      squeeze_str(str);
      eq=0;
      addr=get_expression(str,1);
      if (!addr)
	skip_if();
      break;
    case 10:  /* .INCLUDE */
      str=get_nxt_word(0);
      if (str[0]==34) {
	str++;
	str[strlen(str)-1]=0;
      }
      open_file(str);
      break;
    case 11: /* .LOCAL */
      local++;
      if (local>62)
	error("Over 62 local regions defined, will not compile on MAC/65.",0);
      break;
    case 12: /* .OPT */
    case 13: /* .PAGE */
    case 14: /* .SET */
    case 15: /* .TAB */
    case 16: /* .TITLE */
      do {
	str=get_nxt_word(4);
      } while(strlen(str));
      break;
    case 17: /* .WORD */
      do_xword(sym->addr);
      break;
    case 18: /* '*' operator */
      if ((!eq)||(eq==2)) {
	error("Malformed * operator.",1);
      }
      if ((verbose)&&(pass))
	printf("\n");
      eq=0;
      str=get_nxt_word(1);
      squeeze_str(str);
      if (str[0]=='*') {  /* Relative adjustment */
	if (!init_pc)
	  error("No inital address specified.",1);
	if (str[1]!='+')
	  error("Illegal relative adjustment.",1);
	str[0]='0';
	addr=get_expression(str,1);
	pc=pc+addr;
      } else {            /* Absolute value */
	init_pc=1;
	addr=get_expression(str,1);
	pc=addr;
      }
      break;
    case 19:  /* .ENDM */
      error("No matching .MACRO definition for .ENDM",1);
      break;
    case 20:  /* .MACRO definition */
      if (!pass)
	create_macro(sym);
      else
	skip_macro();
      break;
    case 21:  /* .DS directive */
      str=get_nxt_word(1);
      squeeze_str(str);
      addr=get_expression(str,1);
      pc=pc+addr;
      break;
    case 22: /* .INCBIN */
      str=get_nxt_word(0);
      if (str[0]==34) {
	str++;
	str[strlen(str)-1]=0;
      }
      incbin(str);
      break;
    case 23: /* .REPT */
      do_rept(sym);
      break;
    case 24:  /* .ENDR */
      error("No matching .REPT definition for .ENDR",1);
      break;
    case 25: /* .WARN */
      str=get_nxt_word(0);
      error(str,0);
      break;
    case 26: /* .DC */
      str=get_nxt_word(0);
      addr=get_expression(str,1);

      str=get_nxt_word(0);
      stor=get_expression(str,1);
      
      for(i=0;i<addr;i++) {
	if ((verbose)&&(pass)&&(!(i&3))) {
	  if (i)
	    printf("%s\n",outline);
	  outline[0]=0;
	  print_pc();
	}
	if (pass)
	  put_byte(stor);
	else
	  pc++;
      }
      if ((verbose)&&(pass))
	printf("%s\n",outline);
      outline[0]=0;
      break;      
    default:
      error("Illegal directive.",1);
      break;
    }
    break;
  case MACRON: /* MACRO */
    mc=get_macro_call(sym->name);
    if (!mc) 
      error("Missing entry in macro table",1);

    macro_param(mc,str);
    mc->nxt=invoked;
    invoked=mc;
    mc->orig->times++;
    if (pass) {  /* a macro has been instantiated, update mlabel defs */
      mlabels=mc->orig->mlabels;
      while(mlabels) {
	if (mlabels->invoked[mlabels->num*2]+1==mc->orig->times) {
	  mlabels->addr=mlabels->invoked[mlabels->num*2+1];
	  mlabels->num++;
	}
	mlabels=mlabels->mlnk;
      }
    }
    break;
  case MACROL: /* MACRO label/equate/tequate */
  case MACROQ:
    if (!pass) {
      if (eq==1) {
	sprintf(buf,"Equate '%s' defined multiple times",sym->name);
	error(buf,1);
      }
      sym->num++;
      if (sym->num>sym->sz/2) {
	sym->sz+=32;
	sym->invoked=(unsigned short *)realloc(sym->invoked,sym->sz*sizeof(unsigned short));
      }
      sym->invoked[sym->num*2]=invoked->orig->times-1;
      sym->invoked[sym->num*2+1]=pc;
    }
    if (eq) {
      if (sym->tp!=MACROQ) {
	sprintf(buf,"Symbol '%s' is not a transitory equate!",sym->name);
	error(buf,1);
      } 
      str=get_nxt_word(1);
      if (eq==2) {
	squeeze_str(str);
	addr=get_address(str);
	sym->invoked[sym->num*2+1]=addr;
      }
      eq=0;
    }
    break;
  case LABEL:  /* labels and equates */
  case EQUATE:
    if (!pass) {
      if (eq==2) {
	sprintf(buf,"Symbol '%s' is not a transitory equate!",sym->name);
	error(buf,1);
      } else {
	sprintf(buf,"Symbol '%s' already defined!",sym->name);
	error(buf,1);
      }
    } else if (eq==1) {
      if (sym->tp==LABEL) {
	sprintf(buf,"Cannot use label '%s' as an equate",sym->name);
	error(buf,1);
      }
      str=get_nxt_word(1);
      if (sym->addr==0xffff) {  /* allow forward equate references */
	squeeze_str(str);
	addr=get_address(str);
	sym->addr=addr;
      }
      eq=0;
    }
    break;
  case TEQUATE: /* transitory equates */
    if (!pass) {
      if (eq==2) {
	str=get_nxt_word(1);
	eq=0;
      } else {
	sprintf(buf,"Use .= to assign '%s' new a value.",sym->name);
	error(buf,1);
      }
    } else {
      if (eq==2) {   /* allow .= updates */
	str=get_nxt_word(1);
	squeeze_str(str);
	addr=get_address(str);
	sym->addr=addr;
	eq=0;
      }
    }
    break;
  default:
    if (!pass) {
      sprintf(buf,"Symbol '%s' already defined!",sym->name);
      error(buf,1);
    }
  }
  return 1;
}
/*=========================================================================*
 * function do_cmd(char *buf)
 * parameter buf - a string containing a symbol to process
 *
 * this starts the parsing process
 *=========================================================================*/
int do_cmd(char *buf) {
  symbol *sym;
  int i;
  
  for(i=0;i<strlen(buf);i++)
    buf[i]=toupper(buf[i]);
  sym=findsym(buf);
  if (!sym) {  /* must be a label or define */
    add_label(buf);    
  } else {
    proc_sym(sym);
  }
  return 1;
}

/*=========================================================================*
 * function init_pass()
 *
 * This initializes global variables for each assembly pass
 *=========================================================================*/
int init_pass() {
  pc=-1;
  init_pc=local=0;
  clear_ref();  
  return 0;
}
/*=========================================================================*
 * function assemble(char *fname)
 * parameter fname- file to assemble
 *
 * This performs a two pass assembly over the file indicated
 *=========================================================================*/
int assemble(char *fname) {
  char *str;
  int i;

  for(i=0;i<2;i++) {
    init_pass();
    fprintf(stderr,"Pass %d: ",i+1);
    fflush(stderr);
    if ((verbose)&&(pass==1))
      fprintf(stderr,"\n");
    open_file(fname);
    do {
      str=get_nxt_word(0);
      if (str) {
	do_cmd(str);
      }
    } while(str);
    if ((!verbose)||(!pass))
    fprintf(stderr,"Success. (%d warnings)\n",warn);
    pass++;
  }
  return 1;
}
/*=========================================================================*
 * function save_snapshot(char *fname)
 * parameter: fname- filename of snapshot to save
 * 
 * saves an raw memory snapshot
 *=========================================================================*/
int save_snapshot(char *fname) {
  FILE *out;
  out=fopen(fname,"wb");
  if (!out)
    error("Cannot open file for writing.",1);
  fwrite(memmap,65536,1,out);
  fclose(out);
  return 1;
}
/*=========================================================================*
 * function save_word(FILE *out, int b, int e)
 *  parameters:out-file to save word to
 *             b - a word to store 
 *             e - flag denoting storage method (0=lo/hi, 1=hi/lo)
 *
 * saves a bigendian/little endian word to file
 *=========================================================================*/  
int save_word(FILE *out,int b,int e) {
  unsigned char v;

  if (!e) {  /* Lo Hi */
    v=b&0xff;
    fputc(v,out);
    v=b>>8;
    fputc(v,out);
  } else {  /* Hi Lo */
    v=b>>8;
    fputc(v,out);
    v=b&0xff;
    fputc(v,out);
  }
  return 0;
}
/*=========================================================================*
 * function save_block(FILE *out, int b, int sz)
 *  parameters:out-file to save word to
 *             b - beginning of memory location
 *             sz- size to store
 *
 * saves a file block to disk
 *=========================================================================*/  
int save_block(FILE *out,int b,int sz) {
  save_word(out,b,0);
  save_word(out,b+sz-1,0); 
  fwrite(memmap+b,1,sz,out);
  return 1;
}
/*=========================================================================*
 * function save_binary(char *fname)
 * parameter: fname- filename of binary file to save
 *
 * saves a binary file from memory snapshot
 * format:
 * 0xff 0xff
 * .word begin, end, data
 * .word begin, end, data...
 *=========================================================================*/  
int save_binary(char *fname) {
  FILE *out;
  unsigned char *scan, *end;
  int len,a,b,i,walk,start;

  out=fopen(fname,"wb");
  if (!out)
    error("Cannot open file for writing.",1);
  scan=bitmap;
  end=scan+8096;
  
  save_word(out,0xffff,0);
  walk=start=len=0;

  while(scan!=end) {
    a=*scan;
    b=128;
    for(i=0;i<8;i++) {
      if (a&b) {
	if (!len) {
	  len=1;
	  start=walk;
	} else len++;       
      } else {
	if (len) {
	  save_block(out,start,len);     
	  fprintf(stderr,"    Block: %.4x-%.4x (%d bytes)\n",
		  start,start+len-1,len);
	  len=start=0;
	}
      }
      b=b>>1;
      walk++;
    }
    scan++;
  }
  fclose(out);
  
  fprintf(stderr,"\nCompiled to binary file '%s'\n",fname);
  return 1;
}
/*=========================================================================*
 * function main
 *
 * starts the whole process
 *=========================================================================*/
int main(int argc, char *argv[]) {
  char fname[80],snap[80],xname[80],*walk;
  int dsymbol,i,state,xfd,iop;
  
  fprintf(stderr,"ATasm %d.%d (A mostly Mac65 compatible 6502 cross-assembler)\n",MAJOR_VER,MINOR_VER);
  
  verbose=dsymbol=state=xfd=iop=0;
  strcpy(snap,"atari800.a8s");
  fname[0]=0;
  
  for(i=1;i<argc;i++) {
    if (!strcasecmp(argv[i],"-v"))
      verbose=1;
    else if (!strcasecmp(argv[i],"-u"))
      iop=1;
    else if (!strcasecmp(argv[i],"-s"))
      dsymbol=1;
    else if (!strncasecmp(argv[i],"-x",2)) {
      if (strlen(argv[i])>2)
	strcpy(xname,argv[i]+2);
      else {
	fprintf(stderr,"Must specify .XFD file.\n");
	return 1;
      }
      xfd=1;
    } else if (!strncasecmp(argv[i],"-m",2)) {
      if (strlen(argv[i])>2)
	strcpy(snap,argv[i]+2);
      else
	fprintf(stderr,"Using default state file: '%s'\n",snap);
      state=1;
    } else if (!strcasecmp(argv[i],"-h")) {
      fprintf(stderr,"\nUsage: %s [-v] [-s] [-m[fname.state]] <fname.asm>\n",argv[0]);
      fputs("  where  -v: prints assembly trace\n",stderr);
      fputs("         -s: prints symbol table\n",stderr);
      fputs("         -u: enables undocumented opcodes\n",stderr);
      fputs("         -m[fname]: defines template emulator state file\n",stderr);
      fputs("         -x[fname]: saves object file to .XFD image [fname]\n",stderr);
      return 1;
    } else strcpy(fname,argv[i]);
  }

  if (!strlen(fname)) {
    strcpy(fname,"test.asm");
  }

  init_asm(iop);  
  assemble(fname);
  
  if (dsymbol)
    dump_symbols();
  
  fputs("\nAssembly successful\n",stderr);
  fprintf(stderr,"  Compiled %d bytes (~%dk)\n",bsize,bsize/1024);

  walk=strchr(fname,'.');
  if (walk) 
    *walk=0;
  strcat(fname,".65o");
     
  save_binary(fname);
  
  if (xfd) {
    xfd=write_xfd_file(xname,fname);
    if (xfd<0)
      error(".XFD image not updated.",0);
  }
  
  if (state) {
    walk=strchr(fname,'.');
    if (walk) 
      *walk=0;
    strcat(fname,".a8s");
    if (!strcmp(snap,fname)) {
      fprintf(stderr,"Error: template state file and save state file cannot be the same!\n");
      return 1;
    }
    save_state(snap,fname);
  }
  return 1;
}
/*=========================================================================*/
