64 Error Suppression
Tom Nuss
There are times when you don't want error messages (and the resulting interruption) in a program. Here's how to avoid some kinds of system freezes.
While constructing a general graphing program that would handle varied equations, I realized that it would crash when it tried to divide by zero or take the square root of a negative number. Since a graphing program depends on drawing a fairly smooth curve, these two possibilities would definitely occur from time to time in a general program loop that plots. I also found I had to learn machine language to get things to happen before my hair turned gray.
After delving into the BASIC interpreter with my trusty Supermon-64,1 discovered that if the accumulator contained a zero after a division, it would branch to the error routine at $A437, which would jump indirectly (via a vector address in $300 and $301) to $E38B and proceed to print messages and stop the BASIC program.
I soon confirmed that all error messages (at least the ones I tried) went through $A437, then jumped indirectly by the bytes loaded in $300 and $301 to $E38B. All I had to do was change the contents of $300 and $301 to an address pointing to where I would have my own routine that would skip over the error to allow the program to continue.
Back To BASIC
Simple enough, but how to get back in the BASIC program at the right spot once I left? No, not back into the interpreter again; anything but that!
I noticed that just down the page of the memory map at $A906 was the routine "Scan for next statement." Now that I look back on it, I should have started there. Needless to say, that entry point was the last of the major pieces to the puzzle of skipping the BASIC error handler.
To get down to the mechanics, I have made up a demonstration program to illustrate the method to bypass arithmetic errors. If you type in the program and RUN, you will get the results shown in the figure. AC SR XR YR are the Accumulator, Status, X, and Y registers respectively. Directly beneath them are several series of four numbers and either SYNTAX OK or a variation of F(#) = a number.
Ignoring the first line of eights and SYNTAX OK for the moment, you will notice that line 70 in the program defines the function SQR(4 - C???2)/C and that the second line of numbers in the figure is 255 49 14 1. When one has C = -3 in the above equation, mathematicians will shake their heads, and the computer should crash. Why didn't it? In fact it even gave an answer for SQR(-5)/-3 and blithely continued to calculate the rest of the F(C)s from -3 to 3. F(-2), F(-l), F(l), and F(2) give the correct answer, while F(-3), F(0), and F(3) don't.
Remember, our objective is to skip dilemmas like division by zero, so we must first find out if that is what the computer is trying to do. The way to do this is to look at line 10, which POKEs addresses 768 and 769 ($300 and $301) with 52 and 3 (or $34 and $03). These are the bytes indirectly used to tell the error routine where to go after it finds an error; normally these bytes contain the address $E38B, but line 10 changes this to address 820 decimal ($334). This is where our machine language routine is POKEd by line 20. Line 230 changes things back to normal after the program is finished. For those of you who wish to see the disassembled machine language routine, here it is:
STA$FB PHP PLA STA $FC STX$FD STY$FE PHA PLP LDA$FB JMP$A906
The above routine is only used when there is an error. Locations 251–254 ($FB-$FE) are loaded with eights at the start (line 10) and loaded again each time through the loop that calculates F(C). However, if an error occurs in line 150, the error routine will load locations 251–254 with the contents of the registers at the time of the error and then continue with the next BASIC line. Line 160 prints out the contents of the registers and F(C). Thus the contents of 251–254 change only when an error occurs.
So, now the program doesn't crash; it just gives erroneous results, and that should also be avoided. Type in:
185 IF PEEK(252)> = 48 THEN PRINT : GOTO 200
RUN the program again and there should be blanks where F(–3), F(0), and F(3) are involved. In other words, by PEEKing 252 and by comparing it to 48 we have skipped the errors; nothing has been printed, saved, recorded, or crashed. Only the proper numbers are still able to be used.
So much for mathematics. What if we define the function wrongly? LIST the program and change line 70 to: DEFFNF(C) = SQR(4–Ct2/C and RUN. If all is not well you should see a line of four numbers, not eights, a SYNTAX ERROR (70) and line 70. Our error routine kicked in and in line 100 checked location 252 to see if it was less than 112 and told you about the error in syntax. This is really no advantage over the regular system, but if you are using the dynamic keyboard method to enter your DEFFNF(C) (see "Bootmaker for VIC, PET, and 64," COMPUTE!, May 1983), this routine would come in mighty handy.
Errors That Get Through
It should be pointed out that there is a potential problem with this routine. Change line 70 to DEFFNF(C) = SRR(4–C t2)/C. Errors galore, but they weren't caught. Why not? I wish I knew. Please, not the BASIC interpreter again. All I can say is that in an instance like this you will, on most occasions, be able to tell there is an error and that the error is being caused by the DEFFN statement. Also, before including this specific Syntax Error routine in a program of your own, you should try putting a multiply sign (*) before the SQR in line 70 and then RUN. As you can see, the computer locks up. The only way to correct this situation is to turn the power off and reload the program. Weigh the advantages of including the Syntax Error routine described here against the very obvious disadvantage of system lockup.
To sum up:
- POKE 768 and 769 ($300, $301) with the address of your machine language routine that will handle the BASIC errors. In the example presented here, 52 and 3 are POKEd, for location 820 ($334).
- The error handling routine loads byte 252 ($FC) and provides the jump address to "Scan for next statement" at $A906 so you can reenter your program.
- Check byte 252 (Status Register during an error) to see if it is greater than or equal to 48 for a mathematical error or 112 for a syntax error.
- Take the appropriate action either to save an answer or to skip it.
- POKE 768 and 769 with 139 and 227 respectively to restore the normal error vector address ($E38B). This is important since the computer won't be able to function in the immediate mode.
Error Suppression
10 POKE768, 52 : POKE769, 3 : FORC = 0TO3 : POKE251 + C, 8 : NEXTC :rem 108 20 FORC = 0TO16 : READD : POKE820 + C, D : NEXTC :rem 58 30 FORC = 0TO17 : PRINTCHR$(96); : NEXTC : PRINTC HR$(105) rem 51 40 PRINT"AC";TAB(5);"SR";TAB(10);"XR";TA B(15);"YR "; CHR$(125) :rem 122 50 FORC1 = 0TO38 : PRINTCHR$(96); : NEXTC1 : PRINT :rem 178 60 PRINTCHR?(145);TAB(18);CHR$ (177) :rem 215 70 DEFFNF(C) = SQR(4–Ct2)/C :rem 206 80 SX = FNF(1) :rem 172 90 PRINT PEEK(251);TAB(4);PEEK(252);TAB(9) ;PEEK(253);TAB(14); PEEK(254); :rem 27 100 IF PEEK(252)<112THEN PRINT "{3 SPACES}SY NTAX OK" : GOTO 120 :rem 134 110 PRINT"{3 SPACES}SYNTAX ERROR (70)" : GOTO 230 :rem 148 120 FOR C1 = 0 TO 38 : PRINTCHR$(96); : NEXT C1 : PRINT :rem 224 130 FOR C = -3 TO 3 :rem 49 140 C$ = STR$(C) :rem 234 150 X = FNF(C) :rem 153 160 PRINT STR$(PEEK(251)); TAB(4); STR$(PEEK(252)); TAB(9); :rem 198 170 PRINT STRS(PEEK(253)); TAB(14); STR$(PEEK(254)) :rem 37 180 PRINT CHR$(145); TAB(20); "F("C$") = "; :rem 16 190 PRINTX :rem 127 200 FORC1 = 0 TO 38 : PRINT CHR$ (96 ); : EXTC1 : PRINT :rem 223 210 FORCl = 0 TO 3 : POKE 251 + Cl, 8 : NEXTCl :rem 34 220 NEXT C :rem 22 230 POKE 768,139 : POKE 769, 227 :rem 8 240 IF PEEK(252)> = 112 THEN LIST 70 :rem 21 300 DATA 133, 251, 8, 104, 133, 252, 134, 253, 132, 254, 72, 40, 165, 252, 76, 6, 169 :rem 65