/*==========================================================================
 * 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!
 *  05/25/02 mws Added .OPT NO ERR/ERR, .OPT NO OBJ/OBJ
 *               Thanks to Chris Hutt (cghutt@hotmail.com) for suggesting
 *		 these features;  Fixed problem with missing last line of a
 *               file if no carriage return, thanks to Chris Hutt,
 *               Thompsen (twh@havemeister.net) and others for reporting
 *               this.
 *  05/27/02 mws fixed hideous .incbin error from last release
 *  06/12/02 mws added several zero page -> absolute address operators
 *               (sbc/adc/and/eor/ora/cmp);  Allowed compiliation to
 *               addresses >$fd00;  Thanks to Manual Polik
 *               (cybergoth@nexgo.de) for finding these!
 *  06/14/02 mws fixed problem with immediate value of a comma: #',
 *               fixed problem with using same labels/equates names
 *               inside different macro definitions
 *
 *               dreaded RELEASE 1.00!
 *
 *  08/05/02 mws Merged changes from B.Watson, release 1.02
 *  03/03/03 mws Fixed zero page JSR
 *               Enforce labels to begin with '@','?' or letter to help
 *               find typos;
 *               Added interpretation of #$LABEL (with warnings)
 *               Thanks to Maitthias Reichl for finding/suggesting these
 *  08/20/03 mws Fixed a problem with predefinitions
 *  10/08/03 mws Added .SET 6,offset for assembling offset of storage from
 *               the location counter;  Added .BANK directive for cartrige
 *               images (and allows INITAD to work for the first time)
 *               Also Added the .OPT LIST/NO LIST options.  Chris Hutt is
 *               again instrumental in requesting these new features.
 *  10/17/03 mws Finished initial .BANK support; Added initial Atari++
 *               snapshot support
 *  10/19/03 mws Fixed bug where -o files were not correctly saved to .XFD
 *               image.
 *  10/23/03 mws Added find_extension to fix problem with relative path
 *               names on the command-line;
 *  04/20/04 mws Added patches from Mattias Reichl to allow negative values
 *               for .SET 6
 *  10/22/04 mws Added support for MAE-like handling of local variables
 *               This mode prepends the most-recent global label, so:
 *                   DELAY  LDX #100
 *                   ?L     DEX
 *                          BNE ?L
 *               ...will add DELAY?L to symbol table
 *  02/12/10 mws calculate .SET 6 on second pass to allow for symbol refs
 *  04/19/10 mws applied patch frm 8bit-man to fix token grab routine ';'
 *==========================================================================*
 * 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
 *   .opt are not properly set at beginning of each pass
 *   i.e. .opt no list causes listing to be suppressed completely
 *
 * COMPLETED TODO
 *   determine how to allow INITAD ($2e2) (10/08/2003 use .BANK directive)
 *
 * 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)
 *   missing last line of files           (fixed 05/25/02 mws)
 *==========================================================================*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *==========================================================================*/
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>

#include "compat.h"
#include "ops.h"
#include "directive.h"
#include "symbol.h"
#include "inc_path.h"
#include "atasm_err.h"

unsigned short pc;    /* program counter */
int init_pc;          /* pc orig flag */
int pass;             /* assembly pass number */
int eq, verbose;      /* assignment flag, verbosity flag */
/* eq of 1 =; eq of 2 .= */
int local,numwarn,bsize; /* number of local regions, number of warnings
       size in bytes of compiled code */
str_list *includes;   /* list of dirs to search for .INCLUDEd files */
str_list *predefs;    /* predefined stuff (like -dfoo=1) */

options opt;
file_stack *fin;
memBank *banks, *activeBank;
int bankID;
char *outline;  /* the line of text written out in verbose mode */
char *nxt_char; /* character pointer saved between calls of get_nxt_word */
char *lfin; /* last filename encountered, used in aprintf */

FILE *listFile;
FILE *errFile;
/*=========================================================================*
 * function kill_banks
 * removes all banks
 *=========================================================================*/
void kill_banks() {
  memBank *bkill;
  /* Free mem-maps */
  while(banks) {
    bkill=banks;
    free(bkill->memmap);
    free(bkill->bitmap);
    banks=banks->nxt;
    free(bkill);
  }
  banks=NULL;
}

/*=========================================================================*
 * function get_bank
 * initializes and returns a new memory bank object
 *=========================================================================*/
