ftp.nice.ch/pub/next/tools/emulators/vice.0.15.0.NeXT.sd.tgz#/vice-0.15.0/src/mon.c

This is mon.c in view mode; [Download] [Up]

/*
 * mon.c - The VICE built-in monitor.
 *
 * Written by
 *  Daniel Sladic (sladic@eecg.toronto.edu)
 *
 * Patches and improvements by
 *  Ettore Perazzoli (ettore@comm2000.it)
 *
 * This file is part of VICE, the Versatile Commodore Emulator.
 * See README for copyright notice.
 *
 *  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., 59 Temple Place, Suite 330, Boston, MA
 *  02111-1307  USA.
 *
 */

/* FIXME: Remove MS-DOS specific cruft.  */

#include "vice.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <assert.h>

#ifdef __MSDOS__
#include <conio.h>
#include <fcntl.h>
#include <io.h>
#include "log.h"
#include "video.h"
#endif

#include "asm.h"
#include "mon.h"
#include "charsets.h"
#include "file.h"
#include "misc.h"
#include "mshell.h"
#include "interrupt.h"
#include "resources.h"
#include "vsync.h"
#include "drive.h"
#include "mon_parse.h"
#include "utils.h"

/* Defines */

#define HASH_ARRAY_SIZE 256
#define HASH_ADDR(x) ((x)%0xff)
#define OP_JSR 0x20
#define OP_RTI 0x40
#define OP_RTS 0x60

#define TEST(x) ((x)!=0)
#define ADDR_LIMIT(x) (LO16(x))
#define BAD_ADDR (new_addr(e_invalid_space, 0))

#define GET_OPCODE(mem) (get_mem_val(mem, mon_get_reg_val(mem, e_PC)))

FILE *mon_output, *mon_input;

/* External functions */

extern void parse_and_execute_line(char *input);

#ifdef HAVE_READLINE
extern char *readline ( const char *prompt );
extern void add_history ( const char *str );
#else
static char *readline(const char *prompt)
{
    char *p = xmalloc(1024);

    /* Yeah, this sucks, but you have readline anyway, don't you?  ;-) */
    fputs(prompt, mon_output);
    fgets(p, 1024, mon_input);

    /* Remove trailing newlines.  */
    {
        int len;

        for (len = strlen(p);
             len > 0 && (p[len - 1] == '\r'
                         || p[len - 1] == '\n');
             len--)
            p[len - 1] = '\0';
    }

    return p;
}

static void add_history(const char *str)
{
    return;
}
#endif

/* Types */

struct symbol_entry {
   ADDRESS addr;
   char *name;
   struct symbol_entry *next;
};
typedef struct symbol_entry symbol_entry_t;

struct symbol_table {
   symbol_entry_t *name_list;
   symbol_entry_t *addr_hash_table[HASH_ARRAY_SIZE];
};
typedef struct symbol_table symbol_table_t;

/* Global variables */

static monitor_interface_t *maincpu_interface;
static monitor_interface_t *true1541_interface;

/* Defined in file generated by bison. Set to 1 to get
 * parsing debug information. */
extern int yydebug;

static char *myinput = NULL;
static char *last_cmd = NULL;
int exit_mon = 0;

int sidefx;
RADIXTYPE default_radix;
MEMSPACE default_memspace;
static bool inside_monitor = FALSE;
static unsigned instruction_count;
static bool skip_jsrs;
static int wait_for_return_level;
BREAK_LIST *breakpoints[NUM_MEMSPACES];
BREAK_LIST *watchpoints_load[NUM_MEMSPACES];
BREAK_LIST *watchpoints_store[NUM_MEMSPACES];
static monitor_interface_t *mon_interfaces[NUM_MEMSPACES];
MEMSPACE caller_space;

const char *_mon_space_strings[] = {
    "Default", "Computer", "Disk", "<<Invalid>>"
};

static ADDRESS watch_load_array[10][NUM_MEMSPACES];
static ADDRESS watch_store_array[10][NUM_MEMSPACES];
static unsigned watch_load_count[NUM_MEMSPACES];
static unsigned watch_store_count[NUM_MEMSPACES];
static bool force_array[NUM_MEMSPACES];
static symbol_table_t monitor_labels[NUM_MEMSPACES];

static MON_ADDR dot_addr[NUM_MEMSPACES];
static int breakpoint_count;
static unsigned char data_buf[256];
static unsigned data_buf_len;
bool asm_mode;
static MON_ADDR asm_mode_addr;
static unsigned next_or_step_stop;
unsigned mon_mask[NUM_MEMSPACES];

static bool watch_load_occurred;
static bool watch_store_occurred;

static bool recording;
static FILE *recording_fp;
static char *recording_name;
bool playback;
char *playback_name;

struct mon_cmds mon_cmd_array[] = {
   { "",		"",	BAD_CMD,		STATE_INITIAL },

   { "~", 		"", 	CONVERT_OP, 		STATE_INITIAL,
     "<number>",
     "Display the specified number in decimal, hex, octal and binary."},

   { ">", 		"", 	CMD_ENTER_DATA, 	STATE_INITIAL,
     "[<address>] <data_list>",
     "Write the specified data at `address'."},

   { "@", 		"", 	CMD_DISK, 		STATE_ROL,
   "<disk command>",
     "Perform a disk command on the currently attached disk image on drive 8.  The\n"
     "specified disk command is sent to the drive's channel #15." },

   { "]", 		"", 	CMD_ENTER_BIN_DATA, 	STATE_INITIAL },

   { "a", 		"", 	CMD_ASSEMBLE, 		STATE_INITIAL,
     "<address> [ <instruction> [: <instruction>]* ]",
     "Assemble instructions to the specified address.  If only one instruction\n"
     "is specified, enter assembly mode (enter an empty line to exit assembly\n"
     "mode)." },

   { "add_label", 	"al", 	CMD_ADD_LABEL, 		STATE_INITIAL,
    "<address> <label>",
     "Map a given address to a label.  This label can be used when entering\n"
     "assembly code and is shown during disassembly." },

   { "bank",		"",	CMD_BANK,		STATE_INITIAL },

   { "br", 		"", 	CMD_BLOCK_READ, 	STATE_INITIAL,
     "<track> <sector> [<address>]",
     "Read the block at the specified track and sector.  If an address is\n"
     "specified, the data is loaded into memory.  If no address is given, the\n"
     "data is displayed using the default datatype." },

   { "break", 		"", 	CMD_BREAK, 		STATE_INITIAL,
     "[<address> [if <cond_expr>] ]",
     "If no address is given, the currently valid watchpoints are printed.\n"
     "If an address is given, a breakpoint is set for that address and the\n"
     "breakpoint number is printed.  A conditional expression can also be\n"
     "specified for the breakpoint.  For more information on conditions, see\n"
     "the CONDITION command."   },

   { "brmon", 		"", 	CMD_BRMON, 		STATE_INITIAL },

   { "bw", 		"", 	CMD_BLOCK_WRITE, 	STATE_INITIAL,
     "<track> <sector> <address>",
     "Write a block of data at `address' on the specified track and sector\n"
     "of disk in drive 8." },

   { "cd", 		"", 	CMD_CHDIR, 		STATE_ROL,
     "<directory>",
     "Change the working directory."},

   { "command", 	"", 	CMD_COMMAND, 		STATE_INITIAL,
     "<checknum> \"<command>\"",
     "Specify `command' as the command to execute when checkpoint `checknum'\n"
     "is hit.  Note that the `x' command is not yet supported as a\n"
     "command argument." },

   { "compare", 	"c", 	CMD_COMPARE, 		STATE_INITIAL,
     "<address_range> <address>",
     "Compare memory from the source specified by the address range to the\n"
     "destination specified by the address.  The regions may overlap.  Any\n"
     "values that miscompare are displayed using the default displaytype.\n" },

   { "condition", 	"cond", CMD_CONDITION, 		STATE_INITIAL,
     "<checknum> if <cond_expr>",
     "Each time the specified checkpoint is examined, the condition is\n"
     "evaluated.  If it evalutes to true, the checkpoint is activated.\n"
     "Otherwise, it is ignores.  If registers are specified in the expression,\n"
     "the values used are those at the time the checkpoint is examined, not\n"
     "when the condition is set.\n" },

   { "d", 		"", 	CMD_DISASSEMBLE, 	STATE_INITIAL,
     "[<address> [<address>]]",
     "Disassemble instructions.  If two addresses are specified, they are used\n"
     "as a start and end address.  If only one is specified, it is treated as\n"
     "the start address and a default number of instructions are\n"
     "disassembled.  If no addresses are specified, a default number of\n"
     "instructions are disassembled from the dot address." },

   { "delete", 		"del", 	CMD_DELETE, 		STATE_INITIAL,
     "<checknum>",
     "Delete checkpoint `checknum'." },

   { "delete_label", 	"dl", 	CMD_DEL_LABEL, 		STATE_INITIAL },

   { "device", 		"dev", 	CMD_DEVICE, 		STATE_INITIAL,
     "[c:|d:]",
     "Set the default memory device to either the computer `c:' or the\n"
     "disk (`d:')." },

   { "disable", 	"", 	CMD_CHECKPT_ONOFF, 	STATE_INITIAL,
     "<checknum>",
     "Disable checkpoint `checknum'." },

   { "down", 		"", 	CMD_DOWN, 		STATE_INITIAL },

   { "dump", 		"", 	CMD_DUMP, 		STATE_INITIAL },

   { "enable", 		"", 	CMD_CHECKPT_ONOFF, 	STATE_INITIAL,
     "<checknum>",
     "Enable checkpoint `checknum'." },

   { "exit", 		"x", 	CMD_EXIT, 		STATE_INITIAL,
     NULL,
     "Leave the monitor and return to execution." },

   { "fill", 		"f", 	CMD_FILL, 		STATE_INITIAL,
     "<address_range> <data_list>",
     "Fill memory in the specified address range with the data in\n"
     "<data_list>.  If the size of the address range is greater than the size\n"
     "of the data_list, the data_list is repeated." },

   { "goto", 		"g", 	CMD_GOTO, 		STATE_INITIAL,
     "<address>",
     "Change the PC to ADDRESS and continue execution" },

   { "help", 		"?", 	CMD_HELP, 		STATE_ROL },

   { "hunt", 		"h", 	CMD_HUNT, 		STATE_INITIAL,
     "<address_range> <data_list>",
     "Hunt memory in the specified address range for the data in\n"
     "<data_list>.  If the data is found, the starting address of the match is\n"
     "displayed.  The entire range is searched for all possible matches." },

   { "i", 		"", 	CMD_TEXT_DISPLAY, 	STATE_INITIAL,
     "<address_opt_range>",
     "Display memory contents as PETSCII text." },

   { "ignore", 		"", 	CMD_IGNORE, 		STATE_INITIAL,
     "<checknum> [<count>]",
     "Ignore a checkpoint a given number of crossings.  If no count is given,\n"
     "the default value is 1.\n" },

   { "io", 		"", 	CMD_IO, 		STATE_INITIAL },

   { "load", 		"l", 	CMD_LOAD, 		STATE_FNAME,
     "\"<filename>\" <address>",
     "Load the specified file into memory at the specified address."},

   { "load_labels", 	"ll", 	CMD_LOAD_LABELS, 	STATE_FNAME,
     "[<memspace>] \"<filename>\"",
     "Load a file containing a mapping of labels to addresses.  If no memory\n"
     "space is specified, the default readspace is used." },

   { "m", 		"", 	CMD_MEM_DISPLAY, 	STATE_INITIAL,
     "[<data_type>] [<address_opt_range>]"
     "Display the contents of memory.  If no datatype is given, the default is\n"
     "used.  If only one address is specified, the length of data displayed is\n"
     "based on the datatype.  If no addresses are given, the 'dot' address is\n"
     "used." },

   { "mc", 		"", 	CMD_CHAR_DISPLAY, 	STATE_INITIAL,
     "[<data_type>] [<address_opt_range>]",
     "Display the contents of memory as character data.  If only one address\n"
     "is specified, only one character is displayed.  If no addresses are\n"
     "given, the ``dot'' address is used." },

   { "move", 		"t", 	CMD_MOVE, 		STATE_INITIAL,
     "<address_range> <address>",
     "Move memory from the source specified by the address range to\n"
     "the destination specified by the address.  The regions may overlap." },

   { "ms", 		"", 	CMD_SPRITE_DISPLAY, 	STATE_INITIAL,
     "[<data_type>] [<address_opt_range>]",
     "Display the contents of memory as sprite data.  If only one address is\n"
     "specified, only one sprite is displayed.  If no addresses are given, the\n"
     "``dot'' address is used." },

   { "next", 		"n", 	CMD_NEXT, 		STATE_INITIAL,
     NULL,
     "Advance to the next instruction.  Subroutines are treated as\n"
     "a single instruction." },

   { "playback", 	"pb", 	CMD_PLAYBACK, 		STATE_FNAME,
     "\"<filename>\"",
     "Monitor commands from the specified file are read and executed.  This\n"
     "command stops at the end of file or when a STOP command is read." },

   { "print", 		"p", 	CMD_PRINT, 		STATE_INITIAL,
     "<expression>",
     "Evaluate the specified expression and output the result." },

   { "quit", 		"", 	CMD_QUIT, 		STATE_INITIAL,
     NULL,
     "Exit the emulator immediately."},

   { "radix", 		"rad", 	CMD_RADIX, 		STATE_INITIAL,
     "[H|D|O|B]",
     "Set the default radix to hex, decimal, octal, or binary.  With no\n"
     "argument, the current radix is printed." },

   { "record", 		"rec", 	CMD_RECORD, 		STATE_FNAME,
     "\"<filename>\"",
     "After this command, all commands entered are written to the specified\n"
     "file until the STOP command is entered." },

   { "registers", 	"r", 	CMD_REGISTERS, 		STATE_REG_ASGN,
     "[<reg_name> = <number> [, <reg_name> = <number>]*]",
     "Assign respective registers.  With no parameters, display register\n"
     "values." },

   { "return", 		"ret", 	CMD_RETURN, 		STATE_INITIAL,
     NULL,
     "Continues execution and returns to the monitor just before the next\n"
     "RTS or RTI is executed." },

   { "save", 		"s", 	CMD_SAVE, 		STATE_FNAME,
     "\"<filename>\" <address1> <address2>",
     "Save the memory from address1 to address2 to the specified file." },

   { "save_labels", 	"sl", 	CMD_SAVE_LABELS, 	STATE_FNAME,
     "[<memspace>] \"<filename>\"",
     "Save labels to a file.  If no memory space is specified, all of the\n"
     "labels are saved." },

   { "show_labels", 	"shl", 	CMD_SHOW_LABELS, 	STATE_INITIAL,
     "[<memspace>]",
     "Display current label mappings.  If no memory space is specified, show\n"
     "all labels." },

   { "sidefx", 		"sfx", 	CMD_SIDEFX, 		STATE_INITIAL,
     "[on|off|toggle]",
     "Control how monitor generated reads affect memory locations that have\n"
     "read side-effects.  If the argument is 'on' then reads may cause\n"
     "side-effects.  If the argument is 'off' then reads don't cause\n"
     "side-effects.  If the argument is 'toggle' then the current mode is\n"
     "switched.  No argument displays the current state." },

   { "step", 		"z", 	CMD_STEP, 		STATE_INITIAL,
     "[<count>]",
     "Single-step through instructions.  COUNT allows stepping\n"
     "more than a single instruction at a time." },

   { "stop", 		"", 	CMD_STOP, 		STATE_INITIAL,
     NULL,
     "Stop recording commands.  See `record'." },

   { "system", 		"sys", 	CMD_SYSTEM, 		STATE_ROL },

   { "trace", 		"tr", 	CMD_TRACE, 		STATE_INITIAL,
     "[address [address]]",
     "Set a tracepoint.  If a single address is specified, set a tracepoint\n"
     "for that address.  If two addresses are specified, set a tracepoint\n"
     "for the memory locations between the two addresses." },

   { "undump", 		"", 	CMD_UNDUMP, 		STATE_INITIAL },

   { "up", 		"", 	CMD_UP, 		STATE_INITIAL },

   { "verify", 		"v", 	CMD_VERIFY, 		STATE_FNAME },

   { "watch", 		"w", 	CMD_WATCH, 		STATE_INITIAL,
     "[address [address]]",
     "Set a watchpoint.  If a single address is specified, set a watchpoint\n"
     "for that address.  If two addresses are specified, set a watchpoint\n"
     "for the memory locations between the two addresses." },

   { "", 		"", 	-1, 			STATE_INITIAL }

};

static char *cond_op_string[] = { "",
                                  "==",
                                  "!=",
                                  ">",
                                  "<",
                                  ">=",
                                  "<=",
                                  "&&",
                                  "||"
                                 };


static int default_display_number[] = {128, /* default = hex */
                                       128, /* hexadecimal */
                                       80, /* decimal */
                                       40, /* octal */
                                       24, /* binary */
                                      };

static int default_display_per_line[] = { 16, /* default = hex */
                                          16, /* hexadecimal */
                                          8, /* decimal */
                                          8, /* octal */
                                          3, /* binary */
                                        };

static char *memspace_string[] = {"default", "C", "8" };

static char *register_string[] = { "A",
                                   "X",
                                   "Y",
                                   "PC",
                                   "SP"
                                 };


/* *** ADDRESS FUNCTIONS *** */


static void set_addr_memspace(MON_ADDR *a, MEMSPACE m) { *a = LO16(*a) | LO16_TO_HI16(m); }
static void set_addr_location(MON_ADDR *a, unsigned l) { *a = HI16(*a) | LO16(l); }
static bool is_valid_addr(MON_ADDR a) { return HI16_TO_LO16(a) != e_invalid_space; }

static bool inc_addr_location(MON_ADDR *a, unsigned inc)
{
   unsigned new_loc = LO16(*a) + inc;
   *a = HI16(*a) | LO16(new_loc);

   return !(new_loc == LO16(new_loc));
}

static void evaluate_default_addr(MON_ADDR *a)
{
   if (addr_memspace(*a) == e_default_space)
      set_addr_memspace(a,default_memspace);
}

static bool is_in_range(MON_ADDR start_addr, MON_ADDR end_addr, unsigned loc)
{
   unsigned start, end;

   start = addr_location(start_addr);

   if (!is_valid_addr(end_addr))
      return (loc == start);

   end = addr_location(end_addr);

   if (end < start)
      return ((loc>=start) || (loc<=end));

   return ((loc>=start) && (loc<=end));
}

static bool is_valid_addr_range(MON_ADDR start_addr, MON_ADDR end_addr)
{
   if (addr_memspace(start_addr) == e_invalid_space)
      return FALSE;

   if ((addr_memspace(start_addr) != addr_memspace(end_addr)) &&
       ((addr_memspace(start_addr) != e_default_space) ||
        (addr_memspace(end_addr) != e_default_space))) {
      return FALSE;
   }
   return TRUE;
}

static unsigned get_range_len(MON_ADDR addr1, MON_ADDR addr2)
{
   ADDRESS start, end;
   unsigned len = 0;

   start = addr_location(addr1);
   end = addr_location(addr2);

   if (start <= end) {
      len = end - start + 1;
   } else {
      len = (0xffff - start) + end + 1;
   }

   return len;
}

