/* scp.c: simulator control program

   Copyright (c) 1993-2021, Robert M Supnik

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

   Except as contained in this notice, the name of Robert M Supnik shall not be
   used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from Robert M Supnik.

   15-Apr-21    RMS     Changed RUN to store new PC value both before RESET
                        (former behavior) and after (per Mark Pizzolato)
   18-Mar-21    JDB     Revised "attach_unit" and "detach_unit" for pipe support
                        Modified tests to allow UNIT_RO without UNIT_ROABLE
   16-Feb-21    JDB     Rewrote get_rval, put_rval to support arrays of structures
   01-Feb-21    JDB     Added casts for down-conversions
   25-Jan-21    JDB     REG "size" field now determines access size
                        REG "maxval" field now determines maximum allowed value
   30-Nov-20    RMS     Fixed RUN problem if CPU reset clears PC (Mark Pizzolato)
   09-Nov-20    RMS     Added hack for sim_card multiple attach (Mark Pizzolato)
   23-Oct-20    JDB     Added tmxr_post_logs calls to flush and close log files
   04-Jun-20    JDB     Call of "sim_vm_init" is now conditional on USE_VM_INIT
   28-May-20    RMS     Flush stdout after prompting (Mark Pizzolato)
   23-Mar-20    RMS     Added SET <dev|unit> APPEND command
   13-Feb-20    RMS     Spelled out CONTINUE in command table (Dave Bryan)
   09-Jan-20    JDB     Added "sim_vm_unit_name" extension hook
   26-Oct-19    RMS     Removed commented out MTAB_VAL code
   09-Oct-19    JDB     Corrected "sim_ref_type" use for RESTORE and DETACH ALL
   19-Jul-19    JDB     Added "sim_get_radix" extension hook
   13-Apr-19    JDB     Added extension hooks
                        Added "sim_prog_name" and "sim_ref_type" global variables
                        Added global routine declarations
   12-Apr-19    JDB     Permit quoted action lists in "sim_brk_getact"
                        Propagate -V through DO command levels
                        Use DO command replacement consistently
                        Add memory checking to the ASSERT command
   26-Mar-18    RMS     Modified DETACH for static use of UNIT_RO (Mark Pizzolato)
   19-Mar-18    RMS     Removed redundant declarations
   06-Mar-18    RMS     Moved switch routine declaration to scp.h
                        Removed get_ipaddr
   06-Dec-17    JDB     Prevent a disabled unit from being attached
   10-Mar-17    MP      Fixed "noise" bugs (COVERITY)
   08-Mar-16    RMS     Added shutdown flag for detach_all
   28-Mar-15    RMS     Added sim_printf from GitHub master (Mark Pizzolato)
   28-Dec-14    JDB     [4.0] Moved sim_load and sim_emax declarations to scp.h
   14-Dec-14    JDB     [4.0] Added sim_activate_time
                        [4.0] Changed sim_is_active to return t_bool
   10-Nov-14    JDB     Added ATTACH -N
   02-Jul-14    JDB     [4.0] Added sim_error_text
   01-Mar-13    JDB     Fixed SHOW RADIX, etc. for a device that has no modifiers
                        Modified SET and SHOW invalid entry messages for consistency
                        Correct fprintf parameter errors reported by clang
   26-Feb-13    JDB     Moved EX_* and SSH_* constants to scp.h
   24-Feb-13    JDB     Added REG_VMAD check in fprint_stopped_gen for VM address call
                        Added SIM_SW_STOP to examine call in fprint_stopped_gen
   05-Feb-13    JDB     Added sim_vm_fprint_stopped for VM-specific stop messages
                        Modified ex_reg and dep_reg to pass VM-specific register flags
   08-May-12    RMS     Fixed memory leaks in save/restore (Peter Schorn)
   20-Mar-12    MP      Fixes to "SHOW <x> SHOW" commands
   06-Jan-12    JDB     Fixed "SHOW DEVICE" with only one enabled unit (Dave Bryan)
   13-Jan-11    MP      Added "SHOW SHOW" and "SHOW <dev> SHOW" commands
   05-Jan-11    RMS     Fixed bug in deposit stride for numeric input (John Dundas)
   23-Dec-10    RMS     Clarified some help messages (Mark Pizzolato)
   08-Nov-10    RMS     Fixed handling of DO with no arguments (Dave Bryan)
   22-May-10    RMS     Added *nix READLINE support (Mark Pizzolato)
   08-Feb-09    RMS     Fixed warnings in help printouts
   29-Dec-08    RMS     Fixed implementation of MTAB_NC
   24-Nov-08    RMS     Revised RESTORE unit logic for consistency
   05-Sep-08    JDB     "detach_all" ignores error status returns if shutting down
   17-Aug-08    RMS     Revert RUN/BOOT to standard, rather than powerup, reset
   25-Jul-08    JDB     DO cmd missing params now default to null string
   29-Jun-08    JDB     DO cmd sub_args now allows "\\" to specify literal backslash
   31-Mar-08    RMS     Fixed bug in local/global register search (Mark Pizzolato)
                        Fixed bug in restore of RO units (Mark Pizzolato)
   06-Feb-08    RMS     Added SET/SHO/NO BR with default argument
   18-Jul-07    RMS     Modified match_ext for VMS ext;version support
   28-Apr-07    RMS     Modified sim_instr invocation to call sim_rtcn_init_all
                        Fixed bug in get_sim_opt
                        Fixed bug in restoration with changed memory size
   08-Mar-07    JDB     Fixed breakpoint actions in DO command file processing
   30-Jan-07    RMS     Fixed bugs in get_ipaddr
   17-Oct-06    RMS     Added idle support
   04-Oct-06    JDB     DO cmd failure now echoes cmd unless -q
   14-Jul-06    RMS     Added sim_activate_abs
   02-Jun-06    JDB     Fixed do_cmd to exit nested files on assertion failure
                        Added -E switch to do_cmd to exit on any error
   14-Feb-06    RMS     Upgraded save file format to V3.5
   18-Jan-06    RMS     Added fprint_stopped_gen
                        Added breakpoint spaces
                        Fixed unaligned register access (Doug Carman)
   22-Sep-05    RMS     Fixed declarations (Sterling Garwood)
   30-Aug-05    RMS     Revised to trim trailing spaces on file names
   25-Aug-05    RMS     Added variable default device support
   23-Aug-05    RMS     Added Linux line history support
   16-Aug-05    RMS     Fixed C++ declaration and cast problems
   01-May-05    RMS     Revised syntax for SET DEBUG (Dave Bryan)
   22-Mar-05    JDB     Modified DO command to allow ten-level nesting
   18-Mar-05    RMS     Moved DETACH tests into detach_unit (Dave Bryan)
                        Revised interface to fprint_sym, fparse_sym
   07-Feb-05    RMS     Added ASSERT command (Dave Bryan)
   02-Feb-05    RMS     Fixed bug in global register search
   26-Dec-04    RMS     Qualified SAVE examine, RESTORE deposit with SIM_SW_REST
   10-Nov-04    JDB     Fixed logging of errors from cmds in "do" file
   05-Nov-04    RMS     Moved SET/SHOW DEBUG under CONSOLE hierarchy
                        Renamed unit OFFLINE/ONLINE to DISABLED/ENABLED (Dave Bryan)
                        Revised to flush output files after simulation stop (Dave Bryan)
   15-Oct-04    RMS     Fixed HELP to suppress duplicate descriptions
   27-Sep-04    RMS     Fixed comma-separation options in set (David Bryan)
   09-Sep-04    RMS     Added -p option for RESET
   13-Aug-04    RMS     Qualified RESTORE detach with SIM_SW_REST
   17-Jul-04    RMS     Added ECHO command (Dave Bryan)
   12-Jul-04    RMS     Fixed problem ATTACHing to read only files
                        (John Dundas)
   28-May-04    RMS     Added SET/SHOW CONSOLE
   14-Feb-04    RMS     Updated SAVE/RESTORE (V3.2)
                RMS     Added debug print routines (Dave Hittner)
                RMS     Added sim_vm_parse_addr and sim_vm_fprint_addr
                RMS     Added REG_VMAD support
                RMS     Split out libraries
                RMS     Moved logging function to SCP
                RMS     Exposed step counter interface(s)
                RMS     Fixed double logging of SHOW BREAK (Mark Pizzolato)
                RMS     Fixed implementation of REG_VMIO
                RMS     Added SET/SHOW DEBUG, SET/SHOW <device> DEBUG,
                        SHOW <device> MODIFIERS, SHOW <device> RADIX
                RMS     Changed sim_fsize to take uptr argument
   29-Dec-03    RMS     Added Telnet console output stall support
   01-Nov-03    RMS     Cleaned up implicit detach on attach/restore
                        Fixed bug in command line read while logging (Mark Pizzolato)
   01-Sep-03    RMS     Fixed end-of-file problem in dep, idep
                        Fixed error on trailing spaces in dep, idep
   15-Jul-03    RMS     Removed unnecessary test in reset_all
   15-Jun-03    RMS     Added register flag REG_VMIO
   25-Apr-03    RMS     Added extended address support (V3.0)
                        Fixed bug in SAVE (Peter Schorn)
                        Added u5, u6 fields
                        Added logical name support
   03-Mar-03    RMS     Added sim_fsize
   27-Feb-03    RMS     Fixed bug in multiword deposits to files
   08-Feb-03    RMS     Changed sim_os_sleep to void, match_ext to char*
                        Added multiple actions, .ini file support
                        Added multiple switch evaluations per line
   07-Feb-03    RMS     Added VMS support for ! (Mark Pizzolato)
   01-Feb-03    RMS     Added breakpoint table extension, actions
   14-Jan-03    RMS     Added missing function prototypes
   10-Jan-03    RMS     Added attach/restore flag, dynamic memory size support,
                        case sensitive SET options
   22-Dec-02    RMS     Added ! (OS command) feature (Mark Pizzolato)
   17-Dec-02    RMS     Added get_ipaddr
   02-Dec-02    RMS     Added EValuate command
   16-Nov-02    RMS     Fixed bug in register name match algorithm
   13-Oct-02    RMS     Fixed Borland compiler warnings (Hans Pufal)
   05-Oct-02    RMS     Fixed bugs in set_logon, ssh_break (David Hittner)
                        Added support for fixed buffer devices
                        Added support for Telnet console, removed VT support
                        Added help <command>
                        Added VMS file optimizations (Robert Alan Byer)
                        Added quiet mode, DO with parameters, GUI interface,
                           extensible commands (Brian Knittel)
                        Added device enable/disable commands
   14-Jul-02    RMS     Fixed exit bug in do, added -v switch (Brian Knittel)
   17-May-02    RMS     Fixed bug in fxread/fxwrite error usage (found by
                        Norm Lastovic)
   02-May-02    RMS     Added VT emulation interface, changed {NO}LOG to SET {NO}LOG
   22-Apr-02    RMS     Fixed laptop sleep problem in clock calibration, added
                        magtape record length error (Jonathan Engdahl)
   26-Feb-02    RMS     Fixed initialization bugs in do_cmd, get_aval
                        (Brian Knittel)
   10-Feb-02    RMS     Fixed problem in clock calibration
   06-Jan-02    RMS     Moved device enable/disable to simulators
   30-Dec-01    RMS     Generalized timer packaged, added circular arrays
   19-Dec-01    RMS     Fixed DO command bug (John Dundas)
   07-Dec-01    RMS     Implemented breakpoint package
   05-Dec-01    RMS     Fixed bug in universal register logic
   03-Dec-01    RMS     Added read-only units, extended SET/SHOW, universal registers
   24-Nov-01    RMS     Added unit-based registers
   16-Nov-01    RMS     Added DO command
   28-Oct-01    RMS     Added relative range addressing
   08-Oct-01    RMS     Added SHOW VERSION
   30-Sep-01    RMS     Relaxed attach test in BOOT
   27-Sep-01    RMS     Added queue count routine, fixed typo in ex/mod
   17-Sep-01    RMS     Removed multiple console support
   07-Sep-01    RMS     Removed conditional externs on function prototypes
                        Added special modifier print
   31-Aug-01    RMS     Changed int64 to t_int64 for Windoze (V2.7)
   18-Jul-01    RMS     Minor changes for Macintosh port
   12-Jun-01    RMS     Fixed bug in big-endian I/O (Dave Conroy)
   27-May-01    RMS     Added multiple console support
   16-May-01    RMS     Added logging
   15-May-01    RMS     Added features from Tim Litt
   12-May-01    RMS     Fixed missing return in disable_cmd
   25-Mar-01    RMS     Added ENABLE/DISABLE
   14-Mar-01    RMS     Revised LOAD/DUMP interface (again)
   05-Mar-01    RMS     Added clock calibration support
   05-Feb-01    RMS     Fixed bug, DETACH buffered unit with hwmark = 0
   04-Feb-01    RMS     Fixed bug, RESTORE not using device's attach routine
   21-Jan-01    RMS     Added relative time
   22-Dec-00    RMS     Fixed find_device for devices ending in numbers
   08-Dec-00    RMS     V2.5a changes
   30-Oct-00    RMS     Added output file option to examine
   11-Jul-99    RMS     V2.5 changes
   13-Apr-99    RMS     Fixed handling of 32b addresses
   04-Oct-98    RMS     V2.4 changes
   20-Aug-98    RMS     Added radix commands
   05-Jun-98    RMS     Fixed bug in ^D handling for UNIX
   10-Apr-98    RMS     Added switches to all commands
   26-Oct-97    RMS     Added search capability
   25-Jan-97    RMS     Revised data types
   23-Jan-97    RMS     Added bi-endian I/O
   06-Sep-96    RMS     Fixed bug in variable length IEXAMINE
   16-Jun-96    RMS     Changed interface to parse/print_sym
   06-Apr-96    RMS     Added error checking in reset all
   07-Jan-96    RMS     Added register buffers in save/restore
   11-Dec-95    RMS     Fixed ordering bug in save/restore
   22-May-95    RMS     Added symbolic input
   13-Apr-95    RMS     Added symbolic printouts
*/

/* Macros and data structures */

#include "sim_defs.h"
#include "sim_tmxr.h"
#include <signal.h>
#include <ctype.h>
#include <sys/stat.h>

#if defined(HAVE_DLOPEN)                                 /* Dynamic Readline support */
#include <dlfcn.h>
#endif

/* Search definitions */

#define SCH_OR          0                               /* search logicals */
#define SCH_AND         1
#define SCH_XOR         2
#define SCH_E           0                               /* search booleans */
#define SCH_N           1
#define SCH_G           2
#define SCH_L           3
#define SCH_EE          4
#define SCH_NE          5
#define SCH_GE          6
#define SCH_LE          7

#define DO_NEST_LVL     10                              /* DO cmd nesting level */
#define SRBSIZ          1024                            /* save/restore buffer */
#define SIM_BRK_INILNT  4096                            /* bpt tbl length */
#define SIM_BRK_ALLTYP  0xFFFFFFFF
#define UPDATE_SIM_TIME(x) sim_time = sim_time + (x - sim_interval); \
    sim_rtime = sim_rtime + ((uint32) (x - sim_interval)); \
    x = sim_interval

#define SZ_D(dp) (size_map[((dp)->dwidth + CHAR_BIT - 1) / CHAR_BIT])
#if defined (USE_INT64)
#define SZ_LOAD(sz,v,mb,j) \
    if (sz == sizeof (uint8)) v = *(((uint8 *) mb) + ((uint32) j)); \
    else if (sz == sizeof (uint16)) v = *(((uint16 *) mb) + ((uint32) j)); \
    else if (sz == sizeof (uint32)) v = *(((uint32 *) mb) + ((uint32) j)); \
    else v = *(((t_uint64 *) mb) + ((uint32) j));
#define SZ_STORE(sz,v,mb,j) \
    if (sz == sizeof (uint8)) *(((uint8 *) mb) + j) = (uint8) v; \
    else if (sz == sizeof (uint16)) *(((uint16 *) mb) + ((uint32) j)) = (uint16) v; \
    else if (sz == sizeof (uint32)) *(((uint32 *) mb) + ((uint32) j)) = (uint32) v; \
    else *(((t_uint64 *) mb) + ((uint32) j)) = v;
#else
#define SZ_LOAD(sz,v,mb,j) \
    if (sz == sizeof (uint8)) v = *(((uint8 *) mb) + ((uint32) j)); \
    else if (sz == sizeof (uint16)) v = *(((uint16 *) mb) + ((uint32) j)); \
    else v = *(((uint32 *) mb) + ((uint32) j));
#define SZ_STORE(sz,v,mb,j) \
    if (sz == sizeof (uint8)) *(((uint8 *) mb) + ((uint32) j)) = (uint8) v; \
    else if (sz == sizeof (uint16)) *(((uint16 *) mb) + ((uint32) j)) = (uint16) v; \
    else *(((uint32 *) mb) + ((uint32) j)) = v;
#endif
#define GET_SWITCHES(cp) \
    if ((cp = get_sim_sw (cp)) == NULL) return SCPE_INVSW
#define GET_RADIX(val,dft) \
    val = sim_get_radix (NULL, sim_switches, dft);

/* The per-simulator pointers can be overrriden by a VM init routine */

char* (*sim_vm_read) (char *ptr, int32 size, FILE *stream) = NULL;
void (*sim_vm_post) (t_bool from_scp) = NULL;
CTAB *sim_vm_cmd = NULL;
void (*sim_vm_fprint_addr) (FILE *st, DEVICE *dptr, t_addr addr) = NULL;
t_addr (*sim_vm_parse_addr) (DEVICE *dptr, char *cptr, char **tptr) = NULL;
t_bool (*sim_vm_fprint_stopped) (FILE *st, t_stat reason) = NULL;
t_bool (*sim_vm_is_subroutine_call) (t_addr **ret_addrs) = NULL;

/* Prototypes */

/* Set and show command processors */