memBank *get_bank(int id, int sym_id) {
  memBank *bank, *walk;

  bank=NULL;
  walk=banks;
  while((walk)&&(walk->nxt)) {
    if ((walk->id==id)&&(walk->sym_id==sym_id))
      return walk;
    else if ((walk->nxt)&&(walk->nxt->id>id)) {
      bank=(memBank *)malloc(sizeof(memBank));
      bank->nxt=walk->nxt;
      break;
    }
    walk=walk->nxt;
  }
  if ((walk)&&(walk->id==id))
    return walk;

  if (!bank) {
    bank=(memBank *)malloc(sizeof(memBank));
    if (bank)
      bank->nxt=NULL;
  }
  if (!bank)
    return NULL;

  bank->id=id;
  bank->sym_id=sym_id;
  bank->offset=0;
  bank->memmap=(unsigned char *)malloc(65536); /* memory snapshot */
  if (!bank->memmap) {
    free(bank);
    return NULL;
  }
  bank->bitmap=(unsigned char *)malloc(8192);
  if (!bank->bitmap) {
    free(bank->memmap);
    free(bank);
    return NULL;
  }
  memset(bank->memmap,0,65536);
  memset(bank->bitmap,0,8192);
  if (walk) {
    walk->nxt=bank;
  } else {
    banks=bank;
  }
  return bank;
}
/*=========================================================================*
 * function init_asm
 * initializes the assembler
 *=========================================================================*/
