Most programs are subject to errors at run time, even if all the typographical/syntactical errors have been removed in the process of entering the program into the computer in the first place. This chapter describes how BASIC programs can respond to these errors, and shows how to write programs that attempt to either correct the problem or direct the program user to take some sort of corrective action. For a complete listing of BASIC error messages, refer to "Error Messages" in the HP BASIC Language Reference manual.
There are three courses of action that you may choose to take with respect to errors:
The remainder of this chapter describes how to implement the first two alternatives.
The last alternative, which may seem frivolous at first glance, is certainly the easiest to implement, and the nature of HP BASIC on HP 9000 Series computers is such that this is often a feasible choice. Upon encountering a run-time error, the computer will pause program execution and display a message giving the error number and the line in which the error happened, and the programmer can then examine the program in light of this information and fix things up. The key word here is "programmer." If the person running the program is also the person who wrote the program, this approach works fine. If the person running the program did not write it, or worse yet, does not know how to program, some attempt should be made to prevent errors from happening in the first place, or to recover from errors and continue running.
When a programmer writes a program, he or she knows exactly what the program is expected to do, and what kinds of inputs make sense for the problem. Given this viewpoint, there is a strong tendency for the programmer not to take into account the possibility that other people using the program might not understand the boundary conditions. A programmer has no choice but to assume that every time a user has the opportunity to feed an input to a program, a mistake can be made and an error can be caused. If the programmer's outlook is noble, he or she will try to save the user from needless anguish and frustration. Even if the programmer's outlook is less altruistic, he or she will try to keep from getting involved in future support problems. In either case, an effort must be made to make the program more resistant to errors.
A classic example of anticipating an operator error is the "division by zero" situation. An INPUT statement is used to get the value for a variable, and the variable is used as a divisor later in the program. If the operator should happen to enter a zero, accidentally or intentionally, the program pauses with an error 31. It is far better to be watching for an out-of-range input and respond gracefully. One method is shown in the following example.
100 INPUT "Miles traveled and total hours",Miles,Hours
110 IF Hours=0 THEN
120 BEEP
130 PRINT "Improper value entered for hours."
140 PRINT "Try again!"
150 GOTO 100
160 END IF
170 Mph=Miles/Hours
Consider another simple example of giving a user the choice of six colors for a certain bar graph. It might be preferable to have the user pick a number corresponding to the color he wished to choose instead of having to type in up to six characters. In this case, the program wouldn't have to check for each number, but rather it could use the logical comparators to check for an entire range:
4030 CLEAR SCREEN
4040 DATA GREEN,BLUE,RED,YELLOW,PURPLE,PINK
4050 ALLOCATE Colors$(1:6)[6]
4060 READ Colors$(*)
4070 FOR I=1 TO 6
4080 PRINT USING "DD,X,K";I,Colors$(I)
4090 NEXT I
4100 Ask: INPUT "Pick the number of a color",I
4110 IF I>=1 AND I<=6 THEN Valid_Color
4140 BEEP
4150 DISP "Invalid answer -- ";
4160 WAIT 1
4170 GOTO Ask
The above example needs a little extra safeguarding.
I
, the variable being input, should be declared to be
an integer, since the only valid inputs are 1, 2, 3, 4, 5, and 6. An answer
like "pick the 3.14th color listed" does not make sense.
Real number boundaries are tested for in a manner similar to that of integers:
7010 INPUT "Enter the waveform's frequency (in KHz)",Frequency
7020 IF Frequency<=0 THEN 7010
7030 INPUT "Enter the amplitude (0-10 volts)",Amplitude
7040 IF Amplitude<0 OR Amplitude>10 THEN 7030
7050 INPUT "Enter the phase angle (in degrees)",Angle
7060 IF Angle<0 OR Angle>180 THEN 7050
7070 Angle=Angle*PI/180
A word of caution is in order about the use of the = comparator in conjunction with REAL and COMPLEX (full-precision) numbers. Numbers on this computer are stored in a binary form, which means that the information stored is not guaranteed to be an exact representation of a decimal number--but it will be very close! What this means is that a program should not use the = comparator in an IF statement where the comparison is being performed on REAL or COMPLEX numbers. The comparison will yield a 'false' or '0' value if the two are different by even one bit, even though the two numbers might really be equal for all practical purposes.
There are two ways around this problem. The first is to try to state the comparison in terms of the <= or >= comparators.
If it is necessary to do an equality comparison with a pair of REAL numbers, then the second method must be used. This involves picking an error tolerance for how close to being equal the two numbers can be to satisfy the test.
So if the difference between two REAL numbers X1 and X2 is less than or equal to a tolerance T0, we'll say that X1 and X2 are "equal" to each other for all practical purposes. The value of T0 will depend upon the application, and must be chosen with care.
For an example, assume that we've picked a tolerance of 10-12 for comparing two real numbers for equality. The proper way to compare the two numbers would be:
950 IF ABS(X1-X2)<=1E-12 THEN Numbers_equal
960 ! Otherwise they're not equal
Another technique for comparing two REAL or COMPLEX values is to use the DROUND function. This is especially suited to applications where the data is known to have a certain number of significant digits. For more details on binary representations of decimal numbers, refer to the "Numeric Computation" chapter.
Note that >=, <=, and DROUND do not work with COMPLEX numbers, but you can compare real parts and imaginary parts. For example, comparing two COMPLEX values for equality would require something like this:
IF (ABS(REAL(C1)-REAL(C2)) <= 1E-12) AND
(ABS(IMAG(C1)-IMAG(C2)) <= 1E-12) THEN ...
Despite the programmer's best efforts at screening the user's inputs in order to avoid errors, errors will still happen occasionally. It is possible to recover from run-time errors, provided the programmer predicts the places where errors are most likely to happen and adequately handles the error cause(s).
The ON ERROR statement sets up a branching condition which will be taken any time a recoverable error is encountered at run time. Here are some example statements (further examples of each type of branch--GOSUB, GOTO, etc.--are given in subsequent sections).
100 ON ERROR GOSUB Fix_it 400 Fix_it: ! Solve problem.
.
.
530 RETURN ! If GOSUB used.
100 ON ERROR GOTO Fix_it
100 ON ERROR RECOVER Fix_it 400 Fix_it: ! Solve problem.
200 ON ERROR CALL Fix_it_sub 800 SUB Fix_it_sub
.
.
950 SUBEND
The type of branch that you choose (GOTO vs. GOSUB, etc.) depends on how you want to handle the error.
GOTO and GOSUB are purely local in scope--that is, they are active only within the context in which the ON ERROR is declared. CALL and RECOVER are global in scope--after the ON ERROR is set up, the CALL or RECOVER will be executed any time an error occurs, regardless of subprogram environment.
When an ON ERROR statement is executed, the BASIC system will make sure that the specified line or subprogram exists in memory before the program will proceed.
In either case, if the system can't find the given line, error 49 is reported.
ON ERROR has a priority of 17, which means that it will always take priority over any other ON EVENT branch, since the highest user-specifiable priority is 15.
The OFF ERROR statement will cancel the effects of the ON ERROR statement, and no branching will take place if an error is encountered.
The DISABLE statement has no effect on ON ERROR branching.
ERRN is a function which returns the error number which caused the branch to be taken. ERRN is a global function, meaning it can be used from the main program or from any subprogram, and it will always return the number of the most recent error.
100 IF ERRN=80 THEN ! Media not present in drive.
110 PRINT "Please insert the 'Examples' disk,"
120 PRINT "and press the 'Continue' key (f2)."
130 PAUSE
140 END IF
ERRLN is a function which returns the line number of the program line in which the most recent error has occurred.
100 IF ERRLN<1280 THEN GOSUB During_init
110 IF (ERRLN>=1280 AND ERRLN<=2440) THEN GOSUB During_main
120 IF ERRLN>2440 THEN GOSUB During_Last
You can use this function, for instance, in determining what sort of situation-dependent action to take. As in the above example, you may want to take a certain action if the error occurred while "initializing" your program, another if during the "main" segment of your program, and yet another if during the "last" part of the program.
ERRL is another function which is used to find the line in which the error was encountered; however, the difference between this and the ERRLN function is that ERRL is a boolean function. The program gives it a line identifier, and either a 1 or a 0 is returned, depending upon whether or not the specified identifier indicates the line which caused the error.
100 IF ERRL(1250) OR ERRL(1270) THEN GOSUB Fix_12xx
110 IF ERRL(1470) THEN GOSUB Fix_1470
120 IF ERRL(2450) OR ERRL(2530) THEN GOSUB Fix_24xx
ERRL is a local function, which means it can only be used in the same environment as the line which caused the error. This implies that ERRL cannot be used in conjunction with ON ERROR CALL, but it can be used with ON ERROR GOTO and ON ERROR GOSUB. ERRL can be used with ON ERROR RECOVER only if the error did not occur in a subprogram which was called by the environment which set up the ON ERROR RECOVER.
The ERRL function will accept either a line number or a line label:
1140 DISP ERRL(710)
910 IF ERRL(Compute) THEN Fix_compute
ERRDS returns the device selector of the device which was involved in the last error. For instance:
IF ERRDS=12 THEN GOSUB Fix_gpio
Note that this function is only updated when an error that involves an interface or device occurs; otherwise, it remains unchanged until another error involving a device selector occurs. Therefore, if the last error did not involve a device, then the value returned by ERRDS may be irrelevant to the current situation.
ERRM$ is a string function which returns the text of the error which caused the branch to be taken.
100 DISP ERRM$ ! Display default message.
ERROR 1 in 10 Configuration error
The ON ERROR GOSUB statement is used when you want to return to the program line where the error occurred. You have two choices of returning:
Note that if you do not correct the problem and subsequently use RETURN, the BASIC system will repeatedly re-execute the problem-causing line (which will result in an infinite loop between the ON ERROR GOSUB branch and the RETURN).
When an error triggers a branch as a result of an ON ERROR GOSUB statement being active, system priority is set at the highest possible level (17) until the RETURN statement is executed, at which point the system priority is restored to the value it was when the error happened.
100 Radical=B*B-4*A*C
110 Imaginary=0
120 ON ERROR GOSUB Esr
130 Partial=SQRT(Radical)
140 OFF ERROR
.
.
.
350 Esr: IF ERRN=30 THEN
360 Imaginary=1
370 Radical=ABS(Radical)
380 ELSE
390 BEEP
400 DISP "Unexpected Error (";ERRN;")"
410 PAUSE
420 END IF
430 RETURN
NOTE |
---|
You cannot trap errors with ON ERROR while in an ON ERROR GOSUB service routine. |
The ON ERROR GOTO statement is often more useful than ON ERROR GOSUB, especially if you are trying to service more than one error condition. However, ON ERROR GOTO does not change system priority.
As with ON ERROR GOSUB, one error service routine can be used to service all the error conditions in a given context. By testing both the ERRN (what went wrong) and the ERRLN (where it went wrong) functions, you can take proper recovery actions.
One advantage of ON ERROR GOTO is that you can use another ON ERROR statement in the service routine (which you cannot use with ON ERROR GOSUB). This technique, however, requires that you re-establish the original error service routine after correcting any errors (by re-executing the original ON ERROR GOTO statement). The disadvantage is that more programming may be necessary in order to resume execution at the appropriate point after each error service.
10 RESTORE
20 PRINT
30 PRINT
40 PRINT "Coefficients of quadratic equation A"
50 DATA 0,0,0
60 READ A,B,C
70 Maxreal=1.79769313486231E+308
80 Overflow=0
90 Coefficients: !
100 INPUT "A?",A
110 IF A=0 THEN
120 DISP "Must be quadratic"
130 WAIT .5
140 GOTO Coefficients
150 END IF
160 PRINT "A=";A
170 INPUT "B?",B
180 PRINT "B=";B
190 INPUT "C?",C
200 PRINT "C=";C
210 Compute_roots: !
220 ON ERROR GOTO Esr
230 Imaginary=0
240 Part1=-B/2.*A
250 Part2=SQR(B*B-4*A*C)/2.*A
260 IF NOT Imaginary THEN
270 Root1=Part1+Part2
280 Root2=Part1-Part2
290 END IF
300 OFF ERROR
310 Print_roots: !
320 IF Imaginary=0 THEN
330 PRINT "Root 1 =";Root1
340 PRINT "Root 2 =";Root2
350 ELSE
360 PRINT "Root 1 =";Part1;" +";Part2;" i"
370 PRINT "Root 2 =";Part1;" -";Part2;" i"
380 END IF
390 IF Overflow THEN PRINT "OVERFLOW"
400 STOP
410 Esr: !
420 IF ERRN=30 THEN ! SQRT of negative number
430 Part2=SQRT(ABS(B*B-4*A*C))/2*A
440 Imaginary=1
450 Branch=1
460 GOTO 270
470 ELSE
480 IF ERRN=22 THEN ! REAL overflow
490 Overflow=1
500 SELECT ERRLN
510 CASE 240
520 Part1=SGN(B)*SGN(A)*Maxreal
530 Branch=2
540 CASE 250
550 Part2=Maxreal
560 Branch=3
580 CASE 270
590 Root1=Maxreal*SGN(Part1)
600 Branch=4
620 CASE 280
630 Root2=Maxreal*SGN(Part1)
640 Branch=5
660 PRINT "Unexpected overflow"
670 Branch=6
680 CASE ELSE
690 DISP "Unexpected error";ERRN
700 Branch=6
710 END SELECT
720 END IF
730 END IF
740 ON Branch GOTO 270,250,260,280,290,10
750 END
ON ERROR CALL is global, meaning once it is activated, the specified subprogram will be called immediately whenever an error is encountered, regardless of the current context. System priority is set to level 17 inside the subprogram, and remains that way until the SUBEXIT is executed, at which time the system priority will be restored to the value it was when the error happened.
As with ON ERROR GOSUB, you will generally use the ON ERROR CALL statement when you want to return to the program where the error occurred. You have two choices of return destinations:
Note that if you do not correct the problem and subsequently use SUBEXIT, the BASIC system will repeatedly re-execute the problem-causing line (which will result in an infinite loop between the ON ERROR CALL branch and the SUBEXIT).
NOTE |
---|
You cannot trap errors with ON ERROR while in an ON ERROR CALL service routine. |
Bear in mind that an ON...CALL statement can not pass parameters to the specified subprogram, so the only way to communicate between the environment in which the error is declared and the error service routine is through a COM block.
You can use the ERRLN function in any context, and it returns the line number of the most recent error. However, the ERRL function will not work in a different environment than the one in which the ON ERROR statement is declared. For instance, the following two statements will only work in the context in which the specified lines are defined:
100 IF ERRL(40) THEN GOTO Fix40
100 IF ERRL(Line_label) THEN Fix_line_label
The line identifier argument in ERRL will be modified properly when the program is renumbered (such as explicitly by REN or implicitly by GET); however, that is not true of expressions used in comparisons with the value returned by the ERRLN function.
So when using an ON ERROR CALL, you should set things up in such a manner that the line number either doesn't matter, or can be guaranteed to always be the same one when the error occurs. This can be accomplished by declaring the ON ERROR immediately before the line in question, and immediately using OFF ERROR after it.
5010 ON ERROR CALL Fix_disc
5020 ASSIGN @File TO "Data_file"
5030 OFF ERROR
.
.
.
7020 SUB Fix_disc
7030 SELECT ERRN
7040 CASE 80
7050 DISP "Door open -- shut it and press CONT"
7060 PAUSE
7080 CASE 83
7090 DISP "Write protected -- fix and press CONT"
7100 PAUSE
7120 CASE 85
7130 DISP "Disk not initialized -- fix and press CONT"
7140 PAUSE
7160 CASE 56
7170 DISP "Creating Data_file"
7180 CREATE BDAT "Data_file",20
7190 CASE ELSE
7200 DISP "Unexpected error ";ERRN
7210 PAUSE
7220 SUBEND
The ON ERROR RECOVER statement sets up an immediate branch to the specified line whenever an error occurs. The line specified must be in the context of the ON...RECOVER statement. ON ERROR RECOVER is global in scope--it is active not only in the environment in which it is defined, but also in any subprograms called by the segment in which it is defined.
If an error is encountered while an ON ERROR RECOVER statement is active, the system will restore the context of the program segment which actually set up the branch, including its system priority, and will resume execution at the given line.
.
.
.
3250 ON ERROR RECOVER Give_up
3260 CALL Model_universe
3270 DISP "Successfully completed"
3280 STOP
3290 Give_up: DISP "Failure ";ERRN
3300 END
.
.
.
Since it is not always convenient to set up the conditions that cause errors, this BASIC system has a simple way of programmatically simulating errors. The following statement does this:
CAUSE ERROR Error_number
The parameter Error_number is the number of the error that you want to simulate (error numbers in the range 1001 through 1080 have special significance, as described later in this section.) Thus, CAUSE ERROR is useful in testing and verifying your error trapping routines.
The effects of this statement are the same as if the error were caused by real error conditions:
Note, however, that CAUSE ERROR does not change ERRDS.
If CAUSE ERROR is executed from the keyboard, the appropriate error message will be reported, but none of the program-related error conditions are affected. (This is also true of other keyboard-related errors.)
Here is an example of modifying one of the preceding examples to simulate an error. (Note that the original statement has been "commented out" so that it will be easy to put back in after the testing is finished.)
100 Radical=B*B-4*A*C
110 Imaginary=0
120 ON ERROR GOSUB Esr
130 CAUSE ERROR 30 ! Partial=SQRT(Radical) Line modified.
140 OFF ERROR
.
.
.
350 Esr: IF ERRN=30 THEN
360 Imaginary=1
370 Radical=ABS(Radical)
380 ELSE
390 BEEP
400 DISP "Unexpected Error (";ERRN;")"
410 PAUSE
420 END IF
430 RETURN
The error-trapping subroutine can then be tested to verify that it properly traps error 30. After this verification, you may want to modify the CAUSE ERROR line to simulate other errors that could possibly occur at that point in the program. (In this example, it is not necessary since all other errors are handled in the same manner; see lines 390 through 410.)
Error numbers 1001 through 1080 have been reserved to have special meaning for BASIC programs. These errors are used to simulate errors which may occur when a binary has not been loaded. The value returned by ERRN will be 1; the ERRM$ function will return either:
ERROR 1 in line_number Missing binary binary_number
or
ERROR 1 in line_number Missing binary binary_name
The second message is returned with language-extension binaries (no binary name is returned with driver binaries).
After you have finished handling an error in a program, it is convenient to clear the indications that an error has occurred. The following statement performs this action:
100 CLEAR ERROR
This statement has the following effects on the error functions' values:
ERRN |
Subsequently returns 0. |
ERRLN |
Subsequently returns 0. |
ERRL |
Subsequently returns 0 for all arguments (line identifiers) sent to it. |
ERRM$ |
Subsequently returns the null string (string with length 0). |
ERRDS |
Is not affected by CLEAR ERROR. |
Note that the CLEAR ERROR statement is not keyboard executable; it can only be executed from a running program.
Certain file system commands can perform operations on multiple files when wildcards are enabled. Consult your HP BASIC Language Reference manual for a complete list of these commands.
All files that match a given wildcard argument will be processed by the command. For example, say you MSI to an SRM directory having the following CAT listing:
USERS/CHARLIE:REMOTE 21, 0
LABEL: Disc1
FORMAT: SDF
AVAILABLE SPACE: 60168
SYS FILE NUMBER RECORD MODIFIED PUB OPEN
FILE NAME LEV TYPE TYPE RECORDS LENGTH DATE TIME ACC STAT
================ === ==== ===== ======== ======== =============== ==== ======
AGENDA 1 ASCII 254 1 24-Apr-90 12:01 RW
MEMOS 1 DIR 12 24 22-Mar-89 23:12 RW
DATA1 1 98X6 BDAT 8 256 1-Apr-90 14:12 RW
DATA2 1 98X6 BDAT 7 256 2-Apr-90 14:12 RW
DATA3 1 98X6 BDAT 8 256 3-Apr-90 14:12 RW
DATA4 1 98X6 BDAT 5 256 4-Apr-90 14:12 RW
DATA5 1 98X6 BDAT 4 256 5-Apr-90 14:12 RW
DATA6 1 98X6 BDAT 8 256 6-Apr-90 14:12 RW
DATA7 1 98X6 BDAT 6 256 7-Apr-90 14:12 RW
DATA8 1 98X6 BDAT 5 256 8-Apr-90 14:12 RW
If HP-UX style wildcards were enabled previously, then you could copy all
of the files with names prefixed by DATA
to an existing
directory named /USERS/FRED
by entering the command:
COPY "DATA*" TO "/USERS/FRED"
If an exception occurs while processing one of the files, the name of the
file will be displayed followed by a warning message. The command will then
continue processing any remaining files which match the wildcard argument.
After the command is finished processing the files, ERROR 293 Operation
failed on some files
will be generated.
Going back to the previous example, say that the
/USERS/FRED
directory already contained a file named
DATA1
before you executed the COPY command. If you typed:
COPY "DATA*" TO "/USERS/FRED"
the following warning message would be displayed to the IDISP line:
DATA1:ERROR 54 Duplicate file name.
The remaining files which matched the wildcard argument would be copied to
the /USERS/FRED
directory, and at end of the operation
the following error would be generated:
ERROR 293 Operation failed on some files
Note that only one error is generated for a command no matter how many warning messages are generated for that command. The warning messages and error messages are all displayed on the IDISP line. Each message writes over the previous message.
To avoid missing any warning messages, you can enable PRINTALL by pressing the PRINTALL function key. By doing this, all warning messages and their corresponding error messages will go to the PRINTALL IS device. See the entry for PRINTALL IS in the HP BASIC Language Reference.
Keeping track of computer interactions and error messages helps you to troubleshoot a program if something goes wrong. The PRINTALL IS statement allows you to do this. Information on this statement can be found in the following sections and chapters of this manual: