Chapter 6 : Error Handling
In the past, most BASIC programmers have largely ignored the error handling provided by BBC BASIC, with the exception of a few instances such as trapping ESCAPE and some disc errors. The reason for this state of affairs is that any error trapped by BASIC caused the stack to be zeroed, with a total consequent loss of all stored information relating to functions, procedures and loops of any kind. Given the highly structured nature of most programs written in BBC BASIC, the chances that a program will be executing a function or procedure, or be within a loop, when an error occurs, are likely to be quite high.
As an example, BBC BASIC will detect any attempt to divide by zero, and generate a corresponding error. Because of the problems resulting from the use of error trapping, most programmers cater for this possibility themselves by checking for a zero (or very small) divisor before executing a divide operation.
BASIC V now provides additional error handling facilities which avoid all the previous problems. At any point in a program, a local error trap can be established, saving the pointer to the previous error-trapping status on the stack. Should an error occur, the current error-handling routine will be invoked with no loss of stack information. Once a routine, for which there is local error handling, has been executed, the pointer to the previously effective error trapping status can be retrieved from the stack and reinstated as the current error handling routine.
Using these facilities, truly hierarchical error-handling systems can be readily established. Although local error handling may be incorporated at any point within a program, it is clearly ideally suited to error handling within procedures and functions. Given the highly structured nature of most BBC BASIC programs, global error trapping (to handle escape for example) might be established early in a program, with each function and procedure containing its own error handling routine whenever appropriate.
It is important to realise, however, that when any local error handling routine is active, all errors will be passed to that routine, including those which we might ideally wish to be handled by a more global routine. It is only too easy to forget this and assume that a local error handling routine will only handle specific local errors.
Local Error Trapping
Let's look at what the new instructions are in BASIC v and then consider how best to use them. The original ON error statement remains as before and, as before, the occurrence of any error corrupts the stack as already described. A new statement, ON ERROR LOCAL, with the same syntax, is now provided, which does not corrupt the stack in the event of an error. Furthermore, local error may now be used to save the existing error trapping status on the stack before establishing a local error-handling routine. restore error, placed at the end of a routine or section of code, will restore the previously saved error status. In practice, restore error is not strictly necessary on exit from a procedure or function, but it will cause no problems if it is included.
Suppose, as an example, we want to write a procedure which will plot the graph of a function specified as a string. A complete program including such a procedure could be written as follows:
Listing 6.1 Plot program with no error trapping.
10 REM >Chap6-l
100 MODE 12
110 REPEAT
120 INPUT F$,X,Y,I
130 PROCplot(X,Y,I,F$)
140 G=GET:CLS
150 UNTIL FALSE
160 END
170 :
1000 DEF PROCplot(Ox,Oy,inc,f$)
1010 LOCAL x,y
1020 MOVE 0,Oy:DRAW 1279,Oy
1030 MOVE Ox,1023:DRAW Ox,0
1040 ORIGIN Ox,0y:a=-0x/100:b=(1279-0x)/100
1050 MOVE -2*0x,0
1060 FOR x=a TO b STEP inc
1070 y=EVAL(f$)
1080 DRAW 100*x,100*y
1090 NEXT x101100 ORIGIN 0,0
1110 ENDPROC
The procedure has four parameters, the position of the origin of the graph on the screen (assuming that the origin is initially in its default position at the bottom left-hand corner), the increment in x to be used when plotting, and the function (of x) itself. The procedure draws in the x and y axis, moves the origin to its new position and then plots the graph using a FOR...next loop. The local variables a and b are the calculated start and end points on the x axis relative to the new origin. Before plotting of the graph begins, a move is made beyond the left-hand edge of the screen ready to commence plotting the graph, using draw. On exit from the procedure, the origin is returned to its default position. Incidentally, note the use of the new statement origin, dealt with more fully in Chapter Nine.
There are many alternative ways in which this procedure could have been written, but this version will suffice for our needs. If we call the procedure, having first cleared the screen in a suitable mode, with the following procedure call:
PROCplot(625,512,0.125,3*SIN(x)+SIN(3*x))
then the graph will be correctly, and quickly, drawn. Note the lower case 'x' to fit in with the procedure definition. If, however, we try this function:
PROCplot(625,512,0.25,10*(SIN(x)/x))
then the error message "Division by zero..." will result when the graph reaches the origin. The procedure can trap this situation by using local error trapping. What then happens is up to the programmer - we'll assume we just skip over the contentious value and go on to the next one.
The revised procedure appears as listing 6.2.
Listing 6.2 Revised plot procedure with local error trapping.
1000 DEF PROCplot(Ox,Oy,inc,f$)
1010 LOCAL x,y
1020 LOCAL ERROR
1030 MOVE 0,Oy:DRAW 1279,Oy
1040 MOVE Ox,1023:DRAW Ox,0
1050 ORIGIN Ox,0y:a=-0x/100:b=(1279-0x)/100
1060 MOVE -2*0x,0
1070 FOR x=a TO b STEP inc
1080 ON ERROR LOCAL x+=inc
1090 y=EVAL(f$)
1100 DRAW 100*x,100*y
1110 NEXT x
1120 ORIGIN 0,0
1130 RESTORE ERROR
1140 ENDPROC
It is essential that LOCAL error be the last thing to be declared LOCAL in a procedure or function definition. The restore error as the penultimate line is included for completeness, but, as already mentioned, it is not essential here. The ON error local statement has been carefully positioned within the for...next loop. Should any error occur, the loop counter x is incremented by the code specified by ON error local, and execution then continues with the statements immediately following that one. The effect in this case is to force the loop counter on by one increment.
This is satisfactory provided, of course, that the only error to occur is the "Division by zero" specifically catered for. Any other error state, such as a syntax error or pressing escape, will have unexpected consequences. The procedure will respond just as though a "Division by zero" error had occurred. One possibility is to place the local error and RESTORE ERROR instructions as brackets around the critical section of code only, but this has little aesthetic appeal and would tend to slow down execution without offering an entirely foolproof solution.
A better way might be to define an error-handling function to contain all the separate local error-handling routines. A parameter would specify which error-handling routine was required. Such a function could then cater readily for syntax errors or pressing escape, while using a function would allow a logical value to be returned which could be true if the expected error had occurred, and false otherwise. A procedure could also return a value using the return keyword. It would then be up to the host procedure to determine how to respond to this information. As.you will appreciate, once you start experimenting with local error trapping, the main problem lies in determining the sequence to be followed in the event of an error, particularly if the requirement is to continue from the point at which the error occurred Hence the reason for the position of the ON error local statement in the examples.
Here's how this approach might work cut in our plotting example:
Listing 6.3 Final plot procedure with error function.
1000 DBF PROCplot(Ox,0y,inc,f$)
1010 LOCAL x,y,err%:err%=TRUE
1020 LOCAL ERROR
1030 MOVE 0,Oy:DRAW 1279,Oy
1040 MOVE Ox,1023:DRAW Ox,0
1050 ORIGIN Ox,0y:a=-0x/100:b=(1279-0x)/100
1060 MOVE -2*0x,0
1070 FOR x=a TO b STEP inc
1080 ON ERROR LOCAL err%=FNerror(1)
1090 IF err% THEN
1100 y=EVAL(f$)
1110 DRAW 100*x,100*y
1120 ELSE
1130 x=b
1140 ENDIF
1150 NEXT x
1160 ORIGIN 0,0
1170 ENDPROC
1180 :
1190 DEF FNerror(error)
1200 ON ERROR OFF
1210 LOCAL flag%:flag%=FALSE
1220 CASE error OF
1230 WHEN 1: IF ERR=18 THEN x+=inc:flag%=TRUE
1240 ENDCASE
1250 IF NOT flag% PRINT REPORT$;" at line ";ERL
1260 =flag%
The main procedure, PROCplot, now uses an additional local variable (err%). Initially true, this variable is assigned the value returned by FNerror in the event of any error occurring. If this is "Division by zero" then the loop continues as normal, otherwise the program forces an immediate termination of the loop and a return to the calling program.
The function, FNerror, uses a case structure which can be very easily extended during program development as further local error traps are required. Clearly, this is a much lengthier solution than our original, quite simple, local error handling, but in the development of any substantial program, this systematic approach will almost certainly prove more effective than a variety of ad hoc and overlapping local error traps.
Notice also, in the function FNerror, the use of a new pseudo-variable in BASIC V, reports. This contains a string equivalent to the error message which BASIC would display if handling errors directly.
A further change has been made to the standard error handler. This now resets @% so that any error messages and line number references are correctly formatted, but it then restores any previous setting of this variable which you may have made.
Local DATA
Under RISC OS, BASIC v allows the current data pointer to be saved to the stack and subsequently retrieved in a manner similar to that used with local error trapping. To save the current data pointer, use:
LOCAL DATA
while the previous value can be restored with:
RESTORE DATA
local data and restore data may be used anywhere within a program, but their main use is likely to be in procedure and function definitions, particularly those included in libraries. As with local error, exit from a procedure or function will automatically restore the saved data pointer. If LOCAL data is used in this way, it must be the last item declared as LOCAL within the procedure or function definition except for LOCAL ERROR, which must always be the last of all.
Thus procedure and function definitions which contain their own data statements can now avoid upsetting the data pointer in the calling program. Note, that in the procedure or function definition, using:
RESTORE 0+
will set the data pointer to the first item of data included within the procedure or function definition.
New ERROR Keyword Usage
The keyword error now has an additional function within BASIC. It allows you to specify your own error numbers and corresponding error messages. The syntax takes the form:
ERROR <error number>,<error message>
For example, we might write:
ERROR 999,"Too many characters in name"
When such a line is executed, BASIC prints a normal style error message but using the string specified. The error number given in the error statement is assigned to the pseudo-variable err. Thus the example above would produce a message:
Too many characters in name at line .....
Revised Error Numbers and Messages
The User Guide lists all the possible error numbers and corresponding error messages. Many of these have been revised or are additions for BASIC V. Note that there are many instances where several different error messages all have the same error number. BASIC chooses which error message to generate depending upon the error number and its context. Note that there are also some differences between Arthur 1.2 and RISC OS which are listed below.
0 Line too long
0 Incorrect in-core file description
2 Assembler limit reached
3 Duplicate register in multiply
6 Type mismatch: numeric array needed
6 Type mismatch: string array needed
10 Arrays cannot be redimensioned
11 No room for this dim
11 No room for this dimension
11 Attempt to allocate insufficient memory
27 Missing (
37 No room for function/procedure call
42 data pointer not found on stack for restore data
48 OF missing from CASE statement
52 Can't install library
52 Bad program used as function/procedure library
52 No room for library
|