static long evaluate_address_range(MON_ADDR *start_addr, MON_ADDR *end_addr, bool must_be_range,
                                   WORD default_len)
{
   MEMSPACE mem1, mem2;
   long len = default_len;

   /* Check if we DEFINITELY need a range. */
   if (!is_valid_addr_range(*start_addr, *end_addr) && must_be_range)
      return -1;

   if (is_valid_addr_range(*start_addr, *end_addr)) {
      /* Resolve any default memory spaces. We wait until now because we
       * need both addresses - if only 1 is a default, use the other to
       * resolve the memory space.
       */
      mem1 = addr_memspace(*start_addr);
      mem2 = addr_memspace(*end_addr);

      if (mem1 == e_default_space) {
         if (mem2 == e_default_space) {
            set_addr_memspace(start_addr,default_memspace);
            set_addr_memspace(end_addr,default_memspace);
         } else if (mem2 != e_invalid_space) {
            set_addr_memspace(start_addr,mem2);
         } else {
            set_addr_memspace(start_addr,default_memspace);
         }
      } else {
         if (mem2 == e_default_space) {
            set_addr_memspace(end_addr,mem1);
         } else if (mem2 != e_invalid_space) {
            assert(mem1 == mem2);
         } else
            assert(FALSE);
      }

      len = get_range_len(*start_addr, *end_addr);
   } else {
      if (!is_valid_addr(*start_addr))
         *start_addr = dot_addr[(int) default_memspace];
      else
         evaluate_default_addr(start_addr);

      assert(!is_valid_addr(*end_addr));
      *end_addr = *start_addr;
      inc_addr_location(end_addr, len);
   }

   return len;
}


/* *** REGISTER AND MEMORY OPERATIONS *** */


static bool check_drive_emu_level_ok(int drive_num)
{
    if (true1541_interface == NULL) {
        fprintf(mon_output, "True1541 emulation not supported for this machine.\n");
        return FALSE;
    }

/* FIXME: IMO, the user should be allowed to access the true 1541 emulation
   even when it's (temporarily?) disabled.
   - but this may confuse the user since they might think the results are valid
   with respect to what is currently being emulated.
*/
#if 0
    else if (!app_resources.true1541) {
        fprintf(mon_output, "True1541 emulation is not turned on.\n");
        return FALSE;
    }
#endif

   return TRUE;
}

static unsigned char get_mem_val(MEMSPACE mem, unsigned mem_addr)
{
   if (mem == e_disk_space) {
       if (!check_drive_emu_level_ok(8))
           return 0;
   }

   return mon_interfaces[mem]->read_func(mem_addr);
}

static void set_mem_val(MEMSPACE mem, unsigned mem_addr, unsigned char val)
{
   if (mem == e_comp_space) {
   } else if (mem == e_disk_space) {
       if (!check_drive_emu_level_ok(8))
           return;
   } else
       assert(FALSE);

   mon_interfaces[mem]->store_func(mem_addr, val);
}

unsigned int mon_get_reg_val(MEMSPACE mem, REG_ID reg_id)
{
    mos6510_regs_t *reg_ptr = NULL;

    if (mem == e_disk_space) {
        if (!check_drive_emu_level_ok(8))
            return 0;
    }

    reg_ptr = mon_interfaces[mem]->cpu_regs;

    switch(reg_id) {
      case e_A:
        return MOS6510_REGS_GET_A(reg_ptr);
      case e_X:
        return MOS6510_REGS_GET_X(reg_ptr);
      case e_Y:
        return MOS6510_REGS_GET_Y(reg_ptr);
      case e_PC:
        return MOS6510_REGS_GET_PC(reg_ptr);
      case e_SP:
        return MOS6510_REGS_GET_SP(reg_ptr);
      default:
        assert(FALSE);
    }
    return 0;
}

void mon_set_reg_val(MEMSPACE mem, REG_ID reg_id, WORD val)
{
    mos6510_regs_t *reg_ptr = NULL;

    if (mem == e_disk_space) {
        if (!check_drive_emu_level_ok(8))
            return;
    }

    reg_ptr = mon_interfaces[mem]->cpu_regs;

    switch(reg_id) {
      case e_A:
        MOS6510_REGS_SET_A(reg_ptr, val);
        break;
      case e_X:
        MOS6510_REGS_SET_X(reg_ptr, val);
        break;
      case e_Y:
        MOS6510_REGS_SET_Y(reg_ptr, val);
        break;
      case e_PC:
        MOS6510_REGS_SET_PC(reg_ptr, val);
        break;
      case e_SP:
        MOS6510_REGS_SET_SP(reg_ptr, val);
        break;
      default:
        assert(FALSE);
    }
    force_array[mem] = TRUE;
}

void mon_print_registers(MEMSPACE mem)
{
    mos6510_regs_t *regs;

    if (mem == e_disk_space) {
        if (!check_drive_emu_level_ok(8))
            return;
    } else if (mem != e_comp_space)
        assert(FALSE);

    regs = mon_interfaces[mem]->cpu_regs;
    fprintf(mon_output, "  ADDR AR XR YP SP 01 NV-BDIZC\n");
    fprintf(mon_output, ".;%04x %02x %02x %02x %02x %02x %d%d%c%d%d%d%d%d\n",
            mon_get_reg_val(mem, e_PC),
            mon_get_reg_val(mem, e_A),
            mon_get_reg_val(mem, e_X),
            mon_get_reg_val(mem, e_Y),
            mon_get_reg_val(mem, e_SP),
            get_mem_val(mem, 1),
            TEST(MOS6510_REGS_GET_SIGN(regs)),
            TEST(MOS6510_REGS_GET_OVERFLOW(regs)),
            '1',
            TEST(MOS6510_REGS_GET_BREAK(regs)),
            TEST(MOS6510_REGS_GET_DECIMAL(regs)),
            TEST(MOS6510_REGS_GET_INTERRUPT(regs)),
            TEST(MOS6510_REGS_GET_ZERO(regs)),
            TEST(MOS6510_REGS_GET_CARRY(regs)));
}

void mon_jump(MON_ADDR addr)
{
   evaluate_default_addr(&addr);
   mon_set_reg_val(addr_memspace(addr), e_PC, addr_location(addr));
   exit_mon = 1;
}


/* *** ULTILITY FUNCTIONS *** */

static void print_bin(int val, char on, char off)
{
   int divisor;
   char digit;

   if (val > 4095)
      divisor = 32768;
   else if (val > 255)
      divisor = 2048;
   else
      divisor = 128;

   while (divisor) {
      digit = (val & divisor) ? on : off;
      fprintf(mon_output, "%c",digit);
      if (divisor == 256)
         fprintf(mon_output, " ");
      divisor /= 2;
   }
}

static void print_hex(int val)
{
   if (val > 255)
      fprintf(mon_output, "$%04x\n",val);
   else
      fprintf(mon_output, "$%02x\n",val);
}

static void print_octal(int val)
{
   if (val > 511)
      fprintf(mon_output, "0%06o\n",val);
   else
      fprintf(mon_output, "0%03o\n",val);
}


void mon_print_convert(int val)
{
   fprintf(mon_output, "+%d\n",val);
   print_hex(val);
   print_octal(val);
   print_bin(val,'1','0'); fprintf(mon_output, "\n");
}

void mon_add_number_to_buffer(int number)
{
   data_buf[data_buf_len++] = (number & 0xff);
   if (number > 0xff)
      data_buf[data_buf_len++] = ( (number>>8) & 0xff);
   data_buf[data_buf_len] = '\0';
}

void mon_add_string_to_buffer(char *str)
{
    printf("%s(`%s')\n", __FUNCTION__, str);
   strcpy(&(data_buf[data_buf_len]), str);
   data_buf_len += strlen(str);
   data_buf[data_buf_len] = '\0';
   free(str);
}

static void clear_buffer(void)
{
   data_buf_len = 0;
}

static void memory_to_string(char *buf, MEMSPACE mem, ADDRESS addr, unsigned len, bool petscii)
{
    int i, val;

    for (i=0;i<len;i++) {
       val = get_mem_val(mem, addr);

       if (petscii)
          buf[i] = p_toascii(val,0);
       if (isprint(val))
          buf[i] = val;
       else
          buf[i] = '.';

       addr++;
    }
}


/* *** MISC COMMANDS *** */


void monitor_init(monitor_interface_t *maincpu_interface_init,
                  monitor_interface_t *true1541_interface_init)
{
   int i, j;

   yydebug = 0;
   sidefx = e_OFF;
   default_radix = e_hexadecimal;
   default_memspace = e_comp_space;
   instruction_count = 0;
   skip_jsrs = FALSE;
   wait_for_return_level = 0;
   breakpoint_count = 1;
   data_buf_len = 0;
   asm_mode = 0;
   next_or_step_stop = 0;
   recording = FALSE;
   playback = FALSE;

   watch_load_occurred = FALSE;
   watch_store_occurred = FALSE;

   for (i=1;i<NUM_MEMSPACES;i++) {
      dot_addr[i] = new_addr(e_default_space + i, 0);
      watch_load_count[i] = 0;
      watch_store_count[i] = 0;
      mon_mask[i] = MI_NONE;
      monitor_labels[i].name_list = NULL;
      for (j=0;j<HASH_ARRAY_SIZE;j++)
         monitor_labels[i].addr_hash_table[j] = NULL;
   }

   caller_space = e_comp_space;

   asm_mode_addr = BAD_ADDR;

   maincpu_interface = maincpu_interface_init;
   true1541_interface = true1541_interface_init;

   mon_interfaces[e_comp_space] = maincpu_interface;
   mon_interfaces[e_disk_space] = true1541_interface;
}

int mon_cmd_lookup_index(char *str)
{
   int num = 0;

   if (str == NULL)
      return -1;

   do {
      if ((strcasecmp(str, mon_cmd_array[num].str) == 0) ||
          (strcasecmp(str, mon_cmd_array[num].abbrev) == 0))
         return num;
      num++;
   } while(mon_cmd_array[num].token > 0);

   return -1;
}

int mon_cmd_get_token(int index)
{
   return mon_cmd_array[index].token;
}

int mon_cmd_get_next_state(int index)
{
   return mon_cmd_array[index].next_state;
}

void mon_print_help(char *cmd)
{
   if (cmd == NULL) {
       struct mon_cmds *c;
       int column = 0;

       /* Print on two columns.  This could be a lot nicer, but I am lazy.  */
       fprintf(mon_output, "\nAvailable commands are:\n\n");
       for (c = mon_cmd_array; c->token != -1; c++) {
           int tot = 0;

           tot += strlen(c->str);
           if (tot == 0)        /* "Empty" command?  */
               continue;
           fputs(c->str, mon_output);

           if (c->abbrev != NULL && c->abbrev[0] != '\0') {
               fprintf(mon_output, " (%s)", c->abbrev);
               tot += 3 + strlen(c->abbrev);
           }

           if (tot > 40 || column == 1) {
               fputc('\n', mon_output);
               column = 0;
           } else {
               for (; tot < 40; tot++)
                   fputc(' ', mon_output);
               column = 1;
           }
       }
       fputs("\n\n", mon_output);
   } else {
       int cmd_num = mon_cmd_lookup_index(cmd);

       if (cmd_num == -1)
           fprintf(mon_output, "Command `%s' unknown.\n", cmd);
       else if (mon_cmd_array[cmd_num].description == NULL)
           fprintf(mon_output, "No help available for `%s'\n",
                   cmd);
       else {
           struct mon_cmds *c = &mon_cmd_array[cmd_num];

           fprintf(mon_output, "\nSyntax: %s %s\n",
                   c->str,
                   c->param_names != NULL ? c->param_names : "");
           if (c->abbrev != NULL && c->abbrev[0] != '\0')
               fprintf(mon_output, "Abbreviation: %s\n", c->abbrev);
           fprintf(mon_output, "\n%s\n\n", c->description);
       }
   }
}


/* *** ASSEMBLY/DISASSEMBLY *** */


void mon_start_assemble_mode(MON_ADDR addr, char *asm_line)
{
   asm_mode = 1;

   evaluate_default_addr(&addr);
   asm_mode_addr = addr;
}

int mon_assemble_instr(char *opcode_name, unsigned operand)
{
   WORD operand_value = LO16(operand);
   WORD operand_mode = HI16_TO_LO16(operand);
   BYTE opcode = 0;
   int i, len, branch_offset;
   bool found = FALSE;
   MEMSPACE mem;
   unsigned loc;

   mem = addr_memspace(asm_mode_addr);
   loc = addr_location(asm_mode_addr);

   /* FIXME (???) : It is impossible to specify absolute mode if the
    * address < 0x100 and there is a zero page mode available.
    */
   for (i=0;i<=0xff;i++) {
      if (!strcasecmp(lookup[i].mnemonic, opcode_name)) {
         if (lookup[i].addr_mode == operand_mode) {
            opcode = i;
            found = TRUE;
            break;
         }

         /* Special case: Register A not specified for ACCUMULATOR mode. */
         if ( (operand_mode == IMPLIED) && (lookup[i].addr_mode == ACCUMULATOR) ) {
            opcode = i;
            operand_mode = ACCUMULATOR;
            found = TRUE;
            break;
         }

         /* Special case: RELATIVE mode looks like ZERO_PAGE or ABSOLUTE modes */
         if ( ((operand_mode == ZERO_PAGE) || (operand_mode == ABSOLUTE)) &&
              (lookup[i].addr_mode == RELATIVE)) {
            branch_offset = operand_value - loc - 2;
            if ( (branch_offset > 127) || (branch_offset < -128) ) {
               fprintf(mon_output, "Branch offset too large\n");
               return -1;
            }
            operand_value = (branch_offset & 0xff);
            operand_mode = RELATIVE;
            opcode = i;
            found = TRUE;
            break;
         }

         /* Special case: opcode A - is A a register or $A? */
         /*   If second case, is it zero page or absolute?  */
         if (operand_mode == ACCUMULATOR && (lookup[i].addr_mode == ZERO_PAGE)) {
            opcode = i;
            operand_mode = ZERO_PAGE;
            operand_value = 0x000a;
            found = TRUE;
            break;
         }
         /* It's safe to assume ABSOULTE if ZERO_PAGE not yet found since ZERO_PAGE
          * versions always precede ABSOLUTE versions if they exist.
          */
         if (operand_mode == ACCUMULATOR && (lookup[i].addr_mode == ABSOLUTE)) {
            opcode = i;
            operand_mode = ABSOLUTE;
            operand_value = 0x000a;
            found = TRUE;
            break;
         }

      }
   }

   if (!found) {
      fprintf(mon_output, "Instruction not valid.\n");
      return -1;
   }

   len = clength[operand_mode];

#if 0
   ram[loc] = opcode;
   if (len >= 2)
      ram[loc+1] = operand_value & 0xff;
   if (len >= 3)
      ram[loc+2] = (operand_value >> 8) & 0xff;
#else
   /* EP 98.08.23 use correct memspace for assembling.  */
   set_mem_val(mem, loc, opcode);
   if (len >= 2)
      set_mem_val(mem, loc + 1, operand_value & 0xff);
   if (len >= 3)
      set_mem_val(mem, loc + 2, (operand_value >> 8) & 0xff);
#endif

   if (len >= 0) {
      inc_addr_location(&asm_mode_addr, len);
      dot_addr[mem] = asm_mode_addr;
   } else {
      fprintf(mon_output, "Assemble error: %d\n",len);
   }
   return len;
}

static unsigned disassemble_instr(MON_ADDR addr)
{
   BYTE op, p1, p2;
   MEMSPACE mem;
   unsigned loc, mode;

   mem = addr_memspace(addr);
   loc = addr_location(addr);

   op = get_mem_val(mem, loc);
   p1 = get_mem_val(mem, loc+1);
   p2 = get_mem_val(mem, loc+2);

   /* sprint_disassembled() only supports hex and decimal.
    * Unless the default radix is decimal, we default
    * to hex.
    */
   mode = (default_radix == e_decimal) ? 0 : MODE_HEX;

   fprintf(mon_output, ".%s:%04x   %s\n",memspace_string[mem],loc,
           sprint_disassembled(loc, op, p1, p2, mode));
   return clength[lookup[op].addr_mode];
}

#if 0
void mon_disassemble_lines(MON_ADDR start_addr, MON_ADDR end_addr)
{
  long len;

  len = evaluate_address_range(&start_addr, &end_addr, FALSE, -1);
  if (len < 0) {
     fprintf(mon_output, "Invalid range.\n");
     return;
  }
  printf("len:%d (%s:%04x) (%s:%04x)\n",len,memspace_string[addr_memspace(start_addr)],addr_location(start_addr),
         memspace_string[addr_memspace(end_addr)], addr_location(end_addr));
}
#else
void mon_disassemble_lines(MON_ADDR start_addr, MON_ADDR end_addr)
{
   MEMSPACE mem;
   unsigned end_loc;
   long len, i, bytes;

   len = evaluate_address_range(&start_addr, &end_addr, FALSE, DEFAULT_DISASSEMBLY_SIZE);
   assert(len >= 0);

   mem = addr_memspace(start_addr);
   dot_addr[mem] = start_addr;
   end_loc = addr_location(end_addr);

   i = 0;
   while (i <= len) {
      bytes = disassemble_instr(dot_addr[mem]);
      i += bytes;
      inc_addr_location(&(dot_addr[mem]), bytes);
   }
}
#endif


/* *** MEMORY COMMANDS *** */


void mon_display_data(MON_ADDR start_addr, MON_ADDR end_addr, int x, int y)
{
   unsigned i,j,len,cnt=0;
   ADDRESS addr=0;
   MEMSPACE mem;

   len = evaluate_address_range(&start_addr, &end_addr, FALSE, (x*y)/8);
   mem = addr_memspace(start_addr);
   addr = addr_location(start_addr);

   while (cnt < len) {
      for(i=0;i<y;i++) {
         fprintf(mon_output, ">%s:%04x ",memspace_string[mem],addr);
         for(j=0;j<(x/8);j++) {
            print_bin(get_mem_val(mem,ADDR_LIMIT(addr+j)),'.','*');
            cnt++;
         }
         fprintf(mon_output, "\n");
         addr = ADDR_LIMIT(addr + (x/8));
      }

      fprintf(mon_output, "\n");
   }

   set_addr_location(&(dot_addr[mem]),addr);
}

void mon_display_memory(int radix_type, MON_ADDR start_addr, MON_ADDR end_addr)
{
   unsigned i, cnt=0, len, max_width, real_width;
   ADDRESS addr=0;
   char printables[50];
   MEMSPACE mem;

   len = evaluate_address_range(&start_addr, &end_addr, FALSE, default_display_number[radix_type]);
   mem = addr_memspace(start_addr);
   addr = addr_location(start_addr);

   if (radix_type)
      max_width = default_display_per_line[radix_type];
   else
      max_width = 40;

   while (cnt < len) {
      fprintf(mon_output, ">%s:%04x ",memspace_string[mem],addr);
      for (i=0,real_width=0;i<max_width;i++) {
         switch(radix_type) {
            case 0: /* special case == petscii text */
               fprintf(mon_output, "%c",p_toascii(get_mem_val(mem,ADDR_LIMIT(addr+i)),0));
               real_width++;
               cnt++;
               break;
            case e_decimal:
               memset(printables,0,50);
               if (cnt < len) {
                  fprintf(mon_output, "%3d ",get_mem_val(mem,ADDR_LIMIT(addr+i)));
                  real_width++;
                  cnt++;
               }
               else
                  fprintf(mon_output, "    ");
               break;
            case e_hexadecimal:
               memset(printables,0,50);
               if (cnt < len) {
		  if(!(cnt%4)) fprintf(mon_output, " ");
                  fprintf(mon_output, "%02x ",get_mem_val(mem,ADDR_LIMIT(addr+i)));
                  real_width++;
                  cnt++;
               }
               else
                  fprintf(mon_output, "   ");
               break;
            case e_octal:
               memset(printables,0,50);
               if (cnt < len) {
                  fprintf(mon_output, "%03o ",get_mem_val(mem,ADDR_LIMIT(addr+i)));
                  real_width++;
                  cnt++;
               }
               else
                  fprintf(mon_output, "    ");
               break;
            case e_binary:
               memset(printables,0,50);
               if (cnt < len) {
                  print_bin(get_mem_val(mem,ADDR_LIMIT(addr+i)),'1','0');
                  fprintf(mon_output, " ");
                  real_width++;
                  cnt++;
               }
               else
                  fprintf(mon_output, "         ");
               break;
            default:
               assert(FALSE);
         }

      }

      if (radix_type != 0) {
         memory_to_string(printables, mem, addr, real_width, FALSE);
         fprintf(mon_output, "  %s",printables);
      }
      fprintf(mon_output, "\n");
      addr = ADDR_LIMIT(addr+real_width);
   }

   set_addr_location(&(dot_addr[mem]),addr);
}


void mon_move_memory(MON_ADDR start_addr, MON_ADDR end_addr, MON_ADDR dest)
{
  unsigned i, len, dst;
  ADDRESS start;
  MEMSPACE src_mem, dest_mem;
  BYTE *buf;

  len = evaluate_address_range(&start_addr, &end_addr, TRUE, -1);
  if (len < 0) {
     fprintf(mon_output, "Invalid range.\n");
     return;
  }
  src_mem = addr_memspace(start_addr);
  start = addr_location(start_addr);

  evaluate_default_addr(&dest);
  dst = addr_location(dest);
  dest_mem = addr_memspace(dest);

  buf = (BYTE *) xmalloc(sizeof(BYTE) * len);

  for (i=0; i<len; i++)
     buf[i] = get_mem_val(src_mem, ADDR_LIMIT(start+i));

  for (i=0; i<len; i++) {
     set_mem_val(dest_mem, ADDR_LIMIT(dst+i), buf[i]);
  }
}


void mon_compare_memory(MON_ADDR start_addr, MON_ADDR end_addr, MON_ADDR dest)
{
  unsigned i, len, dst;
  ADDRESS start;
  MEMSPACE src_mem, dest_mem;
  BYTE byte1, byte2;

  len = evaluate_address_range(&start_addr, &end_addr, TRUE, -1);
  if (len < 0) {
     fprintf(mon_output, "Invalid range.\n");
     return;
  }
  src_mem = addr_memspace(start_addr);
  start = addr_location(start_addr);

  evaluate_default_addr(&dest);
  dst = addr_location(dest);
  dest_mem = addr_memspace(dest);

  for (i=0; i<len; i++) {
     byte1 = get_mem_val(src_mem, ADDR_LIMIT(start+i));
     byte2 = get_mem_val(dest_mem, ADDR_LIMIT(dst+i));

     if (byte1 != byte2)
        fprintf(mon_output, "$%04x $%04x: %02x %02x\n",ADDR_LIMIT(start+i), ADDR_LIMIT(dst+i), byte1, byte2);
  }
}


void mon_fill_memory(MON_ADDR start_addr, MON_ADDR end_addr, unsigned char *data)
{
  unsigned i, index, len = 0;
  ADDRESS start;
  MEMSPACE dest_mem;

  len = evaluate_address_range(&start_addr, &end_addr, FALSE, data_buf_len);
  if (len < 0) {
     fprintf(mon_output, "Invalid range.\n");
     return;
  }
  start = addr_location(start_addr);

  if (!is_valid_addr(start_addr)) {
     fprintf(mon_output, "Invalid start address\n");
     return;
  }

  dest_mem = addr_memspace(start_addr);

  i = 0;
  index = 0;
  while (i < len) {
     set_mem_val(dest_mem, ADDR_LIMIT(start+i), data_buf[index++]);
     if (index >= data_buf_len)
         index = 0;
     i++;
  }

  clear_buffer();
}


void mon_hunt_memory(MON_ADDR start_addr, MON_ADDR end_addr, unsigned char *data)
{
  unsigned len, i, next_read;
  BYTE *buf;
  ADDRESS start;
  MEMSPACE mem;

  len = evaluate_address_range(&start_addr, &end_addr, TRUE, -1);
  if (len < 0 || len < data_buf_len) {
     fprintf(mon_output, "Invalid range.\n");
     return;
  }
  mem = addr_memspace(start_addr);
  start = addr_location(start_addr);

  buf = (BYTE *) xmalloc(sizeof(BYTE) * data_buf_len);

  /* Fill buffer */
  for (i=0; i<data_buf_len; i++)
     buf[i] = get_mem_val(mem, ADDR_LIMIT(start+i));

  /* Do compares */
  next_read = start + data_buf_len;

  for (i=0; i<(len-data_buf_len); i++,next_read++) {
     if (memcmp(buf,data_buf,data_buf_len) == 0)
        fprintf(mon_output, "%04x\n",ADDR_LIMIT(start+i));

     if (data_buf_len > 1)
        memmove(&(buf[0]), &(buf[1]), data_buf_len-1);
     buf[data_buf_len-1] = get_mem_val(mem, next_read);
   }

  clear_buffer();
}


/* *** FILE COMMANDS *** */


void mon_change_dir(char *path)
{
    if (chdir(path) < 0) {
	perror(path);
    }

    fprintf(mon_output, "Changing to directory: %s\n",path);
}


void mon_load_file(char *filename, MON_ADDR start_addr)
{
    FILE   *fp;
    ADDRESS adr;
    int     b1, b2;
    int     ch;

    if (NULL == (fp = fopen(filename, READ))) {
	perror(filename);
	fprintf(mon_output, "Loading failed.\n");
	return;
    }

    b1 = fgetc(fp);
    b2 = fgetc(fp);

    evaluate_default_addr(&start_addr);
    if (!is_valid_addr(start_addr)) {	/* No Load address given */
	if (b1 == 1)	/* Load Basic */
	    mem_get_basic_text(&adr, NULL);
	else
	    adr = (BYTE)b1 | ((BYTE)b2 << 8);
    } else  {
       adr = addr_location(start_addr);
    }

    fprintf(mon_output, "Loading %s", filename);
    fprintf(mon_output, " from %04X\n", adr);

    /* FIXME - check for wraparound */
    ch = fread (ram + adr, 1, ram_size - adr, fp);
    fprintf(mon_output, "%x bytes\n", ch);

    /* set end of load addresses like kernal load */
    mem_set_basic_text(adr, adr + ch);

    fclose(fp);
}

void mon_save_file(char *filename, MON_ADDR start_addr, MON_ADDR end_addr)
{
   FILE   *fp;
   ADDRESS adr, end;
   long len;

   len = evaluate_address_range(&start_addr, &end_addr, TRUE, -1);
   if (len < 0) {
      fprintf(mon_output, "Invalid range.\n");
      return;
   }

   adr = addr_location(start_addr);
   end = addr_location(end_addr);

   if (NULL == (fp = fopen(filename, WRITE))) {
	perror(filename);
	fprintf(mon_output, "Saving failed.\n");
   } else {
	printf("Saving file `%s'...\n", filename);
	fputc((BYTE) adr & 0xff, fp);
	fputc((BYTE) (adr >> 8) & 0xff, fp);
        if (end < adr) {
	   fwrite((char *) (ram + adr), 1, ram_size-adr, fp);
	   fwrite((char *) ram, 1, end, fp);
        } else
	   fwrite((char *) (ram + adr), 1, len, fp);

	fclose(fp);
   }
}

void mon_verify_file(char *filename, MON_ADDR start_addr)
{
   evaluate_default_addr(&start_addr);

   fprintf(mon_output, "Verify file %s at address $%04x\n", filename, addr_location(start_addr));
}

void mon_load_symbols(MEMSPACE mem, char *filename)
{
    FILE   *fp;
    ADDRESS adr;
    char name[256];
    char *name_ptr;
    bool found = FALSE;

    if (NULL == (fp = fopen(filename, READ))) {
	perror(filename);
	fprintf(mon_output, "Loading failed.\n");
	return;
    }

    fprintf(mon_output, "Loading symbol table from %s\n", filename);

    if (mem == e_default_space) {
       fscanf(fp, "%s\n", name);
       for (mem = FIRST_SPACE; mem <= LAST_SPACE; mem++) {
          if (strcmp(name,memspace_string[mem]) == 0) {
             found = TRUE;
             break;
          }
       }
       if (!found) {
          fprintf(mon_output, "Bad label file : expecting a memory space in the first line but found %s\n", name);
          return;
       }
    }

    while (!feof(fp)) {
       fscanf(fp, "%x %s\n", (int *) &adr, name);
       name_ptr = (char *) xmalloc((strlen(name)+1) * sizeof(char));
       strcpy(name_ptr, name);
       fprintf(mon_output, "Read ($%x:%s)\n",adr, name_ptr);
       mon_add_name_to_symbol_table(new_addr(mem, adr), name_ptr);
    }

    fclose(fp);
}

void mon_save_symbols(MEMSPACE mem, char *filename)
{
    FILE   *fp;
    symbol_entry_t *sym_ptr;

    if (NULL == (fp = fopen(filename, WRITE))) {
	perror(filename);
	fprintf(mon_output, "Saving failed : cannot open file for writing.\n");
	return;
    }

    fprintf(mon_output, "Saving symbol table to %s\n", filename);

    sym_ptr = monitor_labels[mem].name_list;
    fprintf(fp, "%s\n", memspace_string[mem]);

    while (sym_ptr) {
       fprintf(fp, "%04x %s\n", sym_ptr->addr, sym_ptr->name);
       fprintf(mon_output, "Write ($%x:%s)\n",sym_ptr->addr,sym_ptr-> name);
       sym_ptr = sym_ptr->next;
    }

    fclose(fp);
}


/* *** COMMAND FILES *** */


void mon_record_commands(char *filename)
{
   if (recording) {
       fprintf(mon_output, "Recording already in progress. Use 'stop' to end recording.\n");
       return;
   }

   recording_name = filename;

   if (NULL == (recording_fp = fopen(recording_name, WRITE))) {
       perror(recording_name);
       fprintf(mon_output, "Record %s failed.\n", recording_name);
       return;
   }
   recording = TRUE;
}

void mon_end_recording(void)
{
   if (!recording) {
       fprintf(mon_output, "No file is currently being recorded.\n");
       return;
   }

   fclose(recording_fp);
   fprintf(mon_output, "Closed file %s.\n",recording_name);
   recording = FALSE;
}

static void playback_commands(char *filename)
{
   FILE *fp;
   char string[255];

   if (NULL == (fp = fopen(filename, READ))) {
       perror(filename);
       fprintf(mon_output, "Playback %s failed.\n", filename);
       return;
   }

   while (1) {
      fgets(string, 255, fp);
      if (strcmp(string, "stop\n") == 0)
         break;
      string[strlen(string)-1] = '\0';
      parse_and_execute_line(string);
   }

   fclose(fp);
   playback = FALSE;
}


/* *** SYMBOL TABLE *** */


static void free_symbol_table(MEMSPACE mem)
{
   symbol_entry_t *sym_ptr, *temp;
   int i;

   /* Remove name list */
   sym_ptr = monitor_labels[mem].name_list;
   while (sym_ptr) {
      /* Name memory is freed below. */
      temp = sym_ptr;
      sym_ptr = sym_ptr->next;
      free(temp);
   }

   /* Remove address hash table */
   for (i=0;i<HASH_ARRAY_SIZE;i++) {
      sym_ptr = monitor_labels[mem].addr_hash_table[i];
      while (sym_ptr) {
         free (sym_ptr->name);
         temp = sym_ptr;
         sym_ptr = sym_ptr->next;
         free(temp);
      }
   }
}

char *mon_symbol_table_lookup_name(MEMSPACE mem, ADDRESS addr)
{
   symbol_entry_t *sym_ptr;

   sym_ptr = monitor_labels[mem].addr_hash_table[HASH_ADDR(addr)];
   while (sym_ptr) {
      if (addr == sym_ptr->addr)
         return sym_ptr->name;
      sym_ptr = sym_ptr->next;
   }

   return NULL;
}

int mon_symbol_table_lookup_addr(MEMSPACE mem, char *name)
{
   symbol_entry_t *sym_ptr;

   sym_ptr = monitor_labels[mem].name_list;
   while (sym_ptr) {
      if (strcmp(sym_ptr->name, name) == 0)
         return sym_ptr->addr;
      sym_ptr = sym_ptr->next;
   }

   return -1;
}

void mon_add_name_to_symbol_table(MON_ADDR addr, char *name)
{
   symbol_entry_t *sym_ptr;
   char *old_name;
   int old_addr;
   MEMSPACE mem = addr_memspace(addr);
   ADDRESS loc = addr_location(addr);

   if (mem == e_default_space)
      mem = default_memspace;

   if ( (old_name = mon_symbol_table_lookup_name(mem, loc)) ) {
      fprintf(mon_output, "Replacing label %s with %s for address $%04x\n",
              old_name, name, loc);
   }
   if ( (old_addr = mon_symbol_table_lookup_addr(mem, name)) >= 0) {
      fprintf(mon_output, "Changing address of label %s from $%04x to $%04x\n",
              name, old_addr, loc);
   }

   /* Add name to name list */
   sym_ptr = (symbol_entry_t *) xmalloc(sizeof(symbol_entry_t));
   sym_ptr->name = name;
   sym_ptr->addr = loc;

   sym_ptr->next = monitor_labels[mem].name_list;
   monitor_labels[mem].name_list = sym_ptr;

   /* Add address to hash table */
   sym_ptr = (symbol_entry_t *) xmalloc(sizeof(symbol_entry_t));
   sym_ptr->name = name;
   sym_ptr->addr = addr;

   sym_ptr->next = monitor_labels[mem].addr_hash_table[HASH_ADDR(addr)];
   monitor_labels[mem].addr_hash_table[HASH_ADDR(addr)] = sym_ptr;
}

void mon_remove_name_from_symbol_table(MEMSPACE mem, char *name)
{
   unsigned addr;
   symbol_entry_t *sym_ptr, *prev_ptr;

   if (mem == e_default_space)
      mem = default_memspace;

   if (name == NULL) {
      /* FIXME - prompt user */
      free_symbol_table(mem);
      return;
   } else if ( (addr = mon_symbol_table_lookup_addr(mem, name)) < 0) {
      fprintf(mon_output, "Symbol %s not found.\n", name);
      return;
   }

   /* Remove entry in name list */
   sym_ptr = monitor_labels[mem].name_list;
   prev_ptr = NULL;
   while (sym_ptr) {
      if (strcmp(sym_ptr->name, name) == 0) {
         /* Name memory is freed below. */
         addr = sym_ptr->addr;
         if (prev_ptr)
            prev_ptr->next = sym_ptr->next;
         else
            monitor_labels[mem].name_list = NULL;

         free(sym_ptr);
         break;
      }
      prev_ptr = sym_ptr;
      sym_ptr = sym_ptr->next;
   }

   /* Remove entry in address hash table */
   sym_ptr = monitor_labels[mem].addr_hash_table[HASH_ADDR(addr)];
   prev_ptr = NULL;
   while (sym_ptr) {
      if (addr == sym_ptr->addr) {
         free (sym_ptr->name);
         if (prev_ptr)
            prev_ptr->next = sym_ptr->next;
         else
            monitor_labels[mem].addr_hash_table[HASH_ADDR(addr)] = NULL;

         free(sym_ptr);
         return;
      }
      prev_ptr = sym_ptr;
      sym_ptr = sym_ptr->next;
   }
}

void mon_print_symbol_table(MEMSPACE mem)
{
   symbol_entry_t *sym_ptr;

   if (mem == e_default_space)
      mem = default_memspace;

   sym_ptr = monitor_labels[mem].name_list;
   while (sym_ptr) {
      fprintf(mon_output, "$%04x %s\n",sym_ptr->addr, sym_ptr->name);
      sym_ptr = sym_ptr->next;
   }
}


/* *** INSTRUCTION COMMANDS *** */


void mon_instructions_step(int count)
{
   if (count >= 0)
       fprintf(mon_output, "Stepping through the next %d instruction(s).\n",
               count);
   instruction_count = (count>=0)?count:1;
   wait_for_return_level = 0;
   skip_jsrs = FALSE;
   exit_mon = 1;

   mon_mask[caller_space] |= MI_STEP;
   monitor_trap_on(mon_interfaces[caller_space]->int_status);
}

void mon_instructions_next(int count)
{
   if (count >= 0)
       fprintf(mon_output, "Nexting through the next %d instruction(s).\n",
               count);
   instruction_count = (count>=0)?count:1;
   wait_for_return_level = (GET_OPCODE(caller_space) == OP_JSR) ? 1 : 0;
   skip_jsrs = TRUE;
   exit_mon = 1;

   mon_mask[caller_space] |= MI_STEP;
   monitor_trap_on(mon_interfaces[caller_space]->int_status);
}

void mon_instruction_return(void)
{
   instruction_count = 1;
   wait_for_return_level = 1;
   exit_mon = 1;

   mon_mask[caller_space] |= MI_STEP;
   monitor_trap_on(mon_interfaces[caller_space]->int_status);
}

void mon_stack_up(int count)
{
   fprintf(mon_output, "Going up %d stack frame(s).\n",
          (count>=0)?count:1);
}

void mon_stack_down(int count)
{
   fprintf(mon_output, "Going down %d stack frame(s).\n",
          (count>=0)?count:1);
}


/* *** DISK COMMANDS *** */


void mon_block_cmd(int op, int track, int sector, MON_ADDR addr)
{
   evaluate_default_addr(&addr);

   if (!op)
   {
      if (is_valid_addr(addr))
         fprintf(mon_output, "Read track %d sector %d to screen\n", track, sector);
      else
         fprintf(mon_output, "Read track %d sector %d into address $%04x\n", track, sector, addr_location(addr));
   }
   else
   {
      fprintf(mon_output, "Write data from address $%04x to track %d sector %d\n",
              addr_location(addr), track, sector);
   }

}


void mon_execute_disk_command(char *cmd)
{
   int len, rc;
   serial_t *p;
   DRIVE *floppy;

   /* FIXME */
   p = serial_get_device(8);
   floppy = (DRIVE *)p->info;

   len = strlen(cmd);
   rc = ip_execute(floppy, cmd, len);
}


/* *** CONDITIONAL EXPRESSIONS *** */


static void print_conditional(CONDITIONAL_NODE *cnode)
{
   /* Do an in-order traversal of the tree */
   if (cnode->is_parenthized)
      fprintf(mon_output, "( ");

   if (cnode->operation != e_INV)
   {
      assert(cnode->child1 && cnode->child2);
      print_conditional(cnode->child1);
      fprintf(mon_output, " %s ",cond_op_string[cnode->operation]);
      print_conditional(cnode->child2);
   }
   else
   {
      if (cnode->is_reg)
         fprintf(mon_output, ".%s",register_string[reg_regid(cnode->reg_num)]);
      else
         fprintf(mon_output, "%d",cnode->value);
   }

   if (cnode->is_parenthized)
      fprintf(mon_output, " )");
}


static int evaluate_conditional(CONDITIONAL_NODE *cnode)
{
   /* Do a post-order traversal of the tree */
   if (cnode->operation != e_INV)
   {
      assert(cnode->child1 && cnode->child2);
      evaluate_conditional(cnode->child1);
      evaluate_conditional(cnode->child2);

      switch(cnode->operation) {
         case e_EQU:
            cnode->value = ((cnode->child1->value) == (cnode->child2->value));
            break;
         case e_NEQ:
            cnode->value = ((cnode->child1->value) != (cnode->child2->value));
            break;
         case e_GT :
            cnode->value = ((cnode->child1->value) > (cnode->child2->value));
            break;
         case e_LT :
            cnode->value = ((cnode->child1->value) < (cnode->child2->value));
            break;
         case e_GTE:
            cnode->value = ((cnode->child1->value) >= (cnode->child2->value));
            break;
         case e_LTE:
            cnode->value = ((cnode->child1->value) <= (cnode->child2->value));
            break;
         case e_AND:
            cnode->value = ((cnode->child1->value) && (cnode->child2->value));
            break;
         case e_OR :
            cnode->value = ((cnode->child1->value) || (cnode->child2->value));
            break;
         default:
            fprintf(mon_output, "Unexpected conditional operator: %d\n",cnode->operation);
            assert(0);
      }
   }
   else
   {
      if (cnode->is_reg)
         cnode->value = mon_get_reg_val(reg_memspace(cnode->reg_num), reg_regid(cnode->reg_num));
   }

   return cnode->value;
}


static void delete_conditional(CONDITIONAL_NODE *cnode)
{
   if (cnode) {
      if (cnode->child1)
         delete_conditional(cnode->child1);
      if (cnode->child2)
         delete_conditional(cnode->child2);
      free(cnode);
   }
}


/* *** BREAKPOINT COMMANDS *** */


static void remove_checkpoint_from_list(BREAK_LIST **head, breakpoint *bp)
{
   BREAK_LIST *cur_entry, *prev_entry;

   cur_entry = *head;
   prev_entry = NULL;

   while (cur_entry) {
      if (cur_entry->brkpt == bp)
         break;

      prev_entry = cur_entry;
      cur_entry = cur_entry->next;
   }

   if (!cur_entry) {
      assert(FALSE);
   } else {
     if (!prev_entry) {
        *head = cur_entry->next;
     } else {
         prev_entry->next = cur_entry->next;
     }

     free(cur_entry);
   }
}

static breakpoint *find_checkpoint(int brknum)
{
   BREAK_LIST *ptr;
   int i;

   for (i=e_comp_space;i<=e_disk_space;i++) {
      ptr = breakpoints[i];
      while (ptr) {
         if (ptr->brkpt->brknum == brknum)
            return ptr->brkpt;
         ptr = ptr->next;
      }

      ptr = watchpoints_load[i];
      while (ptr) {
         if (ptr->brkpt->brknum == brknum)
            return ptr->brkpt;
         ptr = ptr->next;
      }

      ptr = watchpoints_store[i];
      while (ptr) {
         if (ptr->brkpt->brknum == brknum)
            return ptr->brkpt;
         ptr = ptr->next;
      }
   }

   return NULL;
}

void mon_switch_checkpoint(int op, int breakpt_num)
{
   breakpoint *bp;
   bp = find_checkpoint(breakpt_num);

   if (!bp) {
      fprintf(mon_output,"#%d not a valid breakpoint\n",breakpt_num);
   } else {
      bp->enabled = op;
      fprintf(mon_output, "Set breakpoint #%d to state: %s\n",
              breakpt_num, (op == e_ON) ? "enabled" : "disabled");
   }
}

void mon_set_ignore_count(int breakpt_num, int count)
{
   breakpoint *bp;
   bp = find_checkpoint(breakpt_num);

   if (!bp)
   {
      fprintf(mon_output, "#%d not a valid breakpoint\n",breakpt_num);
   }
   else
   {
      bp->ignore_count = count;
      fprintf(mon_output, "Ignoring the next %d crossings of breakpoint #%d\n",count, breakpt_num);
   }
}

static void print_checkpoint_info(breakpoint *bp)
{
   if (bp->trace) {
      fprintf(mon_output, "TRACE: ");
   } else if (bp->watch_load || bp->watch_store) {
      fprintf(mon_output, "WATCH: ");
   } else {
      fprintf(mon_output, "BREAK: ");
   }
   fprintf(mon_output, "%d A:$%04x",bp->brknum,addr_location(bp->start_addr));
   if (is_valid_addr(bp->end_addr))
      fprintf(mon_output, "-$%04x",addr_location(bp->end_addr));

   if (bp->watch_load)
      fprintf(mon_output, " load");
   if (bp->watch_store)
      fprintf(mon_output, " store");

   fprintf(mon_output, "   %s\n",(bp->enabled==e_ON)?"enabled":"disabled");

   if (bp->condition) {
      fprintf(mon_output, "\tCondition: ");
      print_conditional(bp->condition);
      fprintf(mon_output, "\n");
   }
   if (bp->command)
      fprintf(mon_output, "\tCommand: %s\n",bp->command);
}

void mon_print_checkpoints(void)
{
   int i, any_set=0;
   breakpoint *bp;

   for (i=1;i<breakpoint_count;i++)
   {
      if ( (bp = find_checkpoint(i)) )
      {
         print_checkpoint_info(bp);
         any_set = 1;
      }
   }

   if (!any_set)
      fprintf(mon_output, "No breakpoints are set\n");
}

void mon_delete_checkpoint(int brknum)
{
   int i;
   breakpoint *bp = NULL;
   MEMSPACE mem;

   if (brknum == -1)
   {
      /* Add user confirmation here. */
      fprintf(mon_output, "Deleting all breakpoints\n");
      for (i=1;i<breakpoint_count;i++)
      {
         bp = find_checkpoint(i);
         if (bp)
            mon_delete_checkpoint(i);
      }
   }
   else if ( !(bp = find_checkpoint(brknum)) )
   {
      fprintf(mon_output, "#%d not a valid breakpoint\n",brknum);
      return;
   }
   else
   {
      mem = addr_memspace(bp->start_addr);

      if ( !(bp->watch_load) && !(bp->watch_store) ) {
         remove_checkpoint_from_list(&(breakpoints[mem]), bp);

         if (!any_breakpoints(mem)) {
            mon_mask[mem] &= ~MI_BREAK;

            if (!mon_mask[mem])
               monitor_trap_off(mon_interfaces[mem]->int_status);
         }
      } else {
         if ( bp->watch_load )
            remove_checkpoint_from_list(&(watchpoints_load[mem]), bp);
         if ( bp->watch_store )
            remove_checkpoint_from_list(&(watchpoints_store[mem]), bp);

         if (!any_watchpoints(mem)) {
            mon_mask[mem] &= ~MI_WATCH;
            mon_interfaces[mem]->toggle_watchpoints_func(0);

            if (!mon_mask[mem])
               monitor_trap_off(mon_interfaces[mem]->int_status);
         }
      }
   }

   if (bp != NULL) {
      delete_conditional(bp->condition);
      if (bp->command)
         free(bp->command);
   }
}

void mon_set_checkpoint_condition(int brk_num, CONDITIONAL_NODE *cnode)
{
   breakpoint *bp;
   bp = find_checkpoint(brk_num);

   if (!bp)
   {
      fprintf(mon_output, "#%d not a valid breakpoint\n",brk_num);
   }
   else
   {
      bp->condition = cnode;

      fprintf(mon_output, "Setting breakpoint %d condition to: ",brk_num);
      print_conditional(cnode);
      fprintf(mon_output, "\n");
#if 0
      /* DEBUG */
      evaluate_conditional(cnode);
      printf("Condition evaluates to: %d\n",cnode->value);
#endif
   }
}


void mon_set_checkpoint_command(int brk_num, char *cmd)
{
   breakpoint *bp;
   bp = find_checkpoint(brk_num);

   if (!bp)
   {
      fprintf(mon_output, "#%d not a valid breakpoint\n",brk_num);
   }
   else
   {
      bp->command = cmd;
      fprintf(mon_output, "Setting breakpoint %d command to: %s\n",brk_num, cmd);
   }
}


static BREAK_LIST *search_checkpoint_list(BREAK_LIST *head, unsigned loc)
{
   BREAK_LIST *cur_entry;

   cur_entry = head;

   /* The list should be sorted in increasing order. If the current entry
      is > than the search item, we can drop out early.
   */
   while (cur_entry) {
      if (is_in_range(cur_entry->brkpt->start_addr, cur_entry->brkpt->start_addr, loc))
         return cur_entry;

      cur_entry = cur_entry->next;
   }

   return NULL;
}

static int compare_checkpoints(breakpoint *bp1, breakpoint *bp2)
{
   unsigned addr1, addr2;
   /* Returns < 0 if bp1 < bp2
              = 0 if bp1 = bp2
              > 0 if bp1 > bp2
   */

   addr1 = addr_location(bp1->start_addr);
   addr2 = addr_location(bp2->end_addr);

   if ( addr1 < addr2 )
      return -1;

   if ( addr1 > addr2 )
      return 1;

   return 0;
}

bool mon_check_checkpoint(MEMSPACE mem, ADDRESS addr, BREAK_LIST *list)
{
   BREAK_LIST *ptr;
   breakpoint *bp;
   bool result = FALSE;
   MON_ADDR temp;
   char *type;

   ptr = search_checkpoint_list(list,addr);

   while (ptr && is_in_range(ptr->brkpt->start_addr, ptr->brkpt->end_addr, addr)) {
      bp = ptr->brkpt;
      ptr = ptr->next;
      if (bp && bp->enabled==e_ON) {
         /* If condition test fails, skip this checkpoint */
         if (bp->condition) {
            if (!evaluate_conditional(bp->condition)) {
               continue;
            }
         }

         /* Check if the user specified some ignores */
         if (bp->ignore_count) {
            bp->ignore_count--;
            continue;
         }

         bp->hit_count++;

         result = TRUE;

         temp = new_addr(mem, mon_get_reg_val(mem, e_PC));
         if (bp->trace) {
            type = "Trace";
            result = FALSE;
         }
         else if (bp->watch_load)
            type = "Watch-load";
         else if (bp->watch_store)
            type = "Watch-store";
         else
            type = "Break";

         fprintf(mon_output, "#%d (%s) ",bp->brknum, type);
         disassemble_instr(temp);

         if (bp->command) {
            fprintf(mon_output, "Executing: %s\n",bp->command);
            parse_and_execute_line(bp->command);
         }
      }
   }
   return result;
}

static void add_to_checkpoint_list(BREAK_LIST **head, breakpoint *bp)
{
   BREAK_LIST *new_entry, *cur_entry, *prev_entry;

   new_entry = (BREAK_LIST *) xmalloc(sizeof(BREAK_LIST));
   new_entry->brkpt = bp;

   cur_entry = *head;
   prev_entry = NULL;

   /* Make sure the list is in increasing order. (Ranges are entered
      based on the lower bound) This way if the searched for address is
      less than the current ptr, we can skip the rest of the list. Note
      that ranges that wrap around 0xffff aren't handled in this scheme.
      Suggestion: Split the range and create two entries.
   */
   while (cur_entry && (compare_checkpoints(cur_entry->brkpt, bp) <= 0) ) {
      prev_entry = cur_entry;
      cur_entry = cur_entry->next;
   }

   if (!prev_entry) {
      *head = new_entry;
      new_entry->next = cur_entry;
      return;
   }

   prev_entry->next = new_entry;
   new_entry->next = cur_entry;
}

int mon_add_checkpoint(MON_ADDR start_addr, MON_ADDR end_addr, bool is_trace, bool is_load, bool is_store)
{
   breakpoint *new_bp;
   MEMSPACE mem;
   long len;

   len = evaluate_address_range(&start_addr, &end_addr, FALSE, 0);
   new_bp = (breakpoint *) xmalloc(sizeof(breakpoint));

   new_bp->brknum = breakpoint_count++;
   new_bp->start_addr = start_addr;
   new_bp->end_addr = end_addr;
   new_bp->trace = is_trace;
   new_bp->enabled = e_ON;
   new_bp->hit_count = 0;
   new_bp->ignore_count = 0;
   new_bp->condition = NULL;
   new_bp->command = NULL;
   new_bp->watch_load = is_load;
   new_bp->watch_store = is_store;

   mem = addr_memspace(start_addr);
   if (!is_load && !is_store) {
      if (!any_breakpoints(mem)) {
         mon_mask[mem] |= MI_BREAK;
         monitor_trap_on(mon_interfaces[mem]->int_status);
      }

      add_to_checkpoint_list(&(breakpoints[mem]), new_bp);
   } else {
      if (!any_watchpoints(mem)) {
         mon_mask[mem] |= MI_WATCH;
         mon_interfaces[mem]->toggle_watchpoints_func(1);
         monitor_trap_on(mon_interfaces[mem]->int_status);
      }

      if (is_load)
         add_to_checkpoint_list(&(watchpoints_load[mem]), new_bp);
      if (is_store)
         add_to_checkpoint_list(&(watchpoints_store[mem]), new_bp);
   }

   print_checkpoint_info(new_bp);
   return new_bp->brknum;
}


/* *** WATCHPOINTS *** */


void mon_watch_push_load_addr(ADDRESS addr, MEMSPACE mem)
{
   if (inside_monitor)
      return;

   watch_load_occurred = TRUE;
   watch_load_array[watch_load_count[mem]][mem] = addr;
   watch_load_count[mem]++;
}

void mon_watch_push_store_addr(ADDRESS addr, MEMSPACE mem)
{
   if (inside_monitor)
      return;

   watch_store_occurred = TRUE;
   watch_store_array[watch_store_count[mem]][mem] = addr;
   watch_store_count[mem]++;
}

bool watchpoints_check_loads(MEMSPACE mem)
{
   bool trap = FALSE;
   unsigned count;
   ADDRESS addr = 0;

   count = watch_load_count[mem];
   watch_load_count[mem] = 0;
   while (count) {
      count--;
      addr = watch_load_array[count][mem];
      if (mon_check_checkpoint(mem, addr, watchpoints_load[mem]))
         trap = TRUE;
   }
   return trap;
}

bool watchpoints_check_stores(MEMSPACE mem)
{
   bool trap = FALSE;
   unsigned count;
   ADDRESS addr = 0;

   count = watch_store_count[mem];
   watch_store_count[mem] = 0;

   while (count) {
      count--;
      addr = watch_store_array[count][mem];
      if (mon_check_checkpoint(mem, addr, watchpoints_store[mem]))
         trap = TRUE;
   }
   return trap;
}


/* *** CPU INTERFACES *** */


bool mon_force_import(MEMSPACE mem)
{
   bool result;

   result = force_array[mem];
   force_array[mem] = FALSE;

   return result;
}

void mon_check_icount(ADDRESS a)
{
    if (instruction_count) {
        if (!wait_for_return_level)
           instruction_count--;

        if (GET_OPCODE(caller_space) == OP_JSR)
           wait_for_return_level++;

        if (GET_OPCODE(caller_space) == OP_RTS)
           wait_for_return_level--;

        /* FIXME: Should this set the return level to 0? */
        if (GET_OPCODE(caller_space) == OP_RTI)
           wait_for_return_level--;

        if (!instruction_count) {
           if (mon_mask[caller_space] & MI_STEP) {
              mon_mask[caller_space] &= ~MI_STEP;
              disassemble_instr(new_addr(caller_space, a));
           }
           if (!mon_mask[caller_space])
              monitor_trap_off(mon_interfaces[caller_space]->int_status);

           mon(a);
        }
    }
}

void mon_check_watchpoints(ADDRESS a)
{
    if (watch_load_occurred) {
        if (watchpoints_check_loads(e_comp_space)) {
            caller_space = e_comp_space;
            mon(a);
        }
        if (watchpoints_check_loads(e_disk_space)) {
            caller_space = e_disk_space;
            mon(a);
        }
        watch_load_occurred = FALSE;
    }

    if (watch_store_occurred) {
        if (watchpoints_check_stores(e_comp_space)) {
            caller_space = e_comp_space;
            mon(a);
        }
        if (watchpoints_check_stores(e_disk_space)) {
            caller_space = e_disk_space;
            mon(a);
        }
        watch_store_occurred = FALSE;
    }
}

static void make_prompt(char *str)
{
    sprintf(str, "[%c,M:%s] (%s:$%04x) ",
            (sidefx == e_ON) ? 'S' : '-',
            memspace_string[default_memspace],
            memspace_string[caller_space],
            addr_location(dot_addr[caller_space]));
}

void mon(ADDRESS a)
{
   char prompt[40];

#ifdef __MSDOS__
    int old_input_mode, old_output_mode;

    enable_text();
    clrscr();
    _set_screen_lines(43);
    _setcursortype(_SOLIDCURSOR);

    old_input_mode = setmode(STDIN_FILENO, O_TEXT);
    old_output_mode = setmode(STDOUT_FILENO, O_TEXT);

    mon_output = fopen("CON", "wt");
    mon_input = fopen("CON", "rt");
    setbuf(mon_output, NULL);    /* No buffering */
#else
    mon_output = stdout;
    mon_input = stdin;
#endif

    inside_monitor = TRUE;
    suspend_speed_eval();

    dot_addr[caller_space] = new_addr(caller_space, a);

    fprintf(mon_output, "\n** Monitor\n");
    fflush(mon_output);

    do {
        make_prompt(prompt);

        if (asm_mode) {
            sprintf(prompt,".%04x  ", addr_location(asm_mode_addr));
        }

        myinput = readline(prompt);
        if (myinput) {
            if (!myinput[0]) {
                if (!asm_mode) {
                    /* Repeat previous command */
                    free(myinput);

                    if (last_cmd)
                        myinput = stralloc(last_cmd);
                    else
                        myinput = NULL;
                } else {
                    /* Leave asm mode */
                    make_prompt(prompt);
                }
            } else {
                /* Nonempty line */
                add_history(myinput);
            }

            if (myinput) {
                if (recording)
                    fprintf(recording_fp, "%s\n", myinput);

                parse_and_execute_line(myinput);

                if (playback)
                    playback_commands(playback_name);
            }
        }
        if (last_cmd)
            free(last_cmd);

        last_cmd = myinput;
    } while (!exit_mon);
    inside_monitor = FALSE;
    suspend_speed_eval();

    exit_mon--;

    if (exit_mon)
        exit(0);

#ifdef __MSDOS__
    setmode(STDIN_FILENO, old_input_mode);
    setmode(STDIN_FILENO, old_output_mode);

    disable_text();

    fclose(mon_input);
    fclose(mon_output);
#endif
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.