TRAP IT
by D. Pope
from ZX Computing Feb/Mar.1984

The experts said it couldn't be done -
to disable the Break key in a BASIC program,
but Mr D Pope shows us how it CAN be done.


ON-ERROR processing is one of the few useful features
omitted on the Sinclair machines but I realised quite
recently that it would be possible (with the aid of a small
machine code routine) to provide such a function. In the
section of the Spectrum manual which contains details of the
system variables (Chapter 25) there is a WORD entry called
ERR_SP at address 23613 which is described as the "Address
of item on machine stack to be used as error return", which
I examined by using the PEEK command in a function as shown
below.

It was obvious that the word at 23613 pointed at an entry in
the machine stack and this was printed giving the value 4867
which is within the Sinclair ROM. I wrote a small machine
code routine (not included here) which simply jumped to 4867
after placing a required value into the system variable
ERR_NR and found that I got the appropriate error report as
shown in the Sinclair manual in appendix B. It seemed
reasonable to assume that, if the stack entry pointed to by
ERR SP was altered to address a machine code routine other
than 4867, then any subsequent error would cause the
alternative routine to execute.

I developed the machine code routine shown quite quickly and
it worked for all errors except 12 (C in the Sinclair
manual) "Nonsense in BASIC" which, despite considerable
further effort, I could not trap without putting the machine
into a recursive loop. The machine code routine consists of
two parts, the first labelled START is run from BASIC with
an appropriate USR nnnn call and this places the address of
the second part, TRAP, into the machine stack entry pointed
to by ERR_SP. The TRAP routine is then entered on any
"error" completion (from 0 upwards).


Names and addresses

Although comprehensive comments have been included with the
machine code, a little additional explanation is probably
worthwhile, particularly since the routines were written to
be run wherever they are placed in memory. First, the
address of TRAP placed onto the machine stack must be the
correct 'absolute address' regardless of where the routine
is stored. This is calculated dynamically by the first two
instructions, relying on the fact that the BC register pair
contains the address of START on entry from the USR nnnn
function. This absolute address is placed into the machine
stack entry pointed to by ERR_SP by the next five
instructions. Secondly, the TRAP routine must be able to
replace its own entry address onto the machine stack if it
is to handle successive errors and although this is done by
the two instructions starting at PUSH, the value used for
TRAP in the LD DE,TRAP instruction must be modified in order
to allow it to function correctly when loaded at differing
memory addresses. The last five instructions in the START
routine before RET place the corrected value into the
instruction labelled PUSH to overcome this problem.

It must be stressed that instruction modification of this
type is very bad programming practice and should only be
used where there is no alternative. The modified
instructions should be adequately explained as should those
which carry out the modification. In this instance I could
see no way of avoiding dynamic instruction modification but
if anyone can offer an alternative approach I should be most
interested to hear from them.


Basically speaking

When the trap routine is entered (on any BASIC completion)
it first checks the given error code and passes control to
the normal error handling code at 4867 if the error is
either 0 (OK), 9 (STOP), or 12 (Nonsense in BASIC); this
gives the BASIC programmer the option to allow their program
to end normally when required by executing STOP or issuing a
GO TO for a line past the end of the program. Without this
way out the only way to terminate a program with TRAP
incorporated would be to pull out the power lead, a point
which will not be missed by anyone who wishes to "protect"
the code of a BASIC program. As indicated at the beginning
of the article error 12 seems impossible to recover from and
is therefore also given back to the Spectrum routine to
handle.

For any error other than 0, 9 and 12 the TRAP stack entry is
restored and then the number of the BASIC line chosen to
begin ON-ERROR processing is placed into the system variable
NWPPC. I chose line 9900, statement 1 but this could easily
be changed as required by following the comments against the
EQUates for GTOLL, GTOLH and GTOST. In order that the user's
error handling routine (beginning at line 9900) can make
routing decisions based on the line where the error
occurred, the next few instructions save the line, statement
and error numbers. These details could be placed into any
spare memory locations (four bytes are required) but, since
I do not have a printer, I often use the printer buffer as
workspace and the routine stores the crash details there.
The error line number is placed into the word at 23296, the
statement number within that line is put into the byte at
23298 and the actual error number (increased by 1) is stored
in the byte at 23299. It should be noted that the error is
stored as a binary number, not in the equivalent character
form given in the normal error reports expected from the
BASIC; thus 21 would be shown following an attempted "BREAK"
if the contents of 23299 was PEEKED.

Finally in the TRAP routine, the ERR_NR is reset to 255 (its
normal value) and a jump is made to the normal processing
loop used when executing a BASIC program. The address (7030)
of MAINL was found by a small routine (not included here)
which printed the contents of the first few machine stack
entries, which always shows 7030 immediately below the word
with 4867. Although the routine could be used from a direct
command it would serve little purpose since the trap address
would be reset by the Spectrum routine when the command
execution completed. The TRAP only needs to be set up once
in a BASIC program and will continue to pass errors to line
9900 until an error 0, 9 or 12 is encountered.

Any BASIC programmer who has ever wished for an ON-ERROR
function will have few problems finding uses for this
routine but, as a starter, it is fun to produce a small
program which keeps going regardless of attempts to "BREAK"
it and only finishes when it has done its job:

  10 RANDOMIZE USR nnnn
  20 PRINT "STOP ME IF YOU CAN."
  30 FOR a=1 TO 10000
  40 PRINT AT 1,0;a
  50 NEXT a
  60 STOP
9900 PRINT AT 4,0;"NO, I WON'T."
9910 PAUSE 100
9920 CLS
9930 GO TO 4O

Of course the value used for "nnnn" will depend on where you
decide to place the TRAP routine. I have not included a
loader with the routine details because so many good
examples have been printed in the past that most people must
be able to enter a machine code program without problems.

The function for displaying WORD length data from any memory
location which was mentioned at the beginning of the article is:

10 DEF FN a(x)=PEEK x+256*PEEK (x+1)

whereupon PRINT FN a(23613) gives a value just below RAMTOP,
and entering PRINT FN a(FN a(23613)) should return 4867.

Incidentally, I believe a similar function could be produced
to give ON-ERROR handling to ZX81 users since that machine
has a similar configuration of system variables.
Unfortunately I sold my ZX81 to fund the Spectrum and cannot
therefore give any assistance with that project!


--
Another Fine Product transcribed by:
Jim Grimwood (jimg@globalnet.co.uk), Weardale, England
--
