From 766e7058f104ea4bc302bd1d75fa63dfa6fd68da Mon Sep 17 00:00:00 2001
From: Mark Pizzolato <mark@infocomm.com>
Date: Fri, 15 Apr 2011 08:40:33 -0700
Subject: [PATCH] Added SET ON, SET NOON, ON, GOTO and RETURN command support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

    The ControlFlow changes fix a potential bug in breakpoint handling
    which arguably merely could be “not supported”.  That bug is that if
    a breakpoint’s actions have multiple actions AND an action early in
    the list invokes a do command file, the subsequent pending breakpoint
    actions are not performed.

    The ControlFlow patch/changes implement the following extensions to
    the SCP command language without affecting prior behavior:

             GOTO <Label>                 Command is now available.  Labels are lines in which the first non whitespace character is a “:”.  The target of a goto is the first matching label in the current do command file which is encountered.  Since labels don’t do anything else besides being the targets of goto’s, they could be used to provide comments in do command files, for example (“:: This is a comment”)
             SET ON                       Enables error trapping for currently defined traps (by ON commands)
             SET NOON                     Disables error trapping for currently defined traps (by ON commands)
             RETURN                       Return from the current do command file execution with the status from the last executed command
             RETURN <statusvalue>         Return from the current do command file execution with the indicated status.  Status can be a number or a SCPE_<conditionname> name string.
             ON <statusvalue> commandtoprocess{; additionalcommandtoprocess}
                                          Sets the action(s) to take when the specific error status is returned by a command in the currently running do command file.  Multiple actions can be specified with each delimited by a semicolon character (just like breakpoint action commands).
             ON ERROR commandtoprocess{; additionalcommandtoprocess}
                                          Sets the default action(s) to take when any otherwise unspecified error status is returned by a command in the currently running do command file.  Multiple actions can be specified with each delimited by a semicolon character (just like breakpoint action commands).
             ON <statusvalue>
             ON ERROR                     Clears the default actions to take when any otherwise unspecified error status is returned by a command in the currently running do command file.

    Error traps can be taken for any command which returns a status other
    than SCPE_STEP, SCPE_OK, and SCPE_EXIT.

    ON Traps can specify any status value from the following list:
       NXM, UNATT, IOERR, CSUM, FMT, NOATT, OPENERR, MEM, ARG, STEP,
       UNK, RO, INCOMP, STOP, TTIERR, TTOERR, EOF, REL, NOPARAM, ALATT,
       TIMER, SIGERR, TTYERR, SUB, NOFNC, UDIS, NORO, INVSW, MISVAL,
       2FARG, 2MARG, NXDEV, NXUN, NXREG, NXPAR, NEST, IERR, MTRLNT,
       LOST, TTMO, STALL, AFAIL.

    These values can be indicated by name or by their internal numeric
    value (not recommended).

    Interactions with ASSERT command and “DO –e”:
    DO –e		is equivalent to SET ON, which by itself it equivalent
                    to “SET ON; ON ERROR RETURN”.
    ASSERT		failure have several different actions:
    If error trapping is not enabled then AFAIL causes exit from the current
    do command file.
    If error trapping is enabled and an explicit “ON AFAIL” action is defined,
    then the specified action is performed.
    If error trapping is enabled and no “ON AFAIL” action is defined, then
    an AFAIL causes exit from the current do command file.

    Other related changes/extensions:
    •	The “!” command (execute a command on the local OS), now returns
            the command’s exit status as the status from the “!” command.
            This allows ON conditions to handle error status responses from
            OS commands and act as desired.
    •	Argument substitution has been extended for do command file
            execution.  The extended argument substitution include
            substitution of any program environment variable (referenced by
            %ENVVARNAME%) along with dynamic expansion of several pseudo
            environment variables:
    %DATE%	expands to a string of the form: yyyy/mm/dd   (i.e. 2011/01/23)
    %TIME%	expands to a string of the form: hh:mm:ss   (i.e. 17:23:44)
    %CTIME%	expands to a string of the form: www mmm dd hh:mm:ss yyyy
            (i.e. Mon Jan 24 08:22:33 2010)
    %ENVNAME%	expands to a string which has the value of the environment
            variable ENVNAME
---
 AltairZ80/altairZ80_sio.c |   3 +-
 scp.c                     | 416 ++++++++++++++++++++++++++++++--------
 scp.h                     |   5 +
 sim_defs.h                |   2 +
 4 files changed, 339 insertions(+), 87 deletions(-)

diff --git a/AltairZ80/altairZ80_sio.c b/AltairZ80/altairZ80_sio.c
index 1ae1b9357..e6cf3e362 100644
--- a/AltairZ80/altairZ80_sio.c
+++ b/AltairZ80/altairZ80_sio.c
@@ -148,7 +148,6 @@ extern const t_bool rtc_avail;
 extern uint32 PCX;
 extern int32 sim_switches;
 extern int32 sim_quiet;
-extern const char *scp_error_messages[];
 extern int32 SR;
 extern UNIT cpu_unit;
 extern volatile int32 stop_cpu;
@@ -1180,7 +1179,7 @@ static void attachCPM(UNIT *uptr) {
     sim_quiet = sim_switches & SWMASK ('Q');    /* -q means quiet                       */
     lastCPMStatus = attach_unit(uptr, cpmCommandLine);
     if ((lastCPMStatus != SCPE_OK) && (simh_device.dctrl & VERBOSE_MSG))
-        printf("SIMH: " ADDRESS_FORMAT " Cannot open '%s' (%s)." NLP, PCX, cpmCommandLine, scp_error_messages[lastCPMStatus - SCPE_BASE]);
+        printf("SIMH: " ADDRESS_FORMAT " Cannot open '%s' (%s)." NLP, PCX, cpmCommandLine, sim_error_text(lastCPMStatus));
 }
 
 /* setClockZSDOSAdr points to 6 byte block in M: YY MM DD HH MM SS in BCD notation */
diff --git a/scp.c b/scp.c
index f64196a2a..c8b3bf44a 100644
--- a/scp.c
+++ b/scp.c
@@ -23,6 +23,7 @@
    used in advertising or otherwise to promote the sale, use or other dealings
    in this Software without prior written authorization from Robert M Supnik.
 
+   22-Jan-11    MP      Added SET ON, SET NOON, ON, GOTO and RETURN command support
    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
@@ -182,6 +183,7 @@
 #include "sim_rev.h"
 #include <signal.h>
 #include <ctype.h>
+#include <time.h>
 
 #define EX_D            0                               /* deposit */
 #define EX_E            1                               /* examine */
@@ -201,7 +203,7 @@
 #define SSH_SH          1                               /* show */
 #define SSH_CL          2                               /* clear */
 
-#define DO_NEST_LVL     10                              /* DO cmd nesting level */
+#define MAX_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
@@ -286,6 +288,7 @@ t_stat show_dev_logicals (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *
 t_stat show_dev_modifiers (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_on (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);
@@ -347,6 +350,8 @@ 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 (char *instr, char *tmpbuf, int32 maxstr, char *do_arg[]);
+t_stat set_on (int32 flag, char *cptr);
+
 
 /* Global data */
 
@@ -363,7 +368,7 @@ 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;
+char *sim_brk_act[MAX_DO_NEST_LVL];
 BRKTAB *sim_brk_tab = NULL;
 int32 sim_brk_ent = 0;
 int32 sim_brk_lnt = 0;
@@ -380,6 +385,13 @@ t_value *sim_eval = NULL;
 int32 sim_deb_close = 0;                                /* 1 = close debug */
 FILE *sim_log = NULL;                                   /* log file */
 FILE *sim_deb = NULL;                                   /* debug file */
+
+static FILE *sim_gotofile;
+static int32 sim_do_depth = 0;
+
+static int32 sim_on_check[MAX_DO_NEST_LVL+1];
+static char *sim_on_actions[MAX_DO_NEST_LVL+1][SCPE_MAX_ERR+1];
+
 static SCHTAB sim_stab;
 
 static UNIT sim_step_unit = { UDATA (&step_svc, 0, 0)  };
@@ -393,7 +405,7 @@ static const char *sim_sa64 = "64b addresses";
 #else
 static const char *sim_sa64 = "32b addresses";
 #endif
-#if defined USE_NETWORK
+#if defined USE_NETWORK || defined USE_SHARED
 static const char *sim_snet = "Ethernet support";
 #else
 static const char *sim_snet = "no Ethernet";
@@ -404,51 +416,54 @@ static const char *sim_snet = "no Ethernet";
 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 struct scp_error {
+    char *code;
+    char *message;
+    } scp_errors[1+SCPE_MAX_ERR-SCPE_BASE] =
+        {{"NXM",     "Address space exceeded"},
+         {"UNATT",   "Unit not attached"},
+         {"IOERR",   "I/O error"},
+         {"CSUM",    "Checksum error"},
+         {"FMT",     "Format error"},
+         {"NOATT",   "Unit not attachable"},
+         {"OPENERR", "File open error"},
+         {"MEM",     "Memory exhausted"},
+         {"ARG",     "Invalid argument"},
+         {"STEP",    "Step expired"},
+         {"UNK",     "Unknown command"},
+         {"RO",      "Read only argument"},
+         {"INCOMP",  "Command not completed"},
+         {"STOP",    "Simulation stopped"},
+         {"EXIT",    "Goodbye"},
+         {"TTIERR",  "Console input I/O error"},
+         {"TTOERR",  "Console output I/O error"},
+         {"EOF",     "End of file"},
+         {"REL",     "Relocation error"},
+         {"NOPARAM", "No settable parameters"},
+         {"ALATT",   "Unit already attached"},
+         {"TIMER",   "Hardware timer error"},
+         {"SIGERR",  "SIGINT handler setup error"},
+         {"TTYERR",  "Console terminal setup error"},
+         {"SUB",     "Subscript out of range"},
+         {"NOFNC",   "Command not allowed"},
+         {"UDIS",    "Unit disabled"},
+         {"NORO",    "Read only operation not allowed"},
+         {"INVSW",   "Invalid switch"},
+         {"MISVAL",  "Missing value"},
+         {"2FARG",   "Too few arguments"},
+         {"2MARG",   "Too many arguments"},
+         {"NXDEV",   "Non-existent device"},
+         {"NXUN",    "Non-existent unit"},
+         {"NXREG",   "Non-existent register"},
+         {"NXPAR",   "Non-existent parameter"},
+         {"NEST",    "Nested DO command limit exceeded"},
+         {"IERR",    "Internal error"},
+         {"MTRLNT",  "Invalid magtape record length"},
+         {"LOST",    "Console Telnet connection lost"},
+         {"TTMO",    "Console Telnet connection timed out"},
+         {"STALL",   "Console Telnet output stall"},
+         {"AFAIL",   "Assertion failed"},
+};
 
 const size_t size_map[] = { sizeof (int8),
     sizeof (int8), sizeof (int16), sizeof (int32), sizeof (int32)
@@ -543,6 +558,8 @@ static CTAB cmd_table[] = {
       "set <unit> ENABLED       enable unit\n"
       "set <unit> DISABLED      disable unit\n"
       "set <unit> arg{,arg...}  set unit parameters\n"
+      "set on                   enables error checking after command execution\n"
+      "set noon                 disables error checking after command execution\n"
       },
     { "SHOW", &show_cmd, 0,
       "sh{ow} br{eak} <list>    show breakpoints\n"
@@ -560,9 +577,18 @@ static CTAB cmd_table[] = {
       "sh{ow} <dev> MODIFIERS   show device modifiers\n"
       "sh{ow} <dev} NAMES       show device logical name\n"
       "sh{ow} <dev> {arg,...}   show device parameters\n"
-      "sh{ow} <unit> {arg,...}  show unit parameters\n"  },
+      "sh{ow} <unit> {arg,...}  show unit parameters\n"
+      "sh{ow} on                show on condition actions\n"  },
     { "DO", &do_cmd, 1,
       "do <file> {arg,arg...}   process command file\n" },
+    { "GOTO", &goto_cmd, 1,
+      "goto <label>             goto label in command file\n" },
+    { "RETURN", &return_cmd, 0,
+      "return                   return from command file with last command status\n"
+      "return <status>          return from command file with specific status\n" },
+    { "ON", &on_cmd, 0,
+      "on <condition> <action>  perform action after condition\n"
+      "on <condition>           clear action for specific condition\n" },
     { "ECHO", &echo_cmd, 0,
       "echo <string>            display <string>\n" },
     { "ASSERT", &assert_cmd, 0,
@@ -632,7 +658,7 @@ sim_timer_init ();
 
 if ((stat = sim_ttinit ()) != SCPE_OK) {
     fprintf (stderr, "Fatal terminal initialization error\n%s\n",
-        scp_error_messages[stat - SCPE_BASE]);
+        sim_error_text (stat));
     return 0;
     }
 if ((sim_eval = (t_value *) calloc (sim_emax, sizeof (t_value))) == NULL) {
@@ -641,12 +667,12 @@ if ((sim_eval = (t_value *) calloc (sim_emax, sizeof (t_value))) == NULL) {
     };
 if ((stat = reset_all_p (0)) != SCPE_OK) {
     fprintf (stderr, "Fatal simulator initialization error\n%s\n",
-        scp_error_messages[stat - SCPE_BASE]);
+        sim_error_text (stat));
     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]);
+        sim_error_text (stat));
     return 0;
     }
 if (!sim_quiet) {
@@ -687,9 +713,9 @@ while (stat != SCPE_EXIT) {                             /* in case exit */
         stat = cmdp->action (cmdp->arg, cptr);          /* if found, exec */
     else stat = SCPE_UNK;
     if (stat >= SCPE_BASE) {                            /* error? */
-        printf ("%s\n", scp_error_messages[stat - SCPE_BASE]);
+        printf ("%s\n", sim_error_text (stat));
         if (sim_log)
-            fprintf (sim_log, "%s\n", scp_error_messages[stat - SCPE_BASE]);
+            fprintf (sim_log, "%s\n", sim_error_text (stat));
         }
     if (sim_vm_post != NULL)
         (*sim_vm_post) (TRUE);
@@ -769,6 +795,7 @@ return SCPE_OK;
 
 t_stat spawn_cmd (int32 flag, char *cptr)
 {
+t_stat status;
 if ((cptr == NULL) || (strlen (cptr) == 0))
     cptr = getenv("SHELL");
 if ((cptr == NULL) || (strlen (cptr) == 0))
@@ -780,12 +807,12 @@ if ((cptr == NULL) || (strlen (cptr) == 0))
 fflush(stdout);                                         /* flush stdout */
 if (sim_log)                                            /* flush log if enabled */
     fflush (sim_log);
-system (cptr);
+status = system (cptr);
 #if defined (VMS)
 printf ("\n");
 #endif
 
-return SCPE_OK;
+return status;
 }
 
 /* Echo command */
@@ -831,7 +858,7 @@ 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;
+int32 echo, nargs, errabort, i;
 t_bool interactive, isdo, staying;
 t_stat stat;
 char *ocptr;
@@ -875,6 +902,9 @@ if ((fpin = fopen (do_arg[0], "r")) == NULL) {          /* file failed to open?
     }
 if (flag < 1)                                           /* start at level 1 */
     flag = 1;
+++sim_do_depth;
+if (errabort)                                           /* -e flag? */
+    set_on (1, NULL);                                   /* equivalent to ON ERROR RETURN */
 
 do {
     ocptr = cptr = sim_brk_getact (cbuf, CBUFSIZE);     /* get bkpt action */
@@ -891,13 +921,18 @@ do {
         printf("do> %s\n", cptr);
     if (echo && sim_log)
         fprintf (sim_log, "do> %s\n", cptr);
+    if (*cptr == ':')                                   /* ignore label */
+        continue;
     cptr = get_glyph (cptr, gbuf, 0);                   /* get command glyph */
     sim_switches = 0;                                   /* init switches */
     isdo = FALSE;
+    sim_gotofile = fpin;
     if (cmdp = find_cmd (gbuf)) {                       /* lookup command */
+        if ((cmdp->action == &return_cmd))              /* RETURN command? */
+            break;                                      /*    done! */
         isdo = (cmdp->action == &do_cmd);
         if (isdo) {                                     /* DO command? */
-            if (flag >= DO_NEST_LVL)                    /* nest too deep? */
+            if (flag >= MAX_DO_NEST_LVL)                /* nest too deep? */
                 stat = SCPE_NEST;
             else stat = do_cmd (flag + 1, cptr);        /* exec DO cmd */
             }
@@ -907,6 +942,10 @@ do {
     staying = (stat != SCPE_EXIT) &&                    /* decide if staying */
               (stat != SCPE_AFAIL) &&
               (!errabort || (stat < SCPE_BASE) || (stat == SCPE_STEP));
+    if ((stat == SCPE_AFAIL) &&                         /* handle special case AFAIL */
+        sim_on_check[sim_do_depth] &&                   /* and use trap action if defined */
+        sim_on_actions[sim_do_depth][stat])             /* otherwise exit */
+        staying = TRUE;
     if ((stat >= SCPE_BASE) && (stat != SCPE_EXIT) &&   /* error from cmd? */
         (stat != SCPE_STEP)) {
         if (!echo && !sim_quiet &&                      /* report if not echoing */
@@ -919,19 +958,42 @@ do {
         }
     if ((staying || !interactive) &&                    /* report error if staying */
         (stat >= SCPE_BASE)) {                          /* or in cmdline file */
-        printf ("%s\n", scp_error_messages[stat - SCPE_BASE]);
+        printf ("%s\n", sim_error_text (stat));
         if (sim_log)
-            fprintf (sim_log, "%s\n", scp_error_messages[stat - SCPE_BASE]);
+            fprintf (sim_log, "%s\n", sim_error_text (stat));
         }
+    if (staying &&
+        (sim_on_check[sim_do_depth]) && 
+        (stat != SCPE_OK) &&
+        (stat != SCPE_STEP))
+        if (sim_on_actions[sim_do_depth][stat])
+            sim_brk_act[sim_do_depth] = sim_on_actions[sim_do_depth][stat];
+        else
+            sim_brk_act[sim_do_depth] = sim_on_actions[sim_do_depth][0];
     if (sim_vm_post != NULL)
         (*sim_vm_post) (TRUE);
     } while (staying);
 
 fclose (fpin);                                          /* close file */
-return stat;
+sim_gotofile = NULL;
+for (i=0; i<SCPE_MAX_ERR; i++) {                        /* release any on commands */
+    free (sim_on_actions[sim_do_depth][i]);
+    sim_on_actions[sim_do_depth][i] = NULL;
+    }
+sim_on_check[sim_do_depth] = 0;                         /* clear on mode */
+sim_brk_clract ();                                      /* defang breakpoint actions */
+--sim_do_depth;                                         /* unwind nesting */
+if (cmdp && (cmdp->action == &return_cmd)) {            /* return command? */
+    if (0 == *cptr)
+        return stat;                                    /* return with last command status */
+    sim_string_to_stat (cptr, &stat);
+    return stat;                                        /* return with explicit return status */
+    }
+return (stat == SCPE_EXIT) ? SCPE_EXIT : SCPE_OK;
 }
 
 /* Substitute_args - replace %n tokens in 'instr' with the do command's arguments
+                     and other enviroment variables
 
    Calling sequence
    instr        =       input string
@@ -957,16 +1019,49 @@ for (ip = instr, op = tmpbuf; *ip && (op < oend); ) {
         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
+        if (*ip == '%') {                               /* sub? */
+            if ((ip[1] >= '0') && (ip[1] <= ('9'))) {   /* %n = sub */
+                ap = do_arg[ip[1] - '0'];
+                ip = ip + 2;
+                }
+            else {                                      /* environment variable */
+                char gbuf[CBUFSIZE];
+
+                ap = NULL;
+                get_glyph_gen (ip+1, gbuf, '%', FALSE);
+                ip += 1 + strlen (gbuf);
+                if (*ip == '%') ++ip;
+                ap = getenv(gbuf);
+                if (!ap) {
+                    static char rbuf[CBUFSIZE];
+                    time_t now;
+                    struct tm *tmnow;
+
+                    time(&now);
+                    tmnow = localtime(&now);
+                    if (!strcmp ("DATE", gbuf)) {
+                        sprintf (rbuf, "%4d/%02d/%02d", tmnow->tm_year+1900, tmnow->tm_mon+1, tmnow->tm_mday);
+                        ap = rbuf;
+                        }
+                    else if (!strcmp ("TIME", gbuf)) {
+                        sprintf (rbuf, "%02d:%02d:%02d", tmnow->tm_hour, tmnow->tm_min, tmnow->tm_sec);
+                        ap = rbuf;
+                        }
+                    else if (!strcmp ("CTIME", gbuf)) {
+                        strcpy (rbuf, ctime(&now));
+                        rbuf[strlen (rbuf)-1] = '\0';    /* remove trailing \n */
+                        ap = rbuf;
+                        }
+                    }
+                }
+            if (ap) {                                   /* non-null arg? */
+                while (*ap && (op < oend))              /* copy the argument */
+                    *op++ = *ap++;
+                }
             }
-        }
-    else *op++ = *ip++;                                 /* literal character */
+        else
+            *op++ = *ip++;                              /* literal character */
     }
 *op = 0;                                                /* term buffer */
 strcpy (instr, tmpbuf);
@@ -1025,6 +1120,92 @@ if (test_search (val, &sim_stab))                       /* test condition */
 return SCPE_AFAIL;                                      /* condition fails */
 }
 
+
+/* Goto command */
+
+t_stat goto_cmd (int32 flag, char *fcptr)
+{
+char *cptr, cbuf[CBUFSIZE], gbuf[CBUFSIZE], gbuf1[CBUFSIZE];
+long fpos;
+
+if (NULL == sim_gotofile) return SCPE_UNK;		/* only valid inside of do_cmd */
+get_glyph (fcptr, gbuf1, 0);
+if ('\0' == gbuf1[0]) return SCPE_ARG;                  /* unspecified goto target */
+fpos = ftell(sim_gotofile);                             /* Save start position */
+rewind(sim_gotofile);                                   /* start search for label */
+while (1) {
+    cptr = read_line (cbuf, CBUFSIZE, sim_gotofile);    /* get cmd line */
+    if (cptr == NULL) break;                            /* exit on eof */
+    if (*cptr == 0) continue;                           /* ignore blank */
+    if (*cptr != ':') continue;                         /* ignore non-labels */
+    ++cptr;                                             /* skip : */
+    while (isspace (*cptr)) ++cptr;                     /* skip blanks */
+    cptr = get_glyph (cptr, gbuf, 0);                   /* get label glyph */
+    if (0 == strcmp(gbuf, gbuf1)) {
+        sim_brk_clract ();                              /* goto defangs current actions */
+        return SCPE_OK;
+        }
+    }
+fseek(sim_gotofile, fpos, SEEK_SET);                    /* resture start position */
+return SCPE_ARG;
+}
+
+/* Return command */
+/* The return command is invalid unless encountered in a do_cmd context, */
+/* and in that context, it is handled as a special case inside of do_cmd() */
+/* and not dispatched here, so if we get here a return has been issued from */
+/* interactive input */
+
+t_stat return_cmd (int32 flag, char *fcptr)
+{
+return SCPE_UNK;                                        /* only valid inside of do_cmd */
+}
+
+/* On command */
+
+t_stat on_cmd (int32 flag, char *cptr)
+{
+char gbuf[CBUFSIZE];
+int32 cond;
+
+cptr = get_glyph (cptr, gbuf, 0);
+if ('\0' == gbuf[0]) return SCPE_ARG;                   /* unspecified condition */
+if (0 == strcmp("ERROR", gbuf))
+    cond = 0;
+else
+    if (SCPE_OK != sim_string_to_stat (gbuf, &cond))
+        return SCPE_ARG;
+if ((NULL == cptr) || ('\0' == *cptr)) {                /* Empty Action */
+    free(sim_on_actions[sim_do_depth][cond]);           /* Clear existing condition */
+    sim_on_actions[sim_do_depth][cond] = NULL; }
+else {
+    sim_on_actions[sim_do_depth][cond] = 
+        realloc(sim_on_actions[sim_do_depth][cond], 1+strlen(cptr));
+    strcpy(sim_on_actions[sim_do_depth][cond], cptr);
+    }
+return SCPE_OK;
+}
+
+t_stat set_on (int32 flag, char *cptr)
+{
+if (cptr && (*cptr != 0))                               /* now eol? */
+    return SCPE_2MARG;
+sim_on_check[sim_do_depth] = flag;
+if ((sim_do_depth != 0) && 
+    (NULL == sim_on_actions[sim_do_depth][0])) {        /* default handler set? */
+    sim_on_actions[sim_do_depth][0] =                   /* No, so make "RETURN" */
+        malloc(1+strlen("RETURN"));                     /* be the default action */
+    strcpy(sim_on_actions[sim_do_depth][0], "RETURN");
+    }
+if ((sim_do_depth != 0) && 
+    (NULL == sim_on_actions[sim_do_depth][SCPE_AFAIL])) {/* handler set for AFAIL? */
+    sim_on_actions[sim_do_depth][SCPE_AFAIL] =          /* No, so make "RETURN" */
+        malloc(1+strlen("RETURN"));                     /* be the action */
+    strcpy(sim_on_actions[sim_do_depth][SCPE_AFAIL], "RETURN");
+    }
+return SCPE_OK;
+}
+
 /* Set command */
 
 t_stat set_cmd (int32 flag, char *cptr)
@@ -1049,6 +1230,8 @@ static CTAB set_glob_tab[] = {
     { "NODEBUG", &sim_set_deboff, 0 },                  /* deprecated */
     { "THROTTLE", &sim_set_throt, 1 },
     { "NOTHROTTLE", &sim_set_throt, 0 },
+    { "ON", &set_on, 1 },
+    { "NOON", &set_on, 0 },
     { NULL, NULL, 0 }
     };
 
@@ -1311,6 +1494,7 @@ static SHTAB show_glob_tab[] = {
     { "TELNET", &sim_show_telnet, 0 },                  /* deprecated */
     { "DEBUG", &sim_show_debug, 0 },                    /* deprecated */
     { "THROTTLE", &sim_show_throt, 0 },
+    { "ON", &show_on, 0 },
     { NULL, NULL, 0 }
     };
 
@@ -1618,6 +1802,31 @@ if (dptr->flags & DEV_DEBUG) {
 else return SCPE_NOFNC;
 }
 
+/* Show On actions */
+
+t_stat show_on (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr)
+{
+int32 lvl, i;
+if (cptr && (*cptr != 0)) return SCPE_2MARG;            /* now eol? */
+for (lvl=sim_do_depth; lvl >= 0; --lvl) {
+    if (lvl > 0)
+        fprintf(st, "On Processing at Do Nest Level: %d", lvl);
+    else
+        fprintf(st, "On Processing for input commands");
+    fprintf(st, " is %s\n", (sim_on_check[lvl]) ? "enabled" : "disabled");
+    for (i=1; i<SCPE_BASE; ++i) {
+        if (sim_on_actions[lvl][i])
+            fprintf(st, "    on %5d    %s\n", i, sim_on_actions[lvl][i]); }
+    for (i=SCPE_BASE; i<=SCPE_MAX_ERR; ++i) {
+        if (sim_on_actions[lvl][i])
+            fprintf(st, "    on %-5s    %s\n", scp_errors[i-SCPE_BASE].code, sim_on_actions[lvl][i]); }
+    if (sim_on_actions[lvl][0])
+        fprintf(st, "    on ERROR    %s\n", sim_on_actions[lvl][0]);
+    fprintf(st, "\n");
+    }
+return SCPE_OK;
+}
+
 /* Show modifiers */
 
 t_stat show_mod_names (FILE *st, DEVICE *dnotused, UNIT *unotused, int32 flag, char *cptr)
@@ -2686,7 +2895,7 @@ t_addr k;
 t_value pcval;
 
 if (v >= SCPE_BASE) fprintf (st, "\n%s, %s: ",
-    scp_error_messages[v - SCPE_BASE], pc->name);
+    sim_error_text (v), pc->name);
 else fprintf (st, "\n%s, %s: ", sim_stop_messages[v], pc->name);
 pcval = get_rval (pc, 0);
 if (sim_vm_fprint_addr)
@@ -4397,7 +4606,7 @@ 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_act[sim_do_depth] = NULL;
 sim_brk_npc (0);
 return SCPE_OK;
 }
@@ -4598,7 +4807,7 @@ if ((bp = sim_brk_fnd (loc)) && (btyp & bp->typ)) {     /* in table, type match?
     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 */
+    sim_brk_act[sim_do_depth] = bp->act;                /* set up actions */
     return (btyp & bp->typ);
     }
 sim_brk_pend[spc] = FALSE;
@@ -4612,21 +4821,21 @@ char *sim_brk_getact (char *buf, int32 size)
 char *ep;
 size_t lnt;
 
-if (sim_brk_act == NULL)                                /* any action? */
+if (sim_brk_act[sim_do_depth] == NULL)                  /* any action? */
     return NULL;
-while (isspace (*sim_brk_act))                          /* skip spaces */
-    sim_brk_act++;
-if (*sim_brk_act == 0)                              /* now empty? */
-    return (sim_brk_act = NULL);
-if (ep = strchr (sim_brk_act, ';')) {                   /* cmd delimiter? */
-    lnt = ep - sim_brk_act;                             /* cmd length */
-    memcpy (buf, sim_brk_act, lnt + 1);                 /* copy with ; */
+while (isspace (*sim_brk_act[sim_do_depth]))            /* skip spaces */
+    sim_brk_act[sim_do_depth]++;
+if (*sim_brk_act[sim_do_depth] == 0)                    /* now empty? */
+    return (sim_brk_act[sim_do_depth] = NULL);
+if (ep = strchr (sim_brk_act[sim_do_depth], ';')) {     /* cmd delimiter? */
+    lnt = ep - sim_brk_act[sim_do_depth];               /* cmd length */
+    memcpy (buf, sim_brk_act[sim_do_depth], lnt + 1);   /* copy with ; */
     buf[lnt] = 0;                                       /* erase ; */
-    sim_brk_act = sim_brk_act + lnt + 1;                /* adv ptr */
+    sim_brk_act[sim_do_depth] += lnt + 1;               /* adv ptr */
     }
 else {
-    strncpy (buf, sim_brk_act, size);                   /* copy action */
-    sim_brk_act = NULL;                                 /* no more */
+    strncpy (buf, sim_brk_act[sim_do_depth], size);     /* copy action */
+    sim_brk_act[sim_do_depth] = NULL;                   /* no more */
     }
 return buf;
 }
@@ -4635,7 +4844,7 @@ return buf;
 
 void sim_brk_clract (void)
 {
-sim_brk_act = NULL;
+sim_brk_act[sim_do_depth] = NULL;
 }
 
 /* New PC */
@@ -4664,6 +4873,43 @@ if (spc < SIM_BKPT_N_SPC) {
 return;
 }
 
+/* Message Text */
+
+char *sim_error_text (t_stat stat)
+{
+static char msgbuf[64];
+
+stat &= ~(SCPE_KFLAG|SCPE_BREAK);                       /* remove any flags */
+if ((stat >= SCPE_BASE) && (stat <= SCPE_MAX_ERR))
+    return scp_errors[stat-SCPE_BASE].message;
+sprintf(msgbuf, "Error %d", stat);
+return msgbuf;
+}
+
+t_stat sim_string_to_stat (char *cptr, t_stat *stat)
+{
+char gbuf[CBUFSIZE];
+int32 cond;
+
+*stat = SCPE_ARG;
+cptr = get_glyph (cptr, gbuf, 0);
+if (0 == memcmp("SCPE_", gbuf, 5))
+    strcpy (gbuf, gbuf+5);   /* skip leading SCPE_ */
+for (cond=0; cond < (SCPE_MAX_ERR-SCPE_BASE); cond++)
+    if (0 == strcmp(scp_errors[cond].code, gbuf)) {
+        cond += SCPE_BASE;
+        break;
+        }
+if (cond == (SCPE_MAX_ERR-SCPE_BASE)) {       /* not found? */
+    if (0 == (cond = strtol(gbuf, NULL, 0)))  /* try explicit number */
+        return SCPE_ARG;
+    }
+if (cond > SCPE_MAX_ERR)
+    return SCPE_ARG;
+*stat = cond;
+return SCPE_OK;    
+}
+
 /* Debug printout routines, from Dave Hittner */
 
 const char* debug_bstates = "01_^";
diff --git a/scp.h b/scp.h
index cdb2c33a6..cb22baa00 100644
--- a/scp.h
+++ b/scp.h
@@ -69,6 +69,9 @@ t_stat set_cmd (int32 flag, char *ptr);
 t_stat show_cmd (int32 flag, char *ptr);
 t_stat brk_cmd (int32 flag, char *ptr);
 t_stat do_cmd (int32 flag, char *ptr);
+t_stat goto_cmd (int32 flag, char *ptr);
+t_stat return_cmd (int32 flag, char *ptr);
+t_stat on_cmd (int32 flag, char *ptr);
 t_stat assert_cmd (int32 flag, char *ptr);
 t_stat help_cmd (int32 flag, char *ptr);
 t_stat spawn_cmd (int32 flag, char *ptr);
@@ -113,6 +116,8 @@ BRKTAB *sim_brk_fnd (t_addr loc);
 uint32 sim_brk_test (t_addr bloc, uint32 btyp);
 void sim_brk_clrspc (uint32 spc);
 char *match_ext (char *fnam, char *ext);
+char *sim_error_text (t_stat stat);
+t_stat sim_string_to_stat (char *cptr, t_stat *cond);
 t_stat sim_cancel_step (void);
 void sim_debug_u16 (uint32 dbits, DEVICE* dptr, const char* const* bitdefs,
     uint16 before, uint16 after, int terminate);
diff --git a/sim_defs.h b/sim_defs.h
index 66f67d445..89ea0f832 100644
--- a/sim_defs.h
+++ b/sim_defs.h
@@ -242,6 +242,8 @@ typedef uint32          t_addr;
 #define SCPE_TTMO       (SCPE_BASE + 40)                /* Telnet conn timeout */
 #define SCPE_STALL      (SCPE_BASE + 41)                /* Telnet conn stall */
 #define SCPE_AFAIL      (SCPE_BASE + 42)                /* assert failed */
+
+#define SCPE_MAX_ERR    (SCPE_BASE + 43)                /* Maximum SCPE Error Value */
 #define SCPE_KFLAG      0010000                         /* tti data flag */
 #define SCPE_BREAK      0020000                         /* tti break flag */
 
-- 
GitLab