t_stat set_dev_radix (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat set_dev_enbdis (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat set_dev_debug (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat set_dev_unit_append (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat set_unit_enbdis (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat ssh_break (FILE *st, char *cptr, int32 flg);
t_stat show_cmd_fi (FILE *ofile, int32 flag, char *cptr);
t_stat show_config (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_queue (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_time (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_mod_names (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_show_commands (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_log_names (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_dev_radix (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_dev_debug (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_dev_logicals (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_dev_modifiers (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_dev_show_commands (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_version (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_break (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr);
t_stat show_device (FILE *st, DEVICE *dptr, int32 flag);
t_stat show_unit (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag);
t_stat show_all_mods (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flg);
t_stat show_one_mod (FILE *st, DEVICE *dptr, UNIT *uptr, MTAB *mptr, char *cptr, int32 flag);
t_stat sim_check_console (int32 sec);
t_stat sim_save (FILE *sfile);
t_stat sim_rest (FILE *rfile);

/* Breakpoint package */

t_stat sim_brk_init (void);
t_stat sim_brk_set (t_addr loc, int32 sw, int32 ncnt, char *act);
t_stat sim_brk_clr (t_addr loc, int32 sw);
t_stat sim_brk_clrall (int32 sw);
t_stat sim_brk_show (FILE *st, t_addr loc, int32 sw);
t_stat sim_brk_showall (FILE *st, int32 sw);
char *sim_brk_getact (char *buf, int32 size);
void sim_brk_clract (void);
void sim_brk_npc (uint32 cnt);
BRKTAB *sim_brk_new (t_addr loc);

/* Commands support routines */

SCHTAB *get_search (char *cptr, int32 radix, SCHTAB *schptr);
int32 test_search (t_value val, SCHTAB *schptr);
char *get_glyph_gen (char *iptr, char *optr, char mchar, t_bool uc);
int32 get_switches (char *cptr);
t_stat get_aval (t_addr addr, DEVICE *dptr, UNIT *uptr);
t_value get_rval (REG *rptr, uint32 idx);
void put_rval (REG *rptr, uint32 idx, t_value val);
void fprint_help (FILE *st);
void fprint_stopped (FILE *st, t_stat r);
void fprint_capac (FILE *st, DEVICE *dptr, UNIT *uptr);
char *read_line (char *ptr, int32 size, FILE *stream);
char *read_line_p (char *prompt, char *ptr, int32 size, FILE *stream);
REG *find_reg_glob (char *ptr, char **optr, DEVICE **gdptr);

/* Forward references */

t_stat scp_attach_unit (DEVICE *dptr, UNIT *uptr, char *cptr);
t_stat scp_detach_unit (DEVICE *dptr, UNIT *uptr);
t_bool qdisable (DEVICE *dptr);
t_stat attach_err (UNIT *uptr, t_stat stat);
t_stat detach_all (int32 start_device, t_bool shutdown);
t_stat assign_device (DEVICE *dptr, char *cptr);
t_stat deassign_device (DEVICE *dptr);
t_stat ssh_break_one (FILE *st, int32 flg, t_addr lo, int32 cnt, char *aptr);
t_stat run_boot_prep (void);
t_stat exdep_reg_loop (FILE *ofile, SCHTAB *schptr, int32 flag, char *cptr,
    REG *lowr, REG *highr, uint32 lows, uint32 highs);
t_stat ex_reg (FILE *ofile, t_value val, int32 flag, REG *rptr, uint32 idx);
t_stat dep_reg (int32 flag, char *cptr, REG *rptr, uint32 idx);
t_stat exdep_addr_loop (FILE *ofile, SCHTAB *schptr, int32 flag, char *cptr,
    t_addr low, t_addr high, DEVICE *dptr, UNIT *uptr);
t_stat ex_addr (FILE *ofile, int32 flag, t_addr addr, DEVICE *dptr, UNIT *uptr);
t_stat dep_addr (int32 flag, char *cptr, t_addr addr, DEVICE *dptr,
    UNIT *uptr, int32 dfltinc);
t_stat step_svc (UNIT *ptr);
void sub_args_local (char *instr, char *tmpbuf, int32 maxstr, char *do_arg[]);
int32 get_radix_local (const char *cptr, int32 switches, int32 default_radix);

/* Extension interface */

char *sim_vm_release;
void (*sub_args) (char *iptr, char *optr, int32 len, char *args []) = sub_args_local;
int32 (*sim_get_radix) (const char *cptr, int32 switches, int32 default_radix) = get_radix_local;
char * (*sim_vm_unit_name) (const UNIT *uptr) = NULL;

/* Global data */

DEVICE *sim_dflt_dev = NULL;
UNIT *sim_clock_queue = NULL;
int32 sim_interval = 0;
int32 sim_switches = 0;
FILE *sim_ofile = NULL;
SCHTAB *sim_schptr = FALSE;
DEVICE *sim_dfdev = NULL;
UNIT *sim_dfunit = NULL;
int32 sim_opt_out = 0;
int32 sim_is_running = 0;
uint32 sim_brk_summ = 0;
uint32 sim_brk_types = 0;
uint32 sim_brk_dflt = 0;
char *sim_brk_act = NULL;
BRKTAB *sim_brk_tab = NULL;
int32 sim_brk_ent = 0;
int32 sim_brk_lnt = 0;
int32 sim_brk_ins = 0;
t_bool sim_brk_pend[SIM_BKPT_N_SPC] = { FALSE };
t_addr sim_brk_ploc[SIM_BKPT_N_SPC] = { 0 };
int32 sim_quiet = 0;
int32 sim_step = 0;
static double sim_time;
static uint32 sim_rtime;
static int32 noqueue_time;
volatile int32 stop_cpu = 0;
t_value *sim_eval = NULL;
FILE *sim_log = NULL;                                   /* log file */
FILE *sim_deb = NULL;                                   /* debug file */
static SCHTAB sim_stab;
char *sim_prog_name;                                    /* pointer to the executable name */
uint32 sim_ref_type = REF_NONE;                         /* reference type */

static UNIT sim_step_unit = { UDATA (&step_svc, 0, 0)  };
#if defined USE_INT64
static const char *sim_si64 = "64b data";
#else
static const char *sim_si64 = "32b data";
#endif
#if defined USE_ADDR64
static const char *sim_sa64 = "64b addresses";
#else
static const char *sim_sa64 = "32b addresses";
#endif
#if defined (USE_NETWORK) || defined (USE_SHARED)
const char *eth_capabilities(void);
#else
#define eth_capabilities()     "no Ethernet"
#endif


/* Tables and strings */

const char save_vercur[] = "V3.5";
const char save_ver32[] = "V3.2";
const char save_ver30[] = "V3.0";
const char *scp_error_messages[] = {
    "Address space exceeded",
    "Unit not attached",
    "I/O error",
    "Checksum error",
    "Format error",
    "Unit not attachable",
    "File open error",
    "Memory exhausted",
    "Invalid argument",
    "Step expired",
    "Unknown command",
    "Read only argument",
    "Command not completed",
    "Simulation stopped",
    "Goodbye",
    "Console input I/O error",
    "Console output I/O error",
    "End of file",
    "Relocation error",
    "No settable parameters",
    "Unit already attached",
    "Hardware timer error",
    "SIGINT handler setup error",
    "Console terminal setup error",
    "Subscript out of range",
    "Command not allowed",
    "Unit disabled",
    "Read only operation not allowed",
    "Invalid switch",
    "Missing value",
    "Too few arguments",
    "Too many arguments",
    "Non-existent device",
    "Non-existent unit",
    "Non-existent register",
    "Non-existent parameter",
    "Nested DO command limit exceeded",
    "Internal error",
    "Invalid magtape record length",
    "Console Telnet connection lost",
    "Console Telnet connection timed out",
    "Console Telnet output stall",
    "Assertion failed"
    };

const size_t size_map[] = { sizeof (int8),
    sizeof (int8), sizeof (int16), sizeof (int32), sizeof (int32)
#if defined (USE_INT64)
    , sizeof (t_int64), sizeof (t_int64), sizeof (t_int64), sizeof (t_int64)
#endif
};

const t_value width_mask[] = { 0,
    0x1, 0x3, 0x7, 0xF,
    0x1F, 0x3F, 0x7F, 0xFF,
    0x1FF, 0x3FF, 0x7FF, 0xFFF,
    0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF,
    0x1FFFF, 0x3FFFF, 0x7FFFF, 0xFFFFF,
    0x1FFFFF, 0x3FFFFF, 0x7FFFFF, 0xFFFFFF,
    0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF, 0xFFFFFFF,
    0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF
#if defined (USE_INT64)
    , 0x1FFFFFFFF, 0x3FFFFFFFF, 0x7FFFFFFFF, 0xFFFFFFFFF,
    0x1FFFFFFFFF, 0x3FFFFFFFFF, 0x7FFFFFFFFF, 0xFFFFFFFFFF,
    0x1FFFFFFFFFF, 0x3FFFFFFFFFF, 0x7FFFFFFFFFF, 0xFFFFFFFFFFF,
    0x1FFFFFFFFFFF, 0x3FFFFFFFFFFF, 0x7FFFFFFFFFFF, 0xFFFFFFFFFFFF,
    0x1FFFFFFFFFFFF, 0x3FFFFFFFFFFFF, 0x7FFFFFFFFFFFF, 0xFFFFFFFFFFFFF,
    0x1FFFFFFFFFFFFF, 0x3FFFFFFFFFFFFF, 0x7FFFFFFFFFFFFF, 0xFFFFFFFFFFFFFF,
    0x1FFFFFFFFFFFFFF, 0x3FFFFFFFFFFFFFF,
    0x7FFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFF,
    0x1FFFFFFFFFFFFFFF, 0x3FFFFFFFFFFFFFFF,
    0x7FFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF
#endif
    };

static CTAB cmd_table[] = {
    { "RESET", &reset_cmd, 0,
      "r{eset} {ALL|<device>}   reset simulator\n" },
    { "EXAMINE", &exdep_cmd, EX_E,
      "e{xamine} <list>         examine memory or registers\n" },
    { "IEXAMINE", &exdep_cmd, EX_E+EX_I,
      "ie{xamine} <list>        interactive examine memory or registers\n" },
    { "DEPOSIT", &exdep_cmd, EX_D,
      "d{eposit} <list> <val>   deposit in memory or registers\n" },
    { "IDEPOSIT", &exdep_cmd, EX_D+EX_I,
      "id{eposit} <list>        interactive deposit in memory or registers\n" },
    { "EVALUATE", &eval_cmd, 0,
      "ev{aluate} <expr>        evaluate symbolic expression\n" },
    { "RUN", &run_cmd, RU_RUN,
      "ru{n} {new PC}           reset and start simulation\n" },
    { "GO", &run_cmd, RU_GO,
      "go {new PC}              start simulation\n" },
    { "STEP", &run_cmd, RU_STEP,
      "s{tep} {n}               simulate n instructions\n" },
    { "CONTINUE", &run_cmd, RU_CONT,
      "c{ont}                   continue simulation\n" },
    { "BOOT", &run_cmd, RU_BOOT,
      "b{oot} <unit>            bootstrap unit\n" },
    { "BREAK", &brk_cmd, SSH_ST,
      "br{eak} <list>           set breakpoints\n" },
    { "NOBREAK", &brk_cmd, SSH_CL,
      "nobr{eak} <list>         clear breakpoints\n" },
    { "ATTACH", &attach_cmd, 0,
      "at{tach} <unit> <file>   attach file to simulated unit\n" },
    { "DETACH", &detach_cmd, 0,
      "det{ach} <unit>          detach file from simulated unit\n" },
    { "ASSIGN", &assign_cmd, 0,
      "as{sign} <device> <name> assign logical name for device\n" },
    { "DEASSIGN", &deassign_cmd, 0,
      "dea{ssign} <device>      deassign logical name for device\n" },
    { "SAVE", &save_cmd, 0,
      "sa{ve} <file>            save simulator to file\n" },
    { "RESTORE", &restore_cmd, 0,
      "rest{ore}|ge{t} <file>   restore simulator from file\n" },
    { "GET", &restore_cmd, 0, NULL },
    { "LOAD", &load_cmd, 0,
      "l{oad} <file> {<args>}   load binary file\n" },
    { "DUMP", &load_cmd, 1,
      "du(mp) <file> {<args>}   dump binary file\n" },
    { "EXIT", &exit_cmd, 0,
      "exi{t}|q{uit}|by{e}      exit from simulation\n" },
    { "QUIT", &exit_cmd, 0, NULL },
    { "BYE", &exit_cmd, 0, NULL },
    { "SET", &set_cmd, 0,
      "set console arg{,arg...} set console options\n"
      "set break <list>         set breakpoints\n"
      "set nobreak <list>       clear breakpoints\n"
      "set throttle x{M|K|%%}    set simulation rate\n"
      "set nothrottle           set simulation rate to maximum\n"
      "set <dev> OCT|DEC|HEX    set device display radix\n"
      "set <dev> ENABLED        enable device\n"
      "set <dev> DISABLED       disable device\n"
      "set <dev> DEBUG{=arg}    set device debug flags\n"
      "set <dev> NODEBUG={arg}  clear device debug flags\n"
      "set <dev> APPEND         set first unit's position for appending\n"
      "set <dev> arg{,arg...}   set device parameters (see show modifiers)\n"
      "set <unit> ENABLED       enable unit\n"
      "set <unit> DISABLED      disable unit\n"
      "set <unit> APPEND        set unit's position for appending\n"
      "set <unit> arg{,arg...}  set unit parameters (see show modifiers)\n"
      },
    { "SHOW", &show_cmd, 0,
      "sh{ow} br{eak} <list>    show breakpoints\n"
      "sh{ow} con{figuration}   show configuration\n"
      "sh{ow} cons{ole} {arg}   show console options\n"
      "sh{ow} dev{ices}         show devices\n"
      "sh{ow} m{odifiers}       show modifiers for all devices\n"
      "sh{ow} s{how}            show SHOW commands for all devices\n"
      "sh{ow} n{ames}           show logical names\n"
      "sh{ow} q{ueue}           show event queue\n"
      "sh{ow} ti{me}            show simulated time\n"
      "sh{ow} th{rottle}        show simulation rate\n"
      "sh{ow} ve{rsion}         show simulator version\n"
      "sh{ow} <dev> RADIX       show device display radix\n"
      "sh{ow} <dev> DEBUG       show device debug flags\n"
      "sh{ow} <dev> MODIFIERS   show device modifiers\n"
      "sh{ow} <dev> NAMES       show device logical name\n"
      "sh{ow} <dev> SHOW        show device SHOW commands\n"
      "sh{ow} <dev> {arg,...}   show device parameters\n"
      "sh{ow} <unit> {arg,...}  show unit parameters\n"  },
    { "DO", &do_cmd, 1,
      "do <file> {arg,arg...}   process command file\n" },
    { "ECHO", &echo_cmd, 0,
      "echo <string>            display <string>\n" },
    { "ASSERT", &assert_cmd, 0,
      "assert {<dev>} <cond>    test simulator state against condition\n" },
    { "HELP", &help_cmd, 0,
      "h{elp}                   type this message\n"
      "h{elp} <command>         type help for command\n" },
    { "!", &spawn_cmd, 0,
      "!                        execute local command interpreter\n"
      "! <command>              execute local host command\n" },
    { NULL, NULL, 0 }
    };

/* SET command tables */

static CTAB set_glob_tab[] = {
    { "CONSOLE", &sim_set_console, 0 },
    { "BREAK", &brk_cmd, SSH_ST },
    { "NOBREAK", &brk_cmd, SSH_CL },
    { "TELNET", &sim_set_telnet, 0 },                   /* deprecated */
    { "NOTELNET", &sim_set_notelnet, 0 },               /* deprecated */
    { "LOG", &sim_set_logon, 0 },                       /* deprecated */
    { "NOLOG", &sim_set_logoff, 0 },                    /* deprecated */
    { "DEBUG", &sim_set_debon, 0 },                     /* deprecated */
    { "NODEBUG", &sim_set_deboff, 0 },                  /* deprecated */
    { "THROTTLE", &sim_set_throt, 1 },
    { "NOTHROTTLE", &sim_set_throt, 0 },
    { NULL, NULL, 0 }
    };

static C1TAB set_dev_tab[] = {
    { "OCTAL", &set_dev_radix, 8 },
    { "DECIMAL", &set_dev_radix, 10 },
    { "HEX", &set_dev_radix, 16 },
    { "ENABLED", &set_dev_enbdis, 1 },
    { "DISABLED", &set_dev_enbdis, 0 },
    { "APPEND", &set_dev_unit_append, 0 },
    { "DEBUG", &set_dev_debug, 1 },
    { "NODEBUG", &set_dev_debug, 0 },
    { NULL, NULL, 0 }
    };

static C1TAB set_unit_tab[] = {
    { "ENABLED", &set_unit_enbdis, 1 },
    { "DISABLED", &set_unit_enbdis, 0 },
    { "APPEND", &set_dev_unit_append, 0 },
    { NULL, NULL, 0 }
    };

/* SHOW command tables */

static SHTAB show_glob_tab[] = {
    { "CONFIGURATION", &show_config, 0 },
    { "DEVICES", &show_config, 1 },
    { "QUEUE", &show_queue, 0 },
    { "TIME", &show_time, 0 },
    { "MODIFIERS", &show_mod_names, 0 },
    { "NAMES", &show_log_names, 0 },
    { "SHOW", &show_show_commands, 0 },
    { "VERSION", &show_version, 1 },
    { "CONSOLE", &sim_show_console, 0 },
    { "BREAK", &show_break, 0 },
    { "LOG", &sim_show_log, 0 },                        /* deprecated */
    { "TELNET", &sim_show_telnet, 0 },                  /* deprecated */
    { "DEBUG", &sim_show_debug, 0 },                    /* deprecated */
    { "THROTTLE", &sim_show_throt, 0 },
    { "CLOCKS", &sim_show_timers, 0 },
    { NULL, NULL, 0 }
    };

static SHTAB show_dev_tab[] = {
    { "RADIX", &show_dev_radix, 0 },
    { "DEBUG", &show_dev_debug, 0 },
    { "MODIFIERS", &show_dev_modifiers, 1 },            /* 1 = return error if no mods */
    { "NAMES", &show_dev_logicals, 0 },
    { "SHOW", &show_dev_show_commands, 0 },
    { NULL, NULL, 0 }
    };

static SHTAB show_unit_tab[] = {
    { NULL, NULL, 0 }
    };

/* Main command loop */

int main (int argc, char *argv[])
{
char cbuf[CBUFSIZE], gbuf[CBUFSIZE], *cptr, *cmdargs[10] = { NULL };
int32 i, sw;
t_bool lookswitch;
t_stat stat;
CTAB *cmdp;

#if defined (__MWERKS__) && defined (macintosh)
argc = ccommand (&argv);
#endif

*cbuf = 0;                                              /* init arg buffer */
sim_switches = 0;                                       /* init switches */
lookswitch = TRUE;
sim_prog_name = cmdargs [0] = argv [0];                 /* save a pointer to the program name */
for (i = 1; i < argc; i++) {                            /* loop thru args */
    if (argv[i] == NULL)                                /* paranoia */
        continue;
    else                                                /* if not null */
        cmdargs[i] = argv[i];                           /*   then set the corresponding argument pointer */
    if ((*argv[i] == '-') && lookswitch) {              /* switch? */
        if ((sw = get_switches (argv[i])) < 0) {
            fprintf (stderr, "Invalid switch %s\n", argv[i]);
            return 0;
            }
        sim_switches = sim_switches | sw;
        }
    else {
        if ((strlen (argv[i]) + strlen (cbuf) + 1) >= CBUFSIZE) {
            fprintf (stderr, "Argument string too long\n");
            return 0;
            }
        if (*cbuf)                                      /* concat args */
            strcat (cbuf, " ");
        strcat (cbuf, argv[i]);
        lookswitch = FALSE;                             /* no more switches */
        }
    }                                                   /* end for */
sim_quiet = sim_switches & SWMASK ('Q');                /* -q means quiet */

sim_init_sock ();                                       /* init socket capabilities */

#if defined (USE_VM_INIT)
(*sim_vm_init)();                                       /* call once only */
#endif
sim_finit();                                            /* init fio package */
stop_cpu = 0;
sim_interval = 0;
sim_time = sim_rtime = 0;
noqueue_time = 0;
sim_clock_queue = NULL;
sim_is_running = 0;
sim_log = NULL;
if (sim_emax <= 0)
    sim_emax = 1;
sim_timer_init ();

if ((stat = sim_ttinit ()) != SCPE_OK) {
    fprintf (stderr, "Fatal terminal initialization error\n%s\n",
        scp_error_messages[stat - SCPE_BASE]);
    return 0;
    }
if ((sim_eval = (t_value *) calloc (sim_emax, sizeof (t_value))) == NULL) {
    fprintf (stderr, "Unable to allocate examine buffer\n");
    return 0;
    };
if ((stat = reset_all_p (0)) != SCPE_OK) {
    fprintf (stderr, "Fatal simulator initialization error\n%s\n",
        scp_error_messages[stat - SCPE_BASE]);
    return 0;
    }
if ((stat = sim_brk_init ()) != SCPE_OK) {
    fprintf (stderr, "Fatal breakpoint table initialization error\n%s\n",
        scp_error_messages[stat - SCPE_BASE]);
    return 0;
    }
if (!sim_quiet) {
    printf ("\n");
    show_version (stdout, NULL, NULL, 0, NULL);
    }
if (sim_dflt_dev == NULL)                               /* if no default */
    sim_dflt_dev = sim_devices[0];

if (*cbuf)                                              /* cmd file arg? */
    stat = find_cmd ("DO")->action (0, cbuf);           /* proc cmd file */
else if (*argv[0]) {                                    /* sim name arg? */
    char nbuf[PATH_MAX + 7], *np;                       /* "path.ini" */
    nbuf[0] = '"';                                      /* starting " */
    strncpy (nbuf + 1, argv[0], PATH_MAX + 1);          /* copy sim name */
    if (np = match_ext (nbuf, "EXE"))                   /* remove .exe */
        *np = 0;
    strcat (nbuf, ".ini\"");                            /* add .ini" */
    stat = find_cmd ("DO")->action (-1, nbuf);          /* proc cmd file */
    }

while (stat != SCPE_EXIT) {                             /* in case exit */
    if (cptr = sim_brk_getact (cbuf, CBUFSIZE))         /* pending action? */
        printf ("sim> %s\n", cptr);                     /* echo */
    else if (sim_vm_read != NULL) {                     /* sim routine? */
        printf ("sim> ");                               /* prompt */
        cptr = (*sim_vm_read) (cbuf, CBUFSIZE, stdin);
        }
    else cptr = read_line_p ("sim> ", cbuf, CBUFSIZE, stdin);/* read with prompt*/
    if (cptr == NULL)                                   /* ignore EOF */
        continue;
    if (*cptr == 0)                                     /* ignore blank */
        continue;
    sub_args (cbuf, gbuf, CBUFSIZE, cmdargs);           /* substitute arguments */
    if (sim_log)                                        /* log cmd */
        fprintf (sim_log, "sim> %s\n", cptr);
    cptr = get_glyph (cptr, gbuf, 0);                   /* get command glyph */
    sim_switches = 0;                                   /* init switches */
    if (cmdp = find_cmd (gbuf))                         /* lookup command */
        stat = cmdp->action (cmdp->arg, cptr);          /* if found, exec */
    else stat = SCPE_UNK;
    if (stat >= SCPE_BASE)                              /* error? */
        sim_printf ("%s\n", scp_error_messages[stat - SCPE_BASE]);
    if (sim_vm_post != NULL)
        (*sim_vm_post) (TRUE);
    }                                                   /* end while */

detach_all (0, TRUE);                                   /* close device files */
tmxr_post_logs (TRUE);                                  /* close all mux log files */
sim_set_deboff (0, NULL);                               /* close debug */
sim_set_logoff (0, NULL);                               /* close log */
sim_set_notelnet (0, NULL);                             /* close Telnet */
sim_ttclose ();                                         /* close console */
return 0;
}

/* Find command routine */

CTAB *find_cmd (char *gbuf)
{
CTAB *cmdp = NULL;

if (sim_vm_cmd)                                         /* try ext commands */
    cmdp = find_ctab (sim_vm_cmd, gbuf);
if (cmdp == NULL)                                       /* try regular cmds */
    cmdp = find_ctab (cmd_table, gbuf);
return cmdp;
}

/* Exit command */

t_stat exit_cmd (int32 flag, char *cptr)
{
return SCPE_EXIT;
}

/* Help command */

void fprint_help (FILE *st)
{
CTAB *cmdp;

for (cmdp = sim_vm_cmd; cmdp && (cmdp->name != NULL); cmdp++) {
    if (cmdp->help)
        fputs (cmdp->help, st);
    }
for (cmdp = cmd_table; cmdp && (cmdp->name != NULL); cmdp++) {
    if (cmdp->help && (!sim_vm_cmd || !find_ctab (sim_vm_cmd, cmdp->name)))
        fputs (cmdp->help, st);
    }
return;
}

t_stat help_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
CTAB *cmdp;

GET_SWITCHES (cptr);
if (*cptr) {
    cptr = get_glyph (cptr, gbuf, 0);
    if (*cptr)
        return SCPE_2MARG;
    if (cmdp = find_cmd (gbuf))
        sim_printf ("%s", cmdp->help);
    else return SCPE_ARG;
    }
else {
    fprint_help (stdout);
    if (sim_log)
        fprint_help (sim_log);
    }
return SCPE_OK;
}

/* Spawn command */

t_stat spawn_cmd (int32 flag, char *cptr)
{
if ((cptr == NULL) || (strlen (cptr) == 0))
    cptr = getenv("SHELL");
if ((cptr == NULL) || (strlen (cptr) == 0))
    cptr = getenv("ComSpec");
#if defined (VMS)
if ((cptr == NULL) || (strlen (cptr) == 0))
    cptr = "SPAWN/INPUT=SYS$COMMAND:";
#endif
fflush(stdout);                                         /* flush stdout */
if (sim_log)                                            /* flush log if enabled */
    fflush (sim_log);
system (cptr);
#if defined (VMS)
printf ("\n");
#endif

return SCPE_OK;
}

/* Echo command */

t_stat echo_cmd (int32 flag, char *cptr)
{
sim_printf ("%s\n", cptr);
return SCPE_OK;
}

/* Do command

   Syntax: DO {-E} {-V} <filename> {<arguments>...}

   -E causes all command errors to be fatal; without it, only EXIT and ASSERT
   failure will stop a command file.

   -V causes commands to be echoed before execution.

   Note that SCPE_STEP ("Step expired") is considered a note and not an error
   and so does not abort command execution when using -E.

   Inputs:
        flag    =   caller and nesting level indicator
        fcptr   =   filename and optional arguments, space-separated
   Outputs:
        status  =   error status

   The "flag" input value indicates the source of the call, as follows:

        -1      =   initialization file (no error if not found)
         0      =   command line file
         1      =   "DO" command
        >1      =   nested "DO" command


   Implementation notes:

    1. The "SWMASK ('V')" flag is 010000000, so we use that as an upper bit flag
       to propagate echo status to sub-DOs.  This does not interfere with the
       level count in the lower bits of the "flag" parameter.
*/

#define SCPE_DOFAILED   0040000                         /* fail in DO, not subproc */

t_stat do_cmd (int32 flag, char *fcptr)
{
char *cptr, cbuf[CBUFSIZE], gbuf[CBUFSIZE], *c, quote, *do_arg[10];
FILE *fpin;
CTAB *cmdp;
int32 echo, nargs, errabort;
t_bool interactive, isdo, staying;
t_stat stat;
char *ocptr;

stat = SCPE_OK;
staying = TRUE;
interactive = (flag > 0);                               /* issued interactively? */
if (interactive) {                                      /* get switches */
    GET_SWITCHES (fcptr);
    }
echo = sim_switches & SWMASK ('V');                     /* -v means echo */
errabort = sim_switches & SWMASK ('E');                 /* -e means abort on error */

if (flag >= 0)                                          /* if this is not the initialization file */
    echo = echo | flag & ~0377;                         /*   then propagate the echo flag to the current level */

c = fcptr;
for (nargs = 0; nargs < 10; ) {                         /* extract arguments */
    while (isspace (*c))                                /* skip blanks */
        c++;
    if (*c == 0)                                        /* all done? */
        do_arg [nargs++] = NULL;                        /* null argument */
    else {
        if (*c == '\'' || *c == '"')                    /* quoted string? */
            quote = *c++;
        else quote = 0;
        do_arg[nargs++] = c;                            /* save start */
        while (*c && (quote ? (*c != quote) : !isspace (*c)))
            c++;
        if (*c)                                         /* term at quote/spc */
            *c++ = 0;
        }
    }                                                   /* end for */

if ((nargs <= 0) || (do_arg [0] == NULL))               /* need at least 1 */
    return SCPE_2FARG;
if ((fpin = fopen (do_arg[0], "r")) == NULL) {          /* file failed to open? */
    if (flag == 0)                                      /* cmd line file? */
         fprintf (stderr, "Can't open file %s\n", do_arg[0]);
    if (flag > 1)
        return SCPE_OPENERR | SCPE_DOFAILED;            /* return failure with flag */
    else
        return SCPE_OPENERR;                            /* return failure */
    }
if (flag < 1)                                           /* start at level 1 */
    flag = 1;

do {
    ocptr = cptr = sim_brk_getact (cbuf, CBUFSIZE);     /* get bkpt action */
    if (!ocptr)                                         /* no pending action? */
         ocptr = cptr = read_line (cbuf, CBUFSIZE, fpin); /* get cmd line */
    sub_args (cbuf, gbuf, CBUFSIZE, do_arg);            /* substitute args */
    if (cptr == NULL) {                                 /* EOF? */
        stat = SCPE_OK;                                 /* set good return */
        break;
        }
    if (*cptr == 0)                                     /* ignore blank */
        continue;
    if (echo)                                           /* echo if -v */
        sim_printf("%s> %s\n", do_arg[0], cptr);        /* include command filename in echo */
    cptr = get_glyph (cptr, gbuf, 0);                   /* get command glyph */
    sim_switches = 0;                                   /* init switches */
    isdo = FALSE;
    if (cmdp = find_cmd (gbuf)) {                       /* lookup command */
        isdo = (MATCH_CMD (gbuf, "DO") == 0);
        if (isdo) {                                     /* DO command? */
            if (flag >= DO_NEST_LVL)                    /* nest too deep? */
                stat = SCPE_NEST;
            else stat = cmdp->action (echo | flag + 1, cptr); /* exec DO cmd and propagate echo flag */
            }
        else stat = cmdp->action (cmdp->arg, cptr);     /* exec other cmd */
        }
    else stat = SCPE_UNK;                               /* bad cmd given */
    staying = (stat != SCPE_EXIT) &&                    /* decide if staying */
              (stat != SCPE_AFAIL) &&
              (!errabort || (stat < SCPE_BASE) || (stat == SCPE_STEP));
    if ((stat >= SCPE_BASE) && (stat != SCPE_EXIT) &&   /* error from cmd? */
        (stat != SCPE_STEP)) {
        if (!echo &&                                    /* report if not echoing */
            (!isdo || (stat & SCPE_DOFAILED))) {        /* and not from DO return */
            sim_printf("%s> %s\n", do_arg[0], ocptr);
            }
        stat = stat & ~SCPE_DOFAILED;                   /* remove possible flag */
        }
    if ((staying || !interactive) &&                    /* report error if staying */
        (stat >= SCPE_BASE))                            /* or in cmdline file */
        sim_printf ("%s\n", scp_error_messages[stat - SCPE_BASE]);
    if (sim_vm_post != NULL)
        (*sim_vm_post) (TRUE);
    } while (staying);

fclose (fpin);                                          /* close file */
return stat;
}

/* Substitute_args - replace %n tokens in 'instr' with the do command's arguments

   Calling sequence
   instr        =       input string
   tmpbuf       =       temp buffer
   maxstr       =       min (len (instr), len (tmpbuf))
   do_arg[10]   =       arguments

   Token "%0" represents the command file name.

   The input sequence "\%" represents a literal "%", and "\\" represents a
   literal "\".  All other character combinations are rendered literally.

   Omitted parameters result in null-string substitutions.
*/

void sub_args_local (char *instr, char *tmpbuf, int32 maxstr, char *do_arg[])
{
char *ip, *op, *ap, *oend = tmpbuf + maxstr - 2;

for (ip = instr, op = tmpbuf; *ip && (op < oend); ) {
    if ((ip [0] == '\\') &&                             /* literal escape? */
        ((ip [1] == '%') || (ip [1] == '\\'))) {        /*   and followed by '%' or '\'? */
        ip++;                                           /* skip '\' */
        *op++ = *ip++;                                  /* copy escaped char */
        }
    else if ((*ip == '%') &&                            /* %n = sub */
             ((ip[1] >= '0') && (ip[1] <= ('9')))) {
        ap = do_arg[ip[1] - '0'];
        ip = ip + 2;
        if (ap) {                                       /* non-null arg? */
            while (*ap && (op < oend))                  /* copy the argument */
                *op++ = *ap++;
            }
        }
    else *op++ = *ip++;                                 /* literal character */
    }
*op = 0;                                                /* term buffer */
strcpy (instr, tmpbuf);
return;
}

/* ASSERT command.

   The ASSERT command tests the value of a device register or memory location.
   The syntax is:

     ASSERT {<dev>} <reg/addr>{<logical-op><value>}<conditional-op><value>

   If <dev> is not specified, CPU is assumed.  If a register is specified, the
   <values> are expressed in the radix specified by the register.  If a memory
   address is specified, it is expressed in the device's address radix, and the
   <value>s are expressed in the device's data radix.  The <logical-op> and
   <conditional-op> are the same as those allowed for examine and deposit search
   specifications.

   If the assertion is true, the command returns SCPE_OK.  If it is false,
   SCPE_AFAIL is returned.
*/

t_stat assert_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE], *gptr, *tptr;
REG *rptr;
uint32 idx;
t_value val;
t_stat r;
t_addr addr = 0;                                        /* quiet uninitialized warning */

cptr = get_sim_opt (CMD_OPT_SW|CMD_OPT_DFT, cptr, &r);  /* get sw, default */
sim_stab.boolop = -1;                                   /* no relational op dflt */
if (*cptr == 0)                                         /* must be more */
    return SCPE_2FARG;
cptr = get_glyph (cptr, gbuf, 0);                       /* get register */
rptr = find_reg (gbuf, &gptr, sim_dfdev);               /* parse register */

if (rptr == NULL) {                                                 /* if the test is not a register */
    if (sim_vm_parse_addr != NULL)                                  /*   then if a VM address parser is present */
        addr = sim_vm_parse_addr (sim_dfdev, gbuf, &gptr);          /*     then call it to parse the address */
    else                                                            /*   otherwise */
        addr = (t_addr) strtotv (gbuf, &gptr, sim_dfdev->aradix);   /*     parse a numeric address */

    if (gbuf == gptr)                                   /* if neither a register nor an address is found */
        return SCPE_NXREG;                              /*   then fail with an error */
    }

else {                                                  /* otherwise a valid register was given */
    if (*gptr == '[') {                                 /* subscript? */
        if (rptr->depth <= 1)                           /* array register? */
            return SCPE_ARG;
        idx = (uint32) strtotv (++gptr, &tptr, 10);     /* convert index */
        if ((gptr == tptr) || (*tptr++ != ']'))
            return SCPE_ARG;
        gptr = tptr;                                    /* update */
        }
    else
        idx = 0;                                        /* not array */
    if (idx >= rptr->depth)                             /* validate subscript */
        return SCPE_SUB;
    }

if (*gptr != 0)                                         /* more? must be search */
    get_glyph (gptr, gbuf, 0);
else {
    if (*cptr == 0)                                     /* must be more */
        return SCPE_2FARG;
    cptr = get_glyph (cptr, gbuf, 0);                   /* get search cond */
    }
if (*cptr != 0)                                         /* must be done */
    return SCPE_2MARG;

if (rptr == NULL) {                                         /* if this is a memory test */
    if (!get_search (gbuf, sim_dfdev->dradix, &sim_stab)    /*   then parse the test condition; if it fails */
      || (sim_stab.boolop == -1))                           /*     or if there is no test operator */
        return SCPE_MISVAL;                                 /*       then report that the tested value is missing */

    r = get_aval (addr, sim_dfdev, sim_dfunit);         /* get the memory data into sim_eval */

    if (r != SCPE_OK)                                   /* if the access failed */
        return r;                                       /*   then report the reason */

    if (test_search (sim_eval [0], &sim_stab))          /* test the condition; if it succeeds */
        return SCPE_OK;                                 /*   then return success */
    }

else {                                                  /* otherwise this is a register test */
    if (!get_search (gbuf, rptr->radix, &sim_stab)      /*   so parse the test condition; if it fails */
      || (sim_stab.boolop == -1))                       /*     or if there is no test operator */
        return SCPE_MISVAL;                             /*       then report that the tested value is missing */
    val = get_rval (rptr, idx);                         /* get register value */
    if (test_search (val, &sim_stab))                   /* test condition */
        return SCPE_OK;
    }

return SCPE_AFAIL;                                      /* condition fails */
}

/* Set command */

t_stat set_cmd (int32 flag, char *cptr)
{
int32 lvl, radix;
t_stat r;
char gbuf[CBUFSIZE], *cvptr, *svptr;
DEVICE *dptr;
UNIT *uptr;
MTAB *mptr;
CTAB *gcmdp;
C1TAB *ctbr, *glbr;

GET_SWITCHES (cptr);                                    /* get switches */
if (*cptr == 0)                                         /* must be more */
    return SCPE_2FARG;
cptr = get_glyph (cptr, gbuf, 0);                       /* get glob/dev/unit */

if (dptr = find_dev (gbuf)) {                           /* device match? */
    uptr = dptr->units;                                 /* first unit */
    ctbr = set_dev_tab;                                 /* global table */
    lvl = MTAB_VDV;                                     /* device match */
    }
else if (dptr = find_unit (gbuf, &uptr)) {              /* unit match? */
    if (uptr == NULL)                                   /* invalid unit */
        return SCPE_NXUN;
    ctbr = set_unit_tab;                                /* global table */
    lvl = MTAB_VUN;                                     /* unit match */
    }
else if (gcmdp = find_ctab (set_glob_tab, gbuf))        /* global? */
    return gcmdp->action (gcmdp->arg, cptr);            /* do the rest */
else return SCPE_NXDEV;                                 /* no match */
if (*cptr == 0)                                         /* must be more */
    return SCPE_2FARG;

while (*cptr != 0) {                                    /* do all mods */
    cptr = get_glyph (svptr = cptr, gbuf, ',');         /* get modifier */
    if (cvptr = strchr (gbuf, '='))                     /* = value? */
        *cvptr++ = 0;
    for (mptr = dptr->modifiers; mptr && (mptr->mask != 0); mptr++) {
        if ((mptr->mstring) &&                          /* match string */
            (MATCH_CMD (gbuf, mptr->mstring) == 0)) {   /* matches option? */
            if (mptr->mask & MTAB_XTD) {                /* extended? */
                if ((lvl & mptr->mask) == 0)
                    return SCPE_ARG;
                if ((lvl & MTAB_VUN) && (uptr->flags & UNIT_DIS))
                    return SCPE_UDIS;                   /* unit disabled? */
                if (mptr->valid) {                      /* validation rtn? */
                    if (cvptr && (mptr->mask & MTAB_NC)) {
                        get_glyph_nc (svptr, gbuf, ',');
                        if (cvptr = strchr (gbuf, '='))
                            *cvptr++ = 0;
                        }
                    r = mptr->valid (uptr, mptr->match, cvptr, mptr->desc);
                    if (r != SCPE_OK)
                        return r;
                    }
                else if (!mptr->desc)                   /* value desc? */
                    break;
                else if (cvptr)                         /* = value? */
                    return SCPE_ARG;
                else *((int32 *) mptr->desc) = mptr->match;
                }                                       /* end if xtd */
            else {                                      /* old style */
                if (cvptr)                              /* = value? */
                    return SCPE_ARG;
                if (uptr->flags & UNIT_DIS)             /* disabled? */
                     return SCPE_UDIS;
                if ((mptr->valid) &&                    /* invalid? */
                    ((r = mptr->valid (uptr, mptr->match, cvptr, mptr->desc)) != SCPE_OK))
                    return r;
                uptr->flags = (uptr->flags & ~(mptr->mask)) |
                    (mptr->match & mptr->mask);         /* set new value */
                }                                       /* end else xtd */
            break;                                      /* terminate for */
            }                                           /* end if match */
        }                                               /* end for */
    if (!mptr || (mptr->mask == 0)) {                   /* no match? */
        if (glbr = find_c1tab (ctbr, gbuf)) {           /* global match? */
            r = glbr->action (dptr, uptr, glbr->arg, cvptr);    /* do global */
            if (r != SCPE_OK)
                return r;
            }
        else if (lvl == MTAB_VDV                            /* otherwise if this is a SET <dev> command */
          && (radix = sim_get_radix (gbuf, 0, 0))) {        /*   and a radix extension keyword was supplied */
            r = set_dev_radix (dptr, uptr, radix, cvptr);   /*     then set the requested radix default */
            if (r != SCPE_OK)                               /* if the set failed */
                return r;                                   /*   then report the error */
            }
        else if (!dptr->modifiers)                      /* no modifiers? */
            return SCPE_NOPARAM;
        else return SCPE_NXPAR;
        }                                               /* end if no mat */
    }                                                   /* end while */
return SCPE_OK;                                         /* done all */
}

/* Match CTAB/CTAB1 name */

CTAB *find_ctab (CTAB *tab, char *gbuf)
{
for (; tab->name != NULL; tab++) {
    if (MATCH_CMD (gbuf, tab->name) == 0)
        return tab;
    }
return NULL;
}

C1TAB *find_c1tab (C1TAB *tab, char *gbuf)
{
for (; tab->name != NULL; tab++) {
    if (MATCH_CMD (gbuf, tab->name) == 0)
        return tab;
    }
return NULL;
}

/* Set device data radix routine */

t_stat set_dev_radix (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
if (cptr)
    return SCPE_ARG;
dptr->dradix = flag & 037;
return SCPE_OK;
}

/* Set device enabled/disabled routine */

t_stat set_dev_enbdis (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
UNIT *up;
uint32 i;

if (cptr)
    return SCPE_ARG;
if ((dptr->flags & DEV_DISABLE) == 0)                   /* allowed? */
    return SCPE_NOFNC;
if (flag) {                                             /* enable? */
    if ((dptr->flags & DEV_DIS) == 0)                   /* already enb? ok */
        return SCPE_OK;
    dptr->flags = dptr->flags & ~DEV_DIS;               /* no, enable */
    }
else {
    if (dptr->flags & DEV_DIS)                          /* already dsb? ok */
        return SCPE_OK;
    for (i = 0; i < dptr->numunits; i++) {              /* check units */
        up = (dptr->units) + i;                         /* att or active? */
        if ((up->flags & UNIT_ATT) || sim_is_active (up))
            return SCPE_NOFNC;                          /* can't do it */
        }
    dptr->flags = dptr->flags | DEV_DIS;                /* disable */
    }
if (dptr->reset)                                        /* reset device */
    return dptr->reset (dptr);
else return SCPE_OK;
}

/* Set unit enabled/disabled routine */

t_stat set_unit_enbdis (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
if (cptr)
    return SCPE_ARG;
if (!(uptr->flags & UNIT_DISABLE))                      /* allowed? */
    return SCPE_NOFNC;
if (flag)                                               /* enb? enable */
    uptr->flags = uptr->flags & ~UNIT_DIS;
else {
    if ((uptr->flags & UNIT_ATT) ||                     /* dsb */
        sim_is_active (uptr))                           /* more tests */
        return SCPE_NOFNC;
    uptr->flags = uptr->flags | UNIT_DIS;               /* disable */
    }
return SCPE_OK;
}

/* Set unit position to end for appending */

t_stat set_dev_unit_append (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
if (cptr)
    return SCPE_ARG;
if (uptr == NULL)
    return SCPE_NOFNC;
if (((uptr->flags & UNIT_SEQ) == 0) ||                  /* must be sequential, */
    ((uptr->flags & (UNIT_RO | UNIT_ROABLE)) != 0) ||   /* not RO settable */
    ((uptr->flags & UNIT_MUSTBUF) != 0) ||              /* not buffered */
    ((uptr->dynflags & UNIT_PIPE) != 0))                /* not a pipe */
    return SCPE_NOFNC;
if ((uptr->flags & UNIT_ATT) == 0)                      /* must be attached */
    return SCPE_UNATT;
if (sim_fseek (uptr->fileref, 0, SEEK_END) != 0)        /* seek to end */
   return SCPE_IOERR;
uptr->pos = (t_addr) sim_ftell (uptr->fileref);         /* set at EOF */
return SCPE_OK;
}
/* Set device debug enabled/disabled routine */

t_stat set_dev_debug (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
DEBTAB *dep;

if ((dptr->flags & DEV_DEBUG) == 0)
    return SCPE_NOFNC;
if (cptr == NULL) {                                     /* no arguments? */
    dptr->dctrl = flag;                                 /* disable/enable w/o table */
    if (flag && dptr->debflags) {                       /* enable with table? */
        for (dep = dptr->debflags; dep->name != NULL; dep++)
            dptr->dctrl = dptr->dctrl | dep->mask;      /* set all */
        }
    return SCPE_OK;
    }
if (dptr->debflags == NULL)                             /* must have table */
    return SCPE_ARG;
while (*cptr) {
    cptr = get_glyph (cptr, gbuf, ';');                 /* get debug flag */
    for (dep = dptr->debflags; dep->name != NULL; dep++) {
        if (strcmp (dep->name, gbuf) == 0) {            /* match? */
            if (flag)
                dptr->dctrl = dptr->dctrl | dep->mask;
            else dptr->dctrl = dptr->dctrl & ~dep->mask;
            break;
            }
        }                                               /* end for */
    if (dep->mask == 0)                                 /* no match? */
        return SCPE_ARG;
    }                                                   /* end while */
return SCPE_OK;
}

/* Show command */

t_stat show_cmd (int32 flag, char *cptr)
{
t_stat r;

cptr = get_sim_opt (CMD_OPT_SW|CMD_OPT_OF, cptr, &r);   /* get sw, ofile */
if (!cptr)                                              /* error? */
    return r;
if (sim_ofile) {                                        /* output file? */
    r = show_cmd_fi (sim_ofile, flag, cptr);            /* do show */
    fclose (sim_ofile);
    }
else {
    r = show_cmd_fi (stdout, flag, cptr);               /* no, stdout, log */
    if (sim_log)
        show_cmd_fi (sim_log, flag, cptr);
    }
return r;
}

t_stat show_cmd_fi (FILE *ofile, int32 flag, char *cptr)
{
int32 lvl;
t_stat r;
char gbuf[CBUFSIZE], *cvptr;
DEVICE *dptr;
UNIT *uptr;
MTAB *mptr;
SHTAB *shtb, *shptr;

GET_SWITCHES (cptr);                                    /* get switches */
if (*cptr == 0)                                         /* must be more */
    return SCPE_2FARG;
cptr = get_glyph (cptr, gbuf, 0);                       /* get next glyph */
if (shptr = find_shtab (show_glob_tab, gbuf))           /* global? */
    return shptr->action (ofile, NULL, NULL, shptr->arg, cptr);

if (dptr = find_dev (gbuf)) {                           /* device match? */
    uptr = dptr->units;                                 /* first unit */
    shtb = show_dev_tab;                                /* global table */
    lvl = MTAB_VDV;                                     /* device match */
    }
else if (dptr = find_unit (gbuf, &uptr)) {              /* unit match? */
    if (uptr == NULL)                                   /* invalid unit */
        return SCPE_NXUN;
    if (uptr->flags & UNIT_DIS)                         /* disabled? */
        return SCPE_UDIS;
    shtb = show_unit_tab;                               /* global table */
    lvl = MTAB_VUN;                                     /* unit match */
    }
else return SCPE_NXDEV;                                 /* no match */

if (*cptr == 0) {                                       /* now eol? */
    return (lvl == MTAB_VDV)?
        show_device (ofile, dptr, 0):
        show_unit (ofile, dptr, uptr, -1);
    }

while (*cptr != 0) {                                    /* do all mods */
    cptr = get_glyph (cptr, gbuf, ',');                 /* get modifier */
    if (cvptr = strchr (gbuf, '='))                     /* = value? */
        *cvptr++ = 0;
    for (mptr = dptr->modifiers; mptr && (mptr->mask != 0); mptr++) {
        if (((mptr->mask & MTAB_XTD)?                   /* right level? */
            (mptr->mask & lvl): (MTAB_VUN & lvl)) &&
            ((mptr->disp && mptr->pstring &&            /* named disp? */
            (MATCH_CMD (gbuf, mptr->pstring) == 0)))) {
            if (cvptr && !(mptr->mask & MTAB_SHP))
                return SCPE_ARG;
            show_one_mod (ofile, dptr, uptr, mptr, cvptr, 1);
            break;
            }                                           /* end if */
        }                                               /* end for */
    if (!mptr || (mptr->mask == 0)) {                   /* no match? */
        if (shptr = find_shtab (shtb, gbuf)) {          /* global match? */
            r = shptr->action (ofile, dptr, uptr, shptr->arg, cptr);
            if (r != SCPE_OK)
                return r;
            }
        else if (!dptr->modifiers)                      /* no modifiers? */
            return SCPE_NOPARAM;
        else
            return SCPE_NXPAR;
        }                                               /* end if */
    }                                                   /* end while */
return SCPE_OK;
}

SHTAB *find_shtab (SHTAB *tab, char *gbuf)
{
for (; tab->name != NULL; tab++) {
    if (MATCH_CMD (gbuf, tab->name) == 0)
        return tab;
    }
return NULL;
}

/* Show device and unit */

t_stat show_device (FILE *st, DEVICE *dptr, int32 flag)
{
uint32 j, udbl, ucnt;
UNIT *uptr;

fprintf (st, "%s", sim_dname (dptr));                   /* print dev name */
if (qdisable (dptr)) {                                  /* disabled? */
    fprintf (st, ", disabled\n");
    return SCPE_OK;
    }
for (j = ucnt = udbl = 0; j < dptr->numunits; j++) {    /* count units */
    uptr = dptr->units + j;
    if (!(uptr->flags & UNIT_DIS))                      /* count enabled units */
        ucnt++;
    else if (uptr->flags & UNIT_DISABLE)
        udbl++;                                         /* count user-disabled */
    }
show_all_mods (st, dptr, dptr->units, MTAB_VDV);        /* show dev mods */
if (dptr->numunits == 0)
    fprintf (st, "\n");
else {
    if (ucnt == 0)
        fprintf (st, ", all units disabled\n");
    else if ((ucnt > 1) || (udbl > 0))
        fprintf (st, ", %d units\n", ucnt + udbl);
    else if (flag)
        fprintf (st, "\n");
    }
if (flag)                                               /* dev only? */
    return SCPE_OK;
for (j = 0; j < dptr->numunits; j++) {                  /* loop thru units */
    uptr = dptr->units + j;
    if ((uptr->flags & UNIT_DIS) == 0)
        show_unit (st, dptr, uptr, ucnt + udbl);
    }
return SCPE_OK;
}

t_stat show_unit (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag)
{
int32 u = (int32)(uptr - dptr->units);

if (flag > 1)
    fprintf (st, "  %s%d", sim_dname (dptr), u);
else if (flag < 0)
    fprintf (st, "%s%d", sim_dname (dptr), u);
if (uptr->flags & UNIT_FIX) {
    fprintf (st, ", ");
    fprint_capac (st, dptr, uptr);
    }
if (uptr->flags & UNIT_ATT) {
    fprintf (st, ", attached to %s", uptr->filename);
    if (uptr->flags & UNIT_RO)
        fprintf (st, ", read only");
    }
else if (uptr->flags & UNIT_ATTABLE)
    fprintf (st, ", not attached");
show_all_mods (st, dptr, uptr, MTAB_VUN);               /* show unit mods */
fprintf (st, "\n");
return SCPE_OK;
}

void fprint_capac (FILE *st, DEVICE *dptr, UNIT *uptr)
{
t_addr kval = (uptr->flags & UNIT_BINK)? 1024: 1000;
t_addr mval = kval * kval;
t_addr psize = uptr->capac;
char scale, width;

if ((dptr->dwidth / dptr->aincr) > 8)
    width = 'W';
else width = 'B';
if (uptr->capac < (kval * 10))
    scale = 0;
else if (uptr->capac < (mval * 10)) {
    scale = 'K';
    psize = psize / kval;
    }
else {
    scale = 'M';
    psize = psize / mval;
    }
fprint_val (st, (t_value) psize, 10, T_ADDR_W, PV_LEFT);
if (scale)
    fputc (scale, st);
fputc (width, st);
return;
}

/* Show <global name> processors  */

t_stat show_version (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
int32 vmaj = SIM_MAJOR, vmin = SIM_MINOR, vpat = SIM_PATCH, vdelt = SIM_DELTA;

if (cptr && (*cptr != 0))
    return SCPE_2MARG;
fprintf (st, "%s simulator V%d.%d-%d", sim_name, vmaj, vmin, vpat);
if (sim_vm_release != NULL)                             /* if a release string is defined */
    fprintf (st, " Release %s", sim_vm_release);        /*   then display it */
if (vdelt)
    fprintf (st, "(%d)", vdelt);
if (flag)
    fprintf (st, " [%s, %s, %s]", sim_si64, sim_sa64, eth_capabilities());
fprintf (st, "\n");
return SCPE_OK;
}

t_stat show_config (FILE *st, DEVICE *dnotused, UNIT *unotused, int32 flag, char *cptr)
{
int32 i;
DEVICE *dptr;

if (cptr && (*cptr != 0))
    return SCPE_2MARG;
fprintf (st, "%s simulator configuration\n\n", sim_name);
for (i = 0; (dptr = sim_devices[i]) != NULL; i++)
    show_device (st, dptr, flag);
return SCPE_OK;
}

t_stat show_log_names (FILE *st, DEVICE *dnotused, UNIT *unotused, int32 flag, char *cptr)
{
int32 i;
DEVICE *dptr;

if (cptr && (*cptr != 0))
    return SCPE_2MARG;
for (i = 0; (dptr = sim_devices[i]) != NULL; i++)
    show_dev_logicals (st, dptr, NULL, 1, cptr);
return SCPE_OK;
}

t_stat show_dev_logicals (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
if (dptr->lname)
    fprintf (st, "%s -> %s\n", dptr->lname, dptr->name);
else if (!flag)
    fputs ("no logical name assigned\n", st);
return SCPE_OK;
}

t_stat show_queue (FILE *st, DEVICE *dnotused, UNIT *unotused, int32 flag, char *cptr)
{
DEVICE *dptr;
UNIT *uptr;
int32 accum;
char *vptr;

if (cptr && (*cptr != 0))
    return SCPE_2MARG;
if (sim_clock_queue == NULL) {
    fprintf (st, "%s event queue empty, time = %.0f\n",
        sim_name, sim_time);
    return SCPE_OK;
    }
fprintf (st, "%s event queue status, time = %.0f\n",
     sim_name, sim_time);
accum = 0;
for (uptr = sim_clock_queue; uptr != NULL; uptr = uptr->next) {
    if (uptr == &sim_step_unit)
        fprintf (st, "  Step timer");
    else if (sim_vm_unit_name && (vptr = sim_vm_unit_name (uptr)))
        fprintf (st, "  %s", vptr);
    else if ((dptr = find_dev_from_unit (uptr)) != NULL) {
        fprintf (st, "  %s", sim_dname (dptr));
        if (dptr->numunits > 1)
            fprintf (st, " unit %d", (int32) (uptr - dptr->units));
        }
    else fprintf (st, "  Unknown");
    fprintf (st, " at %d\n", accum + uptr->time);
    accum = accum + uptr->time;
    }
return SCPE_OK;
}

t_stat show_time (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
if (cptr && (*cptr != 0))
    return SCPE_2MARG;
fprintf (st, "Time:\t%.0f\n", sim_time);
return SCPE_OK;
}

t_stat show_break (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
t_stat r;

if (cptr && (*cptr != 0))
    r = ssh_break (st, cptr, 1);  /* more? */
else r = sim_brk_showall (st, sim_switches);
return r;
}

t_stat show_dev_radix (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
fprintf (st, "Radix=%d\n", dptr->dradix);
return SCPE_OK;
}

t_stat show_dev_debug (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
int32 any = 0;
DEBTAB *dep;

if (dptr->flags & DEV_DEBUG) {
    if (dptr->dctrl == 0)
        fputs ("Debugging disabled", st);
    else if (dptr->debflags == NULL)
        fputs ("Debugging enabled", st);
    else {
        fputs ("Debug=", st);
        for (dep = dptr->debflags; dep->name != NULL; dep++) {
            if (dptr->dctrl & dep->mask) {
                if (any)
                    fputc (';', st);
                fputs (dep->name, st);
                any = 1;
                }
            }
        }
    fputc ('\n', st);
    return SCPE_OK;
    }
else return SCPE_NOFNC;
}

/* Show modifiers */

t_stat show_mod_names (FILE *st, DEVICE *dnotused, UNIT *unotused, int32 flag, char *cptr)
{
int32 i;
DEVICE *dptr;

if (cptr && (*cptr != 0))                               /* now eol? */
    return SCPE_2MARG;
for (i = 0; (dptr = sim_devices[i]) != NULL; i++)
    show_dev_modifiers (st, dptr, NULL, flag, cptr);
return SCPE_OK;
}

t_stat show_dev_modifiers (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
int32 any, enb;
MTAB *mptr;
DEBTAB *dep;

any = enb = 0;
if (dptr->modifiers) {
    for (mptr = dptr->modifiers; mptr->mask != 0; mptr++) {
        if (mptr->mstring) {
            if (strcmp (mptr->mstring, "ENABLED") == 0)
                enb = 1;
            if (any++)
                fprintf (st, ", %s", mptr->mstring);
            else fprintf (st, "%s\t%s", sim_dname (dptr), mptr->mstring);
            }
        }
    }
if (dptr->flags & DEV_DEBUG) {
    if (any++)
        fprintf (st, ", DEBUG, NODEBUG");
    else fprintf (st, "%s\tDEBUG, NODEBUG", sim_dname (dptr));
    }
if (!enb && (dptr->flags & DEV_DISABLE)) {
    if (any++)
        fprintf (st, ", ENABLED, DISABLED");
    else fprintf (st, "%s\tENABLED, DISABLED", sim_dname (dptr));
    }
if (any)
    fprintf (st, "\n");
else if (flag == 1)
    return SCPE_NOPARAM;
if ((dptr->flags & DEV_DEBUG) && dptr->debflags) {
    fprintf (st, "%s\tDEBUG=", sim_dname (dptr));
    for (dep = dptr->debflags; dep->name != NULL; dep++)
        fprintf (st, "%s%s", ((dep == dptr->debflags) ? "" : ";"), dep->name);
    fprintf (st, "\n");
    }
return SCPE_OK;
}

t_stat show_all_mods (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag)
{
MTAB *mptr;

if (dptr->modifiers == NULL)
    return SCPE_OK;
for (mptr = dptr->modifiers; mptr->mask != 0; mptr++) {
    if (mptr->pstring && ((mptr->mask & MTAB_XTD)?
        ((mptr->mask & flag) && !(mptr->mask & MTAB_NMO)):
        ((MTAB_VUN & flag) && ((uptr->flags & mptr->mask) == mptr->match)))) {
        fputs (", ", st);
        show_one_mod (st, dptr, uptr, mptr, NULL, 0);
        }
    }
return SCPE_OK;
}

t_stat show_one_mod (FILE *st, DEVICE *dptr, UNIT *uptr, MTAB *mptr,
    char *cptr, int32 flag)
{
//t_value val;

if (mptr->disp)
    mptr->disp (st, uptr, mptr->match, cptr? cptr: mptr->desc);
//else if ((mptr->mask & MTAB_XTD) && (mptr->mask & MTAB_VAL)) {
//    REG *rptr = (REG *) mptr->desc;
//    fprintf (st, "%s=", mptr->pstring);
//    val = get_rval (rptr, 0);
//    fprint_val (st, val, rptr->radix, rptr->width,
//        rptr->flags & REG_FMT);
//    }
else fputs (mptr->pstring, st);
if (flag && !((mptr->mask & MTAB_XTD) && (mptr->mask & MTAB_NMO)))
    fputc ('\n', st);
return SCPE_OK;
}

/* Show show commands */

t_stat show_show_commands (FILE *st, DEVICE *dnotused, UNIT *unotused, int32 flag, char *cptr)
{
int32 i;
DEVICE *dptr;

if (cptr && (*cptr != 0))                               /* now eol? */
    return SCPE_2MARG;
for (i = 0; (dptr = sim_devices[i]) != NULL; i++)
    show_dev_show_commands (st, dptr, NULL, flag, cptr);
return SCPE_OK;
}

t_stat show_dev_show_commands (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
{
int32 any = 0;
MTAB *mptr;

if (dptr->modifiers) {
    any = 0;
    for (mptr = dptr->modifiers; mptr->mask != 0; mptr++) {
        if ((!mptr->disp) || (!mptr->pstring))
            continue;
        if (('\0' == *mptr->pstring) ||
            (0 == (mptr->mask & MTAB_XTD)) ||
            (0 == (mptr->mask & MTAB_VDV)))     /* Device Option */
            continue;
        if (any++)
            fprintf (st, ", %s", mptr->pstring);
        else fprintf (st, "sh{ow} %s\t%s", sim_dname (dptr), mptr->pstring);
        }
    if (any)
        fprintf (st, "\n");
    any = 0;
    for (mptr = dptr->modifiers; mptr->mask != 0; mptr++) {
        if ((!mptr->disp) || (!mptr->pstring))
            continue;
        if (('\0' == *mptr->pstring) ||
            (0 == (mptr->mask & MTAB_XTD)) ||
            (0 == (mptr->mask & MTAB_VUN)))     /* Unit Option */
            continue;
        if (any++)
            fprintf (st, ", %s", mptr->pstring);
        else fprintf (st, "sh{ow} %sn\t%s", sim_dname (dptr), mptr->pstring);
        }
    if (any)
        fprintf (st, "\n");
    }
return SCPE_OK;
}

/* Breakpoint commands */

t_stat brk_cmd (int32 flg, char *cptr)
{
GET_SWITCHES (cptr);                                    /* get switches */
return ssh_break (NULL, cptr, flg);                     /* call common code */
}

t_stat ssh_break (FILE *st, char *cptr, int32 flg)
{
char gbuf[CBUFSIZE], *tptr, *t1ptr, *aptr;
DEVICE *dptr = sim_dflt_dev;
UNIT *uptr;
t_stat r;
t_addr lo, hi, max;
int32 cnt;

if (sim_brk_types == 0)
    return SCPE_NOFNC;
if (dptr == NULL)                                       /* sanity checks */
    return SCPE_IERR;
uptr = dptr->units;
if (uptr == NULL)
    return SCPE_IERR;
max = uptr->capac - 1;
if (aptr = strchr (cptr, ';')) {                        /* ;action? */
    if (flg != SSH_ST)                                  /* only on SET */
        return SCPE_ARG;
    *aptr++ = 0;                                        /* separate strings */
    }
if (*cptr == 0) {                                       /* no argument? */
    lo = (t_addr) get_rval (sim_PC, 0);                 /* use PC */
    return ssh_break_one (st, flg, lo, 0, aptr);
    }
while (*cptr) {
    cptr = get_glyph (cptr, gbuf, ',');
    tptr = get_range (dptr, gbuf, &lo, &hi, dptr->aradix, max, 0);
    if (tptr == NULL)
        return SCPE_ARG;
    if (*tptr == '[') {
        cnt = (int32) strtotv (tptr + 1, &t1ptr, 10);
        if ((tptr == t1ptr) || (*t1ptr != ']') || (flg != SSH_ST))
            return SCPE_ARG;
        tptr = t1ptr + 1;
        }
    else cnt = 0;
    if (*tptr != 0)
        return SCPE_ARG;
    if ((lo == 0) && (hi == max)) {
        if (flg == SSH_CL)
            sim_brk_clrall (sim_switches);
        else if (flg == SSH_SH)
            sim_brk_showall (st, sim_switches);
        else return SCPE_ARG;
        }
    else {
        for ( ; lo <= hi; lo = lo + 1) {
            r = ssh_break_one (st, flg, lo, cnt, aptr);
            if (r != SCPE_OK)
                return r;
            }
        }
    }
return SCPE_OK;
}

t_stat ssh_break_one (FILE *st, int32 flg, t_addr lo, int32 cnt, char *aptr)
{
switch (flg) {

    case SSH_ST:
        return sim_brk_set (lo, sim_switches, cnt, aptr);
        break;

    case SSH_CL:
        return sim_brk_clr (lo, sim_switches);
        break;

    case SSH_SH:
        return sim_brk_show (st, lo, sim_switches);
        break;

    default:
        return SCPE_ARG;
    }
}

/* Reset command and routines

   re[set]              reset all devices
   re[set] all          reset all devices
   re[set] device       reset specific device
*/

t_stat reset_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
DEVICE *dptr;

GET_SWITCHES (cptr);                                    /* get switches */
if (*cptr == 0)                                         /* reset(cr) */
    return (reset_all (0));
cptr = get_glyph (cptr, gbuf, 0);                       /* get next glyph */
if (*cptr != 0)                                         /* now eol? */
    return SCPE_2MARG;
if (strcmp (gbuf, "ALL") == 0)
    return (reset_all (0));
dptr = find_dev (gbuf);                                 /* locate device */
if (dptr == NULL)                                       /* found it? */
    return SCPE_NXDEV;
if (dptr->reset != NULL)
    return dptr->reset (dptr);
else return SCPE_OK;
}

/* Reset devices start..end

   Inputs:
        start   =       number of starting device
   Outputs:
        status  =       error status
*/

t_stat reset_all (uint32 start)
{
DEVICE *dptr;
uint32 i;
t_stat reason;

for (i = 0; i < start; i++) {
    if (sim_devices[i] == NULL)
        return SCPE_IERR;
    }
for (i = start; (dptr = sim_devices[i]) != NULL; i++) {
    if (dptr->reset != NULL) {
        reason = dptr->reset (dptr);
        if (reason != SCPE_OK)
            return reason;
        }
    }
return SCPE_OK;
}

/* Reset to powerup state

   Inputs:
        start   =       number of starting device
   Outputs:
        status  =       error status
*/

t_stat reset_all_p (uint32 start)
{
t_stat r;
int32 old_sw = sim_switches;

sim_switches = SWMASK ('P');
r = reset_all (start);
sim_switches = old_sw;
return r;
}

/* Load and dump commands

   lo[ad] filename {arg}        load specified file
   du[mp] filename {arg}        dump to specified file
*/

t_stat load_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
FILE *loadfile;
t_stat reason;

GET_SWITCHES (cptr);                                    /* get switches */
if (*cptr == 0)                                         /* must be more */
    return SCPE_2FARG;
cptr = get_glyph_nc (cptr, gbuf, 0);                    /* get file name */
loadfile = sim_fopen (gbuf, flag? "wb": "rb");          /* open for wr/rd */
if (loadfile == NULL)
    return SCPE_OPENERR;
GET_SWITCHES (cptr);                                    /* get switches */
reason = sim_load (loadfile, cptr, gbuf, flag);         /* load or dump */
fclose (loadfile);
return reason;
}

/* Attach command

   at[tach] unit file   attach specified unit to file
*/

t_stat attach_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
DEVICE *dptr;
UNIT *uptr;
t_stat r;

GET_SWITCHES (cptr);                                    /* get switches */
if (*cptr == 0)                                         /* must be more */
    return SCPE_2FARG;
cptr = get_glyph (cptr, gbuf, 0);                       /* get next glyph */
GET_SWITCHES (cptr);                                    /* get switches */
if (*cptr == 0)                                         /* now eol? */
    return SCPE_2FARG;
dptr = find_unit (gbuf, &uptr);                         /* locate unit */
if (dptr == NULL)                                       /* found dev? */
    return SCPE_NXDEV;
if (uptr == NULL)                                       /* valid unit? */
    return SCPE_NXUN;
if (uptr->flags & UNIT_ATT) {                           /* already attached? */
    if (!(uptr->dynflags & UNIT_ATTMULT)) {
        r = scp_detach_unit (dptr, uptr);               /* detach it */
        if (r != SCPE_OK)                               /* error? */
            return r;
        }
    else
        return SCPE_ALATT;
    }
sim_trim_endspc (cptr);                                 /* trim trailing spc */
return scp_attach_unit (dptr, uptr, cptr);              /* attach */
}

/* Call device-specific or file-oriented attach unit routine */

t_stat scp_attach_unit (DEVICE *dptr, UNIT *uptr, char *cptr)
{
if (uptr->flags & UNIT_DIS)                             /* disabled? */
    return SCPE_UDIS;
if (dptr->attach != NULL)                               /* device routine? */
    return dptr->attach (uptr, cptr);                   /* call it */
return attach_unit (uptr, cptr);                        /* no, std routine */
}

/* Attach unit to file */

#if !defined (S_ISFIFO)
  #if !defined (S_IFMT) && defined (_S_IFMT)
  #define S_IFMT   _S_IFMT
  #endif
  #if defined (_S_IFIFO)
  #define S_ISFIFO(m)   (((m) & S_IFMT) == _S_IFIFO)
  #else
  #define S_ISFIFO(m)   0
  #endif
#endif

t_stat attach_unit (UNIT *uptr, char *cptr)
{
DEVICE *dptr;
struct stat info;

if (!(uptr->flags & UNIT_ATTABLE))                      /* not attachable? */
    return SCPE_NOATT;
if ((dptr = find_dev_from_unit (uptr)) == NULL)
    return SCPE_NOATT;
if (dptr->flags & DEV_RAWONLY)                          /* raw mode only? */
    return SCPE_NOFNC;
uptr->filename = (char *) calloc (CBUFSIZE, sizeof (char)); /* alloc name buf */
if (uptr->filename == NULL)
    return SCPE_MEM;
strncpy (uptr->filename, cptr, CBUFSIZE);               /* save name */

if ((!stat (cptr, &info)) && S_ISFIFO (info.st_mode))   /* if file exists and is a pipe */
    if (uptr->flags & UNIT_SEQ) {                       /*   then if the unit is sequential */
        if (uptr->flags & (UNIT_RO | UNIT_ROABLE))      /* if the unit is readable */
            uptr->fileref = sim_fopen (cptr, "rb");     /*   then open the pipe for reading */
        else                                            /* otherwise */
            uptr->fileref = sim_fopen (cptr, "wb");     /*   open the pipe for writing */

        if (uptr->fileref == NULL)                          /* if the file failed to open */
            return attach_err (uptr, SCPE_OPENERR);         /*   then report the error */
        else                                                /* otherwise */
            uptr->dynflags = uptr->dynflags | UNIT_PIPE;    /*   set the pipe flag */
        }
    else                                                /* otherwise the unit is not sequential */
        return SCPE_NOFNC;                              /*   so it cannot be attached to a pipe */

else if (sim_switches & SWMASK ('R')) {                 /* read only? */
    if ((uptr->flags & (UNIT_RO | UNIT_ROABLE)) == 0)   /* allowed? */
        return attach_err (uptr, SCPE_NORO);            /* no, error */
    uptr->fileref = sim_fopen (cptr, "rb");             /* open rd only */
    if (uptr->fileref == NULL)                          /* open fail? */
        return attach_err (uptr, SCPE_OPENERR);         /* yes, error */
    uptr->flags = uptr->flags | UNIT_RO;                /* set rd only */
    if (!sim_quiet)
        sim_printf ("%s: unit is read only\n", sim_dname (dptr));
    }
else if (sim_switches & SWMASK ('N')) {                 /* new file only? */
    uptr->fileref = sim_fopen (cptr, "wb+");            /* open new file */
    if (uptr->fileref == NULL)                          /* open fail? */
        return attach_err (uptr, SCPE_OPENERR);         /* yes, error */
    if (!sim_quiet)
        sim_printf ("%s: creating new file\n", sim_dname (dptr));
    }
else {                                                  /* normal */
    uptr->fileref = sim_fopen (cptr, "rb+");            /* open r/w */
    if (uptr->fileref == NULL) {                        /* open fail? */
        if ((errno == EROFS) || (errno == EACCES)) {    /* read only? */
            if ((uptr->flags & (UNIT_RO | UNIT_ROABLE)) == 0)   /* allowed? */
                return attach_err (uptr, SCPE_NORO);            /* no error */
            uptr->fileref = sim_fopen (cptr, "rb");     /* open rd only */
            if (uptr->fileref == NULL)                  /* open fail? */
                return attach_err (uptr, SCPE_OPENERR); /* yes, error */
            uptr->flags = uptr->flags | UNIT_RO;        /* set rd only */
            if (!sim_quiet)
                sim_printf ("%s: unit is read only\n", sim_dname (dptr));
            }
        else {                                          /* doesn't exist */
            if (sim_switches & SWMASK ('E'))            /* must exist? */
                return attach_err (uptr, SCPE_OPENERR); /* yes, error */
            uptr->fileref = sim_fopen (cptr, "wb+");    /* open new file */
            if (uptr->fileref == NULL)                  /* open fail? */
                return attach_err (uptr, SCPE_OPENERR); /* yes, error */
            if (!sim_quiet)
                sim_printf ("%s: creating new file\n", sim_dname (dptr));
            }
        }                                               /* end if null */
    }                                                   /* end else */
if (uptr->flags & UNIT_BUFABLE) {                       /* buffer? */
    uint32 cap = ((uint32) uptr->capac) / dptr->aincr;  /* effective size */
    if (uptr->flags & UNIT_MUSTBUF)                     /* dyn alloc? */
        uptr->filebuf = calloc (cap, SZ_D (dptr));      /* allocate */
    if (uptr->filebuf == NULL)                          /* no buffer? */
        return attach_err (uptr, SCPE_MEM);             /* error */
    if (!sim_quiet)
        sim_printf ("%s: buffering file in memory\n", sim_dname (dptr));
    uptr->hwmark = (uint32)sim_fread (uptr->filebuf,    /* read file */
        SZ_D (dptr), cap, uptr->fileref);
    uptr->flags = uptr->flags | UNIT_BUF;               /* set buffered */
    }
uptr->flags = uptr->flags | UNIT_ATT;
uptr->pos = 0;
return SCPE_OK;
}

t_stat attach_err (UNIT *uptr, t_stat stat)
{
free (uptr->filename);
uptr->filename = NULL;
return stat;
}

/* Detach command

   det[ach] all         detach all units
   det[ach] unit        detach specified unit
*/

t_stat detach_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
DEVICE *dptr;
UNIT *uptr;

GET_SWITCHES (cptr);                                    /* get switches */
if (*cptr == 0)                                         /* must be more */
    return SCPE_2FARG;
cptr = get_glyph (cptr, gbuf, 0);                       /* get next glyph */
if (*cptr != 0)                                         /* now eol? */
    return SCPE_2MARG;
if (strcmp (gbuf, "ALL") == 0)
    return (detach_all (0, FALSE));
dptr = find_unit (gbuf, &uptr);                         /* locate unit */
if (dptr == NULL)                                       /* found dev? */
    return SCPE_NXDEV;
if (uptr == NULL)                                       /* valid unit? */
    return SCPE_NXUN;
return scp_detach_unit (dptr, uptr);                    /* detach */
}

/* Detach devices start..end

   Inputs:
        start   =       number of starting device
        shutdown =      TRUE if simulator shutting down
   Outputs:
        status  =       error status

   Note that during shutdown, detach routines for non-attachable devices
   will be called.  These routines can implement simulator shutdown.  Error
   returns during shutdown are ignored.
*/

t_stat detach_all (int32 start, t_bool shutdown)
{
uint32 i, j;
DEVICE *dptr;
UNIT *uptr;
t_stat r;

if ((start < 0) || (start > 1))
    return SCPE_IERR;
sim_ref_type = REF_NONE;                                /* use no references */
if (shutdown)
    sim_switches = sim_switches | SIM_SW_SHUT;          /* flag shutdown */
for (i = start; (dptr = sim_devices[i]) != NULL; i++) { /* loop thru dev */
    for (j = 0; j < dptr->numunits; j++) {              /* loop thru units */
        uptr = (dptr->units) + j;
        if ((uptr->flags & UNIT_ATT) ||                 /* attached? */
            (shutdown && dptr->detach &&                /* shutdown, spec rtn, */
            !(uptr->flags & UNIT_ATTABLE))) {           /* !attachable? */
            r = scp_detach_unit (dptr, uptr);           /* detach unit */

            if ((r != SCPE_OK) && !shutdown)            /* error and not shutting down? */
                return r;                               /* bail out now with error status */
            }
        }
    }
return SCPE_OK;
}

/* Call device-specific or file-oriented detach unit routine */

t_stat scp_detach_unit (DEVICE *dptr, UNIT *uptr)
{
if (dptr->detach != NULL)                               /* device routine? */
    return dptr->detach (uptr);
return detach_unit (uptr);                              /* no, standard */
}

/* Detach unit from file */

t_stat detach_unit (UNIT *uptr)
{
DEVICE *dptr;

if (uptr == NULL)
    return SCPE_IERR;
if (!(uptr->flags & UNIT_ATTABLE))                      /* attachable? */
    return SCPE_NOATT;
if (!(uptr->flags & UNIT_ATT))                          /* attached? */
    return SCPE_OK;
if ((dptr = find_dev_from_unit (uptr)) == NULL)
    return SCPE_OK;
if ((uptr->flags & UNIT_BUF) && (uptr->filebuf)) {      /* buffered? */
    uint32 cap = (uptr->hwmark + dptr->aincr - 1) / dptr->aincr;
    if (uptr->hwmark && ((uptr->flags & UNIT_RO) == 0)) {
        if (!sim_quiet)
            sim_printf ("%s: writing buffer to file\n", sim_dname (dptr));
        rewind (uptr->fileref);
        sim_fwrite (uptr->filebuf, SZ_D (dptr), cap, uptr->fileref);
        if (ferror (uptr->fileref))
            sim_perror ("I/O error");
        }
    if (uptr->flags & UNIT_MUSTBUF) {                   /* dyn alloc? */
        free (uptr->filebuf);                           /* free buf */
        uptr->filebuf = NULL;
        }
    uptr->flags = uptr->flags & ~UNIT_BUF;
    }
uptr->flags = uptr->flags & ~(UNIT_ATT |                /* clear ATT */
    ((uptr->flags & UNIT_ROABLE) ? UNIT_RO : 0));       /* clear RO if dynamic */
uptr->dynflags = uptr->dynflags & ~UNIT_PIPE;           /* clear the pipe flag */
free (uptr->filename);
uptr->filename = NULL;
if (fclose (uptr->fileref) == EOF)
    return SCPE_IOERR;
return SCPE_OK;
}

/* Assign command

   as[sign] device name assign logical name to device
*/

t_stat assign_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
DEVICE *dptr;

GET_SWITCHES (cptr);                                    /* get switches */
if (*cptr == 0)                                         /* must be more */
    return SCPE_2FARG;
cptr = get_glyph (cptr, gbuf, 0);                       /* get next glyph */
GET_SWITCHES (cptr);                                    /* get switches */
if (*cptr == 0)                                         /* now eol? */
    return SCPE_2FARG;
dptr = find_dev (gbuf);                                 /* locate device */
if (dptr == NULL)                                       /* found dev? */
    return SCPE_NXDEV;
cptr = get_glyph (cptr, gbuf, 0);                       /* get next glyph */
if (*cptr != 0)                                         /* must be eol */
    return SCPE_2MARG;
if (find_dev (gbuf))                                    /* name in use */
    return SCPE_ARG;
deassign_device (dptr);                                 /* release current */
return assign_device (dptr, gbuf);
}

t_stat assign_device (DEVICE *dptr, char *cptr)
{
dptr->lname = (char *) calloc (CBUFSIZE, sizeof (char));
if (dptr->lname == NULL)
    return SCPE_MEM;
strncpy (dptr->lname, cptr, CBUFSIZE);
return SCPE_OK;
}

/* Deassign command

   dea[ssign] device    deassign logical name
*/

t_stat deassign_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE];
DEVICE *dptr;

GET_SWITCHES (cptr);                                    /* get switches */
if (*cptr == 0)                                         /* must be more */
    return SCPE_2FARG;
cptr = get_glyph (cptr, gbuf, 0);                       /* get next glyph */
if (*cptr != 0)                                         /* now eol? */
    return SCPE_2MARG;
dptr = find_dev (gbuf);                                 /* locate device */
if (dptr == NULL)                                       /* found dev? */
    return SCPE_NXDEV;
return deassign_device (dptr);
}

t_stat deassign_device (DEVICE *dptr)
{
if (dptr->lname)
    free (dptr->lname);
dptr->lname = NULL;
return SCPE_OK;
}

/* Get device display name */

const char *sim_dname (DEVICE *dptr)
{
return (dptr->lname? dptr->lname: dptr->name);
}

/* Get unit display name */

const char *sim_uname (UNIT *uptr)
{
DEVICE *d;
static char uname[CBUFSIZE];

d = find_dev_from_unit(uptr);
if (!d)
    return "";
if (d->numunits == 1)
    sprintf (uname, "%s", sim_dname (d));
else
    sprintf (uname, "%s%d", sim_dname (d), (int)(uptr-d->units));
return uname;
}

/* Save command

   sa[ve] filename              save state to specified file
*/

t_stat save_cmd (int32 flag, char *cptr)
{
FILE *sfile;
t_stat r;
GET_SWITCHES (cptr);                                    /* get switches */
if (*cptr == 0)                                         /* must be more */
    return SCPE_2FARG;
sim_trim_endspc (cptr);
if ((sfile = sim_fopen (cptr, "wb")) == NULL)
    return SCPE_OPENERR;
r = sim_save (sfile);
fclose (sfile);
return r;
}

t_stat sim_save (FILE *sfile)
{
void *mbuf;
int32 l, t;
uint32 i, j;
t_addr k, high;
t_value val;
t_stat r;
t_bool zeroflg;
size_t sz;
DEVICE *dptr;
UNIT *uptr;
REG *rptr;

#define WRITE_I(xx) sim_fwrite (&(xx), sizeof (xx), 1, sfile)

fprintf (sfile, "%s\n%s\n%s\n%s\n%s\n%.0f\n",
    save_vercur,                                        /* [V2.5] save format */
    sim_name,                                           /* sim name */
    sim_si64, sim_sa64, eth_capabilities (),            /* [V3.5] options */
    sim_time);                                          /* [V3.2] sim time */
WRITE_I (sim_rtime);                                    /* [V2.6] sim rel time */

for (i = 0; (dptr = sim_devices[i]) != NULL; i++) {     /* loop thru devices */
    fputs (dptr->name, sfile);                          /* device name */
    fputc ('\n', sfile);
    if (dptr->lname)                                    /* [V3.0] logical name */
        fputs (dptr->lname, sfile);
    fputc ('\n', sfile);
    WRITE_I (dptr->flags);                              /* [V2.10] flags */
    for (j = 0; j < dptr->numunits; j++) {
        uptr = dptr->units + j;
        t = sim_activate_time (uptr);
        WRITE_I (j);                                    /* unit number */
        WRITE_I (t);                                    /* activation time */
        WRITE_I (uptr->u3);                             /* unit specific */
        WRITE_I (uptr->u4);
        WRITE_I (uptr->u5);                             /* [V3.0] more unit */
        WRITE_I (uptr->u6);
        WRITE_I (uptr->flags);                          /* [V2.10] flags */
        WRITE_I (uptr->capac);                          /* [V3.5] capacity */
        if (uptr->flags & UNIT_ATT)
            fputs (uptr->filename, sfile);
        fputc ('\n', sfile);
        if (((uptr->flags & (UNIT_FIX + UNIT_ATTABLE)) == UNIT_FIX) &&
             (dptr->examine != NULL) &&
             ((high = uptr->capac) != 0)) {             /* memory-like unit? */
            WRITE_I (high);                             /* [V2.5] write size */
            sz = SZ_D (dptr);
            if ((mbuf = calloc (SRBSIZ, sz)) == NULL) {
                fclose (sfile);
                return SCPE_MEM;
                }
            for (k = 0; k < high; ) {                   /* loop thru mem */
                zeroflg = TRUE;
                for (l = 0; (l < SRBSIZ) && (k < high); l++,
                     k = k + (dptr->aincr)) {           /* check for 0 block */
                    r = dptr->examine (&val, k, uptr, SIM_SW_REST);
                    if (r != SCPE_OK)
                        return r;
                    if (val) zeroflg = FALSE;
                    SZ_STORE (sz, val, mbuf, l);
                    }                                   /* end for l */
                if (zeroflg) {                          /* all zero's? */
                    l = -l;                             /* invert block count */
                    WRITE_I (l);                        /* write only count */
                    }
                else {
                    WRITE_I (l);                        /* block count */
                    sim_fwrite (mbuf, sz, l, sfile);
                    }
                }                                       /* end for k */
            free (mbuf);                                /* dealloc buffer */
            }                                           /* end if mem */
        else {                                          /* no memory */
            high = 0;                                   /* write 0 */
            WRITE_I (high);
            }                                           /* end else mem */
        }                                               /* end unit loop */
    t = -1;                                             /* end units */
    WRITE_I (t);                                        /* write marker */
    for (rptr = dptr->registers; (rptr != NULL) &&      /* loop thru regs */
         (rptr->name != NULL); rptr++) {
        fputs (rptr->name, sfile);                      /* name */
        fputc ('\n', sfile);
        WRITE_I (rptr->depth);                          /* [V2.10] depth */
        for (j = 0; j < rptr->depth; j++) {             /* loop thru values */
            val = get_rval (rptr, j);                   /* get value */
            WRITE_I (val);                              /* store */
            }
        }
    fputc ('\n', sfile);                                /* end registers */
    }
fputc ('\n', sfile);                                    /* end devices */
return (ferror (sfile))? SCPE_IOERR: SCPE_OK;           /* error during save? */
}

/* Restore command

   re[store] filename           restore state from specified file
*/

t_stat restore_cmd (int32 flag, char *cptr)
{
FILE *rfile;
t_stat r;

GET_SWITCHES (cptr);                                    /* get switches */
if (*cptr == 0)                                         /* must be more */
    return SCPE_2FARG;
sim_trim_endspc (cptr);
if ((rfile = sim_fopen (cptr, "rb")) == NULL)
    return SCPE_OPENERR;
r = sim_rest (rfile);
fclose (rfile);
return r;
}

t_stat sim_rest (FILE *rfile)
{
char buf[CBUFSIZE];
void *mbuf;
int32 j, blkcnt, limit, unitno, time, flg;
uint32 us, depth;
t_addr k, high, old_capac;
t_value val, max;
t_stat r;
size_t sz;
t_bool v35, v32;
DEVICE *dptr;
UNIT *uptr;
REG *rptr;

#define READ_S(xx) if (read_line ((xx), CBUFSIZE, rfile) == NULL) \
    return SCPE_IOERR;
#define READ_I(xx) if (sim_fread (&xx, sizeof (xx), 1, rfile) == 0) \
    return SCPE_IOERR;

sim_ref_type = REF_NONE;                                /* use no references */
READ_S (buf);                                           /* [V2.5+] read version */
v35 = v32 = FALSE;
if (strcmp (buf, save_vercur) == 0)                     /* version 3.5? */
    v35 = v32 = TRUE;
else if (strcmp (buf, save_ver32) == 0)                 /* version 3.2? */
    v32 = TRUE;
else if (strcmp (buf, save_ver30) != 0) {               /* version 3.0? */
    sim_printf ("Invalid file version: %s\n", buf);
    return SCPE_INCOMP;
    }
READ_S (buf);                                           /* read sim name */
if (strcmp (buf, sim_name)) {                           /* name match? */
    sim_printf ("Wrong system type: %s\n", buf);
    return SCPE_INCOMP;
    }
if (v35) {                                              /* [V3.5+] options */
    READ_S (buf);                                       /* integer size */
    if (strcmp (buf, sim_si64) != 0) {
        sim_printf ("Incompatible integer size, save file = %s\n", buf);
        return SCPE_INCOMP;
        }
    READ_S (buf);                                       /* address size */
    if (strcmp (buf, sim_sa64) != 0) {
        sim_printf ("Incompatible address size, save file = %s\n", buf);
        return SCPE_INCOMP;
        }
    READ_S (buf);                                       /* Ethernet */
    }
if (v32) {                                              /* [V3.2+] time as string */
    READ_S (buf);
    sscanf (buf, "%lf", &sim_time);
    }
else READ_I (sim_time);                                 /* sim time */
READ_I (sim_rtime);                                     /* [V2.6+] sim rel time */

for ( ;; ) {                                            /* device loop */
    READ_S (buf);                                       /* read device name */
    if (buf[0] == 0)                                    /* last? */
        break;
    if ((dptr = find_dev (buf)) == NULL) {              /* locate device */
        sim_printf ("Invalid device name: %s\n", buf);
        return SCPE_INCOMP;
        }
    READ_S (buf);                                       /* [V3.0+] logical name */
    deassign_device (dptr);                             /* delete old name */
    if ((buf[0] != 0) &&
        ((r = assign_device (dptr, buf)) != SCPE_OK))
        return r;
    READ_I (flg);                                       /* [V2.10+] ctlr flags */
    if (!v32)
        flg = ((flg & DEV_UFMASK_31) << (DEV_V_UF - DEV_V_UF_31)) |
            (flg & ~DEV_UFMASK_31);                     /* [V3.2+] flags moved */
    dptr->flags = (dptr->flags & ~DEV_RFLAGS) |         /* restore ctlr flags */
         (flg & DEV_RFLAGS);
    for ( ;; ) {                                        /* unit loop */
        sim_switches = SIM_SW_REST;                     /* flag rstr, clr RO */
        READ_I (unitno);                                /* unit number */
        if (unitno < 0)                                 /* end units? */
            break;
        if ((uint32) unitno >= dptr->numunits) {        /* too big? */
            sim_printf ("Invalid unit number: %s%d\n", sim_dname (dptr), unitno);
            return SCPE_INCOMP;
            }
        READ_I (time);                                  /* event time */
        uptr = (dptr->units) + unitno;
        sim_cancel (uptr);
        if (time > 0)
            sim_activate (uptr, time - 1);
        READ_I (uptr->u3);                              /* device specific */
        READ_I (uptr->u4);
        READ_I (uptr->u5);                              /* [V3.0+] more dev spec */
        READ_I (uptr->u6);
        READ_I (flg);                                   /* [V2.10+] unit flags */
        old_capac = uptr->capac;                        /* save current capacity */
        if (v35) {                                      /* [V3.5+] capacity */
            READ_I (uptr->capac);
            }
        if (!v32)
            flg = ((flg & UNIT_UFMASK_31) << (UNIT_V_UF - UNIT_V_UF_31)) |
                (flg & ~UNIT_UFMASK_31);                /* [V3.2+] flags moved */
        uptr->flags = (uptr->flags & ~UNIT_RFLAGS) |
            (flg & UNIT_RFLAGS);                        /* restore */
        READ_S (buf);                                   /* attached file */
        if ((uptr->flags & UNIT_ATT) &&                 /* unit currently attached? */
            !(dptr->flags & DEV_NET)) {                 /*  and not a net device? */
            r = scp_detach_unit (dptr, uptr);           /* detach it */
            if (r != SCPE_OK)
                return r;
            }
        if ((buf[0] != '\0') &&                         /* unit to be reattached? */
            !(dptr->flags & DEV_NET) &&                 /*  and not a net device? */
            ((uptr->flags & UNIT_ATTABLE) ||            /*  and unit is attachable */
             (dptr->attach != NULL))) {                 /*    or VM attach routine provided? */
            uptr->flags = uptr->flags & ~UNIT_DIS;      /* ensure device is enabled */
            if (flg & UNIT_RO)                          /* [V2.10+] saved flgs & RO? */
                sim_switches |= SWMASK ('R');           /* RO attach */
            r = scp_attach_unit (dptr, uptr, buf);      /* reattach unit */
            if (r != SCPE_OK)
                return r;
            }
        READ_I (high);                                  /* memory capacity */
        if (high > 0) {                                 /* [V2.5+] any memory? */
            if (((uptr->flags & (UNIT_FIX + UNIT_ATTABLE)) != UNIT_FIX) ||
                 (dptr->deposit == NULL)) {
                sim_printf ("Can't restore memory: %s%d\n", sim_dname (dptr), unitno);
                return SCPE_INCOMP;
                }
            if (high != old_capac) {                    /* size change? */
                uptr->capac = old_capac;                /* temp restore old */
                if ((dptr->flags & DEV_DYNM) &&
                    ((dptr->msize == NULL) ||
                     (dptr->msize (uptr, (int32) high, NULL, NULL) != SCPE_OK))) {
                    sim_printf ("Can't change memory size: %s%d\n",
                        sim_dname (dptr), unitno);
                    return SCPE_INCOMP;
                    }
                uptr->capac = high;                     /* new memory size */
                sim_printf ("Memory size changed: %s%d = ", sim_dname (dptr), unitno);
                fprint_capac (stdout, dptr, uptr);
                sim_printf ("\n");
                }
            sz = SZ_D (dptr);                           /* allocate buffer */
            if ((mbuf = calloc (SRBSIZ, sz)) == NULL)
                return SCPE_MEM;
            for (k = 0; k < high; ) {                   /* loop thru mem */
                READ_I (blkcnt);                        /* block count */
                if (blkcnt < 0)                         /* compressed? */
                    limit = -blkcnt;
                else limit = sim_fread (mbuf, sz, blkcnt, rfile);
                if (limit <= 0)                         /* invalid or err? */
                    return SCPE_IOERR;
                for (j = 0; j < limit; j++, k = k + (dptr->aincr)) {
                    if (blkcnt < 0)                     /* compressed? */
                        val = 0;
                    else SZ_LOAD (sz, val, mbuf, j);    /* saved value */
                    r = dptr->deposit (val, k, uptr, SIM_SW_REST);
                    if (r != SCPE_OK)
                        return r;
                    }                                   /* end for j */
                }                                       /* end for k */
            free (mbuf);                                /* dealloc buffer */
            }                                           /* end if high */
        }                                               /* end unit loop */
    for ( ;; ) {                                        /* register loop */
        READ_S (buf);                                   /* read reg name */
        if (buf[0] == 0)                                /* last? */
            break;
        READ_I (depth);                                 /* [V2.10+] depth */
        if ((rptr = find_reg (buf, NULL, dptr)) == NULL) {
            sim_printf ("Invalid register name: %s %s\n", sim_dname (dptr), buf);
            for (us = 0; us < depth; us++) {            /* skip values */
                READ_I (val);
                }
            continue;
            }
        if (depth != rptr->depth)                       /* [V2.10+] mismatch? */
            sim_printf ("Register depth mismatch: %s %s, file = %d, sim = %d\n",
                sim_dname (dptr), buf, depth, rptr->depth);
        if (rptr->maxval > 0)                           /* if a maximum value is defined */
            max = rptr->maxval;                         /*   then use it */
        else                                            /* otherwise */
            max = width_mask[rptr->width];              /*   the mask defines the maximum value */
        for (us = 0; us < depth; us++) {                /* loop thru values */
            READ_I (val);                               /* read value */
            if (val > max)                              /* value ok? */
                sim_printf ("Invalid register value: %s %s\n", sim_dname (dptr), buf);
            else if (us < rptr->depth)                  /* in range? */
                put_rval (rptr, us, val);
            }
        }
    }                                                   /* end device loop */
return SCPE_OK;
}

/* Run, go, cont, step commands

   ru[n] [new PC]       reset and start simulation
   go [new PC]          start simulation
   co[nt]               start simulation
   s[tep] [step limit]  start simulation for 'limit' instructions
   b[oot] device        bootstrap from device and start simulation
*/

t_stat run_cmd (int32 flag, char *cptr)
{
char *tptr, gbuf[CBUFSIZE];
uint32 i, j;
int32 unitno;
t_value pcv;
t_stat r;
DEVICE *dptr;
UNIT *uptr;
void int_handler (int signal);

GET_SWITCHES (cptr);                                    /* get switches */
sim_step = 0;
if ((flag == RU_RUN) || (flag == RU_GO)) {              /* run or go */
    t_bool new_pcv = (*cptr != 0);                      /* new PC value? */
    if (new_pcv) {                                      /* argument? */
        cptr = get_glyph (cptr, gbuf, 0);               /* get next glyph */
        if (*cptr != 0)                                 /* should be end */
            return SCPE_2MARG;
        if (sim_vm_parse_addr)                          /* address parser? */
            pcv = sim_vm_parse_addr (sim_dflt_dev, gbuf, &tptr);
        else pcv = strtotv (gbuf, &tptr, sim_PC->radix);/* parse PC */
        if ((tptr == gbuf) || (*tptr != 0) ||           /* error? */
            (pcv > (sim_PC->maxval > 0
                     ? sim_PC->maxval
                     : width_mask[sim_PC->width])))
            return SCPE_ARG;
        put_rval (sim_PC, 0, pcv);			/* store new PC */
        }
    if ((flag == RU_RUN) &&                             /* run? */
        ((r = run_boot_prep ()) != SCPE_OK))            /* reset sim */
        return r;
    if (new_pcv)                                        /* new PC value? */
        put_rval (sim_PC, 0, pcv);                      /* store again */
    }

else if (flag == RU_STEP) {                             /* step */
   if (*cptr != 0) {                                    /* argument? */
        cptr = get_glyph (cptr, gbuf, 0);               /* get next glyph */
        if (*cptr != 0)                                 /* should be end */
            return SCPE_2MARG;
        sim_step = (int32) get_uint (gbuf, 10, INT_MAX, &r);
        if ((r != SCPE_OK) || (sim_step <= 0))          /* error? */
            return SCPE_ARG;
        }
    else sim_step = 1;
    }

else if (flag == RU_BOOT) {                             /* boot */
    if (*cptr == 0)                                     /* must be more */
        return SCPE_2FARG;
    cptr = get_glyph (cptr, gbuf, 0);                   /* get next glyph */
    if (*cptr != 0)                                     /* should be end */
        return SCPE_2MARG;
    dptr = find_unit (gbuf, &uptr);                     /* locate unit */
    if (dptr == NULL)                                   /* found dev? */
        return SCPE_NXDEV;
    if (uptr == NULL)                                   /* valid unit? */
        return SCPE_NXUN;
    if (dptr->boot == NULL)                             /* can it boot? */
        return SCPE_NOFNC;
    if (uptr->flags & UNIT_DIS)                         /* disabled? */
        return SCPE_UDIS;
    if ((uptr->flags & UNIT_ATTABLE) &&                 /* if attable, att? */
        !(uptr->flags & UNIT_ATT))
        return SCPE_UNATT;
    unitno = (int32) (uptr - dptr->units);              /* recover unit# */
    if ((r = run_boot_prep ()) != SCPE_OK)              /* reset sim */
        return r;
    if ((r = dptr->boot (unitno, dptr)) != SCPE_OK)     /* boot device */
        return r;
    }

else if (flag != RU_CONT)                               /* must be cont */
    return SCPE_IERR;

for (i = 1; (dptr = sim_devices[i]) != NULL; i++) {     /* reposition all */
    for (j = 0; j < dptr->numunits; j++) {              /* seq devices */
        uptr = dptr->units + j;
        if ((uptr->flags & (UNIT_ATT + UNIT_SEQ)) == (UNIT_ATT + UNIT_SEQ) &&
            (uptr->dynflags & UNIT_PIPE) == 0)
            sim_fseek (uptr->fileref, uptr->pos, SEEK_SET);
        }
    }
stop_cpu = 0;
if (signal (SIGINT, int_handler) == SIG_ERR) {          /* set WRU */
    return SCPE_SIGERR;
    }
if (sim_ttrun () != SCPE_OK) {                          /* set console mode */
    sim_ttcmd ();
    return SCPE_TTYERR;
    }
if ((r = sim_check_console (30)) != SCPE_OK) {          /* check console, error? */
    sim_ttcmd ();
    return r;
    }
if (sim_step)                                           /* set step timer */
    sim_activate (&sim_step_unit, sim_step);
sim_throt_sched ();                                     /* set throttle */
sim_is_running = 1;                                     /* flag running */
sim_brk_clract ();                                      /* defang actions */
sim_rtcn_init_all ();                                   /* re-init clocks */
r = sim_instr();

sim_is_running = 0;                                     /* flag idle */
sim_ttcmd ();                                           /* restore console */
signal (SIGINT, SIG_DFL);                               /* cancel WRU */
sim_cancel (&sim_step_unit);                            /* cancel step timer */
sim_throt_cancel ();                                    /* cancel throttle */
if (sim_clock_queue != NULL) {                          /* update sim time */
    UPDATE_SIM_TIME (sim_clock_queue->time);
    }
else {
    UPDATE_SIM_TIME (noqueue_time);
    }
if (sim_log)                                            /* flush console log */
    fflush (sim_log);
if (sim_deb)                                            /* flush debug log */
    fflush (sim_deb);
for (i = 1; (dptr = sim_devices[i]) != NULL; i++) {     /* flush attached files */
    for (j = 0; j < dptr->numunits; j++) {              /* if not buffered in mem */
        uptr = dptr->units + j;
        if ((uptr->flags & UNIT_ATT) &&                 /* attached, */
            !(uptr->flags & UNIT_BUF) &&                /* not buffered, */
            (uptr->fileref) &&                          /* real file, */
            !(uptr->flags & UNIT_RAW) &&                /* not raw, */
            !(uptr->flags & UNIT_RO))                   /* not read only? */
            fflush (uptr->fileref);
        }
    }
tmxr_post_logs (FALSE);                                 /* flush all mux log files */
#if defined (VMS)
sim_printf ("\n");
#endif
if (r == SCPE_OK)                                       /* if no stop reason is given */
    return SCPE_OK;                                     /*   then return quietly */
fprint_stopped (stdout, r);                             /* print msg */
if (sim_log)                                            /* log if enabled */
    fprint_stopped (sim_log, r);
return SCPE_OK;
}

/* Common setup for RUN or BOOT */

t_stat run_boot_prep (void)
{
sim_interval = 0;                                       /* reset queue */
sim_time = sim_rtime = 0;
noqueue_time = 0;
sim_clock_queue = NULL;
return reset_all (0);
}

/* Print stopped message.

   For VM stops, if a VM-specific "sim_vm_fprint_stopped" pointer is defined,
   call the indicated routine to print additional information after the message
   and before the PC value is printed.  If the routine returns FALSE, skip
   printing the PC.
*/

void fprint_stopped_gen (FILE *st, t_stat v, REG *pc, DEVICE *dptr)
{
int32 i;
t_stat r = 0;
t_addr k;
t_value pcval;

fputc ('\n', st);                                       /* skip a line */

if (v >= SCPE_BASE)                                     /* SCP error? */
    fputs (sim_error_text (v), st);                     /* print it from the SCP list */

else {                                                  /* VM error */
    fputs (sim_stop_messages [v], st);                  /* print the VM-specific message */

    if (sim_vm_fprint_stopped != NULL                   /* if a VM-specific stop handler is defined */
      && !sim_vm_fprint_stopped (st, v))                /*   call it; if it returned FALSE, */
        return;                                         /*     we're done */
    }

fprintf (st, ", %s: ", pc->name);                       /* print the name of the PC register */

pcval = get_rval (pc, 0);
if ((pc->flags & REG_VMAD) && sim_vm_fprint_addr)       /* if reg wants VM-specific printer */
    sim_vm_fprint_addr (st, dptr, (t_addr) pcval);      /*   call it to print the PC address */
else fprint_val (st, pcval, pc->radix, pc->width,       /* otherwise, print as a numeric value */
    pc->flags & REG_FMT);                               /*   with the radix and formatting specified */
if ((dptr != NULL) && (dptr->examine != NULL)) {
    for (i = 0; i < sim_emax; i++)
        sim_eval[i] = 0;
    for (i = 0, k = (t_addr) pcval; i < sim_emax; i++, k = k + dptr->aincr) {
        if ((r = dptr->examine (&sim_eval[i], k, dptr->units, SWMASK ('V')|SIM_SW_STOP)) != SCPE_OK)
            break;
        }
    if ((r == SCPE_OK) || (i > 0)) {
        fprintf (st, " (");
        if (fprint_sym (st, (t_addr) pcval, sim_eval, NULL, SWMASK('M')|SIM_SW_STOP) > 0)
            fprint_val (st, sim_eval[0], dptr->dradix, dptr->dwidth, PV_RZRO);
        fprintf (st, ")");
        }
    }
fprintf (st, "\n");
return;
}

void fprint_stopped (FILE *st, t_stat v)
{
fprint_stopped_gen (st, v, sim_PC, sim_dflt_dev);
return;
}

/* Unit service for step timeout, originally scheduled by STEP n command
   Return step timeout SCP code, will cause simulation to stop */

t_stat step_svc (UNIT *uptr)
{
return SCPE_STEP;
}

/* Cancel scheduled step service */

t_stat sim_cancel_step (void)
{
return sim_cancel (&sim_step_unit);
}

/* Signal handler for ^C signal - set stop simulation flag */

void int_handler (int sig)
{
stop_cpu = 1;
return;
}

/* Get radix from switches or keyword */

int32 get_radix_local (const char *cptr, int32 switches, int32 default_radix)
{
if (cptr != NULL)                                       /* if this is a SET radix keyword request */
    return 0;                                           /*   then report that it is invalid */
else if (switches & SWMASK ('O'))                       /* otherwise if the O switch is present */
    return 8;                                           /*   then use base 8 */
else if (switches & SWMASK ('D'))                       /* otherwise if the D switch is present */
    return 10;                                          /*   then use base 10 */
else if (switches & SWMASK ('H'))                       /* otherwise if the H switch is present */
    return 16;                                          /*   then use base 16 */
else                                                    /* otherwise */
    return default_radix;                               /*   use the supplied default */
}

/* Examine/deposit commands

   ex[amine] [modifiers] list           examine
   de[posit] [modifiers] list val       deposit
   ie[xamine] [modifiers] list          interactive examine
   id[eposit] [modifiers] list          interactive deposit

   modifiers
        @filename                       output file
        -letter(s)                      switches
        devname'n                       device name and unit number
        [{&|^}value]{=|==|!|!=|>|>=|<|<=} value search specification

   list                                 list of addresses and registers
        addr[:addr|-addr]               address range
        ALL                             all addresses
        register[:register|-register]   register range
        STATE                           all registers
*/

t_stat exdep_cmd (int32 flag, char *cptr)
{
char gbuf[CBUFSIZE], *gptr, *tptr;
int32 opt;
t_addr low, high;
t_stat reason;
DEVICE *tdptr;
REG *lowr, *highr;
FILE *ofile;

opt = CMD_OPT_SW|CMD_OPT_SCH|CMD_OPT_DFT;               /* options for all */
if (flag == EX_E)                                       /* extra for EX */
    opt = opt | CMD_OPT_OF;
cptr = get_sim_opt (opt, cptr, &reason);                /* get cmd options */
if (!cptr)                                              /* error? */
    return reason;
if (*cptr == 0)                                         /* must be more */
    return SCPE_2FARG;
if (sim_dfunit == NULL)                                 /* got a unit? */
    return SCPE_NXUN;
cptr = get_glyph (cptr, gbuf, 0);                       /* get list */
if ((flag == EX_D) && (*cptr == 0))                     /* deposit needs more */
    return SCPE_2FARG;
ofile = sim_ofile? sim_ofile: stdout;                   /* no ofile? use stdout */

for (gptr = gbuf, reason = SCPE_OK;
    (*gptr != 0) && (reason == SCPE_OK); gptr = tptr) {
    tdptr = sim_dfdev;                                  /* working dptr */
    if (strncmp (gptr, "STATE", strlen ("STATE")) == 0) {
        tptr = gptr + strlen ("STATE");
        if (*tptr && (*tptr++ != ','))
            return SCPE_ARG;
        if ((lowr = sim_dfdev->registers) == NULL)
            return SCPE_NXREG;
        for (highr = lowr; highr->name != NULL; highr++) ;
        sim_switches = sim_switches | SIM_SW_HIDE;
        reason = exdep_reg_loop (ofile, sim_schptr, flag, cptr,
            lowr, --highr, 0, 0);
        continue;
        }

    if ((lowr = find_reg (gptr, &tptr, tdptr)) ||       /* local reg or */
        (!(sim_opt_out & CMD_OPT_DFT) &&                /* no dflt, global? */
        (lowr = find_reg_glob (gptr, &tptr, &tdptr)))) {
        low = high = 0;
        if ((*tptr == '-') || (*tptr == ':')) {
            highr = find_reg (tptr + 1, &tptr, tdptr);
            if (highr == NULL)
                return SCPE_NXREG;
            }
        else {
            highr = lowr;
            if (*tptr == '[') {
                if (lowr->depth <= 1)
                    return SCPE_ARG;
                tptr = get_range (NULL, tptr + 1, &low, &high,
                    10, lowr->depth - 1, ']');
                if (tptr == NULL)
                    return SCPE_ARG;
                }
            }
        if (*tptr && (*tptr++ != ','))
            return SCPE_ARG;
        reason = exdep_reg_loop (ofile, sim_schptr, flag, cptr,
            lowr, highr, (uint32) low, (uint32) high);
        continue;
        }

    tptr = get_range (sim_dfdev, gptr, &low, &high, sim_dfdev->aradix,
        (((sim_dfunit->capac == 0) || (flag == EX_E))? 0:
        sim_dfunit->capac - sim_dfdev->aincr), 0);
    if (tptr == NULL)
        return SCPE_ARG;
    if (*tptr && (*tptr++ != ','))
        return SCPE_ARG;
    reason = exdep_addr_loop (ofile, sim_schptr, flag, cptr, low, high,
        sim_dfdev, sim_dfunit);
    }                                                   /* end for */
if (sim_ofile)                                          /* close output file */
    fclose (sim_ofile);
return reason;
}

/* Loop controllers for examine/deposit

   exdep_reg_loop       examine/deposit range of registers
   exdep_addr_loop      examine/deposit range of addresses
*/

t_stat exdep_reg_loop (FILE *ofile, SCHTAB *schptr, int32 flag, char *cptr,
    REG *lowr, REG *highr, uint32 lows, uint32 highs)
{
t_stat reason;
uint32 idx;
t_value val;
REG *rptr;

if ((lowr == NULL) || (highr == NULL))
    return SCPE_IERR;
if (lowr > highr)
    return SCPE_ARG;
for (rptr = lowr; rptr <= highr; rptr++) {
    if ((sim_switches & SIM_SW_HIDE) &&
        (rptr->flags & REG_HIDDEN))
        continue;
    for (idx = lows; idx <= highs; idx++) {
        if (idx >= rptr->depth)
            return SCPE_SUB;
        val = get_rval (rptr, idx);
        if (schptr && !test_search (val, schptr))
            continue;
        if (flag != EX_D) {
            reason = ex_reg (ofile, val, flag, rptr, idx);
            if (reason != SCPE_OK)
                return reason;
            if (sim_log && (ofile == stdout))
                ex_reg (sim_log, val, flag, rptr, idx);
            }
        if (flag != EX_E) {
            reason = dep_reg (flag, cptr, rptr, idx);
            if (reason != SCPE_OK)
                return reason;
            }
        }
    }
return SCPE_OK;
}

t_stat exdep_addr_loop (FILE *ofile, SCHTAB *schptr, int32 flag, char *cptr,
    t_addr low, t_addr high, DEVICE *dptr, UNIT *uptr)
{
t_addr i, mask;
t_stat reason;

if (uptr->flags & UNIT_DIS)                             /* disabled? */
    return SCPE_UDIS;
mask = (t_addr) width_mask[dptr->awidth];
if ((low > mask) || (high > mask) || (low > high))
    return SCPE_ARG;
for (i = low; i <= high; ) {                            /* all paths must incr!! */
    reason = get_aval (i, dptr, uptr);                  /* get data */
    if (reason != SCPE_OK)                              /* return if error */
        return reason;
    if (schptr && !test_search (sim_eval[0], schptr))
        i = i + dptr->aincr;                            /* sch fails, incr */
    else {                                              /* no sch or success */
        if (flag != EX_D) {                             /* ex, ie, or id? */
            reason = ex_addr (ofile, flag, i, dptr, uptr);
            if (reason > SCPE_OK)
                return reason;
            if (sim_log && (ofile == stdout))
                ex_addr (sim_log, flag, i, dptr, uptr);
            }
        else reason = 1 - dptr->aincr;                  /* no, dflt incr */
        if (flag != EX_E) {                             /* ie, id, or d? */
            reason = dep_addr (flag, cptr, i, dptr, uptr, reason);
            if (reason > SCPE_OK)
                return reason;
            }
        i = i + (1 - reason);                           /* incr */
        }
    }
return SCPE_OK;
}

/* Examine register routine

   Inputs:
        ofile   =       output stream
        val     =       current register value
        flag    =       type of ex/mod command (ex, iex, idep)
        rptr    =       pointer to register descriptor
        idx     =       index
   Outputs:
        return  =       error status
*/

t_stat ex_reg (FILE *ofile, t_value val, int32 flag, REG *rptr, uint32 idx)
{
int32 rdx;

if (rptr == NULL)
    return SCPE_IERR;
if (rptr->depth > 1)
    fprintf (ofile, "%s[%d]:\t", rptr->name, idx);
else fprintf (ofile, "%s:\t", rptr->name);
if (!(flag & EX_E))
    return SCPE_OK;
GET_RADIX (rdx, rptr->radix);
if ((rptr->flags & REG_VMAD) && sim_vm_fprint_addr)
    sim_vm_fprint_addr (ofile, sim_dflt_dev, (t_addr) val);
else if (!(rptr->flags & REG_VMFLAGS) ||
    (fprint_sym (ofile, (rptr->flags & REG_UFMASK) | rdx, &val,
                 NULL, sim_switches | SIM_SW_REG) > 0))
        fprint_val (ofile, val, rdx, rptr->width, rptr->flags & REG_FMT);
if (flag & EX_I)
    fprintf (ofile, "\t");
else fprintf (ofile, "\n");
return SCPE_OK;
}

/* Get register value

   Inputs:
        rptr    =       pointer to register descriptor
        idx     =       index
   Outputs:
        return  =       register value

   Implementation notes:

    1. The stride is the size of the element spacing for arrays, which is
       equivalent to the addressing increment for array subscripting.  For
       scalar registers, the stride will be zero (as will the idx value), so the
       access pointer is same as the specified location pointer.

    2. The size of the t_value type is determined by the USE_INT64 symbol and
       will be either a 32-bit or a 64-bit type.  It represents the largest
       value that can be returned and so is the default if one of the smaller
       sizes is not indicated.  If USE_INT64 is not defined, t_value will be
       identical to uint32.  In this case, compilers are generally smart enough
       to eliminate the 32-bit size test and combine the two assignments into a
       single default assignment.
*/

t_value get_rval (REG *rptr, uint32 idx)
{
t_value val;
void    *ptr;

if ((rptr->depth > 1) && (rptr->flags & REG_CIRC)) {    /* if the register is a circular queue */
    idx = idx + rptr->qptr;                             /*   then adjust the index relative to the queue */
    if (idx >= rptr->depth)                             /* if the index is beyond the end of the array */
        idx = idx - rptr->depth;                        /*   then wrap it around */
    }

ptr = ((char *) rptr->loc) + (idx * rptr->stride);      /* point at the starting byte of the item */

if (rptr->size == sizeof (uint8))                       /* get the value */
    val = *((uint8 *) ptr);                             /*   using a size */
                                                        /*     appropriate to */
else if (rptr->size == sizeof (uint16))                 /*       the size of */
    val = *((uint16 *) ptr);                            /*         the underlying type */

else if (rptr->size == sizeof (uint32))
    val = *((uint32 *) ptr);

else                                                    /* if the element size is non-standard */
    val = *((t_value *) ptr);                           /*   then access using the largest size permitted */

val = (val >> rptr->offset) & width_mask[rptr->width];  /* shift and mask to obtain the final value */

return val;
}

/* Deposit register routine

   Inputs:
        flag    =       type of deposit (normal/interactive)
        cptr    =       pointer to input string
        rptr    =       pointer to register descriptor
        idx     =       index
   Outputs:
        return  =       error status
*/

t_stat dep_reg (int32 flag, char *cptr, REG *rptr, uint32 idx)
{
t_stat r;
t_value val, max;
int32 rdx;
char *tptr, gbuf[CBUFSIZE];

if ((cptr == NULL) || (rptr == NULL))
    return SCPE_IERR;
if (rptr->flags & REG_RO)
    return SCPE_RO;
if (flag & EX_I) {
    cptr = read_line (gbuf, CBUFSIZE, stdin);
    if (sim_log)
        fprintf (sim_log, "%s\n", cptr? cptr: "");      /* fix clang error */
    if (cptr == NULL)                                   /* force exit */
        return 1;
    if (*cptr == 0)                                     /* success */
        return SCPE_OK;
    }
if (rptr->maxval > 0)                                   /* if a maximum value is defined */
    max = rptr->maxval;                                 /*   then use it */
else                                                    /* otherwise */
    max = width_mask[rptr->width];                      /*   the mask defines the maximum value */
GET_RADIX (rdx, rptr->radix);
if ((rptr->flags & REG_VMAD) && sim_vm_parse_addr) {    /* address form? */
    val = sim_vm_parse_addr (sim_dflt_dev, cptr, &tptr);
    if ((tptr == cptr) || (*tptr != 0) || (val > max))
        return SCPE_ARG;
    }
else if (!(rptr->flags & REG_VMFLAGS) ||                /* dont use sym? */
    (parse_sym (cptr, (rptr->flags & REG_UFMASK) | rdx, NULL,
                &val, sim_switches | SIM_SW_REG) > SCPE_OK)) {
    val = get_uint (cptr, rdx, max, &r);
    if (r != SCPE_OK)
        return SCPE_ARG;
    }
if ((rptr->flags & REG_NZ) && (val == 0))
    return SCPE_ARG;
put_rval (rptr, idx, val);
return SCPE_OK;
}

/* Put register value

   Inputs:
        rptr    =       pointer to register descriptor
        idx     =       index
        val     =       new value
   Outputs:
        none


   Implementation notes:

    1. mask and val are of type t_value, so an explicit cast is not needed for
       that type of assignment.

    2. See the notes for the get_rval routine for additional information
       regarding the stride calculation and the t_value default assignment,
*/

void put_rval (REG *rptr, uint32 idx, t_value val)
{
t_value mask;
void    *ptr;

if (rptr == sim_PC)                                     /* if the PC is changing */
    sim_brk_npc (0);                                    /*   then notify the breakpoint package */

mask = ~(width_mask [rptr->width] << rptr->offset);     /* set up a mask to produce a hole in the element */
val  = val << rptr->offset;                             /*   and position the new value to fit the hole */

if ((rptr->depth > 1) && (rptr->flags & REG_CIRC)) {    /* if the register is a circular queue */
    idx = idx + rptr->qptr;                             /*   then adjust the index relative to the queue */
    if (idx >= rptr->depth)                             /* if the index is beyond the end of the array */
        idx = idx - rptr->depth;                        /*   then wrap it around */
    }

ptr = ((char *) rptr->loc) + (idx * rptr->stride);      /* point at the starting byte of the item */

if (rptr->size == sizeof (uint8))                       /* store the value */
    *((uint8 *) ptr) =                                  /*   using a size */
      (uint8) (*((uint8 *) ptr) & mask | val);          /*     appropriate to */
                                                        /*       the size of */
else if (rptr->size == sizeof (uint16))                 /*         the underlying type */
    *((uint16 *) ptr) =
      (uint16) (*((uint16 *) ptr) & mask | val);

else if (rptr->size == sizeof (uint32))
    *((uint32 *) ptr) =
      (uint32) (*((uint32 *) ptr) & mask | val);

else                                                    /* if the element size is non-standard */
    *((t_value *) ptr) =                                /*   then access using the largest size permitted */
      *((t_value *) ptr) & mask | val;

return;
}

/* Examine address routine

   Inputs: (sim_eval is an implicit argument)
        ofile   =       output stream
        flag    =       type of ex/mod command (ex, iex, idep)
        addr    =       address to examine
        dptr    =       pointer to device
        uptr    =       pointer to unit
   Outputs:
        return  =       if > 0, error status
                        if <= 0,-number of extra addr units retired
*/

t_stat ex_addr (FILE *ofile, int32 flag, t_addr addr, DEVICE *dptr, UNIT *uptr)
{
t_stat reason;
int32 rdx;

if (sim_vm_fprint_addr)
    sim_vm_fprint_addr (ofile, dptr, addr);
else fprint_val (ofile, addr, dptr->aradix, dptr->awidth, PV_LEFT);
fprintf (ofile, ":\t");
if (!(flag & EX_E))
    return (1 - dptr->aincr);

GET_RADIX (rdx, dptr->dradix);
if ((reason = fprint_sym (ofile, addr, sim_eval, uptr, sim_switches)) > 0) {
    fprint_val (ofile, sim_eval[0], rdx, dptr->dwidth, PV_RZRO);
    reason = 1 - dptr->aincr;
    }
if (flag & EX_I)
    fprintf (ofile, "\t");
else fprintf (ofile, "\n");
return reason;
}

/* Get address routine

   Inputs:
        flag    =       type of ex/mod command (ex, iex, idep)
        addr    =       address to examine
        dptr    =       pointer to device
        uptr    =       pointer to unit
   Outputs: (sim_eval is an implicit output)
        return  =       error status
*/

t_stat get_aval (t_addr addr, DEVICE *dptr, UNIT *uptr)
{
int32 i;
t_value mask;
t_addr j, loc;
size_t sz;
t_stat reason = SCPE_OK;

if ((dptr == NULL) || (uptr == NULL))
    return SCPE_IERR;
mask = width_mask[dptr->dwidth];
for (i = 0; i < sim_emax; i++)
    sim_eval[i] = 0;
for (i = 0, j = addr; i < sim_emax; i++, j = j + dptr->aincr) {
    if (dptr->examine != NULL) {
        reason = dptr->examine (&sim_eval[i], j, uptr, sim_switches);
        if (reason != SCPE_OK)
            break;
        }
    else {
        if (!(uptr->flags & UNIT_ATT))
            return SCPE_UNATT;
        if (((uptr->flags & UNIT_RAW) != 0) ||
            (uptr->fileref == NULL) ||
            ((uptr->dynflags & UNIT_PIPE) != 0))
            return SCPE_NOFNC;
        if (((uptr->flags & UNIT_FIX) != 0) &&
            (j >= uptr->capac)) {
            reason = SCPE_NXM;
            break;
            }
        sz = SZ_D (dptr);
        loc = j / dptr->aincr;
        if (uptr->flags & UNIT_BUF) {
            SZ_LOAD (sz, sim_eval[i], uptr->filebuf, loc);
            }
        else {
            sim_fseek (uptr->fileref, (t_addr)(sz * loc), SEEK_SET);
            sim_fread (&sim_eval[i], sz, 1, uptr->fileref);
            if ((feof (uptr->fileref)) &&
               !(uptr->flags & UNIT_FIX)) {
                reason = SCPE_EOF;
                break;
                }
            else if (ferror (uptr->fileref)) {
                clearerr (uptr->fileref);
                reason = SCPE_IOERR;
                break;
                }
            }
        }
    sim_eval[i] = sim_eval[i] & mask;
    }
if ((reason != SCPE_OK) && (i == 0))
    return reason;
return SCPE_OK;
}

/* Deposit address routine

   Inputs:
        flag    =       type of deposit (normal/interactive)
        cptr    =       pointer to input string
        addr    =       address to examine
        dptr    =       pointer to device
        uptr    =       pointer to unit
        dfltinc =       value to return on cr input
   Outputs:
        return  =       if > 0, error status
                        if <= 0, -number of extra address units retired
*/

t_stat dep_addr (int32 flag, char *cptr, t_addr addr, DEVICE *dptr,
    UNIT *uptr, int32 dfltinc)
{
int32 i, count, rdx;
t_addr j, loc;
t_stat r, reason;
t_value mask;
size_t sz;
char gbuf[CBUFSIZE];

if (dptr == NULL)
    return SCPE_IERR;
if (flag & EX_I) {
    cptr = read_line (gbuf, CBUFSIZE, stdin);
    if (sim_log)
        fprintf (sim_log, "%s\n", cptr? cptr: "");      /* fix clang error */
    if (cptr == NULL)                                   /* force exit */
        return 1;
    if (*cptr == 0)                                     /* success */
        return dfltinc;
    }
if (uptr->flags & UNIT_RO)                              /* read only? */
    return SCPE_RO;
mask = width_mask[dptr->dwidth];

GET_RADIX (rdx, dptr->dradix);
if ((reason = parse_sym (cptr, addr, uptr, sim_eval, sim_switches)) > 0) {
    sim_eval[0] = get_uint (cptr, rdx, mask, &reason);
    if (reason != SCPE_OK)
        return reason;
    reason = dfltinc;
    }
count = (1 - reason + (dptr->aincr - 1)) / dptr->aincr;

for (i = 0, j = addr; i < count; i++, j = j + dptr->aincr) {
    sim_eval[i] = sim_eval[i] & mask;
    if (dptr->deposit != NULL) {
        r = dptr->deposit (sim_eval[i], j, uptr, sim_switches);
        if (r != SCPE_OK)
            return r;
        }
    else {
        if (!(uptr->flags & UNIT_ATT))
            return SCPE_UNATT;
        if ((uptr->flags & UNIT_RAW) ||
            (uptr->dynflags & UNIT_PIPE))
            return SCPE_NOFNC;
        if ((uptr->flags & UNIT_FIX) && (j >= uptr->capac))
            return SCPE_NXM;
        sz = SZ_D (dptr);
        loc = j / dptr->aincr;
        if (uptr->flags & UNIT_BUF) {
            SZ_STORE (sz, sim_eval[i], uptr->filebuf, loc);
            if (loc >= uptr->hwmark)
                uptr->hwmark = (uint32) loc + 1;
            }
        else {
            sim_fseek (uptr->fileref, (t_addr)(sz * loc), SEEK_SET);
            sim_fwrite (&sim_eval[i], sz, 1, uptr->fileref);
            if (ferror (uptr->fileref)) {
                clearerr (uptr->fileref);
                return SCPE_IOERR;
                }
            }
        }
    }
return reason;
}

/* Evaluate command */

t_stat eval_cmd (int32 flg, char *cptr)
{
DEVICE *dptr = sim_dflt_dev;
int32 i, rdx, a, lim;
t_stat r;

GET_SWITCHES (cptr);
GET_RADIX (rdx, dptr->dradix);
for (i = 0; i < sim_emax; i++)
sim_eval[i] = 0;
if (*cptr == 0)
    return SCPE_2FARG;
if ((r = parse_sym (cptr, 0, dptr->units, sim_eval, sim_switches)) > 0) {
    sim_eval[0] = get_uint (cptr, rdx, width_mask[dptr->dwidth], &r);
    if (r != SCPE_OK)
        return r;
    }
lim = 1 - r;
for (i = a = 0; a < lim; ) {
    sim_printf ("%d:\t", a);
    if ((r = fprint_sym (stdout, a, &sim_eval[i], dptr->units, sim_switches)) > 0)
        r = fprint_val (stdout, sim_eval[i], rdx, dptr->dwidth, PV_RZRO);
    sim_printf ("\n");
    if (sim_log) {
        fprintf (sim_log, "%d\t", i);
        if ((r = fprint_sym (sim_log, a, &sim_eval[i], dptr->units, sim_switches)) > 0)
            r = fprint_val (sim_log, sim_eval[i], rdx, dptr->dwidth, PV_RZRO);
        fprintf (sim_log, "\n");
        }
    if (r < 0)
        a = a + 1 - r;
    else a = a + dptr->aincr;
    i = a / dptr->aincr;
    }
return SCPE_OK;
}

/* String processing routines

   read_line            read line

   Inputs:
        cptr    =       pointer to buffer
        size    =       maximum size
        stream  =       pointer to input stream
   Outputs:
        optr    =       pointer to first non-blank character
                        NULL if EOF
*/

char *read_line (char *cptr, int32 size, FILE *stream)
{
return read_line_p (NULL, cptr, size, stream);
}

/* read_line_p          read line with prompt

   Inputs:
        prompt  =       pointer to prompt string
        cptr    =       pointer to buffer
        size    =       maximum size
        stream  =       pointer to input stream
   Outputs:
        optr    =       pointer to first non-blank character
                        NULL if EOF
*/

char *read_line_p (char *prompt, char *cptr, int32 size, FILE *stream)
{
char *tptr;
#if defined(HAVE_DLOPEN)
static int initialized = 0;
static char *(*p_readline)(const char *) = NULL;
static void (*p_add_history)(const char *) = NULL;

if (!initialized) {
    initialized = 1;
    void *handle;

#define __STR_QUOTE(tok) #tok
#define __STR(tok) __STR_QUOTE(tok)
    handle = dlopen("libncurses." __STR(HAVE_DLOPEN), RTLD_NOW|RTLD_GLOBAL);
    handle = dlopen("libcurses." __STR(HAVE_DLOPEN), RTLD_NOW|RTLD_GLOBAL);
    handle = dlopen("libreadline." __STR(HAVE_DLOPEN), RTLD_NOW|RTLD_GLOBAL);
    if (!handle)
        handle = dlopen("libreadline." __STR(HAVE_DLOPEN) ".6", RTLD_NOW|RTLD_GLOBAL);
    if (!handle)
        handle = dlopen("libreadline." __STR(HAVE_DLOPEN) ".5", RTLD_NOW|RTLD_GLOBAL);
    if (handle) {
        p_readline = dlsym(handle, "readline");
        p_add_history = dlsym(handle, "add_history");
        }
    }
if (prompt) {                                           /* interactive? */
    if (p_readline) {
        char *tmpc = p_readline (prompt);               /* get cmd line */
        if (tmpc == NULL)                               /* bad result? */
            cptr = NULL;
        else {
            strncpy (cptr, tmpc, size);                 /* copy result */
            free (tmpc) ;                               /* free temp */
            }
        }
    else {
        printf ("%s", prompt);                          /* display prompt */
        fflush (stdout);
        cptr = fgets (cptr, size, stream);              /* get cmd line */
        }
    }
else cptr = fgets (cptr, size, stream);                 /* get cmd line */
#else
if (prompt) {                                           /* interactive? */
    printf ("%s", prompt);                              /* display prompt */
    fflush (stdout);
    }
cptr = fgets (cptr, size, stream);                      /* get cmd line */
#endif

if (cptr == NULL) {
    clearerr (stream);                                  /* clear error */
    return NULL;                                        /* ignore EOF */
    }
for (tptr = cptr; tptr < (cptr + size); tptr++) {       /* remove cr or nl */
    if ((*tptr == '\n') || (*tptr == '\r') ||
        (tptr == (cptr + size - 1))) {                  /* str max length? */
        *tptr = 0;                                      /* terminate */
        break;
        }
    }
while (isspace (*cptr))                                 /* trim leading spc */
    cptr++;
if (*cptr == ';')                                       /* ignore comment */
    *cptr = 0;

#if defined (HAVE_DLOPEN)
if (prompt && p_add_history && *cptr)                   /* Save non blank lines in history */
    p_add_history (cptr);
#endif

return cptr;
}

/* get_glyph            get next glyph (force upper case)
   get_glyph_nc         get next glyph (no conversion)
   get_glyph_gen        get next glyph (general case)

   Inputs:
        iptr    =       pointer to input string
        optr    =       pointer to output string
        mchar   =       optional end of glyph character
        flag    =       TRUE for convert to upper case (_gen only)
   Outputs
        result  =       pointer to next character in input string
*/

char *get_glyph_gen (char *iptr, char *optr, char mchar, t_bool uc)
{
while ((isspace (*iptr) == 0) && (*iptr != 0) && (*iptr != mchar)) {
    if (islower (*iptr) && uc)
        *optr = (char) toupper (*iptr);
    else *optr = *iptr;
    iptr++; optr++;
    }
*optr = 0;
if (mchar && (*iptr == mchar))                          /* skip terminator */
    iptr++;
while (isspace (*iptr))                                 /* absorb spaces */
    iptr++;
return iptr;
}

char *get_glyph (char *iptr, char *optr, char mchar)
{
return get_glyph_gen (iptr, optr, mchar, TRUE);
}

char *get_glyph_nc (char *iptr, char *optr, char mchar)
{
return get_glyph_gen (iptr, optr, mchar, FALSE);
}

/* Trim trailing spaces from a string

    Inputs:
        cptr    =       pointer to string
    Outputs:
        cptr    =       pointer to string
*/

char *sim_trim_endspc (char *cptr)
{
char *tptr;

tptr = cptr + strlen (cptr);
while ((--tptr >= cptr) && isspace (*tptr))
    *tptr = 0;
return cptr;
}

int sim_isspace (char c)
{
return (c & 0x80) ? 0 : isspace (c);
}

int sim_islower (char c)
{
return (c & 0x80) ? 0 : islower (c);
}

int sim_isalpha (char c)
{
return (c & 0x80) ? 0 : isalpha (c);
}

int sim_isprint (char c)
{
return (c & 0x80) ? 0 : isprint (c);
}

int sim_isdigit (char c)
{
return (c & 0x80) ? 0 : isdigit (c);
}

int sim_isgraph (char c)
{
return (c & 0x80) ? 0 : isgraph (c);
}

int sim_isalnum (char c)
{
return (c & 0x80) ? 0 : isalnum (c);
}

/* strncasecmp() is not available on all platforms */
int sim_strncasecmp (const char* string1, const char* string2, size_t len)
{
size_t i;
unsigned char s1, s2;

for (i=0; i<len; i++) {
    s1 = (unsigned char)string1[i];
    s2 = (unsigned char)string2[i];
    if (sim_islower (s1))
        s1 = (unsigned char)toupper (s1);
    if (sim_islower (s2))
        s2 = (unsigned char)toupper (s2);
    if (s1 < s2)
        return -1;
    if (s1 > s2)
        return 1;
    if (s1 == 0)
        return 0;
    }
return 0;
}

/* strcasecmp() is not available on all platforms */
int sim_strcasecmp (const char *string1, const char *string2)
{
size_t i = 0;
unsigned char s1, s2;

while (1) {
    s1 = (unsigned char)string1[i];
    s2 = (unsigned char)string2[i];
    if (sim_islower (s1))
        s1 = (unsigned char)toupper (s1);
    if (sim_islower (s2))
        s2 = (unsigned char)toupper (s2);
    if (s1 == s2) {
        if (s1 == 0)
            return 0;
        i++;
        continue;
        }
    if (s1 < s2)
        return -1;
    if (s1 > s2)
        return 1;
    }
return 0;
}

/* strlcat() and strlcpy() are not available on all platforms */
/* Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> */
/*
 * Appends src to string dst of size siz (unlike strncat, siz is the
 * full size of dst, not space left).  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
 * Returns strlen(src) + MIN(siz, strlen(initial dst)).
 * If retval >= siz, truncation occurred.
 */
size_t sim_strlcat(char *dst, const char *src, size_t size)
{
char *d = dst;
const char *s = src;
size_t n = size;
size_t dlen;

/* Find the end of dst and adjust bytes left but don't go past end */
while (n-- != 0 && *d != '\0')
    d++;
dlen = d - dst;
n = size - dlen;

if (n == 0)
    return (dlen + strlen(s));
while (*s != '\0') {
    if (n != 1) {
        *d++ = *s;
        n--;
        }
    s++;
    }
*d = '\0';

return (dlen + (s - src));          /* count does not include NUL */
}

/*
 * Copy src to string dst of size siz.  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz == 0).
 * Returns strlen(src); if retval >= siz, truncation occurred.
 */
size_t sim_strlcpy (char *dst, const char *src, size_t size)
{
char *d = dst;
const char *s = src;
size_t n = size;

/* Copy as many bytes as will fit */
if (n != 0) {
    while (--n != 0) {
        if ((*d++ = *s++) == '\0')
            break;
        }
    }

    /* Not enough room in dst, add NUL and traverse rest of src */
    if (n == 0) {
        if (size != 0)
            *d = '\0';              /* NUL-terminate dst */
        while (*s++)
            ;
        }
return (s - src - 1);               /* count does not include NUL */
}

/* get_yn               yes/no question

   Inputs:
        cptr    =       pointer to question
        deflt   =       default answer
   Outputs:
        result  =       true if yes, false if no
*/

t_stat get_yn (char *ques, t_stat deflt)
{
char cbuf[CBUFSIZE], *cptr;

sim_printf ("%s ", ques);
cptr = read_line (cbuf, CBUFSIZE, stdin);
if ((cptr == NULL) || (*cptr == 0))
    return deflt;
if ((*cptr == 'Y') || (*cptr == 'y'))
    return TRUE;
return FALSE;
}

/* get_uint             unsigned number

   Inputs:
        cptr    =       pointer to input string
        radix   =       input radix
        max     =       maximum acceptable value
        *status =       pointer to error status
   Outputs:
        val     =       value
*/

t_value get_uint (char *cptr, uint32 radix, t_value max, t_stat *status)
{
t_value val;
char *tptr;

*status = SCPE_OK;
val = strtotv (cptr, &tptr, radix);
if ((cptr == tptr) || (val > max))
    *status = SCPE_ARG;
else {
    while (isspace (*tptr)) tptr++;
    if (*tptr != 0)
        *status = SCPE_ARG;
    }
return val;
}

/* get_range            range specification

   Inputs:
        dptr    =       pointer to device (NULL if none)
        cptr    =       pointer to input string
        *lo     =       pointer to low result
        *hi     =       pointer to high result
        aradix  =       radix
        max     =       default high value
        term    =       terminating character, 0 if none
   Outputs:
        tptr    =       input pointer after processing
                        NULL if error
*/

char *get_range (DEVICE *dptr, char *cptr, t_addr *lo, t_addr *hi,
    uint32 rdx, t_addr max, char term)
{
char *tptr;

if (max && strncmp (cptr, "ALL", strlen ("ALL")) == 0) { /* ALL? */
    tptr = cptr + strlen ("ALL");
    *lo = 0;
    *hi = max;
    }
else {
    if (dptr && sim_vm_parse_addr)                      /* get low */
        *lo = sim_vm_parse_addr (dptr, cptr, &tptr);
    else *lo = (t_addr) strtotv (cptr, &tptr, rdx);
    if (cptr == tptr)                                   /* error? */
            return NULL;
    if ((*tptr == '-') || (*tptr == ':')) {             /* range? */
        cptr = tptr + 1;
        if (dptr && sim_vm_parse_addr)                  /* get high */
            *hi = sim_vm_parse_addr (dptr, cptr, &tptr);
        else *hi = (t_addr) strtotv (cptr, &tptr, rdx);
        if (cptr == tptr)
            return NULL;
        if (*lo > *hi)
            return NULL;
        }
    else if (*tptr == '/') {                            /* relative? */
        cptr = tptr + 1;
        *hi = (t_addr) strtotv (cptr, &tptr, rdx);      /* get high */
        if ((cptr == tptr) || (*hi == 0))
            return NULL;
        *hi = *lo + *hi - 1;
        }
    else *hi = *lo;
    }
if (term && (*tptr++ != term))
    return NULL;
return tptr;
}

/* Find_device          find device matching input string

   Inputs:
        cptr    =       pointer to input string
   Outputs:
        result  =       pointer to device
*/

DEVICE *find_dev (char *cptr)
{
int32 i;
DEVICE *dptr;

for (i = 0; (dptr = sim_devices[i]) != NULL; i++) {
    if ((strcmp (cptr, dptr->name) == 0) ||
        (dptr->lname &&
        (strcmp (cptr, dptr->lname) == 0)))
        return dptr;
    }
return NULL;
}

/* Find_unit            find unit matching input string

   Inputs:
        cptr    =       pointer to input string
        uptr    =       pointer to unit pointer
   Outputs:
        result  =       pointer to device (null if no dev)
        *iptr   =       pointer to unit (null if nx unit)

   The "sim_ref_type" global is set to the type of reference specified by cptr
   (device or unit).
*/

DEVICE *find_unit (char *cptr, UNIT **uptr)
{
uint32 i, u;
char *nptr, *tptr;
t_stat r;
DEVICE *dptr;

sim_ref_type = REF_NONE;                                /* start with no reference type */
if (uptr == NULL)                                       /* arg error? */
    return NULL;
if (dptr = find_dev (cptr)) {                           /* exact match? */
    sim_ref_type = REF_DEVICE;                          /* indicate a device reference */
    if (qdisable (dptr))                                /* disabled? */
        return NULL;
    *uptr = dptr->units;                                /* unit 0 */
    return dptr;
    }

for (i = 0; (dptr = sim_devices[i]) != NULL; i++) {     /* base + unit#? */
    if (dptr->numunits &&                               /* any units? */
        (((nptr = dptr->name) &&
          (strncmp (cptr, nptr, strlen (nptr)) == 0)) ||
         ((nptr = dptr->lname) &&
          (strncmp (cptr, nptr, strlen (nptr)) == 0)))) {
        tptr = cptr + strlen (nptr);
        if (isdigit (*tptr)) {
            sim_ref_type = REF_UNIT;                    /* indicate a unit reference */
            if (qdisable (dptr))                        /* disabled? */
                return NULL;
            u = (uint32) get_uint (tptr, 10, dptr->numunits - 1, &r);
            if (r != SCPE_OK)                           /* error? */
                *uptr = NULL;
            else *uptr = dptr->units + u;
            return dptr;
            }
        }
    }
return NULL;
}

/* Find_dev_from_unit   find device for unit

   Inputs:
        uptr    =       pointer to unit
   Outputs:
        result  =       pointer to device
*/

DEVICE *find_dev_from_unit (UNIT *uptr)
{
DEVICE *dptr;
uint32 i, j;

if (uptr == NULL)
    return NULL;
for (i = 0; (dptr = sim_devices[i]) != NULL; i++) {
    for (j = 0; j < dptr->numunits; j++) {
        if (uptr == (dptr->units + j))
            return dptr;
        }
    }
return NULL;
}

/* Test for disabled device */

t_bool qdisable (DEVICE *dptr)
{
return (dptr->flags & DEV_DIS? TRUE: FALSE);
}

/* find_reg_glob        find globally unique register

   Inputs:
        cptr    =       pointer to input string
        optr    =       pointer to output pointer (can be null)
        gdptr   =       pointer to global device
   Outputs:
        result  =       pointer to register, NULL if error
        *optr   =       pointer to next character in input string
        *gdptr  =       pointer to device where found
*/

REG *find_reg_glob (char *cptr, char **optr, DEVICE **gdptr)
{
int32 i;
DEVICE *dptr;
REG *rptr, *srptr = NULL;

for (i = 0; (dptr = sim_devices[i]) != 0; i++) {        /* all dev */
    if (dptr->flags & DEV_DIS)                          /* skip disabled */
        continue;
    if (rptr = find_reg (cptr, optr, dptr)) {           /* found? */
        if (srptr)                                      /* ambig? err */
            return NULL;
        srptr = rptr;                                   /* save reg */
        *gdptr = dptr;                                  /* save unit */
        }
    }
return srptr;
}

/* find_reg             find register matching input string

   Inputs:
        cptr    =       pointer to input string
        optr    =       pointer to output pointer (can be null)
        dptr    =       pointer to device
   Outputs:
        result  =       pointer to register, NULL if error
        *optr   =       pointer to next character in input string
*/

REG *find_reg (char *cptr, char **optr, DEVICE *dptr)
{
char *tptr;
REG *rptr;
size_t slnt;

if ((cptr == NULL) || (dptr == NULL) || (dptr->registers == NULL))
    return NULL;
tptr = cptr;
do {
    tptr++;
    } while (isalnum (*tptr) || (*tptr == '*') || (*tptr == '_'));
slnt = tptr - cptr;
for (rptr = dptr->registers; rptr->name != NULL; rptr++) {
    if ((slnt == strlen (rptr->name)) &&
        (strncmp (cptr, rptr->name, slnt) == 0)) {
        if (optr != NULL)
            *optr = tptr;
        return rptr;
        }
    }
return NULL;
}

/* get_switches         get switches from input string

   Inputs:
        cptr    =       pointer to input string
   Outputs:
        sw      =       switch bit mask
                        0 if no switches, -1 if error
*/

int32 get_switches (char *cptr)
{
int32 sw;

if (*cptr != '-')
    return 0;
sw = 0;
for (cptr++; (isspace (*cptr) == 0) && (*cptr != 0); cptr++) {
    if (isalpha (*cptr) == 0)
        return -1;
    sw = sw | SWMASK (toupper (*cptr));
    }
return sw;
}

/* get_sim_sw           accumulate sim_switches

   Inputs:
        cptr    =       pointer to input string
   Outputs:
        ptr     =       pointer to first non-string glyph
                        NULL if error
*/

char *get_sim_sw (char *cptr)
{
int32 lsw;
char gbuf[CBUFSIZE];

while (*cptr == '-') {                                  /* while switches */
    cptr = get_glyph (cptr, gbuf, 0);                   /* get switch glyph */
    lsw = get_switches (gbuf);                          /* parse */
    if (lsw <= 0)                                       /* invalid? */
        return NULL;
    sim_switches = sim_switches | lsw;                  /* accumulate */
    }
return cptr;
}

/* get_sim_opt          get simulator command options

   Inputs:
        opt     =       command options
        cptr    =       pointer to input string
   Outputs:
        ptr     =       pointer to next glypsh, NULL if error
        *stat   =       error status
*/

char *get_sim_opt (int32 opt, char *cptr, t_stat *st)
{
int32 t;
char *svptr, gbuf[CBUFSIZE];
DEVICE *tdptr;
UNIT *tuptr;

sim_switches = 0;                                       /* no switches */
sim_ofile = NULL;                                       /* no output file */
sim_schptr = NULL;                                      /* no search */
sim_stab.logic = SCH_OR;                                /* default search params */
sim_stab.boolop = SCH_GE;
sim_stab.mask = 0;
sim_stab.comp = 0;
sim_dfdev = sim_dflt_dev;
sim_dfunit = sim_dfdev->units;
sim_opt_out = 0;                                        /* no options yet */
*st = SCPE_OK;
while (*cptr) {                                         /* loop through modifiers */
    svptr = cptr;                                       /* save current position */
    if ((opt & CMD_OPT_OF) && (*cptr == '@')) {         /* output file spec? */
        if (sim_ofile) {                                /* already got one? */
            fclose (sim_ofile);                         /* one per customer */
            *st = SCPE_ARG;
            return NULL;
            }
        cptr = get_glyph_nc (cptr + 1, gbuf, 0);
        sim_ofile = sim_fopen (gbuf, "a");              /* open for append */
        if (sim_ofile == NULL) {                        /* open failed? */
            *st = SCPE_OPENERR;
            return NULL;
            }
        sim_opt_out |= CMD_OPT_OF;                      /* got output file */
        continue;
        }
    cptr = get_glyph (cptr, gbuf, 0);
    if ((t = get_switches (gbuf)) != 0) {               /* try for switches */
        if (t < 0) {                                    /* err if bad switch */
            *st = SCPE_INVSW;
            return NULL;
            }
        sim_switches = sim_switches | t;                /* or in new switches */
        }
    else if ((opt & CMD_OPT_SCH) &&                     /* if allowed, */
        get_search (gbuf, sim_dfdev->dradix, &sim_stab)) { /* try for search */
        sim_schptr = &sim_stab;                         /* set search */
        sim_opt_out |= CMD_OPT_SCH;                     /* got search */
        }
    else if ((opt & CMD_OPT_DFT) &&                     /* default allowed? */
        ((sim_opt_out & CMD_OPT_DFT) == 0) &&           /* none yet? */
        (tdptr = find_unit (gbuf, &tuptr)) &&           /* try for default */
        (tuptr != NULL)) {
        sim_dfdev = tdptr;                              /* set as default */
        sim_dfunit = tuptr;
        sim_opt_out |= CMD_OPT_DFT;                     /* got default */
        }
    else return svptr;                                  /* not rec, break out */
    }
return cptr;
}

/* Match file extension

   Inputs:
        fnam    =       file name
        ext     =       extension, without period
   Outputs:
        cp      =       pointer to final '.' if match, NULL if not
*/

char *match_ext (char *fnam, char *ext)
{
char *pptr, *fptr, *eptr;

if ((fnam == NULL) || (ext == NULL))                    /* bad arguments? */
     return NULL;
pptr = strrchr (fnam, '.');                             /* find last . */
if (pptr) {                                             /* any? */
    for (fptr = pptr + 1, eptr = ext;                   /* match characters */
#if defined (VMS)                                       /* VMS: stop at ; or null */
    (*fptr != 0) && (*fptr != ';');
#else
    *fptr != 0;                                         /* others: stop at null */
#endif
    fptr++, eptr++) {
        if (toupper (*fptr) != toupper (*eptr))
            return NULL;
        }
    if (*eptr != 0)                                     /* ext exhausted? */
        return NULL;
    }
return pptr;
}

/* Get search specification

   Inputs:
        cptr    =       pointer to input string
        radix   =       radix for numbers
        schptr =        pointer to search table
   Outputs:
        return =        NULL if error
                        schptr if valid search specification
*/

SCHTAB *get_search (char *cptr, int32 radix, SCHTAB *schptr)
{
int32 c, logop, cmpop;
t_value logval, cmpval;
char *sptr, *tptr;
const char logstr[] = "|&^", cmpstr[] = "=!><";

logval = cmpval = 0;
if (*cptr == 0)                                         /* check for clause */
    return NULL;
for (logop = cmpop = -1; c = *cptr++; ) {               /* loop thru clauses */
    if (sptr = strchr (logstr, c)) {                    /* check for mask */
        logop = (int32)(sptr - logstr);
        logval = strtotv (cptr, &tptr, radix);
        if (cptr == tptr)
            return NULL;
        cptr = tptr;
        }
    else if (sptr = strchr (cmpstr, c)) {               /* check for boolop */
        cmpop = (int32)(sptr - cmpstr);
        if (*cptr == '=') {
            cmpop = cmpop + strlen (cmpstr);
            cptr++;
            }
        cmpval = strtotv (cptr, &tptr, radix);
        if (cptr == tptr)
            return NULL;
        cptr = tptr;
        }
    else return NULL;
    }                                                   /* end for */
if (logop >= 0) {
    schptr->logic = logop;
    schptr->mask = logval;
    }
if (cmpop >= 0) {
    schptr->boolop = cmpop;
    schptr->comp = cmpval;
    }
return schptr;
}

/* Test value against search specification

   Inputs:
        val     =       value to test
        schptr =        pointer to search table
   Outputs:
        return =        1 if value passes search criteria, 0 if not
*/

int32 test_search (t_value val, SCHTAB *schptr)
{
if (schptr == NULL) return 0;

switch (schptr->logic) {                                /* case on logical */

    case SCH_OR:
        val = val | schptr->mask;
        break;

    case SCH_AND:
        val = val & schptr->mask;
        break;

    case SCH_XOR:
        val = val ^ schptr->mask;
        break;
        }

switch (schptr->boolop) {                                       /* case on comparison */

    case SCH_E: case SCH_EE:
        return (val == schptr->comp);

    case SCH_N: case SCH_NE:
        return (val != schptr->comp);

    case SCH_G:
        return (val > schptr->comp);

    case SCH_GE:
        return (val >= schptr->comp);

    case SCH_L:
        return (val < schptr->comp);

    case SCH_LE:
        return (val <= schptr->comp);
        }

return 0;
}

/* Radix independent input/output package

   strtotv - general radix input routine

   Inputs:
        inptr   =       string to convert
        endptr  =       pointer to first unconverted character
        radix   =       radix for input
   Outputs:
        value   =       converted value

   On an error, the endptr will equal the inptr.
*/

t_value strtotv (CONST char *inptr, CONST char **endptr, uint32 radix)
{
int32 nodigit;
t_value val;
uint32 c, digit;

*endptr = inptr;                                        /* assume fails */
if ((radix < 2) || (radix > 36))
    return 0;
while (isspace (*inptr))                                /* bypass white space */
    inptr++;
val = 0;
nodigit = 1;
for (c = *inptr; isalnum(c); c = *++inptr) {            /* loop through char */
    if (islower (c))
        c = toupper (c);
    if (isdigit (c))                                    /* digit? */
        digit = c - (uint32) '0';
    else if (radix <= 10)                               /* stop if not expected */
        break;
    else digit = c + 10 - (uint32) 'A';                 /* convert letter */
    if (digit >= radix)                                 /* valid in radix? */
        return 0;
    val = (val * radix) + digit;                        /* add to value */
    nodigit = 0;
    }
if (nodigit)                                            /* no digits? */
    return 0;
*endptr = inptr;                                        /* result pointer */
return val;
}

/* fprint_val - general radix printing routine

   Inputs:
        stream  =       stream designator
        val     =       value to print
        radix   =       radix to print
        width   =       width to print
        format  =       leading zeroes format
   Outputs:
        status  =       error status
*/

t_stat fprint_val (FILE *stream, t_value val, uint32 radix,
    uint32 width, uint32 format)
{
#define MAX_WIDTH ((int) (CHAR_BIT * sizeof (t_value)))
t_value owtest, wtest;
int32 d, digit, ndigits;
char dbuf[MAX_WIDTH + 1];

for (d = 0; d < MAX_WIDTH; d++)
    dbuf[d] = (format == PV_RZRO)? '0': ' ';
dbuf[MAX_WIDTH] = 0;
d = MAX_WIDTH;
do {
    d = d - 1;
    digit = (int32) (val % radix);
    val = val / radix;
    dbuf[d] = (char) ((digit <= 9)? '0' + digit: 'A' + (digit - 10));
    } while ((d > 0) && (val != 0));

if (format != PV_LEFT) {
    wtest = owtest = radix;
    ndigits = 1;
    while ((wtest < width_mask[width]) && (wtest >= owtest)) {
        owtest = wtest;
        wtest = wtest * radix;
        ndigits = ndigits + 1;
        }
    if ((MAX_WIDTH - ndigits) < d)
        d = MAX_WIDTH - ndigits;
    }
if (fputs (&dbuf[d], stream) == EOF)
    return SCPE_IOERR;
return SCPE_OK;
}

/* Print message to stdout and sim_log (if enabled)

   Inputs:
        fmt     =       printf style format string
        ...     =       variable argument list
    Output:
        none

   If this is called from a running simulator, it will
   interpolate \r in front of bare \n.
*/

void sim_printf (const char* fmt, ...)
{
char buf[STACKBUFSIZE];
int32 bufsize = STACKBUFSIZE;
int32 len;
va_list arglist;

va_start (arglist, fmt);
len = VSNPRINTF (buf, bufsize - 1, fmt, arglist);
va_end (arglist);
buf[bufsize - 1] = '\0';
if ((len < 0) || (len >= bufsize - 1)) {
    printf ("%%TRUNCATED!\a ");
    if (sim_log)
        fprintf (sim_log, "%%TRUNCATED!\a ");
    }

if (sim_is_running) {
    char *c, *remnant = buf;
    while ((c = strchr(remnant, '\n'))) {
        if ((c != buf) && (*(c - 1) != '\r'))
            printf("%.*s\r\n", (int)(c - remnant), remnant);
        else printf ("%.*s\n", (int)(c - remnant), remnant);
        remnant = c + 1;
        }
    printf("%s", remnant);
    }
else printf ("%s", buf);
if (sim_log)
    fprintf (sim_log, "%s", buf);
return;
}

t_stat sim_messagef (t_stat stat, const char *fmt, ...)
{
return stat;
}

void sim_perror (char *msg)
{
int saved_errno = errno;

//perror (msg);
sim_printf ("%s: %s\n", msg, strerror (saved_errno));
}

/* Event queue package

        sim_activate            add entry to event queue
        sim_cancel              remove entry from event queue
        sim_process_event       process entries on event queue
        sim_is_active           see if entry is on event queue
        sim_atime               return absolute time for an entry
        sim_gtime               return global time
        sim_qcount              return event queue entry count

   Asynchronous events are set up by queueing a unit data structure
   to the event queue with a timeout (in simulator units, relative
   to the current time).  Each simulator 'times' these events by
   counting down interval counter sim_interval.  When this reaches
   zero the simulator calls sim_process_event to process the event
   and to see if further events need to be processed, or sim_interval
   reset to count the next one.

   The event queue is maintained in clock order; entry timeouts are
   RELATIVE to the time in the previous entry.

   sim_process_event - process event

   Inputs:
        none
   Outputs:
        reason  =       reason code returned by any event processor,
                        or 0 (SCPE_OK) if no exceptions
*/

t_stat sim_process_event (void)
{
UNIT *uptr;
t_stat reason;

if (stop_cpu)                                           /* stop CPU? */
    return SCPE_STOP;
if (sim_clock_queue == NULL) {                          /* queue empty? */
    UPDATE_SIM_TIME (noqueue_time);                     /* update sim time */
    sim_interval = noqueue_time = NOQUEUE_WAIT;         /* flag queue empty */
    return SCPE_OK;
    }
UPDATE_SIM_TIME (sim_clock_queue->time);                /* update sim time */
do {
    uptr = sim_clock_queue;                             /* get first */
    sim_clock_queue = uptr->next;                       /* remove first */
    uptr->next = NULL;                                  /* hygiene */
    uptr->time = 0;
    if (sim_clock_queue != NULL)
        sim_interval = sim_clock_queue->time;
    else sim_interval = noqueue_time = NOQUEUE_WAIT;
    if (uptr->action != NULL)
        reason = uptr->action (uptr);
    else reason = SCPE_OK;
    } while ((reason == SCPE_OK) && (sim_interval <= 0));

/* Empty queue forces sim_interval != 0 */

return reason;
}

/* sim_activate - activate (queue) event

   Inputs:
        uptr    =       pointer to unit
        event_time =    relative timeout
   Outputs:
        reason  =       result (SCPE_OK if ok)
*/

t_stat sim_activate (UNIT *uptr, int32 event_time)
{
UNIT *cptr, *prvptr;
int32 accum;

if (event_time < 0)
    return SCPE_IERR;
if (sim_is_active (uptr))                               /* already active? */
    return SCPE_OK;
if (sim_clock_queue == NULL) {
    UPDATE_SIM_TIME (noqueue_time);
    }
else  {                                                 /* update sim time */
    UPDATE_SIM_TIME (sim_clock_queue->time);
    }

prvptr = NULL;
accum = 0;
for (cptr = sim_clock_queue; cptr != NULL; cptr = cptr->next) {
    if (event_time < (accum + cptr->time))
        break;
    accum = accum + cptr->time;
    prvptr = cptr;
    }
if (prvptr == NULL) {                                   /* insert at head */
    cptr = uptr->next = sim_clock_queue;
    sim_clock_queue = uptr;
    }
else {
    cptr = uptr->next = prvptr->next;                   /* insert at prvptr */
    prvptr->next = uptr;
    }
uptr->time = event_time - accum;
if (cptr != NULL)
    cptr->time = cptr->time - uptr->time;
sim_interval = sim_clock_queue->time;
return SCPE_OK;
}

/* sim_activate_abs - activate (queue) event even if event already scheduled

   Inputs:
        uptr    =       pointer to unit
        event_time =    relative timeout
   Outputs:
        reason  =       result (SCPE_OK if ok)
*/

t_stat sim_activate_abs (UNIT *uptr, int32 event_time)
{
sim_cancel (uptr);
return sim_activate (uptr, event_time);
}

/* sim_cancel - cancel (dequeue) event

   Inputs:
        uptr    =       pointer to unit
   Outputs:
        reason  =       result (SCPE_OK if ok)

*/

t_stat sim_cancel (UNIT *uptr)
{
UNIT *cptr, *nptr;

if (sim_clock_queue == NULL)
    return SCPE_OK;
UPDATE_SIM_TIME (sim_clock_queue->time);                /* update sim time */
nptr = NULL;
if (sim_clock_queue == uptr)
    nptr = sim_clock_queue = uptr->next;
else {
    for (cptr = sim_clock_queue; cptr != NULL; cptr = cptr->next) {
        if (cptr->next == uptr) {
            nptr = cptr->next = uptr->next;
            break;                                      /* end queue scan */
            }
        }
    }
if (nptr != NULL)
    nptr->time = nptr->time + uptr->time;
uptr->next = NULL;                                      /* hygiene */
uptr->time = 0;
if (sim_clock_queue != NULL)
    sim_interval = sim_clock_queue->time;
else sim_interval = noqueue_time = NOQUEUE_WAIT;
return SCPE_OK;
}

/* sim_is_active - test for entry in queue, return activation time

   Inputs:
        uptr    =       pointer to unit
   Outputs:
        result =        absolute activation time + 1, 0 if inactive
*/

int32 sim_is_active (UNIT *uptr)
{
UNIT *cptr;
int32 accum;

accum = 0;
for (cptr = sim_clock_queue; cptr != NULL; cptr = cptr->next) {
    if (cptr == sim_clock_queue) {
        if (sim_interval > 0)
            accum = accum + sim_interval;
        }
    else accum = accum + cptr->time;
    if (cptr == uptr)
        return accum + 1;
    }
return 0;
}

/* sim_gtime - return global time
   sim_grtime - return global time with rollover

   Inputs: none
   Outputs:
        time    =       global time
*/

double sim_gtime (void)
{
if (sim_clock_queue == NULL) {
    UPDATE_SIM_TIME (noqueue_time);
    }
else  {
    UPDATE_SIM_TIME (sim_clock_queue->time);
    }
return sim_time;
}

uint32 sim_grtime (void)
{
if (sim_clock_queue == NULL) {
    UPDATE_SIM_TIME (noqueue_time);
    }
else  {
    UPDATE_SIM_TIME (sim_clock_queue->time);
    }
return sim_rtime;
}

/* sim_qcount - return queue entry count

   Inputs: none
   Outputs:
        count   =       number of entries on the queue
*/

int32 sim_qcount (void)
{
int32 cnt;
UNIT *uptr;

cnt = 0;
for (uptr = sim_clock_queue; uptr != NULL; uptr = uptr->next)
    cnt++;
return cnt;
}

/* Breakpoint package.  This module replaces the VM-implemented one
   instruction breakpoint capability.

   Breakpoints are stored in table sim_brk_tab, which is ordered by address for
   efficient binary searching.  A breakpoint consists of a four entry structure:

        addr                    address of the breakpoint
        type                    types of breakpoints set on the address
                                a bit mask representing letters A-Z
        cnt                     number of iterations before breakp is taken
        action                  pointer command string to be executed
                                when break is taken

   sim_brk_summ is a summary of the types of breakpoints that are currently set (it
   is the bitwise OR of all the type fields).  A simulator need only check for
   a breakpoint of type X if bit SWMASK('X') is set in sim_brk_sum.

   The package contains the following public routines:

        sim_brk_init            initialize
        sim_brk_set             set breakpoint
        sim_brk_clr             clear breakpoint
        sim_brk_clrall          clear all breakpoints
        sim_brk_show            show breakpoint
        sim_brk_showall         show all breakpoints
        sim_brk_test            test for breakpoint
        sim_brk_npc             PC has been changed
        sim_brk_getact          get next action
        sim_brk_clract          clear pending actions

   Initialize breakpoint system.
*/

t_stat sim_brk_init (void)
{
sim_brk_lnt = SIM_BRK_INILNT;
sim_brk_tab = (BRKTAB *) calloc (sim_brk_lnt, sizeof (BRKTAB));
if (sim_brk_tab == NULL)
    return SCPE_MEM;
sim_brk_ent = sim_brk_ins = 0;
sim_brk_act = NULL;
sim_brk_npc (0);
return SCPE_OK;
}

/* Search for a breakpoint in the sorted breakpoint table */

BRKTAB *sim_brk_fnd (t_addr loc)
{
int32 lo, hi, p;
BRKTAB *bp;

if (sim_brk_ent == 0) {                                 /* table empty? */
    sim_brk_ins = 0;                                    /* insrt at head */
    return NULL;                                        /* sch fails */
    }
lo = 0;                                                 /* initial bounds */
hi = sim_brk_ent - 1;
do {
    p = (lo + hi) >> 1;                                 /* probe */
    bp = sim_brk_tab + p;                               /* table addr */
    if (loc == bp->addr)                                /* match? */
        return bp;
    else if (loc < bp->addr)                            /* go down? p is upper */
        hi = p - 1;
    else lo = p + 1;                                    /* go up? p is lower */
    } while (lo <= hi);
if (loc < bp->addr)                                     /* insrt before or */
    sim_brk_ins = p;
else sim_brk_ins = p + 1;                               /* after last sch */
return NULL;
}

/* Insert a breakpoint */

BRKTAB *sim_brk_new (t_addr loc)
{
int32 i, t;
BRKTAB *bp, *newp;

if (sim_brk_ins < 0)
    return NULL;
if (sim_brk_ent >= sim_brk_lnt) {                       /* out of space? */
    t = sim_brk_lnt + SIM_BRK_INILNT;                   /* new size */
    newp = (BRKTAB *) calloc (t, sizeof (BRKTAB));      /* new table */
    if (newp == NULL)                                   /* can't extend */
        return NULL;
    for (i = 0; i < sim_brk_lnt; i++)                   /* copy table */
        *(newp + i) = *(sim_brk_tab + i);
    free (sim_brk_tab);                                 /* free old table */
    sim_brk_tab = newp;                                 /* new base, lnt */
    sim_brk_lnt = t;
    }
if (sim_brk_ins != sim_brk_ent) {                       /* move needed? */
    for (bp = sim_brk_tab + sim_brk_ent;
         bp > sim_brk_tab + sim_brk_ins; bp--)
        *bp = *(bp - 1);
    }
bp = sim_brk_tab + sim_brk_ins;
bp->addr = loc;
bp->typ = 0;
bp->cnt = 0;
bp->act = NULL;
sim_brk_ent = sim_brk_ent + 1;
return bp;
}

/* Set a breakpoint of type sw */

t_stat sim_brk_set (t_addr loc, int32 sw, int32 ncnt, char *act)
{
BRKTAB *bp;

if (sw == 0)
    sw = sim_brk_dflt;
if ((sim_brk_types & sw) == 0)
    return SCPE_NOFNC;
bp = sim_brk_fnd (loc);                                 /* present? */
if (!bp)                                                /* no, allocate */
    bp = sim_brk_new (loc);
if (!bp)                                                /* still no? mem err */
    return SCPE_MEM;
bp->typ = sw;                                           /* set type */
bp->cnt = ncnt;                                         /* set count */
if ((bp->act != NULL) && (act != NULL)) {               /* replace old action? */
    free (bp->act);                                     /* deallocate */
    bp->act = NULL;                                     /* now no action */
    }
if ((act != NULL) && (*act != 0)) {                     /* new action? */
    char *newp = (char *) calloc (CBUFSIZE, sizeof (char)); /* alloc buf */
    if (newp == NULL)                                   /* mem err? */
        return SCPE_MEM;
    strncpy (newp, act, CBUFSIZE);                      /* copy action */
    bp->act = newp;                                     /* set pointer */
    }
sim_brk_summ = sim_brk_summ | sw;
return SCPE_OK;
}

/* Clear a breakpoint */

t_stat sim_brk_clr (t_addr loc, int32 sw)
{
BRKTAB *bp = sim_brk_fnd (loc);

if (!bp)                                                /* not there? ok */
    return SCPE_OK;
if (sw == 0)
    sw = SIM_BRK_ALLTYP;
bp->typ = bp->typ & ~sw;
if (bp->typ)                                            /* clear all types? */
    return SCPE_OK;
if (bp->act != NULL)                                    /* deallocate action */
    free (bp->act);
for ( ; bp < (sim_brk_tab + sim_brk_ent - 1); bp++)     /* erase entry */
    *bp = *(bp + 1);
sim_brk_ent = sim_brk_ent - 1;                          /* decrement count */
sim_brk_summ = 0;                                       /* recalc summary */
for (bp = sim_brk_tab; bp < (sim_brk_tab + sim_brk_ent); bp++)
    sim_brk_summ = sim_brk_summ | bp->typ;
return SCPE_OK;
}

/* Clear all breakpoints */

t_stat sim_brk_clrall (int32 sw)
{
BRKTAB *bp;

if (sw == 0)
    sw = SIM_BRK_ALLTYP;
for (bp = sim_brk_tab; bp < (sim_brk_tab + sim_brk_ent); ) {
    if (bp->typ & sw)
        sim_brk_clr (bp->addr, sw);
    else bp++;
    }
return SCPE_OK;
}

/* Show a breakpoint */

t_stat sim_brk_show (FILE *st, t_addr loc, int32 sw)
{
BRKTAB *bp = sim_brk_fnd (loc);
DEVICE *dptr;
int32 i, any;

if (sw == 0)
    sw = SIM_BRK_ALLTYP;
if (!bp || (!(bp->typ & sw)))
    return SCPE_OK;
dptr = sim_dflt_dev;
if (dptr == NULL)
    return SCPE_OK;
if (sim_vm_fprint_addr)
    sim_vm_fprint_addr (st, dptr, loc);
else fprint_val (st, loc, dptr->aradix, dptr->awidth, PV_LEFT);
fprintf (st, ":\t");
for (i = any = 0; i < 26; i++) {
    if ((bp->typ >> i) & 1) {
        if (any)
            fprintf (st, ", ");
        fputc (i + 'A', st);
        any = 1;
        }
    }
if (bp->cnt > 0)
    fprintf (st, " [%d]", bp->cnt);
if (bp->act != NULL)
    fprintf (st, "; %s", bp->act);
fprintf (st, "\n");
return SCPE_OK;
}

/* Show all breakpoints */

t_stat sim_brk_showall (FILE *st, int32 sw)
{
BRKTAB *bp;

if (sw == 0)
    sw = SIM_BRK_ALLTYP;
for (bp = sim_brk_tab; bp < (sim_brk_tab + sim_brk_ent); bp++) {
    if (bp->typ & sw)
        sim_brk_show (st, bp->addr, sw);
    }
return SCPE_OK;
}

/* Test for breakpoint */

uint32 sim_brk_test (t_addr loc, uint32 btyp)
{
BRKTAB *bp;
uint32 spc = (btyp >> SIM_BKPT_V_SPC) & (SIM_BKPT_N_SPC - 1);

if ((bp = sim_brk_fnd (loc)) && (btyp & bp->typ)) {     /* in table, type match? */
    if ((sim_brk_pend[spc] && (loc == sim_brk_ploc[spc])) || /* previous location? */
        (--bp->cnt > 0))                                /* count > 0? */
        return 0;
    bp->cnt = 0;                                        /* reset count */
    sim_brk_ploc[spc] = loc;                            /* save location */
    sim_brk_pend[spc] = TRUE;                           /* don't do twice */
    sim_brk_act = bp->act;                              /* set up actions */
    return (btyp & bp->typ);
    }
sim_brk_pend[spc] = FALSE;
return 0;
}

/* Get the next pending action command, if any.

   This routine copies the next pending action command to the buffer designated
   by "buf" of maximum size "size".  It returns the "buf" pointer if a command
   is pending and NULL if one is not.  Pending actions are optionally set as
   part of BREAK commands.

   On entry, the "sim_brk_act" global variable points at the current set of
   pending action commands or is NULL if no commands are pending.  If the set
   contains more than one command, they are separated by semicolons.  On exit,
   the next command is copied to "buf", and "sim_brk_act" is advanced to point
   at the succeeding command.  If no command remains after copying,
   "sim_brk_act" is set to NULL.

   Searching for command separators is complicated by the potential inclusion of
   semicolons within single commands.  For example, the SET DEBUG command
   separates its parameters with semicolons.  So this command:

     BREAK 100 ; SET CPU DEBUG=INSTR;DATA

   ...would fail if the second semicolon is interpreted as a command separator.
   Therefore, context must be considered when searching for separators.

   To resolve the ambiguity, an action command may be entirely enclosed by
   quotation marks or apostrophes, e.g.:

     BREAK 100 ; 'SET CPU DEBUG=INSTR;DATA'

   ...that are stripped off by this routine.  This ensures that the SET command
   is processed in its entirety.

   The general rule employed is that semicolons within quoted strings are not
   separators.  This yields four possible parsing cases for the "sim_brk_act"
   command string when this routine is called:

     1. There are no semicolons in the string.

     2. A semicolon is present outside of a quoted string.

     3. A semicolon is present within a quoted string that does not encompass
        the entire command (i.e., the command does not start with a quote).

     4. A semicolon is present within a command that is quoted.

   For case 1, the command encompasses the entire string, which is simply
   copied to the output buffer before setting "sim_brk_act" to NULL.  For case
   2, the command up to the semicolon is copied, and the pointer is advanced to
   point at the remainder of the string after the semicolon.

   For cases 3 and 4, the quoted string is skipped, and then another search for
   a semicolon is made.  Depending on the result of that second search, the
   string is handled as one of the four cases above.  In addition, case 4
   adjusts the command pointer and length to omit the surrounding quotes.


   Implementation notes:

    1. The buffer pointed to by "sim_brk_act" must be considered to be
       read-only, so we cannot remove the quotes surrounding an entire command
       by replacing them with blanks.  For breakpoint actions, the variable is
       set to the "act" field of a BRKTAB structure.  Modifying the buffer would
       mean that the command would not be quoted on the second and subsequent
       breakpoint occurrences.  Therefore, we must remove the quotes by copying
       the command to the output buffer without them.

    2. If a command separator is surrounded by blanks, this routine will exit
       with "sim_brk_act" pointing at leading blanks.  Therefore, we strip
       leading blanks on entry.
*/

char *sim_brk_getact (char *buf, int32 size)
{
char   quote, *ep;
size_t cmd_len = 0;                                     /* the length of the next command */
t_bool quoted_cmd = FALSE;                              /* TRUE if the next command is quoted */

if (sim_brk_act == NULL)                                /* if no action is pending */
    return NULL;                                        /*   then we are done */

while (isspace (*sim_brk_act))                          /* skip any leading blanks */
    sim_brk_act++;                                      /*   in the next command */

if (*sim_brk_act == '\0') {                             /* if the next action was entirely blank */
    sim_brk_act = NULL;                                 /*   then clear the action pointer */
    return NULL;                                        /*     and we are done */
    }

ep = strpbrk (sim_brk_act, ";\"'");                     /* search for a semicolon or single or double quote */

while (ep != NULL && (*ep == '\'' || *ep == '"')) {     /* if a quoted string is present */
    quoted_cmd = (ep == sim_brk_act);                   /*   then check whether the entire command is quoted */

    quote = *ep++;                                      /* save the opening quotation mark */

    while (ep [0] != '\0' && ep [0] != quote)           /* while characters remain within the quotes */
        if (ep [0] == '\\' && ep [1] == quote)          /*   if an escaped quote sequence follows */
            ep = ep + 2;                                /*     then skip over the pair */
        else                                            /*   otherwise */
            ep = ep + 1;                                /*     skip the non-quote character */

    if (quoted_cmd && *ep == quote) {                   /* if the entire command is quoted */
        sim_brk_act++;                                  /*   then skip the leading quote */
        cmd_len = (size_t) (ep - sim_brk_act);          /*     and set the size to trim the trailing quote */
        }

    ep = strpbrk (++ep, ";\"'");                        /* search for the next semicolon or single or double quote */
    }

if (cmd_len == 0)                                       /* if the command length has not been set */
    if (ep == NULL)                                     /*   then if no semicolon is present */
        cmd_len = strlen (sim_brk_act);                 /*     then only a single, non-quoted command remains */
    else                                                /*   otherwise */
        cmd_len = (size_t) (ep - sim_brk_act);          /*     the command extends to the semicolon */

if (cmd_len > (size_t) size - 1)                        /* if the command length won't fit in the buffer */
    cmd_len = (size_t) size - 1;                        /*   then copy only as much as will fit */

memcpy (buf, sim_brk_act, cmd_len);                     /* copy the next action command */
buf [cmd_len] = '\0';                                   /*   and terminate it */

if (ep == NULL)                                         /* if the line does not contain a semicolon */
    sim_brk_act = NULL;                                 /*   then this is the last command */
else                                                    /* otherwise */
    sim_brk_act = ep + 1;                               /*   skip the semicolon and point at the next command */

return buf;                                             /* return a pointer to the command to execute */
}

/* Clear pending actions */

void sim_brk_clract (void)
{
sim_brk_act = NULL;
}

/* New PC */

void sim_brk_npc (uint32 cnt)
{
uint32 i;

if ((cnt == 0) || (cnt > SIM_BKPT_N_SPC))
    cnt = SIM_BKPT_N_SPC;
for (i = 0; i < cnt; i++) {
    sim_brk_pend[i] = FALSE;
    sim_brk_ploc[i] = 0;
    }
return;
}

/* Clear breakpoint space */

void sim_brk_clrspc (uint32 spc)
{
if (spc < SIM_BKPT_N_SPC) {
    sim_brk_pend[spc] = FALSE;
    sim_brk_ploc[spc] = 0;
    }
return;
}

/* Message Text */

const char *sim_error_text (t_stat stat)
{
if (stat >= SCPE_BASE)                                  /* SCP error? */
    return scp_error_messages [stat - SCPE_BASE];       /* return it from the SCP list */
else                                                    /* VM error */
    return sim_stop_messages [stat];                    /* return the VM-specific message */
}

/* Debug printout routines, from Dave Hittner */

const char* debug_bstates = "01_^";
const char* debug_fmt     = "DBG> %s %s: ";
int32 debug_unterm  = 0;

/* Finds debug phrase matching bitmask from from device DEBTAB table */

static char* get_dbg_verb (uint32 dbits, DEVICE* dptr)
{
static char* debtab_none    = "DEBTAB_ISNULL";
static char* debtab_nomatch = "DEBTAB_NOMATCH";
int32 offset = 0;

if (dptr->debflags == 0)
    return debtab_none;

/* Find matching words for bitmask */

while (dptr->debflags[offset].name && (offset < 32)) {
    if (dptr->debflags[offset].mask & dbits)
        return dptr->debflags[offset].name;
    offset++;
    }
return debtab_nomatch;
}

/* Prints standard debug prefix unless previous call unterminated */

static void sim_debug_prefix (uint32 dbits, DEVICE* dptr)
{
if (!debug_unterm) {
    char* debug_type = get_dbg_verb (dbits, dptr);
    fprintf(sim_deb, debug_fmt, dptr->name, debug_type);
    }
}

/* Prints state of a register: bit translation + state (0,1,_,^)
   indicating the state and transition of the bit. States:
   0=steady(0->0), 1=steady(1->1), _=falling(1->0), ^=rising(0->1) */

void sim_debug_u16(uint32 dbits, DEVICE* dptr, const char* const* bitdefs,
    uint16 before, uint16 after, int terminate)
{
if (sim_deb && (dptr->dctrl & dbits)) {
    int32 i;

    sim_debug_prefix(dbits, dptr);                      /* print prefix if required */
    for (i = 15; i >= 0; i--) {                         /* print xlation, transition */
        int off = ((after >> i) & 1) + (((before ^ after) >> i) & 1) * 2;
        fprintf(sim_deb, "%s%c ", bitdefs[i], debug_bstates[off]);
        }
    if (terminate)
        fprintf(sim_deb, "\r\n");
    debug_unterm = terminate ? 0 : 1;                   /* set unterm for next */
    }
}

#if defined (_WIN32)
#define vsnprintf _vsnprintf
#endif
#if defined (__DECC) && defined (__VMS) && (defined (__VAX) || (__CRTL_VER <= 70311000))
#define NO_vsnprintf
#endif
#if defined( NO_vsnprintf)
#define STACKBUFSIZE 16384
#else
#define STACKBUFSIZE 2048
#endif

/* Inline debugging - will print debug message if debug file is
   set and the bitmask matches the current device debug options.
   Extra returns are added for un*x systems, since the output
   device is set into 'raw' mode when the cpu is booted,
   and the extra returns don't hurt any other systems. */

void sim_debug (uint32 dbits, DEVICE* dptr, const char* fmt, ...)
{
if (sim_deb && (dptr->dctrl & dbits)) {

    char stackbuf[STACKBUFSIZE];
    int32 bufsize = sizeof(stackbuf);
    char *buf = stackbuf;
    va_list arglist;
    int32 i, j, len;

    buf[bufsize-1] = '\0';
    sim_debug_prefix(dbits, dptr);                      /* print prefix if required */

    while (1) {                                         /* format passed string, args */
        va_start (arglist, fmt);
#if defined(NO_vsnprintf)
#if defined(HAS_vsprintf_void)

/* Note, this could blow beyond the buffer, and we couldn't tell */
/* That is a limitation of the C runtime library available on this platform */

        vsprintf (buf, fmt, arglist);
        for (len = 0; len < bufsize-1; len++)
            if (buf[len] == 0) break;
#else
        len = vsprintf (buf, fmt, arglist);
#endif                                                  /* HAS_vsprintf_void */
#else                                                   /* NO_vsnprintf */
#if defined(HAS_vsnprintf_void)
        vsnprintf (buf, bufsize-1, fmt, arglist);
        for (len = 0; len < bufsize-1; len++)
            if (buf[len] == 0) break;
#else
        len = vsnprintf (buf, bufsize-1, fmt, arglist);
#endif                                                  /* HAS_vsnprintf_void */
#endif                                                  /* NO_vsnprintf */
        va_end (arglist);

/* If it didn't fit into the buffer, then grow it and try again */

        if ((len < 0) || (len >= bufsize-1)) {
            if (buf != stackbuf)
                free (buf);
            bufsize = bufsize * 2;
            buf = (char *) malloc (bufsize);
            if (buf == NULL)                            /* out of memory */
                return;
            buf[bufsize-1] = '\0';
            continue;
            }
        break;
        }

/* Output the formatted data expanding newlines where they exist */

    for (i = j = 0; i < len; ++i) {
        if ('\n' == buf[i]) {
            if (i > j)
                fwrite (&buf[j], 1, i-j, sim_deb);
            j = i;
            fputc('\r', sim_deb);
            }
        }
    if (i > j)
        fwrite (&buf[j], 1, i-j, sim_deb);

/* Set unterminated flag for next time */

    debug_unterm = (len && (buf[len-1]=='\n')) ? 0 : 1;
    if (buf != stackbuf)
        free (buf);
    }
return;
}