int init_asm() {
  int i,ops;
  symbol *sym;

  pass=numwarn=bsize=repass=double_fwd=0;
  fin=NULL;
  macro_list=NULL;
  invoked=NULL;
  unkLabels=NULL;
  banks=NULL;
  bankID=-1;
  errFile=stderr;
  nxt_char=NULL;
  lfin=NULL;

  for(i=0;i<HSIZE;i++)  /* clear symbol table */
    hash[i]=NULL;
  for(i=0;i<ISIZE;i++)  /* clear error/warning table */
    ihash[i]=NULL;

  process_predef(predefs);

  outline=(char *)malloc(256);
  activeBank=get_bank(0,0);

  if ((!outline)||(!activeBank)) {
    error("Cannot alocate memory snapshot.",1);
  }

  ops=NUM_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[256];

  fnew=(file_stack *)malloc(sizeof(file_stack));
  fnew->name=(char *)malloc(strlen(fname)+1);
  strcpy(fnew->name,fname);

  fnew->in = fopen_include(includes, fname, 0);
  if (!fnew->in) {
    snprintf(buf,256,"Cannot open file '%s'\n",fname);
    error(buf,1);
  }
  fnew->line=0;
  fnew->nxt=fin;
  fin=fnew;
  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
 *=========================================================================*/
void aprintf(char *msg, ...) {
  char buf[256], line[256];

  va_list args;

  buf[0]=line[0]=0;
  va_start(args,msg);
  vsprintf(buf,msg,args);
  strcat(line,buf);
  va_end(args);

  /* normal verbose output */
  if (verbose&1) {
    fputs(line,stdout);
  }

  /* list file output */
  if ((verbose&2)&&(listFile)) {
    strcpy(buf,line);
    squeeze_str(buf);
    if (buf[0]) {
      if ((!lfin)||(lfin!=fin->name)) {
        lfin=fin->name;
        fprintf(listFile,"\nSource: %s\n",fin->name);
      }
      /* convert address from LE to BE display */
      strncpy(buf,line+3,2);
      strncpy(buf+2,line,2);
      buf[4]=0;
      fprintf(listFile,"%05d %s%s",fin->line,buf,line+5);
    }
  }
}

/*=========================================================================*
 * 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];
  static char line2[257];
  char l,*look,*walk;
  int instr,i,len;
  file_stack *kill;
  macro_call *mkill;
  macro_line *lkill;

  if (tp==6) {
    look=nxt_char;
    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 (!nxt_char)
      return buf;
    strcpy(buf,nxt_char);
    instr=0;
    len=strlen(buf);
    for(i=0;i<len;i++) {
      if (buf[i]==34)    /* fix embedded ';' problem - mws 11/10/99 */
        instr^=1;
      else if ((buf[i]==';')&&(!instr)) {
        if ((i)&&(buf[i-1]=='\''))  /* allow quoted semicolons */
          continue;
        buf[i]=0;
        break;
      }
    }
    if (tp==1) {
      nxt_char=NULL;
    }
    return buf;
  } else if (tp==2) {
    line2[0] = ' ';
    strcpy(&line2[1], line);
    return line2;
  }

  /* skip over empty space, blank lines and comments */
  do {
    while ((!nxt_char)||(!(*nxt_char))) {
      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++;
        if (!fgets(line,256,fin->in)) {
          line[0]=0;
        }
      }
      /* strip line number, if one exists */
      if (ISDIGIT(line[0])) {
        i=0;
        while(ISDIGIT(line[i]))
          line[i++]=32;
      }
      /* Remove EOL characters */
      len=strlen(line);
      for(i=0;i<len;i++) {
        if ((line[i]==10)||(line[i]==13))
          line[i]=0;
      }
      walk=line+strlen(line)-1;
      while(ISSPACE(*walk)) {
        *walk=0;
        walk--;
      }
      nxt_char=line;
      if ((feof(fin->in))&&(!strlen(line))) {  /* fixed early close -- 05/25/02 mws */
        kill=fin;
        fin=fin->nxt;
        fclose(kill->in);
        free(kill->name);
        free(kill);
        nxt_char=NULL;
        if (!fin)
          return NULL;
      }
    }
    while(ISSPACE(*nxt_char)) {
      nxt_char++;
    }
    if (((nxt_char)&&(!(*nxt_char)))||(*nxt_char==';')) {
      nxt_char=NULL;
    }
  } while ((!nxt_char)||(!(*nxt_char)));

  instr=0;

  /* Get next token (one word or string) */
  do {
    l=*nxt_char;
    if (l)
      nxt_char++;
    if (l==34)
      instr^=1;
    if (!instr) {
      if (l=='=') {
        l=0;
        eq=1;
      } else if ((l=='.')&&(*nxt_char=='=')) {
        l=0;
        eq=2;
        nxt_char++;
      }
    }
    *look++=l;
  } while((l)&&((instr)||(!ISSPACE(l)))&&(l!=';'));
  /* Fix to stop on ';' comment above, from 8bit-man;  04/19/2010 */

  if (l)
    *(look-1)=0;
  else
    *look=0;

  if (instr)
    error("Unterminated string constant.",1);

  if (l) {
    /* Skip to next token if available */
    while((nxt_char)&&(ISSPACE(*nxt_char))) {
      nxt_char++;
    }
    /* Check for '=' or '.=' */
    if (nxt_char) {
      if (*nxt_char=='=') {
        eq=1;
        nxt_char++;
      } else if ((*nxt_char=='.')&&(*(nxt_char+1)=='=')) {
        eq=2;
        nxt_char+=2;
      } else nxt_char--;
    }
  }

  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, opc;

  v=b&0xff;
  if (verbose) {
    if (opt.obj)
      snprintf(buf,64,"%.2X ",v);
    else
      snprintf(buf,64,"   ");  /* If no obj opt set, output space... */
    strcat(outline,buf);
  }
  opc=pc+activeBank->offset;
  if (opc<0)
    error("Address underflow",1);

  a=opc>>3;
  if (a>8192)
    error("Address overflow",1);

  if (opt.obj) {
    activeBank->memmap[opc]=v;
    if (activeBank->bitmap[a]&(128>>(opc&7))) {
      snprintf(buf,64,"Warning: Address %.4x over-written",opc);
      error(buf,0);
    } else bsize++;

    activeBank->bitmap[a]=activeBank->bitmap[a]|(128>>(opc&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)) {
    snprintf(buf,64,"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;
    snprintf(buf,64,"%.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)
    snprintf(buf,32,"      ");
  else {
    int opc=(pc+activeBank->offset)&0xffff;
    snprintf(buf,32,"%.2X:%.2X  ",opc&0xff,opc>>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) {
  unsigned short v,i;

  v=get_expression(str,1);
  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 (!((label[0]=='@')||(label[0]=='?')||(label[0]=='_')||(ISALPHA(label[0])))) {
      error("Illegal label name, must start with '@','?', or a letter.",1);
    }
    if (strchr(label,'='))
      error("Illegal label (cannot contain '=')",1);
    sym=get_sym();
    if (label[0]=='?') {
      if (opt.MAElocals) {
        int len;
        if (!opt.MAEname) {
          error("Local label must occur after a global label.",1);
        }
        len=strlen(opt.MAEname)+strlen(label)+1;
        sym->name=(char *)malloc(len);
        snprintf(sym->name,len,"%s%s",opt.MAEname,label);
      } else {
        int len;
        snprintf(num,32,"=%d=",local);
        len=strlen(label)+strlen(num)+1;
        sym->name=(char *)malloc(len);
        snprintf(sym->name,len,"=%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;

      /* Create new name... */
      free(sym->name);
      sym->name=malloc(strlen(label)+strlen(invoked->orig->name)+10);
      sprintf(sym->name,"=%.4x_%s=%s",invoked->orig->times,invoked->orig->name,label);
      sym->macroShadow=strchr(sym->name+1,'=')+1;

      if (eq==1) {
        error("Defining an equate in macro",0);
      } else if (eq==2) {
        sym->tp=MACROQ;
      } else {
        macro_call *hold=invoked;
        invoked=NULL;
        if (!findsym(label)) {
          symbol *nsym=get_sym();
          nsym->name=(char *)malloc(strlen(label)+1);
          strcpy(nsym->name,label);
          nsym->tp=MACROL;
          nsym->addr=pc;
          nsym->bank=activeBank->sym_id;
          addsym(nsym);
        }
        invoked=hold;

        sym->tp=MACROL;
        sym->addr=pc;
        sym->bank=activeBank->sym_id;
      }
      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;
      sym->bank=activeBank->sym_id;
    }
    if (sym->tp==LABEL) {
      defUnk(sym->name,pc);
      sym->bank=activeBank->sym_id;
    }
    addsym(sym);
  }
  if (eq) {  /*  an equate */
    sym=findsym(label);
    if (!sym) {
      char buf[256];
      sprintf(buf,"Cannot find label %s", label);
      error(buf,1);
    }
    str=get_nxt_word(1);

    if (pass) {
      squeeze_str(str);
      sym->addr=get_address(str);
      sym->bank=activeBank->sym_id;
    } else {
      squeeze_str(str);
      v=get_expression(str,0);  /* allow forward reference in equates */
      sym->addr=v&0xffff;       /* But causes problems... crazy.m65 */
      sym->bank=activeBank->sym_id;
    }
    eq=0;
    defUnk(sym->name,sym->addr);
  }
  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;
  cmd=0;

  if (idx) {
    if ((idx>str)&&(*(idx-1)!='\'')) {  /* allow quoting: cmp #', */
      *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)) {
              snprintf(buf,80,"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==a_y[sym->addr])  /* pad LDA/STA ZP,y and friends */
                  xtnd=1;
              } else
                cmd=a_y[sym->addr];
            }
          } else {
            if (a<256) {
              cmd=zpg[sym->addr];
              if ((sym->addr==30)||(sym->addr==31))
                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 */
              (sym->addr==31)||
              ((vidx=='Y')&&((a_y[sym->addr]==z_y[sym->addr]))))
            pc+=3;
          else
            pc+=2;
        } else if ((str[0]=='>')||(str[0]=='<'))  /* hi,lo are 2 bytes long */
          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]=='~') || (num[0]=='%'))
      tp=2;
    else {
      tp=0; /* remove annoying compiler warning */
      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 ((*look=='\'')&&(*(look+1)==' ')&&(*(look+2)=='\'')) {  /* Allow quoted space */
      *walk++=*look++;
      *walk++=*look++;
      *walk++=*look++;
      continue;
    }
    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) {
        aprintf("%-24s%s\n",outline,get_nxt_word(2));
        p=1;
      } else {
        aprintf("%s\n",outline);
      }
      outline[0]=0;
      c=0;
    }
  }

  if ((pass)&&(verbose)&&(c)) {
    if (!p) {
      aprintf("%-24s%s\n",outline,get_nxt_word(2));
    } else {
      aprintf("%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;

  if(!init_pc)
    error("No initial address specified.",1);

  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) {
        aprintf("%-24s%s\n",outline,get_nxt_word(2));
        p=1;
      } else {
        aprintf("%s\n",outline);
      }
      outline[0]=0;
      c=0;
    }
  }
  if ((pass)&&(verbose)&&(c)) {
    if (!p) {
      aprintf("%-24s%s\n",outline,get_nxt_word(2));
    } else {
      aprintf("%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;

  if(!init_pc)
    error("No initial address specified.",1);

  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))||
          ((d>0)&&(buf[0]==34)&&(buf[d]!=34))) {
        error("Malformed string.",1);
      } 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) {
              aprintf("%-24s%s\n",outline,get_nxt_word(2));
              p=1;
            } else {
              aprintf("%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) {
        aprintf("%-24s%s\n",outline,get_nxt_word(2));
        p=1;
      } else {
        aprintf("%s\n",outline);
      }
      c=0;
      outline[0]=0;
    }
  }
  if ((pass)&&(verbose)&&(c)) {
    if (!p) {
      aprintf("%-24s%s\n",outline,get_nxt_word(2));
    } else {
      aprintf("%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_include(includes, fname, 1);
  if (!in) {
    error("Cannot open binary file",1);
  }
  v=verbose;
  verbose=0;
  while(!feof(in)) {
    b=fgetc(in);
    if (!feof(in)) {
      if (pass)
        put_byte(b);
      else
        pc++;
    }
  }
  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;
  char buf[80];

  switch (sym->tp) {
  case OPCODE:  /* opcode */
    if (!init_pc)
      error("No initial address specified.",1);
    if ((verbose)&&(pass))
      print_pc();

    if(sym->addr >= LEGAL_OPS && !opt.ill)
      error("6502 `illegal' opcode used without `.OPT ILL' or -u",1);

    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);
      aprintf("%-24s%s\n",outline,line);
      /*      if (invoked) {
  printf("\t\t[inside %s]\n",invoked->orig->name);
      } else {
  printf("\n");
      }  */
    }
    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 */
        int i,len,negated=0;
        do {
          str=get_nxt_word(4);
          len=strlen(str);
          for(i=0;i<len;i++) {
            str[i]=TOUPPER(str[i]);
          }
          if (!strcmp(str,"NO")) {
            negated=1;
          } else if (!strcmp(str,"OBJ")) {
            opt.obj=!negated;
          } else if (!strcmp(str,"ERR")) {
            opt.warn=!negated;
          } else if (!strcmp(str,"LIST")) {
            verbose=!negated;
          } else if (!strcmp(str,"ILL")) {
            opt.ill=!negated;
            error(".OPT ILL encountered (code would not compile on MAC/65)",0);
          } else if (strlen(str)) {
            error("Unknown .OPT directive.",0);
          }
        } while(strlen(str));
        break;
      }
    case 14: /* .SET */
      get_nxt_word(6);
      str=get_nxt_word(4);
      if (!str)
        squeeze_str(str);
      if (strlen(str)) {
        int opt;
        sscanf(str,"%d",&opt);
        /* only honor option 6 at the moment... */
        if (opt==6) {
          int ofs;
          str=get_nxt_word(1);
          squeeze_str(str);
          if (strlen(str)) {
            if (pass) {
              ofs=get_signed_expression(str,1);
            } else {
              ofs=0;
            }
            if ((ofs>=-65535)&&(ofs<=65535)) {
              activeBank->offset=ofs;
            } else {
              error("Illegal .SET 6 offset ignored",0);
            }
          }
        } else {
          error("Unhandled .SET ignored",0);
          str=get_nxt_word(1);
        }
      }
      break;
    case 13: /* .PAGE */
    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))
        aprintf("\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();
      eq=0;
      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)
            aprintf("%s\n",outline);
          outline[0]=0;
          print_pc();
        }
        if (pass)
          put_byte(stor);
        else
          pc++;
      }
      if ((verbose)&&(pass))
        aprintf("%s\n",outline);
      outline[0]=0;
      break;
    case 27: { /* .BANK */
      unsigned short symbolID=0;
      int p1,p2;

      p1=p2=-1;
      str=get_nxt_word(3);
      if (strlen(str)) {
        squeeze_str(str);
        if (str[0]==',')
          p2=-2;

        get_nxt_word(6);
        str=get_nxt_word(4);
        if (strlen(str)) {
          squeeze_str(str);
          if (strlen(str)) {
            p1=get_expression(str,1);
          }
          str=get_nxt_word(4);
          if (strlen(str)) {
            squeeze_str(str);
            if (strlen(str)) {
              p2=get_expression(str,1);
            }
          }
          if (p2>0) {
            bankID=p1;
            symbolID=p2;
          } else if (p2==-1) {
            bankID=symbolID=p1;
          } else {
            bankID++;
            symbolID=p1;
          }
        }
      } else {
        bankID++;
        symbolID=bankID;
      }
      if (pass) {
        if (bankID>=0) {
          char buf[256];
          sprintf(buf,"Using bank %d,%d\n",bankID,symbolID);
          error(buf,0);
          activeBank=get_bank(bankID,symbolID);
        }
      } else if (bankID>=0) {
        activeBank=get_bank(bankID,symbolID);
      }
      /* Skip for now... */
      do {
        str=get_nxt_word(4);
      } while(strlen(str));
      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++;
    break;
  case MACROL: /* MACRO label/equate/tequate */
  case MACROQ:
    if (!pass) {
      if (eq==1) {
        snprintf(buf,80,"Equate '%s' defined multiple times",sym->name);
        error(buf,1);
      }
      sym->num++;
    }
    if (eq) {
      if (sym->tp!=MACROQ) {
        snprintf(buf,80,"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->addr=addr;
        sym->bank=activeBank->sym_id;
      }
      eq=0;
    } else {
      /* Update macro shadow definition */
      if (sym->macroShadow) {
        symbol *update;
        macro_call *hold=invoked;
        invoked=NULL;
        update=findsym(sym->macroShadow);
        invoked=hold;
        if (update) {
          update->addr=pc;
        }
        defUnk(sym->name,pc);
      }
    }
    break;
  case LABEL:  /* labels and equates */
  case EQUATE:
    if (!pass) {
      if (eq==1) {  /* was 2? probably a typo - mws */
        snprintf(buf,80,"Symbol '%s' is not a transitory equate!",sym->name);
        error(buf,1);
      } else {
        if (sym->addr>255) {
          snprintf(buf,80,"Symbol '%s' already defined!",sym->name);
          error(buf,1);
        }
      }
    } else {
      if ((sym->tp==LABEL)&&(sym->name)&&(sym->name[0]!='?'))
        if (!strchr(sym->name,'?'))
          opt.MAEname=sym->name;
    }
    if (eq==1) {
      if (sym->tp==LABEL) {
        snprintf(buf,80,"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;
        sym->bank=activeBank->sym_id;
        defUnk(sym->name,addr);
      }
      eq=0;
    }
    break;
  case TEQUATE: /* transitory equates */
    if (!pass) {
      if (eq==2) {
        str=get_nxt_word(1);

        /* even in first pass allow .= updates for .IFs */
        squeeze_str(str);
        addr=get_address(str);
        sym->addr=addr;
        sym->bank=activeBank->sym_id;
        defUnk(sym->name,addr);

        eq=0;
      } else {
        snprintf(buf,80,"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;
        sym->bank=activeBank->sym_id;
        defUnk(sym->name,addr);
        eq=0;
      }
    }
    break;
  default:
    if (!pass) {
      snprintf(buf,80,"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,len;

  len=strlen(buf);
  for(i=0;i<len;i++)
    buf[i]=TOUPPER(buf[i]);

  sym=findsym(buf);

  if ((!sym)||((sym->tp==MACROL)&&(!sym->macroShadow))) {
    /* must be a label or define */
    add_label(buf);
    sym=findsym(buf);
    if ((sym)&&(sym->tp==LABEL)&&(sym->name)&&(sym->name[0]!='?')) {
      if (!strchr(sym->name,'?'))
        opt.MAEname=sym->name;
    }
  } else {
    proc_sym(sym);
  }
  return 1;
}

/*=========================================================================*
 * function init_pass()
 *
 * This initializes global variables for each assembly pass
 *=========================================================================*/
int init_pass() {
  pc=-1;
  bankID=-1;
  init_pc=local=0;
  verbose=opt.verbose;
  opt.warn=opt.obj=1;
  opt.MAEname=NULL;
  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, ip, zlabels=0;

  for(ip=i=0;i<2;i++) {
    ip++;
    init_pass();
    fprintf(stderr,"Pass %d: ",ip);
    fflush(stderr);
    open_file(fname);
    do {
      str=get_nxt_word(0);
      if (str) {
        do_cmd(str);
      }
      if (repass) {
        fixRepass();
        while(get_nxt_word(0))
          ;
        break;
      }
    } while(str);

    if (repass) {
      zlabels++;
      i=-1;
      pass=repass=double_fwd=0;
      continue;
    }
    if (double_fwd) {
      i=-1;
      pass=repass=double_fwd=0;
      continue;
    }
    if ((verbose)&&(pass==1))
      fprintf(stderr,"\n");
    if ((!verbose)||(!pass)) {
      fprintf(stderr,"Success. (%d warnings",numwarn);
      if (zlabels) {
        fprintf(stderr,"/%d label",zlabels);
        if (zlabels>1)
          fprintf(stderr,"s");
        fprintf(stderr," resized");
      }
      fprintf(stderr,")\n");
    }
    pass++;
  }
  return 1;
}
/*=========================================================================*
 * function clear_banks()
 * parameter: none
 *
 * clear all memory banks
 *=========================================================================*/
int clear_banks() {
  memBank *walk=banks;
  while(walk) {
    memset(walk->memmap,0,65536);
    memset(walk->bitmap,0,8192);
    walk=walk->nxt;
  }
  bsize=0;
  return 1;
}
/*=========================================================================*
 * function count_banks()
 * parameter: none
 *
 * returns the number of banks that have been compiled...
 *=========================================================================*/
int count_banks() {
  int num=0;
  memBank *walk=banks;
  while(walk) {
    num++;
    walk=walk->nxt;
  }
  return num;
}

/*=========================================================================*
 * 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,unsigned char *mem,int b,int sz) {
  save_word(out,b,0);
  save_word(out,b+sz-1,0);
  fwrite(mem+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;
  memBank *walkBank=activeBank;

  out=fopen(fname,"wb");
  if (!out)
    error("Cannot open file for writing.",1);

  save_word(out,0xffff,0);

  while(walkBank) {
    scan=walkBank->bitmap;
    end=scan+8192;
    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,walkBank->memmap,start,len);
            fprintf(stderr,"    Block: %.4x-%.4x (%d bytes)\n",
                    start,start+len-1,len);
            len=start=0;
          }
        }
        b=b>>1;
        walk++;
      }
      scan++;
    }
    walkBank=walkBank->nxt;
  }
  fclose(out);
  if (count_banks()>1)
    fprintf(stderr,"\nCompiled %d banks to binary file '%s'\n",count_banks(),fname);
  else
    fprintf(stderr,"\nCompiled to binary file '%s'\n",fname);

  return 1;
}
/*=========================================================================*
 * function save_raw(char *fname)
 * parameter: fname- filename of binary file to save
 *
 * saves a raw file from memory snapshot, padding empty space with 0xff
 * this is used primarily for raw cartridge dumps
 *=========================================================================*/
int save_raw(char *fname, unsigned char fillByte) {
  FILE *out;
  unsigned char *scan, *end;
  int map,last,a,b,i,walk,start,banks,bankNum;
  memBank *walkBank=activeBank;

  out=fopen(fname,"wb");
  if (!out)
    error("Cannot open file for writing.",1);

  banks=count_banks();
  bankNum=0;
  while(walkBank) {
    scan=walkBank->bitmap;
    end=scan+8192;

    walk=map=0;
    last=start=-1;
    /* First change uninitialized data to fillByte (default 0xff) */
    while(scan!=end) {
      a=*scan;
      b=128;
      for(i=0;i<8;i++) {
        if (!(a&b)) {
          walkBank->memmap[walk]=fillByte;
        } else {
          if (start<0)
            start=walk;
          last=map;
        }
        b=b>>1;
        walk++;
        map++;
      }
      scan++;
    }
    if (last<0) {
      fprintf(stderr,"No image written\n");
      return 1;
    }
    if (banks>1)
      fprintf(stderr,"Writing bank: %d\n",bankNum);
    fprintf(stderr,"  Writing raw binary image:\n");
    fprintf(stderr,"    %.4x-%.4x\n",start,last);

    fwrite(walkBank->memmap+start,1,last-start+1,out);
    bankNum++;
    walkBank=walkBank->nxt;
  }
  fclose(out);

  if (fillByte!=0xff) {
    fprintf(stderr,"  Using $%.2x as fill byte\n",fillByte);
  }

  fprintf(stderr,"\nCompiled to binary file '%s'\n",fname);
  return 1;
}
/*=========================================================================*
 * function process_predefs(char *def)
 * parameter: def- string of the form `foo=1'
 *
 * creates a new symbol for the definition (value must be numeric!)
 *=========================================================================*/
void process_predef(str_list *head) {
  char *svalue;
  symbol *sym;
  char *def;
  str_list *walk=head;

  while(walk) {
    def=walk->str;
    sym=get_sym();
    if (def==NULL)
      error("NULL symbol defined on command line",1);
    if (*def=='\0')
      error("-D option missing symbol",1);

    sym->name=malloc(strlen(def)+1);
    strcpy(sym->name,def);

    svalue=strchr(sym->name,'=');
    if(svalue==NULL) {
      sym->addr = 1; /* -Dfoo defines foo to 1, like cpp does */
    } else {
      svalue++;
      if(*svalue == '\0') {
        sym->addr = 0; /* -Dfoo= defines foo to 0 (like cpp *doesn't*!) */
        *(svalue-1) = '\0';
      } else {
        sym->addr = get_address(svalue);
        *(svalue-1) = '\0';
      }
    }
    sym->tp=LABEL; /* should be EQUATE? */
    addsym(sym);
    walk=walk->next;
  }
}
/*=========================================================================*
 * function find_extension
 * parameter: name, the file name
 *
 * return the location to zero out to remove the last file extension
 *=========================================================================*/
int find_extension(char *name) {
  char *look,*end;

  if (!name)
    return -1;
  end=look=name+strlen(name);
  while(look!=name) {
    if (*look=='.')
      return look-name;
    if ((*look=='/')||(*look=='\\'))
      return end-name;
    look--;
  }
  return end-name;
}
/*=========================================================================*
 * function main
 *
 * starts the whole process
 *=========================================================================*/
int main(int argc, char *argv[]) {
  char outfile[256],fname[256],snap[256],xname[256],labelfile[256],listfile[256];
  int dsymbol,i,state,retval;

  fprintf(stderr,"ATasm %d.%.2d%s(A mostly Mac65 compatible 6502 cross-assembler)\n",MAJOR_VER,MINOR_VER,BETA_VER?" beta ":" ");

  dsymbol=state=retval=0;
  strcpy(snap,"atari800.a8s");
  fname[0]=outfile[0]=labelfile[0]=listfile[0]='\0';
  opt.savetp=opt.verbose=opt.MAElocals=0;
  opt.fillByte=0xff;

  includes=init_include();
  predefs=NULL;
  listFile=NULL;

  for(i=1;i<argc;i++) {
    if (!STRCASECMP(argv[i],"-v"))
      opt.verbose|=1;
    else if (!STRCASECMP(argv[i],"--version"))
      return 0;
    else if (!STRCASECMP(argv[i],"-u"))
      opt.ill=1;
    else if (!STRCASECMP(argv[i],"-mae"))
      opt.MAElocals=1;
    else if (!STRCASECMP(argv[i],"-r"))
      opt.savetp=2;
    else if (!STRNCASECMP(argv[i],"-f",2)) {
      if (strlen(argv[i])>2)
        opt.fillByte=get_address(argv[i]+2);
      else {
        fprintf(stderr, "Missing fill value for -f (example: -f0)\n");
      }
    } else if (!STRNCASECMP(argv[i],"-o",2)) {
      if (strlen(argv[i])>2) {
        strcpy(outfile, argv[i]+2);
      } else {
        fprintf(stderr, "Must specify output file for -o (example: -omyprog.bin)\n");
        return 1;
      }
    } else if (!STRNCASECMP(argv[i],"-l",2)) {
      if (strlen(argv[i])>2) {
        strcpy(labelfile, argv[i]+2);
      } else {
        fprintf(stderr, "Must specify label output file for -l (example: -llabels.lab)\n");
        return 1;
      }
    }
    else if (!STRNCASECMP(argv[i],"-g",2)) {
      if (strlen(argv[i])>2) {
        strcpy(listfile, argv[i]+2);
        listFile=fopen(listfile,"wt");
        if (!listFile) {
          fprintf(stderr, "Cannot write to list file'%s'\n",listfile);
          return 1;
        }
        opt.verbose|=2;
      } else {
        fprintf(stderr, "Must specify list output file for -g (example: -glist.lst)\n");
        return 1;
      }
    } else if (!strncmp(argv[i], "-I", 2))
      if (strlen(argv[i])>2)
        append_include(includes, argv[i]+2);
    else {
      fprintf(stderr, "Must specify directory for -I (example: -Imyincludes or -I" DIR_SEP "stuff" DIR_SEP "nonsense)\n");
      return 1;
    }
    else if (!STRNCASECMP(argv[i],"-d",2)) {
      str_list *str=(str_list *)malloc(sizeof(str_list));
      str->str=(char *)malloc(strlen(argv[i]+1));
      strcpy(str->str,argv[i]+2);
      str->next=predefs;
      predefs=str;
    } 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;
      }
      opt.savetp=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] [-r] [-d[symbol=value] [-o[fname.out] [-m[fname.state]] <fname.m65>\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/.ATR disk image [fname]\n",stderr);
      fputs("         -r: saves object code as a raw binary image\n",stderr);
      fputs("         -f[value]: set raw binary fill byte to [value]\n",stderr);
      fputs("         -o[fname]: saves object file to [fname] instead of <fname>.65o\n",stderr);
      fputs("         -d[symbol=value]: pre-defines [symbol] to [value]\n",stderr);
      fputs("         -l[fname]: dumps labels to file [fname]\n",stderr);
      fputs("         -g[fname]: dumps debug list to file [fname]\n",stderr);
      fputs("         -Idirectory: search [directory] for .INCLUDE files\n",stderr);
      fputs("         -mae: treats local labels like MAE assembler\n",stderr);
      return 1;
    } else strcpy(fname,argv[i]);
  }

  if (!strlen(fname)) {
    strcpy(fname,"test.m65");
  }

  init_asm();
  TRY {
    assemble(fname);

    if (dsymbol)
      dump_symbols();

    if (labelfile[0])
      dump_labels(labelfile);

    fputs("\nAssembly successful\n",stderr);
    fprintf(stderr,"  Compiled %d bytes (~%dk)\n",bsize,bsize/1024);
  }
  CATCH {
    /* don't produce any symbol or labels on error */
    retval=1;
  }
  END_TRY;

  if (listFile) {
    fclose(listFile);
    listFile=NULL;
    listfile[0]=0;
  }

  activeBank=banks;

  if(!strlen(outfile)) {
    fname[find_extension(fname)]=0;
    strcat(fname,(opt.savetp)&2 ? ".bin" : ".65o");
    strcpy(outfile, fname);
  }
  if (retval==0) {
  if (opt.savetp&2)
    save_raw(outfile,opt.fillByte);
  else
    save_binary(outfile);

  if (opt.savetp&1) {
    if(write_xfd_file(xname,outfile)<0)
      error("Atari disk image not updated.",0);
  }

  if (state) {
    if (count_banks()>1) {
      error("Banks currently not supported in save state files.  Save state not updated.",0);
    } else {
      save_state(snap,fname);
    }
  }
  }
  else {
    /* 
    remove output file on error to prevent old file from being confused with
    successful assembly
    */
    remove(outfile);
  }

  clean_up();
  return retval;
}
/*=========================================================================*/
