Handling Errors

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.

Overview of Error Responses

There are three courses of action that you may choose to take with respect to errors:

  1. Try to prevent the error from happening in the first place (by communicating clearly with the program user, by using range-checking routines, and so forth).
  2. Once an error occurs, try to recover from it and continue execution (this involves the BASIC program trapping and correcting errors).
  3. Do nothing--let the system stop the program when an error happens.

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.

Anticipating Operator Errors

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.

Boundary Conditions

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
 

REAL and COMPLEX Numbers and Comparisons

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 ...
 

Trapping Errors with BASIC Programs

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).

Setting Up Error Service Routines

(ON/OFF ERROR)

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
 

Choosing a Branch Type

The type of branch that you choose (GOTO vs. GOSUB, etc.) depends on how you want to handle the error.

Scope of Error Trapping and Recovery

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.

ON ERROR Execution at Run-Time

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 Priority

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.

Disabling Error Trapping\(OFF ERROR)

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.

Determining Error Number and Location\(ERRN, ERRLN, ERRL, ERRDS, ERRM$)

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
 

A Closer Look at ON ERROR GOSUB

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.

A Closer Look At ON ERROR GOTO

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
 

A Closer Look At ON ERROR CALL

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.

Cannot Pass Parameters Using ON ERROR CALL

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.

Using ERRLN and ERRL in Subprograms

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
 

A Closer Look At ON ERROR RECOVER

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
     .
     .
     .
 

Simulating Errors\(CAUSE ERROR)

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.)

Example of Simulating an Error

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.)

CAUSE ERROR and Error Numbers 1001 thru 1080

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).

Clearing Error Conditions\(CLEAR ERROR)

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.

Error handling for File Name Expansion

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.

Recording Interaction and\Troubleshooting Messages

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: