/*==========================================================================
 * 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 compilation 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 pre-definitions
 *  10/08/03 mws Added .SET 6,offset for assembling offset of storage from
 *               the location counter;  Added .BANK directive for cartridge
 *               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 ';'
 *  03/20/21 ph  Fixed / and \ in filename when saving to ATR
 *               Fixed stack-based buffer overflow in the 
 *               get_signed_expression() function aka CVE-2019-19787
 *               Fixed stacked-base buffer overflow in the parse_expr()
 *               function, aka  CVE-2019-19786
 *  05/10/21 ph  Fixed #7 Error calculating address with forward references
 *               If lda ..,x was used the forward reference was not 
 *               correctly calculated.  Fix was to never return an invalid
 *               forward reference, nor one that rolls over to 0. 
 *               65535+1 would have been 0, now it stays at 65535
 *  05/14/21 ph  Fix a buffer overflow in put_float()
 *               Changes the output format of the list command slightly
 *               to allow source level debugging in the Altirra emulator.
 *               Allows for ;##TRACE and ;##ASSERT Altirra commands to be
 *               inserted into the source code.
 *  28/11/21 ph  Added code to print the defined symbol for a memory location
 *               even if its defined on another line
 *  01/12/21 ph  Added -hc and -ha switches.  Dumps equated and symbol info
 *               in CC65 header and Atasm include format.
 *
 *               RELEASE 1.14
 * 02/12/21 ph  -hc and -ha switch dumps now include the source file and line
 *              number where the equate or label was defined.
 *              Labels can now contain .
 *              i.e. DATA.CMD, DATA.LEN, DATA.AUX
 * 
 *              RELEASE 1.15
 * 21/12/21 ph  Added -hv switch to dump all equates, labels, macro defs and
 *              included source files to the 'plugin.json' file located
 *              at the root folder from where atasm starts searching for
 *              source files.  This is to be used my the Atasm-Altirra-Bridge
 *              VSCode plugin to allow you to quickly maneuver your code
 *              Added modulo/remainder operator. %% or .MOD
 *              You can now say '.byte 15%%10' and it will store the value of 5.
 *
 *              RELEASE 1.16
 * 22/12/21 ph  Added the << and >> shift operators to the expression parser.
 *              Code like this now assembles:
 *              lda #1<<4
 *              Fixed a bug in .LST output generator.  Filenames were not 
 *              tracked correctly which caused source lines to be allocated to
 *              the wrong source file. Thank you to Lars Langhans for reporting this.
 *
 *              RELEASE 1.17
 * 02/02/22 ph  Extended the -hv[clm] option. You can now limit the data export
 *              to (c)onstants, (l)abels and (m)acros. Or any combination of them.
 *              -hvcl exports constants and labels. By default all data is exported.
 *              Used by the "Atasm-Altirra-Bridge" VSCode plugin. Info here:
 *              (https://bit.ly/3ATTHVR)
 *              Breaking default behavior change: When running atasm without a
 *              target assembler file (was test.m65) the command line help is shown.
 *              Same when specifying -h on the command line.
 *
 *              RELEASE 1.18
 * 15/04/22 ph  The -hv[clm] export option has an extra parameter: L
 *              -hv[clLm] are the available options.
 *                l = export defined labels; excluding local labels, those starting with a ?
 *                L = export ALL labels (including local labels)
 *              You can use .OPT NO SYMDUMP to turn off the dumping for constants.
 *                .OPT NO SYMDUMP
 *                .INCLUDE "ANTIC.asm"
 *                .OPT SYMDUMP
 *              First .opt turns off constant tracking. This means all constants
 *              created from then until the ".opt SYMDUMP" will NOT make it into
 *              the "asm-symbols.json" file.
 * 
 *              RELEASE 1.19
 * 20/08/22 ph  Modified the * program counter command to also name a memory
 *              region.
 *              * = $2000 "BOOT"
 *              Will name the region starting at $2000 as "Boot", which will be
 *              dumped after the compile for the vscode extension. Same can be
 *              done via: .NAME "BOOT" directly after the * command.
 *              Added -eval command line option to only do the compile and NOT
 *              write anything to disc.
 *              Slight warning and error format change to make it external parsable.
 * 
 *              RELEASE 1.20
 * 29/12/22 ph  Added the .ELSEIF directive to build easier .IF .ELSEIF .ELSE .ENDIF 
 *              control blocks.
 * 
 *==========================================================================*
 * 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 (this should work now)
 *   .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)
 *   allow '.' within label names
 *   .IF was fully tested and expanded to include .ELSEIF
 *
 * 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.
 *==========================================================================*/
/*
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
*/

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>

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

int do_cmd(char* buf);

#define MAX_IF_DEPTH    100
int dotIfLevel = 0;    // No .IF active, >0 means we are inside a .IF
int doneIfPart[MAX_IF_DEPTH] = { 0 };

int nextLabelIsAProcedure = 0;
int dotProcLevel = 0;   // Make sure that there is only one .PROC active

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,warn,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;
MemoryBank *memoryBanks, *activeBank;
int bankID;     // Id of the bank to write data into
char *outline;  /* the line of text written out in verbose mode */

symbol* lastSymbol = NULL;
symbol* lastLabel = NULL;           // Which is the last label that was defined

symbol* currentProcedure = NULL;    // Which is the current procedure

file_tracking* trackedFiles;

memory_name* namedMemoryRegions;    // What name is given to a specific memory region, used in vscode extension

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

/** =========================================================================
 * \brief function get_bank
 *           Initializes and returns a new memory bank object
 * \param bank_nr Primary id of the memory bank 0...
 * \param reported_bank_nr Use this number in the .BANKNUM operator
 * \return 
 */
MemoryBank *get_bank(const int bank_nr, const int reported_bank_nr) {
	MemoryBank* bank = NULL;
	MemoryBank* walk = memoryBanks;

	while (walk && walk->next)
	{
        if (walk->bankNr == bank_nr && walk->reportedBankNr == reported_bank_nr) 
        {
            return walk;        // Found a bank with the required ids. Use it
        }
        else if (walk->next && walk->next->bankNr > bank_nr) {
            // If the next bank in the list has a higher bank # than the new one
            // then create a new container now
			bank = (MemoryBank*)malloc(sizeof(MemoryBank));
			if (!bank)
				return NULL;
			bank->next = walk->next;
			break;
		}
		walk = walk->next;
	}
	if (walk && walk->bankNr == bank_nr)
		return walk;

	if (bank == NULL)
	{
		// Create a new bank container
		bank = (MemoryBank*)malloc(sizeof(MemoryBank));
		if (bank == NULL)
			return NULL;
		bank->next = NULL;
	}

	bank->bankNr = bank_nr;
	bank->reportedBankNr = reported_bank_nr;
	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);

    // Add the bank to the list
	if (walk) {
		walk->next = bank;
	}
	else {
		memoryBanks = bank;
	}
	return bank;
}

/*=========================================================================*
 * function init_asm
 * initializes the assembler
 *=========================================================================*/
int init_asm() {
	int i, ops;
	symbol* sym;

	pass = warn = bsize = repass = double_fwd = 0;
	fin = NULL;
	macro_list = NULL;
	invoked = NULL;
	unknownLabels = NULL;
	memoryBanks = NULL;
	bankID = -1;
	trackedFiles = NULL; /* keep track of all filenames used, 0 indexed */
	namedMemoryRegions = 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;

	for (i = 0; i < HSIZE; i++)  /* clear long jump hash table */
		ljHash[i] = NULL;

	process_predef(predefs);

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

	if (outline == NULL || activeBank == NULL) {
        fatal_error("Cannot allocate memory snapshot.");
	}

	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];
		sym->orig = sym->name;
		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];
		sym->orig = sym->name;
		addsym(sym);
	}
	return 1;
}

/*=========================================================================*
 * function track_filename(char *fname)
 * parameters: fname is the file name to track
 *=========================================================================*/
file_tracking* track_filename(char* fname)
{
    /* First try and find the filename in the currently tracked list */
    file_tracking* runner;
    for (runner = trackedFiles; runner; runner = runner->nxt)
    {
        if (strcmp(fname, runner->name) == 0)
            return runner;
    }

    /* not found, let add the file to the list */
    file_tracking* tnew = (file_tracking*)malloc(sizeof(file_tracking));
    if (tnew == NULL) {
        fatal_error("Out of memory allocating filename tracker");
    }
    tnew->name = STRDUP(fname);
    tnew->nxt = trackedFiles;
    trackedFiles = tnew;
    
    return tnew;
}

void cleanup_FilenameTracking()
{
    file_tracking* runner;
    for (runner = trackedFiles; runner; )
    {
        file_tracking* old = runner;

        if (runner->name) free(runner->name);
        runner = runner->nxt;
        free(old);
    }
}


static char* UnknownMemoryRegionName = "CODE";
char* findNameOfMemoryRegion(int addr)
{
    /* Try and find the referenced memory location in the currently tracked list */
    memory_name* runner;
    for (runner = namedMemoryRegions; runner; runner = runner->nxt)
    {
        if (addr == runner->addr)
            return runner->name;
    }
    return UnknownMemoryRegionName;
}

memory_name* saveNamedMemoryRegion(int addr, char* name)
{
    // Sanity check
    if (name == NULL || strlen(name) == 0)
        name = "CODE";

    /* First try and find the referenced memory location in the currently tracked list */
    memory_name* runner;
    for (runner = namedMemoryRegions; runner; runner = runner->nxt)
    {
        if (addr == runner->addr)
            return runner;
    }

    /* not found, let add the file to the list */
    memory_name* tnew = (memory_name*)malloc(sizeof(memory_name));
    if (tnew == NULL) {
        fatal_error("Out of memory allocating memory region name tracker");
    }
    tnew->addr = addr;
    tnew->name = STRDUP(name);
    tnew->nxt = namedMemoryRegions;
    namedMemoryRegions = tnew;

    return tnew;
}

void cleanup_MemoryRegionNameTracking()
{
    memory_name* runner;
    for (runner = namedMemoryRegions; runner; )
    {
        memory_name* old = runner;

        if (runner->name) free(runner->name);
        runner = runner->nxt;
        free(old);
    }
}


/*=========================================================================*
 * 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
 * being processed.  This allows for nested .INCLUDEs
 *=========================================================================*/
int open_file(char *fname) {
  file_stack *fnew;
  char buf[256];
  char final_fname[256];

  fnew=(file_stack *)malloc(sizeof(file_stack));
  if (!fnew) {
      fatal_error("Out of memory allocating filename");
  }
  fnew->name=(char *)malloc(strlen(fname)+1);
  if (!fnew->name) {
      fatal_error("Out of memory allocating filename");
  }
  strcpy(fnew->name,fname);

  fnew->in = fopen_include(includes, fname, 0, final_fname);
  if (!fnew->in) {
    snprintf(buf,256,"Cannot open file '%s'\n",fname);
    fatal_error(buf);
  }
  fnew->trackClear = NULL;          /* Location in aprintf to clear when the file_stack object is deleted */
  fnew->line=0;
  fnew->nxt=fin;
  fnew->ftrack = track_filename(final_fname);
  fin=fnew;
  return 1;
}

/*=========================================================================*
 * function aprintf(char *msg, ...)
 * parameters: msg is the formater for the following parameters
 *
 * This function outputs the information to stdout (verbose bit 1 set)
 * and to the list file (verbose bit 2 set)
 * PH: 22-12-2021 - Fixed a problem with the tracking of the filename.
 * The static lfin would point to fin->name, but that entry could have been
 * freed when another source file was included.  Added a 'trackClear' field.
 * Now when the fin structure is removed at the end of a file it clears lfin
 * as well.
 *=========================================================================*/
void aprintf(char* msg, ...) {
	char buf[512], line[512];     /* Increased the size of the buffers.  Each line can be 256 bytes but in this case the output can be over the line length */
	static char* lfin = NULL;

	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 == NULL || (lfin != fin->name)) {
				lfin = fin->name;
                fin->trackClear = &lfin;                            /* this ptrs to lfin to clear when the fin structure is freed */
				fprintf(listFile, "\nSource: %s\n", fin->name);
			}
			/* convert address from LE to BE display */
            buf[0] = line[3];
            buf[1] = line[4];
            buf[2] = line[0];
			buf[3] = line[1];
			buf[4] = 0;
			fprintf(listFile, "%d %s%s", fin->line, buf, line + 5);
		}
	}
}

/*=========================================================================*
 * function get_nxt_word(int tp)
 * parameters: tp denotes the processing mode where(?)
 *   0/PARSE_NEXT_LINE: normal processing, getting next line
 *   1/PARSE_LINE_REST: get rest of current line
 *   2/PARSE_CURRENT_LINE: return entire current line
 *   3/PARSE_PEEK_LINE_REST: get rest of current line, but don't advance buffer (peeking ahead)
 *   4/PARSE_NEXT_WORD: get next word, returning "" when eol is hit (no advance to next line)
 *   5/PARSE_SKIP: 'skip' mode, don't subst macro params!
 *   6/PARSE_REPLACE_COMMAS: replace commas with spaces in reading buffer and return NULL
 *   7/PEEK_COMMENT: look for a comment starting ; and return everything after it
 * 
 * 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,len;
  file_stack *kill;
  macro_call *mkill;
  macro_line *lkill;

    if (tp == PEEK_COMMENT) {
        if (!fget)
            return NULL;
        strcpy(buf, fget);
        instr = 0;
        len = (int)strlen(buf);
        for (i = 0; i < len; i++) {
            if (buf[i] == '"')    /* fix embedded ';' problem - mws 11/10/99 */
                instr ^= 1;
            else if ((buf[i] == ';') && (!instr)) {
                if ((i) && (buf[i - 1] == '\''))  /* allow quoted semicolons */
                    continue;
                {
                    char* runner = &buf[i];
                    ++runner;
                    while ((*runner == ' ' || *runner == '\t') && *runner != 0) ++runner;
                    if ((int)strlen(runner) > 0)
                        return runner;
                    return NULL;
                }
                break;
            }
        }
        return NULL;
    }
  if (tp==PARSE_REPLACE_COMMAS) {
    look=fget;
    instr=0;
    while(*look) {
      if ((*look==',')&&(!instr)&&(*(look-1)!='\''))
        *look=' ';  /* replace , with space */
      if (*look=='"')   /* start or end of string */
        instr^=1;
      look++;
    }
    return NULL;
  }

  look=buf;
  *look=0;

  if (tp == PARSE_LINE_REST || tp == PARSE_PEEK_LINE_REST)
  {
	  if (!fget)
		  return buf;
	  strcpy(buf, fget);
	  instr = 0;
	  len = (int)strlen(buf);
	  for (i = 0; i < len; i++) 
      {
		  if (buf[i] == '"')    /* 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 == PARSE_LINE_REST) 
      {
		  fget = NULL;
	  }
	  return buf;
  }

  if (tp == PARSE_CURRENT_LINE)
  {
      return line;
  }

  /* skip over empty space, blank lines and comments */
  int inComment = 0;
  do
  {
	  while (!fget || !(*fget))
	  {
		  if (tp == PARSE_NEXT_WORD)
		  {
			  buf[0] = 0;
			  return buf;
		  }
		  if (!fin)
              fatal_error("No active file handle.");
		  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 != PARSE_SKIP)
				  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++] = ' '; /* space = 32 */
		  }
		  /* Remove EOL characters */
		  len = (int)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--;
		  }
		  fget = 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);
			  if (kill->trackClear) *kill->trackClear = NULL;
			  free(kill);
			  fget = NULL;
			  if (!fin)
				  return NULL;
		  }
	  }
	  while (ISSPACE(*fget)) {
		  fget++;
	  }
	  if ((fget && !(*fget)) || *fget == ';')
	  {
		  /* check for full comment line */
		  if (*fget == ';' && pass) {
			  /* We have a comment on the 2nd pass
			   * 1. Skip white space after the ;
			   * Check if its a ; ## TRACE or ; ## ASSERT for Altirra debugging
			   */
			  ++fget;
			  while ((*fget == ' ' || *fget == '\t') && *fget != 0) ++fget;
			  if (verbose & 2 && fget[0] == '#' && fget[1] == '#') {
				  fprintf(listFile, " %d ;%s\n", fin->line, fget); /* NB: Note the space at the beginning of the line! */
			  }
			  // If the current line and file are the same as that of the last label
			  // then append the comment to the label
			  if (lastLabel && lastLabel->ftrack == fin->ftrack && lastLabel->lineNr == fin->line)
			  {
				  // Add the comment to the label
				  lastLabel->comment = STRDUP(fget);
			  }
		  }
		  fget = NULL;
	  }
	  if (fget && *fget && !STRNCASECMP(fget, "/*", 2))
	  {
		  // Start of comment block
		  inComment = 1;
          fget = NULL;
	  }
      if (fget && *fget && inComment)
      {
	      // Looking for the end of a comment block
          // Skip everything until we find */
          do 
          {
              if (!STRNCASECMP(fget, "*/", 2))
              {
					// Found the end of the comment block
                  fget += 2;
                  inComment = 0;
              }
              fget++;
          } while (inComment && fget && *fget);
      }
  } while (!fget || !(*fget));

  instr=0;

  /* Get next token (one word or string) */
  do {
    l=*fget;
    if (l)
      fget++;
    if (l=='"')
      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)))&&(l!=';'));
  /* Fix to stop on ';' comment above, from 8bit-man;  04/19/2010 */

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

  if (instr)
      fatal_error("Unterminated string constant.");

  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, 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)
      fatal_error("Address underflow");

  a=opc>>3;
  if (a>8192)
      fatal_error("Address overflow");

  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;
}
/*=========================================================================*
  function 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 (consistent 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[256],buf[256],*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))
      fatal_error("Malformed floating point constant.");

  /* 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 if necessary */
  if (!strchr(tmp,'.'))
    strcat(tmp,".");

  /* Pad number */
  strcat(tmp,"00000000000000");
  look=tmp;

  while(*look) { /* strip out decimal point */
    if (*look=='.') {
      if (d!=-1)
          fatal_error("Malformed floating point constant.");
      d=i;
      look++;
    } else if (!ISDIGIT(*look))
        fatal_error("Malformed floating point constant.");
    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%c%c ", d, look[0], look[1]);
    //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)
      fatal_error("Illegal addressing mode.");
  else
    put_byte(b);

  return 0;
}
/*=========================================================================*
  function print_pc()

  prints the current PC address to output string (or appropriate padding,
  if PC is undefined)
  Returns the PC address (or 0)
 *=========================================================================*/
int print_pc() {
  char buf[32];
  int opc = 0;

  if (!init_pc)
    snprintf(buf,32,"      ");
  else {
    opc=(pc+activeBank->offset)&0xffff;
    snprintf(buf,32,"%.2X:%.2X  ",opc&0xff,opc>>8);
  }
  strcat(outline,buf);
  return opc;
}
/*=========================================================================*
  function get_address(char *str, int withRollover)
  parameters: str - a string containing the address (possibly an expr)
              withRollover = 1 then 65535+1 -> 0 otherwise stays at 65535
  returns the address

  returns a numeric address from a given string
 *=========================================================================*/
unsigned short get_address(char *str, int withRollover) {
	short v;
	unsigned short a;

	v = get_expression(str, 1, withRollover);
	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, 0);
  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

  PH: Lots of assemblers end a label in a :
      Added option to kill the : and make it a valid label definition

  TODO add local labels
 *=========================================================================*/
int add_label(char *label) {
  symbol *sym;
  char *str,num[32];
  int v;

  /* If the last char of a label is a : then remove it*/
  int labelLength = (int)strlen(label);
  if (*(label + labelLength-1) == ':')
  {
      *(label + labelLength-1) = 0;
  }

  if (!strcmp(label,"A")) {
      fatal_error("'A' is a reserved operand.");
  }

  if (!pass) {
    if (!((label[0]=='@')||(label[0]=='?')||(label[0]=='_')||(ISALPHA(label[0])))) {
        fatal_error("Illegal label name, must start with '@','?', or a letter.");
    }
    if (strchr(label,'='))
        fatal_error("Illegal label (cannot contain '=')");
    sym=get_sym();
    /* Set the original name of the symbol, nothing is processed yet */
    sym->orig = STRDUP(label);
    sym->lineNr = fin->line;
    sym->ftrack = fin->ftrack;

    sym->dumpOptions = opt.symbolDumpOptions;

    if (label[0]=='?') {
      if (opt.MAElocals) {
        int len;
        if (!opt.MAEname) {
            fatal_error("Local label must occur after a global label.");
        }
        len=(int)strlen(opt.MAEname)+(int)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=(int)strlen(label)+(int)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);
      if (!sym->name) {
          fatal_error("Cannot allocate memory for label during macro instantiation.");
      }
      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->orig = STRDUP(label);
          nsym->lineNr = fin->line;
          nsym->ftrack = fin->ftrack;
          nsym->name=(char *)malloc(strlen(label)+1);
          if (!nsym->name) {
              fatal_error("Cannot allocate room for macro during macro instantiation.");
          }
          strcpy(nsym->name,label);
          nsym->tp=MACROL;
          nsym->addr=pc;
          nsym->bank=activeBank->reportedBankNr;
          addsym(nsym);
        }
        invoked=hold;

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

    if (pass) {
      squeeze_str(str);
      sym->addr=get_address(str,0);
      sym->bank=activeBank->reportedBankNr;
    } else {
      squeeze_str(str);
      v=get_expression(str,0, 0);  /* allow forward reference in equates */
      sym->addr=v&0xffff;       /* But causes problems... crazy.m65 */
      sym->bank=activeBank->reportedBankNr;
    }
    eq=0;
    defineUnknown(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, determining 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'))
                fatal_error("Illegal index");
			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,0);

			if (sym->addr == OPI_JMP) { /* JMP indirect abs */
				if (vidx)
                    fatal_error("Illegal indirect JMP");
				cmd = ind[sym->addr];
				xtnd = 1;           /* fix indirect JMP size */
			}
			else {
				if (!vidx) {
                    fatal_error("Illegal indirect reference");
				}
				else if (vidx == 'X')
					cmd = i_x[sym->addr];
				else
					cmd = i_y[sym->addr];
				if ((pass) && (a > 255))
                    fatal_error("Illegal zero page reference.");
			}
			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, 0);
			if (pass) 
            {  
                /* determine address */
				a = get_address(str,0);
				if (rel[sym->addr] >= 0) { /* Relative branch */
					if (vidx)
                        fatal_error("Illegal operand for relative branch");
					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);
                            fatal_error(buf);
						}
						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 == OPI_JMP) || (sym->addr == OPI_JSR))
								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 == OPI_JMP) || /* pad a few zero-page opcodes */
						(sym->addr == OPI_JSR) ||
						((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=IS_HEX;
    else if (num[0]=='~' || num[0]=='%')
      tp=IS_BINARY;
    else {
      tp=IS_DECIMAL; /* remove annoying compiler warning */
      fatal_error("Malformed numeric constant.");
    }
  } else {
    tp=IS_DECIMAL;
    txt=num;
  }

  switch (tp) {
  case IS_DECIMAL:   /* decimal */
    sscanf(txt,"%d",&v);
    break;
  case IS_HEX:      /* hex */
    sscanf(txt,"%x",&v);
    break;
  case IS_BINARY:   /* binary */
    v=0;
    bit=1;
    for(i=(int)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=='"')
      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[256];
  int d,c,p;

  str=get_nxt_word(PARSE_LINE_REST);
  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("%s %s\n",outline,get_nxt_word(PARSE_CURRENT_LINE));
        p=1;
      } else {
        aprintf("%s\n",outline);
      }
      outline[0]=0;
      c=0;
    }
  }

  if ((pass)&&(verbose)&&(c)) {
    if (!p) {
      aprintf("%s %s\n",outline,get_nxt_word(PARSE_CURRENT_LINE));
    } 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[256];
  unsigned short add, a;
  int d,c,p;

  if(!init_pc)
      fatal_error("No initial address specified.");

  add=0;
  tp=(tp==3);

  str=get_nxt_word(PARSE_LINE_REST);
  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 '\t':  /* \t = tab = 9*/
    case ' ':   /* space = 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 '"':
        fatal_error("String must be in xbyte format.");
      break;
    default:
      d=to_comma(look,buf);
      look=look+d;
      if (!pass)
        pc+=2;
      else {
        a=get_address(buf,0);
        put_word(a+add,tp);
      }
      break;
    }
    if ((pass)&&(verbose)&&(c==3)) {
      if (!p) {
        aprintf("%s %s\n",outline,get_nxt_word(PARSE_CURRENT_LINE));
        p=1;
      } else {
        aprintf("%s\n",outline);
      }
      outline[0]=0;
      c=0;
    }
  }
  if ((pass)&&(verbose)&&(c)) {
    if (!p) {
      aprintf("%s %s\n",outline,get_nxt_word(PARSE_CURRENT_LINE));
    } 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 buf[256];
	unsigned char cb, hi;
	short a;
	int d, i, p;

	if (!init_pc)
        fatal_error("No initial address specified.");

	unsigned char add = cb = 0;
	char* str = get_nxt_word(PARSE_LINE_REST);
	while (ISSPACE(*str))
		str++;
	char* look = str + strlen(str) - 1;
	while (ISSPACE(*look)) {
		*look = 0;
		look--;
	}

	look = str;
	int c = p = 0;
	while (*look) 
    {
		if (pass && verbose)
        {
			if (!c) 
            {
				print_pc();
				c++;
			}
			c++;
		}
		switch (*look) 
        {
			case '\t':  /* tab = 9 */
			case ' ':   /* space = 32 */
				look++;
				break;
			case '+':
				if (look != str)
					error("Misplaced additive modifier.", 0);
				look++;
				d = to_comma(look, buf);
				look = look + d;
				add = (unsigned char)get_immediate(buf);
				c = 1;
				if (!look)
					error("Useless statement.", 0);
				break;
			case '"':
				d = to_comma(look, buf);
				look = look + d;
				d = (int)strlen(buf) - 1;
				if (d > 255
                    || ((d < 0 || (buf[0] != '"')) && (d > 0 && buf[d] != '"')) 
                    || (d > 0 && buf[0] == '"' && buf[d] != '"')) 
                {
                    fatal_error("Malformed string.");
				}
				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("%s %s\n", outline, get_nxt_word(PARSE_CURRENT_LINE));
									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("%s %s\n", outline, get_nxt_word(PARSE_CURRENT_LINE));
				p = 1;
			}
			else {
				aprintf("%s\n", outline);
			}
			c = 0;
			outline[0] = 0;
		}
	}
	if ((pass) && (verbose) && (c)) {
		if (!p) {
			aprintf("%s %s\n", outline, get_nxt_word(PARSE_CURRENT_LINE));
		}
		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 appropriate, 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) {
      fatal_error("Illegal operand");
  }
  a=get_nxt_word(PARSE_PEEK_LINE_REST);
  squeeze_str(a);
  if ((!strlen(a))||((strlen(a)==1)&&(TOUPPER(*a)=='A'))) {
    a=get_nxt_word(PARSE_LINE_REST);
    if (pass)
      put_opcode(acc[sym->addr]);
    else
      pc++;
  } else {
    a=get_nxt_word(PARSE_LINE_REST);
    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;
	char final_fname[256];

	in = fopen_include(includes, fname, 1, final_fname);
	if (!in) 
    {
        fatal_error("Cannot open binary file");
	}
	track_filename(final_fname);
	v = verbose;
	verbose = 0;
	while ((in) && (!feof(in))) 
    {
		b = fgetc(in);
		if (!feof(in)) 
        {
			if (pass)
				put_byte(b);
			else
				pc++;
		}
	}
	verbose = v;
	fclose(in);
	return 0;
}

/*=========================================================================*
 * function clearDoneIfPartIndicators(int depth)
 *
 * Check if the .IF nesting is ok and clear all processed indicators for
 * levels further up the stack.
 *=========================================================================*/
void clearDoneIfPartIndicators(int depth)
{
    if (depth >= MAX_IF_DEPTH)
        fatal_error("Too many nested .IF blocks!");

    for (; depth < MAX_IF_DEPTH; ++depth)
    {
        doneIfPart[depth] = 0;
    }
}

/*=========================================================================*
 * function skip_if()
 *
 * This skips code until a matching .ENDIF,.ELSE or .ELSEIF is found.
 * Correctly handles nested .IFs
 *=========================================================================*/
int skip_if()
{
	int level = 0;
	char* str;
    int evaledExpression;

	while (level >= 0) 
    {
		str = get_nxt_word(PARSE_SKIP);
		if (!str)
            fatal_error("Mismatched .IF/.ELSEIF/.ELSE/.ENDIF statements.");

		if (!STRCASECMP(str, ".IF"))
			level++;
        if (!STRCASECMP(str, ".ENDIF"))
        {
            level--;
            if (level < 0)
            {
                // Exiting the block
                --dotIfLevel;
                if (dotIfLevel < 0)
                    fatal_error("Mismatched .IF/.ELSEIF/.ELSE/.ENDIF statements.");
            }
        }

        if (!STRCASECMP(str, ".ELSE") && level == 0)
        {
            if (doneIfPart[dotIfLevel] == 0)
            {
                level--;
            }
        }
        if (!STRCASECMP(str, ".ELSEIF") && level == 0)
        {
            if (doneIfPart[dotIfLevel] == 0)
            {
                // Found an .elseif at the top level
                // Already skipping a block, so check if we need to continue skipping or process this block
                str = get_nxt_word(PARSE_LINE_REST);
                squeeze_str(str);
                eq = 0;
                evaledExpression = get_expression(str, 1, 0);
                if (evaledExpression)
                {
                    // This is the part to process
                    doneIfPart[dotIfLevel] = 1;
                    level--;
                }
            }
        }
	}
	eq = 0;
	return 1;
}

char* skipWhiteSpace(char* ptr)
{
    while ((*ptr == ' ' || *ptr == '\t') && *ptr != 0) ++ptr;
    return ptr;
}
/*=========================================================================*
 * function do_long_jump(symbol *sym)
 * parameter sym- the symbol to parse
 *
 * This function implements a long jump macro:
 * jcc bob => bcs $03 --|      $b0 $03
 *            jmp bob   |      $4c Lo Hi
 *                   <--|
 * These macro commands are similar to the 6502 branch instructions BEQ, 
 * BNE, BPL, BMI, BCC, BCS, BVC, BVS, but can target the entire 64KB 
 * address space ("jump").
 *=========================================================================*/
void do_long_jump(symbol* sym)
{
    char* line, * str = NULL;
    int addr;
    char buf[80];
    int currentPC = 0;        /* current PC*/

    longJump* ref = getLongJumpReference(fin->name, fin->line);

    if (pass == 0)
    {
        ref->pc = pc;
        ref->type = sym->addr;
    }

    str = get_nxt_word(PARSE_NEXT_LINE);
    addr = get_expression(str, 0, 0);

    if (pass == 0)
    {
        ref->targetAddr = addr;
        ref->targetName = STRDUP(str);
        if (addr != 65535)
        {
            // We know the target address, so can decide if this is a long or short jump
            int distance = addr - pc - 2;
            if (distance >= -128 && distance < 127)
            {
                // Convert to short jump
                ref->makeShort = 1;
                ref->distance = distance;
            }
        }

        pc += ref->makeShort ? 2 : 5;

        line = get_nxt_word(PARSE_CURRENT_LINE);
        ref->origLine = STRDUP(skipWhiteSpace(line));
        return;
    }

    if (pass)
    {
        ref->targetAddr = addr;
        if (verbose) {
            currentPC = print_pc();
        }
        sprintf(buf, "%s %s", longJump2ShortOpcodeName[ref->type], str);
        ref->altLine = STRDUP(buf);

        if (ref->makeShort)
        {
            // convert from long jump to the short hand version
            int opcode = longJump2ShortOpcode[ref->type];
            put_opcode(opcode);
            put_byte((unsigned char)ref->distance);
        }
        else
        {
            ref->distance = addr - pc;
            // put the inverse of the command
            int opcode = longJump2InverseOpcode[ref->type];
            put_opcode(opcode);
            put_byte(0x03);

            sprintf(buf, "%s $03", longJump2InverseOpcodeName[ref->type]);
        }
        ref->outLine1 = STRDUP(buf);

        if (verbose)
        {
            if (lastSymbol && ((lastSymbol->addr & 0xFFFF) == currentPC) && lastSymbol->lineNr != fin->line) {
                /* Write the original/unmodified symbol name to the outline */
                sprintf(buf, " %s ", lastSymbol->orig);
                strcat(outline, buf);
            }
            while (strlen(outline) < 16)
                strcat(outline, " ");

            aprintf("%s\t%s\n", outline, ref->outLine1);
        }

        if (ref->makeShort == 0)
        {
            outline[0] = 0;
            if (verbose) {
                currentPC = print_pc();
            }
            put_opcode(0x4c);       // JMP
            put_word(addr, 0);

            sprintf(buf, "JMP %s", str);
            ref->outLine2 = STRDUP(buf);

            if (verbose)
            {
                if (lastSymbol && ((lastSymbol->addr & 0xFFFF) == currentPC) && lastSymbol->lineNr != fin->line) {
                    /* Write the original/unmodified symbol name to the outline */
                    sprintf(buf, " %s ", lastSymbol->orig);
                    strcat(outline, buf);
                }
                while (strlen(outline) < 16)
                    strcat(outline, " ");
                strcpy(buf, str);
                line = get_nxt_word(PARSE_CURRENT_LINE);
                aprintf("%s\tJMP %s\t\t;%s\n", outline, buf, line);
            }
        }
    }
}

/*=========================================================================*
  function do_trig(int cmd)
  parameter cmd: indicates type
  First parameter is < or > or skipped
  < = Low byte
  > = High byte
  If left out or = then word in LSB, HSB format

     39- .SIN [< >,] angle, steps, scale
     40- .COS 

  processes a trig command
 *=========================================================================*/
int do_trig(int cmd)
{
    char* str, * look;
    char buf[256];
    int angle, steps, scale;
    int length;

    int outputType = 0;     // 0 = word (low-high), 1=Low, 2=High
    angle = 0;
    steps = 256;
    scale = 8000;

    if (!init_pc)
        fatal_error("No initial address specified.");

    // Get the rest of the line and trim front and back
    str = get_nxt_word(PARSE_LINE_REST);
    while (ISSPACE(*str))
        str++;
    look = str + strlen(str) - 1;
    while (ISSPACE(*look)) {
        *look = 0;
        look--;
    }
    look = str;

    // Check for Low(<), High(>) or Word(=) modifier
    if (*look == '<')
    {
        outputType = 1; // Low
        length = to_comma(look, buf);
        look = look + length;
    }
    if (*look == '>')
    {
        outputType = 2; // High
        length = to_comma(look, buf);
        look = look + length;
    }
    if (*look == '=')
    {
        outputType = 0; // word (LSB, MSB)
        length = to_comma(look, buf);
        look = look + length;
    }

    // Get param 1 = angle
    if (*look)
    {
        length = to_comma(look, buf);
        look = look + length;
        angle = get_expression(buf, 1, 0);
    }
    else {
        fatal_error("No angle parameter");
    }

    // Get param 2 = steps
    if (*look)
    {
        length = to_comma(look, buf);
        look = look + length;
        steps = get_expression(buf, 1, 0);
    }
    else 
    {
        fatal_error("No steps parameter");
    }

	// Get param 3 = scale
	if (*look)
	{
		length = to_comma(look, buf);
		look = look + length;
		scale = get_expression(buf, 1, 0);
	}
	else {
        fatal_error("No scale parameter");
	}
    
    // On pass 0 just note the command length (1 or 2 bytes)
    if (pass == 0)
    {
        pc += outputType == 0 ? 2 : 1;
        return 0;
    }

    if (verbose) {
        print_pc();
    }

    // Not one pass 0
    short result = 0;
    switch (cmd) {
        case DOT_SIN:
            result = (short)((sin((double)angle * 2.0 * 3.14159265359 / (double)steps) * scale)+0.5 );
            break;

        case DOT_COS:
            result = (short)((cos((double)angle * 2.0 * 3.14159265359 / (double)steps) * scale)+0.5 );
            break;
    }
    short val = 0;
    switch (outputType) {
        case 0:
            val = result & 0xFFFF;
            put_word(val, 0); // lo-hi format
            sprintf(buf, "\t%d 0x%04X", val, val);
            break;

        case 1:
            val = result & 0xFF;
            put_byte(val);
            sprintf(buf, "\t%d 0x%02X", val, val);
            break;

        case 2:
            val = (result >> 8) & 0xFF;
            put_byte(val);
            sprintf(buf, "\t%d 0x%02X", val, val);
            break;
    }
    if (pass && verbose) 
    {
        aprintf("%s %s ;(%d,%d,%d)=%s\n", outline, get_nxt_word(PARSE_CURRENT_LINE), angle, steps, scale, buf);
        outline[0] = 0;
    }

    return 0;
}

/*=========================================================================*
  function do_float_expr(int cmd)
  parameter cmd: indicates type
  Parse a float expression and either store it as a 8 or 16-bit unsigned byte/int
 *=========================================================================*/
int do_float_expr(int cmd)
{
    char* str, * look;
    char buf[256];
    int d, hasPrintedPC, p;

    if (!init_pc)
        fatal_error("No initial address specified.");

    // Trim front and back of expression
    str = get_nxt_word(PARSE_LINE_REST);
    while (ISSPACE(*str))
        str++;
    look = str + strlen(str) - 1;
    while (ISSPACE(*look)) {
        *look = 0;
        look--;
    }

    look = str;
    p = hasPrintedPC = 0;
    while (*look) 
    {
        if (pass && verbose) 
        {
            if (!hasPrintedPC) 
            {
                print_pc();
                hasPrintedPC++;
            }
            hasPrintedPC++;
        }

        d = to_comma(look, buf);
        look = look + d;
        float val = 0;
        if (!pass)
        {
            switch (cmd)
            {
                case DOT_FLOAT_2_U16_LH:
                case DOT_FLOAT_2_U16_HL: pc += 2; break;
                case DOT_FLOAT_2_U8: pc += 1; break;
                default:
                    fatal_error("Coding error: Unknown command type in do_float_expr");
                    return 0;
            }
        }
        else
        {
            // Parse the buf into a float expression
            val = get_float_expression(buf, 1);
            switch (cmd)
            {
                case DOT_FLOAT_2_U16_LH: 
                {
                    put_word((unsigned short)val, STORE_LO_HI);
                    break;
                }
                case DOT_FLOAT_2_U16_HL:
                {
                    put_word((unsigned short)val, STORE_HI_LO);
                    break;
                }
                case DOT_FLOAT_2_U8:
                {
                    put_byte((unsigned char)val);
                    break;
                }
            }
        }

        if (pass && verbose && hasPrintedPC == 2) {
            if (!p) 
            {
                aprintf("%s %s %d %x\n", outline, get_nxt_word(PARSE_CURRENT_LINE), (int)val, (int)val);
                p = 1;
            }
            else {
                aprintf("%s\n", outline);
            }
            outline[0] = 0;
            hasPrintedPC = 0;
        }
    }

    if (pass && verbose && hasPrintedPC) 
    {
        if (!p) {
            aprintf("%s %s\n", outline, get_nxt_word(PARSE_CURRENT_LINE));
        }
        else {
            aprintf("%s\n", outline);
        }
    }
    outline[0] = 0;
    return 1;
}

/*=========================================================================*
*=========================================================================*/
void doRunInitCommand(symbol* sym)
{
    if (verbose && pass)
        aprintf("\n");

    // Start a new bank to make sure it gets written on its own
    ++bankID;
    activeBank = get_bank(bankID, bankID);

    init_pc = 1;
	switch (sym->addr)
	{
		case DOT_RUN:
			pc = 0x2e0;
			saveNamedMemoryRegion(pc, "RUN");
			break;
		case DOT_INIT:
			pc = 0x2e2;
			saveNamedMemoryRegion(pc, "INIT");
			break;
		default:
            fatal_error("Illegal .rin or .init command");
			break;
	}

    do_xword(sym->addr);
}

void doProcStart(symbol* sym)
{
	if (dotProcLevel > 0)
	{
        fatal_error("Nested .PROC/.ENDP not allowed");
	}
    ++dotProcLevel;

	char* str = get_nxt_word(PARSE_NEXT_WORD);
    local++;
    nextLabelIsAProcedure = 1;

    if (str && strlen(str) > 0)
        do_cmd(str);
    else
        fatal_error("No name for .PROC");
}

void doProcEnd(symbol* sym)
{
    if (dotProcLevel <= 0)
    {
        fatal_error("No matching .PROC for .ENDP");
    }
    // Finish off the current procedure
	if (currentProcedure && (currentProcedure->hints & HINT_PROCEDURE))
	{
		// This is a procedure
        currentProcedure->procEndAddr = pc;
        currentProcedure->procEndLineNr = fin->line;

        currentProcedure = NULL;
	}
	--dotProcLevel;
}

/*=========================================================================*
 * function doGuard(int isGuard)
 *
 * This function handles checking the current PC against some condition.
 * If the condition is not met then an error is shown.
 * .GUARD Condition, "Error message", param1, param2, ...
 * Where the "Error message" string can contain these replacements:
 * {{*}} = current PC
 * {{%1}} = 1st parameter expression result
 * {{%n}} = nth parameter expression result
 * i.e. .GUARD [* <= $100], "Memory overrun {{%1}} - ${{*}} by {{%2}} bytes", page0_80, [*-$100]
 *=========================================================================*/
void doGuard(const int isGuard)
{
    char buf[256];
    char guardBuffer[256];

    if (!init_pc)
        fatal_error("No initial address specified.");

    // Skip spaces at start and end
    char* str = get_nxt_word(PARSE_LINE_REST);
    while (ISSPACE(*str))
        str++;
    char* look = str + strlen(str) - 1;
    while (ISSPACE(*look)) {
        *look = 0;
        look--;
    }
    look = str;

    // Get the guard condition
    if (*look == 0)
    {
        fatal_error("No guard/notify condition specified.");
    }
    int length = to_comma(look, guardBuffer);
    look = look + length;

    // Evaluate the guard/notify condition
    squeeze_str(guardBuffer);
    int exprValue = get_expression(guardBuffer, 1, 0);

    if ( (isGuard && exprValue) || (!isGuard && !exprValue))
        return;

    // Get the guard/notify error message
    if (*look == 0)
    {
        fatal_error("No guard/notify error message specified.");
    }
    length = to_comma(look, buf);
    look = look + length;

    /*
    str = buf;
    while (*str == '"')
        str++;
    char* clear = buf + strlen(buf) - 1;
    while (*clear == '"') 
    {
        *clear = 0;
        clear--;
    }

	char* errorMessage = STRDUP(str);     // make a copy of the error message
	*/
    char* errorMessage = STRDUP(buf);     // make a copy of the error message

    // Get possible parameters
    int paramNr = 1;
    while (*look)
    {
        // This is the parameter {{%1}} we want to replace
        char targetParam[20];
		sprintf(targetParam, "{{%%%d}}", paramNr);

        // Get the parameter expression
        length = to_comma(look, buf);
        look = look + length;
        squeeze_str(buf);

    	int paramValue = get_expression(buf, 1, 0);

		sprintf(buf, "$%.4x", paramValue);         // Convert to string

        // Do the replacement
        char *processedErrorMsg = replace(errorMessage, targetParam, buf);
        if (errorMessage)
			free(errorMessage);
        errorMessage = processedErrorMsg;

        ++paramNr;
    }

    if (isGuard)
        fatal_error(errorMessage);
    else
        message(errorMessage);
}

/*=========================================================================*
 * 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];
	int currentPC = 0;        /* current PC*/

	if (pass == 1 && lastLabel && lastLabel->ftrack == fin->ftrack && lastLabel->lineNr == fin->line) {
		// On the 2nd pass look if there is a comment after the equate
		str = get_nxt_word(PEEK_COMMENT);
		if (str)
		{
			// Add comment to label
			lastLabel->comment = STRDUP(str);
		}
		str = NULL;
	}

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

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

            if (num_args[sym->addr]) {
                str = get_nxt_word(PARSE_LINE_REST);
                squeeze_str(str);
                parse_operand(sym, str);
            }
            else {
                get_single(sym);
            }
            if ((verbose) && (pass)) {
                if (lastSymbol && ((lastSymbol->addr & 0xFFFF) == currentPC) && lastSymbol->lineNr != fin->line) {
                    /* Write the original/unmodified symbol name to the outline */
                    sprintf(buf, " %s ", lastSymbol->orig);
                    strcat(outline, buf);
                }
                while (strlen(outline) < 16)
                    strcat(outline, " ");
                line = get_nxt_word(PARSE_CURRENT_LINE);
                aprintf("%s%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 DOT_BYTE:  /* .BYTE */
                case DOT_CBYTE:  /* .CBYTE */
                case DOT_SBYTE:  /* .SBYTE */
                    do_xbyte(sym->addr);
                    break;
                case DOT_DBYTE:  /* .DBYTE */
                    do_xword(sym->addr);
                    break;
                case DOT_ELSE: /* .ELSE */
                    skip_if();
                    break;
                case DOT_END:  /* .END */
                    break;
                case DOT_ENDIF: /* .ENDIF */
                    --dotIfLevel;
                    break;
                case DOT_ERROR: /* .ERROR */
                    str = get_nxt_word(PARSE_NEXT_LINE);
                    fatal_error(str);
                    break;
                case DOT_FLOAT: /* .FLOAT */
                    do_float();
                    break;
                case DOT_IF: /* .IF */
                    ++dotIfLevel;
                    clearDoneIfPartIndicators(dotIfLevel);

                    str = get_nxt_word(PARSE_LINE_REST);
                    squeeze_str(str);
                    eq = 0;
                    addr = get_expression(str, 1, 0);
                    if (addr)
                        doneIfPart[dotIfLevel] = 1;
                    else
                        skip_if();      // Skip until .ELSE, .ELSEIF or .ENDIF
                    break;
                case DOT_IFDEF: /* .IFDEF = IF DEFINED */
                {
                    ++dotIfLevel;
                    clearDoneIfPartIndicators(dotIfLevel);

                    str = get_nxt_word(PARSE_LINE_REST);
                    squeeze_str(str);
                    eq = 0;

                    if (!get_not_defined(str))
                        doneIfPart[dotIfLevel] = 1;
                    else
                        skip_if();      // Skip until .ELSE, .ELSEIF or .ENDIF
                    break;
                }
                case DOT_IFNDEF: /* .IFNDEF = IF NOT DEFINED */
                {
                    ++dotIfLevel;
                    clearDoneIfPartIndicators(dotIfLevel);

                    str = get_nxt_word(PARSE_LINE_REST);
                    squeeze_str(str);
                    eq = 0;

                    if (get_not_defined(str))
                        doneIfPart[dotIfLevel] = 1;
                    else
                        skip_if();      // Skip until .ELSE, .ELSEIF or .ENDIF
                    break;
                }
                case DOT_INCLUDE:  /* .INCLUDE */
                    str = get_nxt_word(PARSE_NEXT_LINE);
                    if (str[0] == '"') {
                        str++;
                        str[strlen(str) - 1] = 0;
                    }
                    open_file(str);
                    break;
                case DOT_LOCAL: /* .LOCAL */
                    local++;
                    if (local > 62)
                        error("Over 62 local regions defined, will not compile on MAC/65.", 0);
                    break;
                case DOT_OPT: { /* .OPT */
                    int i, len, negated = 0;
                    do {
                        str = get_nxt_word(PARSE_NEXT_WORD);
                        len = (int)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 (!strcmp(str, "SYMDUMP"))
                        {
                            opt.symbolDumpOptions = negated;
                        }
                        else if (strlen(str)) {
                            error("Unknown .OPT directive.", 0);
                        }
                    } while (strlen(str));
                    break;
                }
                case DOT_SET: /* .SET */
                    get_nxt_word(PARSE_REPLACE_COMMAS);
                    str = get_nxt_word(PARSE_NEXT_WORD);
                    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(PARSE_LINE_REST);
                            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(PARSE_LINE_REST);
                        }
                    }
                    break;
                case DOT_PAGE: /* .PAGE */
                case DOT_TAB: /* .TAB */
                case DOT_TITLE: /* .TITLE */
                    do {
                        str = get_nxt_word(PARSE_NEXT_WORD);
                    } while (strlen(str));
                    break;
                case DOT_WORD: /* .WORD */
                    do_xword(sym->addr);
                    break;
                case DOT_STAR: /* '*' operator */
                {
                    // * = expression ["Region name"]
                    // * = * + expression ["Region name"]
                    if ((!eq) || (eq == 2)) {
                        fatal_error("Malformed * operator.");
                    }
                    if (verbose && pass)
                        aprintf("\n");
                    eq = 0;
                    str = get_nxt_word(PARSE_LINE_REST);
                    char* lineDup = str ? STRDUP(str) : NULL;           // Duplicate the line so that we can look for memory region name in it
                    squeeze_str(str);
                    // Check if there is a " somewhere.  Can't be part of the expression, so must be the region name
                    char* regionName = strchr(str, '"');
                    if (regionName) {
                        *regionName = 0;        // " to 0
                        ++regionName;
                    }

                    if (str[0] == '*') {
                        // Relative adjustment
                        if (!init_pc)
                            fatal_error("No initial address specified.");
                        if (str[1] != '+')
                            fatal_error("Illegal relative adjustment.");
                        str[0] = '0';
                        addr = get_expression(str, 1, 0);
                        pc = pc + addr;
                    }
                    else {            /* Absolute value */
                        init_pc = 1;
                        addr = get_expression(str, 1, 0);
                        pc = addr;
                    }
                    if (regionName && lineDup)
                    {
                        regionName = strchr(lineDup, '"');
                        if (regionName) {
                            *regionName = 0;
                            ++regionName;
                        }
                        // Find the last " in the region name
                        char* lastInvComma = strrchr(regionName, '"');
                        if (lastInvComma) *lastInvComma = 0;
                        saveNamedMemoryRegion(pc, regionName);
                    }
                    if (lineDup)
                        free(lineDup);
					if (opt.autoBankMode)
					{
						// In MAC/65 mode. Every org is a new bank
                        ++bankID;
						activeBank = get_bank(bankID, bankID);
						if (pass && (verbose & 1)) {
							char txtBuffer[256];
							sprintf(txtBuffer, "Autobank: Using bank %d\n", bankID);
							error(txtBuffer, 0);
						}
					}
                    break;
                }
                case DOT_ENDM:  /* .ENDM */
                    fatal_error("No matching .MACRO definition for .ENDM");
                    break;
                case DOT_MACRO:  /* .MACRO definition */
                    if (!pass)
                        create_macro(sym);
                    else
                        skip_macro();
                    eq = 0;
                    break;
                case DOT_DS:  /* .DS directive */
                    str = get_nxt_word(PARSE_LINE_REST);
                    squeeze_str(str);
                    addr = get_expression(str, 1, 0);
                    pc = pc + addr;
                    break;
                case DOT_INCBIN: /* .INCBIN */
                    str = get_nxt_word(PARSE_NEXT_LINE);
                    if (str[0] == '"') {
                        str++;
                        str[strlen(str) - 1] = 0;
                    }
                    incbin(str);
                    break;
                case DOT_REPT: /* .REPT */
                    do_rept(sym);
                    break;
                case DOT_ENDR:  /* .ENDR */
                    fatal_error("No matching .REPT definition for .ENDR");
                    break;
                case DOT_WARN: /* .WARN */
                    str = get_nxt_word(PARSE_NEXT_LINE);
                    error(str, 0);
                    break;
                case DOT_DC: /* .DC */
                    str = get_nxt_word(PARSE_NEXT_LINE);
                    addr = get_expression(str, 1, 0);

                    str = get_nxt_word(PARSE_NEXT_LINE);
                    stor = get_expression(str, 1, 0);

                    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 DOT_BANK: {
					/* .BANK [Bank#] [,ReportedBank#] */
					unsigned short reportedBankNr = 0;
					int param1 = -1;
					int param2 = -1;

                    if (opt.autoBankMode)
                    {
                        fatal_error(".BANK not allowed in -a/-mac65 (autobank) mode");
                    }

					str = get_nxt_word(PARSE_PEEK_LINE_REST);
					if (strlen(str)) {
                        // There are parameters for .BANK 
						squeeze_str(str);       // Remove all spaces
						if (str[0] == ',')      // If the first char in the string is ',' then there is no BankNr parameter
							param2 = -2;        // Indicate that there is a number for param2

						get_nxt_word(PARSE_REPLACE_COMMAS);
						str = get_nxt_word(PARSE_NEXT_WORD);
						if (strlen(str)) {
                            // Get the 1st parameter
							squeeze_str(str);
							if (strlen(str)) {
								param1 = get_expression(str, 1, 0);
							}
							str = get_nxt_word(PARSE_NEXT_WORD);
							if (strlen(str)) {
                                // Get the 2nd parameter
								squeeze_str(str);
								if (strlen(str)) {
									param2 = get_expression(str, 1, 0);
								}
							}
							if (param2 > 0) {
								bankID = param1;
								reportedBankNr = (unsigned short)param2;
							}
							else if (param2 == -1) {
                                bankID = param1;
								reportedBankNr = (unsigned short)param1;
							}
							else {
								bankID++;
								reportedBankNr = (unsigned short)param1;
							}
						}
					}
					else {
                        // No parameters supplied to .BANK
                        // Advance the bank # and use the same # for reporting.
						bankID++;
						reportedBankNr = bankID;
					}
					if (pass) {
						if (bankID >= 0) {
							if (verbose & 1) {
								char txtBuffer[256];
								sprintf(txtBuffer, "Using bank %d,%d\n", bankID, reportedBankNr);  // NOLINT(cert-err33-c)
								error(txtBuffer, 0);
							}
							activeBank = get_bank(bankID, reportedBankNr);
						}
					}
					else if (bankID >= 0) {
						activeBank = get_bank(bankID, reportedBankNr);
                        if (activeBank == NULL)
                        {
                            fatal_error("Cannot allocate memory snapshot.");
                        }
					}
					/* Skip for now... */
					do {
						str = get_nxt_word(PARSE_NEXT_WORD);
					} while (strlen(str));
					break;
				}
                case DOT_ALIGN: { /* .ALIGN */
                    int ok = 0;
                    if (!init_pc)
                        fatal_error("No initial address specified.");
                    str = get_nxt_word(PARSE_LINE_REST);
                    squeeze_str(str);
                    if (strlen(str) == 0) {
                        fatal_error("Need to specify an alignment boundary");
                    }
                    init_pc = 1;
                    addr = get_expression(str, 1, 0);
                    /* Valid boundary values are 2^n (n=0-15) */
                    for (i = 0; i < 16; ++i) {
                        if ((1 << i) == (unsigned short)addr) {
                            pc = (pc + addr - 1) & (0x10000 - addr);
                            ok = 1;
                            break;
                        }
                    }
                    if (ok == 0) {
                        fatal_error("Align boundary needs to be power of 2");
                    }
                    break;
                }
                case DOT_REGION_NAME: /* .NAME to give a name to a memory area*/
                {
                    str = get_nxt_word(PARSE_NEXT_LINE);
                    if (str[0] == '"') {
                        str++;
                        str[strlen(str) - 1] = 0;
                    }
                    if (pass == 1)
                    {
                        saveNamedMemoryRegion(pc, str);
                    }
                    break;
                }
                case DOT_ELSEIF:
                {
                    if (doneIfPart[dotIfLevel])
                        skip_if();
                    else
                    {
                        str = get_nxt_word(PARSE_LINE_REST);
                        squeeze_str(str);
                        eq = 0;
                        addr = get_expression(str, 1, 0);
                        if (addr)
                            doneIfPart[dotIfLevel] = 1;
                        else
                            skip_if();      // Skip until .ELSE, .ELSEIF or .ENDIF
                    }
                    break;
                }

                case EX_JEQ:
                case EX_JNE:
                case EX_JPL:
                case EX_JMI:
                case EX_JCC:
                case EX_JCS:
                case EX_JVC:
                case EX_JVS:
                {
                    // jcc xyz
                    // bcs *+4 --|      $b0 $03
                    // jmp xyz   |      $4c Lo Hi
                    // ....    <-|
                    do_long_jump(sym);
                    break;
                }
                case DOT_SIN:
                case DOT_COS:
                {
                    do_trig(sym->addr);
                    break;
                }
                case DOT_FLOAT_2_U16_LH:
                case DOT_FLOAT_2_U16_HL:
                case DOT_FLOAT_2_U8:
                {
                    do_float_expr(sym->addr);
                    break;
                }
                // RUN and INIT shortcuts
				case DOT_RUN:
                case DOT_INIT:
                {
					// .RUN word  ; * = $2E0 .word ADDRESS
                    // .INIT word  ; * = $2E2 .word ADDRESS
                    doRunInitCommand(sym);
					break;
				}
				// Define a procedure entry/end
				case DOT_PROC:
                    doProcStart(sym);
					break;

				case DOT_ENDP:
                    doProcEnd(sym);
                    break;

				// .GUARD [Condition], "Error message", param1, param2, param3
                // Will terminate assembler
				case DOT_GUARD:
				{
                    doGuard(1);
					break;
				}
                // .NOTIFY [Condition], "Message", param1, param2, param3
                // Will print message and continue with assembly
                case DOT_NOTIFY:
                {
                    doGuard(0);
                    break;
                }

                default:
                    fatal_error("Illegal directive.");
                    break;
            }
            break;
        }
        case MACRON: /* MACRO */
        {
            mc = get_macro_call(sym->name);
            if (!mc)
                fatal_error("Missing entry in macro table");

            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);
                    fatal_error(buf);
                }
                sym->num++;
            }
            if (eq) {
                if (sym->tp != MACROQ) {
                    snprintf(buf, 80, "Symbol '%s' is not a transitory equate!", sym->name);
                    fatal_error(buf);
                }
                str = get_nxt_word(PARSE_LINE_REST);
                if (eq == 2) {
                    squeeze_str(str);
                    addr = get_address(str,0);
                    sym->addr = addr;
                    sym->bank = activeBank->reportedBankNr;
                }
                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;
                    }
                    defineUnknown(sym->name, pc);
                }
            }
            break;
        }
		case LABEL:  /* labels and equates */
		case EQUATE:
        {
            if (!pass) {
                if (eq == 2) {
                    snprintf(buf, 80, "Symbol '%s' is not a transitory equate!", sym->name);
                    fatal_error(buf);
                }
                else {
                    if (sym->addr > 255) {
                        snprintf(buf, 80, "Symbol '%s' already defined!", sym->name);
                        fatal_error(buf);
                    }
                }
            }
            else {
                // 2nd pass
                if ((sym->tp == LABEL) && (sym->name) && (sym->name[0] != '?'))
                    if (!strchr(sym->name, '?'))
                        opt.MAEname = sym->name;
            }
            if (eq == 1) {
                // Equate (=)
                if (sym->tp == LABEL) {
                    snprintf(buf, 80, "Cannot use label '%s' as an equate", sym->name);
                    fatal_error(buf);
                }
                str = get_nxt_word(PARSE_LINE_REST);
                if (sym->addr == 0xffff) {  /* allow forward equate references */
                    squeeze_str(str);
                    addr = get_address(str,0);
                    sym->addr = addr;
                    sym->bank = activeBank->reportedBankNr;
                    defineUnknown(sym->name, addr);
                }
                eq = 0;
            }
            break;
        }
		case TEQUATE: /* transitory equates */
        {
            if (!pass) {
                if (eq == 2) {
                    str = get_nxt_word(PARSE_LINE_REST);

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

                    eq = 0;
                }
                else {
                    snprintf(buf, 80, "Use .= to assign '%s' new a value.", sym->name);
                    fatal_error(buf);
                }
            }
            else {
                if (eq == 2) {   /* allow .= updates */
                    str = get_nxt_word(PARSE_LINE_REST);
                    squeeze_str(str);
                    addr = get_address(str,1);
                    sym->addr = addr;
                    sym->bank = activeBank->reportedBankNr;
                    defineUnknown(sym->name, addr);
                    eq = 0;
                }
            }
            break;
        }
		default:
			if (!pass) {
				snprintf(buf, 80, "Symbol '%s' already defined!", sym->name);
                fatal_error(buf);
			}
	}
	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)
{
	const int len = (int)strlen(buf);
	for (int i = 0; i < len; i++)
		buf[i] = TOUPPER(buf[i]);

	symbol* sym = findsym(buf);

	if (sym && (sym->tp == LABEL || sym->tp == EQUATE) && sym->name) {
		lastLabel = sym;
	}

	if ((!sym) || ((sym->tp == MACROL) && (!sym->macroShadow))) {
		/* must be a label or define */
		add_label(buf);
		sym = findsym(buf);
		lastLabel = sym;
		if (sym && sym->tp == LABEL && sym->name && sym->name[0] != '?')
		{
			if (!strchr(sym->name, '?'))
				opt.MAEname = sym->name;
		}
        if (sym && sym->tp == LABEL && sym->name && nextLabelIsAProcedure)
        {
            nextLabelIsAProcedure = 0;
            sym->hints = HINT_PROCEDURE;
            currentProcedure = sym;
        }
	}
	else
    {
		proc_sym(sym);
		if (pass && sym && sym->tp == LABEL) {
			lastSymbol = sym;
		}
		else
			lastSymbol = NULL;
	}
	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 *filename)
 * parameter filename- file to assemble
 *
 * This performs a two pass assembly over the file indicated
 *=========================================================================*/
int assemble(char *filename)
{
	char* str;
	int i, zlabels = 0;

	for (int ip = i = 0; i < 2; i++) 
    {
		ip++;
		init_pass();
		fprintf(stderr, "Pass %d: ", ip);
		fflush(stderr);
		open_file(filename);
		do {
			str = get_nxt_word(PARSE_NEXT_LINE);
			if (str) {
				do_cmd(str);
			}
			if (repass) {
				fixRepass();
				while (get_nxt_word(PARSE_NEXT_LINE))
					;
				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", warn);
			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(void) {
  MemoryBank *walk=memoryBanks;
  while(walk) {
    memset(walk->memmap,0,65536);
    memset(walk->bitmap,0,8192);
    walk=walk->next;
  }
  bsize=0;
  return 1;
}
/*=========================================================================*
 * function count_banks()
 * parameter: none
 *
 * returns the number of banks that have been compiled...
 *=========================================================================*/
int count_banks() {
  int num=0;
  MemoryBank *walk=memoryBanks;
  while(walk) {
    num++;
    walk=walk->next;
  }
  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;
  MemoryBank *walkBank=activeBank;

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

  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->next;
  }
  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;
  MemoryBank *walkBank=activeBank;

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

  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->next;
  }
  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)
        fatal_error("NULL symbol defined on command line");
    if (*def=='\0')
        fatal_error("-D option missing symbol");

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

    sym->orig = STRDUP(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,0);
        *(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 (int)(look-name);
    if ((*look=='/')||(*look=='\\'))
      return (int)(end-name);
    look--;
  }
  return (int)(end-name);
}

/*=========================================================================*
 * function showMemoryLayout()
 * Dump the memory layout to the output stream
 * Memory Map
 * ----------
 * $xxxx-$yyyy Name ...
 * $xxxx-$yyyy Name ...
 *=========================================================================*/
int showMemoryLayout()
{
	unsigned char* scan, * end;
	int len, a, b, i, walk, start;
	MemoryBank* walkBank = activeBank;

    fprintf(stderr, "Memory Map\n----------\n");

	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) 
                    {
                        fprintf(stderr, "$%.4x-$%.4x %s\n",
                            start,
                            start + len - 1,
                            findNameOfMemoryRegion(start)
                        );
						len = start = 0;
					}
				}
				b = b >> 1;
				walk++;
			}
			scan++;
		}
		walkBank = walkBank->next;
	}
	if (count_banks() > 1)
		fprintf(stderr, "\nCompiled %d banks\n", count_banks());

	return 1;
}

/*=========================================================================*
 * Show command line help
 *=========================================================================*/
void showHelp(char *executable)
{
    fprintf(stderr, "\nUsage: %s [-v] [-s] [-r] [-d[symbol=value] [-o[fname.out] [-m[fname.state]] <fname.m65>\n", executable ? executable : "atasm");
    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);
    fputs("         -hc[fname]: dumps equates and labels to header file for CC65\n", stderr);
    fputs("         -ha[fname]: dumps equates and labels to header file for assembler\n", stderr);
    fputs("         -hv[clLm]: dumps all info for VSCode plugin. c=constants, l=labels (primary/no?), L=labels (ALL), m=macros\n", stderr);
    fputs("         -noshowmem: Do not dump the memory layout\n", stderr);
    fputs("         -eval: Just run the assembler producing data but don't write the output to disc\n", stderr);
    fputs("         -a OR -mac65: autobank: Put each segment in its own bank. For Mac/65 compatibility\n", stderr); 
}

/*=========================================================================*
 * function main
 *
 * starts the whole process
 *=========================================================================*/
int main(int argc, char* argv[]) 
{
	char outfile[256], fname[256], snap[256], xname[256], labelfile[256], listFilename[256];
	char cheaderfile[256 + 2], asmheaderfile[256 + 4];
	int dsymbol, i, state;
	int dumpVSCode;

	int create_c_header_fn, create_asm_header_fn;

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

	create_c_header_fn = create_asm_header_fn = 0;
	dsymbol = state = 0;
	dumpVSCode = DUMP_NOTHING;          // No dumping of constant, label, macros, and includes to 'asm-symbols.json' file
	strcpy(snap, "atari800.a8s");
	fname[0] = outfile[0] = labelfile[0] = listFilename[0] = cheaderfile[0] = asmheaderfile[0] = '\0';
	memset(&opt, 0, sizeof(opt));
	opt.savetp = opt.verbose = opt.MAElocals = 0;
	opt.fillByte = 0xff;
	opt.symbolDumpOptions = 0;        /* no restriction */
	opt.showMemory = 1;               // Show memory layout at end of compilation
	opt.evalOnly = 0;                 // Do the normal assemble and output. If turned on then no binary file is written to disk

	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], "-mac65"))
            opt.autoBankMode = 1;
        else if (!STRCASECMP(argv[i], "-a"))
            opt.autoBankMode = 1;
        else if (!STRCASECMP(argv[i], "-r"))
			opt.savetp = 2;
		else if (!STRNCASECMP(argv[i], "-f", 2)) {
			if (strlen(argv[i]) > 2)
				opt.fillByte = (unsigned char)get_address(argv[i] + 2, 0);
			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], "-hc", 3))
		{
			if (strlen(argv[i]) > 3) {
				strcpy(cheaderfile, argv[i] + 3);
			}
			else {
				create_c_header_fn = 1;
			}
		}
		else if (!STRNCASECMP(argv[i], "-ha", 3)) {
			if (strlen(argv[i]) > 3) {
				strcpy(asmheaderfile, argv[i] + 3);
			}
			else {
				create_asm_header_fn = 1;
			}
		}
		else if (!STRNCASECMP(argv[i], "-hv", 3))
		{
			if (strlen(argv[i]) > 3)
			{
				/* There are special selectors after the -hv switch */
				/* c = constants, l = labels, m = macros */
				char* param = argv[i];
				for (int x = 3; x < (int)strlen(param); ++x) {
					if (param[x] == 'c') { dumpVSCode |= DUMP_CONSTANTS; }
					else if (param[x] == 'l') { dumpVSCode |= DUMP_LABELS_PRIMARY; }
					else if (param[x] == 'L') { dumpVSCode |= DUMP_LABELS_ALL; }
					else if (param[x] == 'm') { dumpVSCode |= DUMP_MACROS; }
					/*
					if (!STRNCASECMP(&param[x], "c", 1)) { dumpVSCode |= DUMP_CONSTANTS; }
					else if (!STRNCASECMP(&param[x], "l", 1)) { dumpVSCode |= DUMP_LABELS_PRIMARY; }
					else if (!STRNCASECMP(&param[x], "L", 1)) { dumpVSCode |= DUMP_LABELS_ALL; }
					else if (!STRNCASECMP(&param[x], "m", 1)) { dumpVSCode |= DUMP_MACROS; }
					*/
				}
				// Hmm, did not select anything useful, so dump it all
				if (dumpVSCode == DUMP_NOTHING)
					dumpVSCode = DUMP_ALL;
			}
			else {
				/* Dump all the items */
				dumpVSCode = DUMP_ALL; /* constants, labels, macros, (includes are always dumped) */
			}
		}
		else if (!STRNCASECMP(argv[i], "-g", 2)) {
			if (strlen(argv[i]) > 2) {
				strcpy(listFilename, argv[i] + 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")) {
			showHelp(argv[0]);
			return 1;
		}
		else if (!STRCASECMP(argv[i], "-noshowmem"))
		{
			opt.showMemory = 0;
		}
		else if (!STRCASECMP(argv[i], "-eval"))
		{
			opt.evalOnly = 1;
		}
		else strcpy(fname, argv[i]);
	}

	if (!strlen(fname)) {
		// strcpy(fname,"test.m65");
		showHelp(argv[0]);
		return 1;
	}

    if (strlen(listFilename) > 0 && opt.evalOnly == 0)
    {
        listFile = fopen(listFilename, "wt");
        if (!listFile) {
            fprintf(stderr, "Cannot write to list file'%s'\n", listFilename);
            return 1;
        }
        opt.verbose |= 2;
        /* Write a fake header so that Altirra will recognise this as a listing */
        fprintf(listFile, "mads (generated by atasm)\n");
    }

	/* If the -hc or -ha options did not specify a filename lets create them now */
	if (create_c_header_fn)
		sprintf(cheaderfile, "%s.h", fname);
	if (create_asm_header_fn)
		sprintf(asmheaderfile, "%s.inc", fname);

	init_asm();
	assemble(fname);

	if (dsymbol)
		dump_symbols();

	if (labelfile[0] && opt.evalOnly == 0)
		dump_labels(labelfile);

	if (cheaderfile[0] && opt.evalOnly == 0)
		dump_c_header(cheaderfile, fname);

	if (asmheaderfile[0] && opt.evalOnly == 0)
		dump_assembler_header(asmheaderfile);

	if (dumpVSCode)
		dump_VSCode(trackedFiles, dumpVSCode);

	fputs("\nAssembly successful\n", stderr);
	fprintf(stderr, "  Compiled %d bytes (~%dk)\n", bsize, bsize / 1024);

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

	activeBank = memoryBanks;

	if (!strlen(outfile)) {
		fname[find_extension(fname)] = 0;
		strcat(fname, (opt.savetp) & 2 ? ".bin" : ".65o");
		strcpy(outfile, fname);
	}
	if (opt.evalOnly == 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);
			}
		}
	}
	if (opt.showMemory)
	{
		showMemoryLayout();
	}

    dumpLongJumpOptimizations();

	clean_up();
	cleanup_FilenameTracking();
	cleanup_MemoryRegionNameTracking();

	/* _CrtDumpMemoryLeaks(); */
	return 0;
}
/*=========================================================================*/
