!lm12
!rm75
DOS Error Trapping from Machine Language........Lee Meador

I have been working on a text editor program for about three years now at the World Bible Translation Center.  It allows us to edit in any two of the following languages:  English, Russian, Greek, Hebrew, and Arabic.  Hebrew and Arabic move from right to left across the screen, as they should.

Recently we have been making some enhancements to this multi-lingual text editor (called ALPHONSE)  which include support of two disk drives (a program disk in drive 1 and a data disk in drive 2).  But we didn't want to require the use of two drives.  That means a routine must look on the various disks to see if the data is there.  We can do this very handily by RENAMEing a certain file -- call it FILE -- and assuming that a DOS error means that the data isn't on that disk.  Then we can look on other drives and finally, if it isn't found anywhere, we can prompt the user to put in the data disk.  Then we look again -- and so on.

A problem with this is that I need to trap from assembly language any DOS errorswhich occur, but I want to return to the program if the user accidentally types the RESET key (with ALPHONSE it will always be accidentally).  A second use for DOS error trapping came up because I/O errors in a disk file print the error message but do not change from the HIRES page to the text page.  That makes it rather difficult to see what the error is -- especially for the less advanced user, who has no idea what is happening.

Here is a program listing of ALPHONSE with all the insides removed.  Where the real program would have large sections of code, I have instead comments that look like this:

*->->->->->->->->->->->->->->->->->->->
* DO SOME ACTION WHICH IS NOT SHOWN
*->->->->->->->->->->->->->->->->->->->

(Of course, ALPHONSE is about 8K long and the listing is nearly 1/2-inch thick, so this isn't the whole listing.)


MAIN PROGRAM OUTLINE  

Here is an outline of the main program:
!lm17

MACH:   GLOBAL INITIALIZATION;
REENT:  LOCAL INITIALIZATION;
        REPEAT
          READ EDITOR COMMAND;
          PROCESS EDITOR COMMAND;
        UNTIL EDITOR COMMAND = QUIT;
END.
!np
!lm12

In the global initialization we have to do four things related to error-trapping:
!lm17
!rm70

   1.  Call SETUP.DOS.TABLE to copy my addresses into the table at $9D56 of DOS.  This makes DOS come back to my program when any soft entry of a funny DOS command occurs.  Just calling SETUP.DOS.TABLE will not really trap any errors, but it will keep DOS from terminating your program if a DOS error does occur (that usually means SYNTAX ERROR, I/O ERROR, or FILE NOT FOUND).

   2.  Call CLEAR.ERROR to initialize the ONERR trapping mechanism in my program.

   3.  Call ON.ERROR with the address of the error-handling routine in the A and Y registers (LO, HI).  This sets up the DOS error-handling capabilities as if Applesoft were running and ONERR were set.

   4.  After doing all the global initialization of files and such, we need to call OFF.ERROR to turn off the error handling that ON.ERROR set up.  After calling OFF.ERROR any DOS error will beep and go to the soft entry point.  (We have already set the soft entry point in step one to be MY.RESET.)
!lm12
!rm75

In the local initialization we take care of a few more things that have to be done every time the program is run -- not just the first time.  The call to OFF.ERROR cleared any error trapping so we can call SETUP.DOS.TABLE and CLEAR.ERROR again without causing any problems.

Note that the call to LOOK.FOR.FILE changes the error address so we have to call ON.ERROR with MY.ERROR again to make sure that an error doesn't throw us off into never-never land.  LOOK.FOR.FILE returns the carry clear if FILE is found.  Carry set signals that the file isn't on any available drives; in that case, ALPHONSE would print a message like "INSERT DATA DISK AND HIT ANY KEY," then wait for a key to be pushed and call LOOK.FOR.FILE another time.

The main program loop is not really of interest here, but it is shown in the listing in skeleton form.


SUB-PROGRAMS

Now, how do the subroutines work?  First, the one that you wouldn't use in your program:  LOOK.FOR.FILE has to save the stack pointer.  This is because we expect DOS errors to occur inside the routine.  A DOS error will mess up the stack.  Saving the stack lets us remember where we were.  (By the way, DOS just adds things to the stack and never removes them when there is an error.  The LOOK.FOR.FILE return addresses will not be messed up.)

LOOK.FOR.FILE sets its own DOS error trap address.  Then the program looks through trying to find FILE on the various slots and drives.  It does this by printing the DOS commands <CTRL-D>, RENAME FILE, FILE, Sx, Dy with x and y filled in.  Appropriate values for x are six, five, and seven; y would be one or two.  The order in which you try the slot/drive combinations will determine which of two disks are chosen if you put two data disks in at the same time.  I used a table of six slot/drive combinations to choose the order and positions to try.  Notice that before printing the DOS RENAME command, I had to check to see if there was a disk card in the slot.  Choosing a slot without a disk card in it for a DOS command will cause DOS to hang when you try the next DOS command with a different slot.  DOS is waiting for the last drive to quit running.  Little does DOS know that an empty slot always seems to be running (to DOS at least).

If the DOS RENAME command fails or there is no disk card in the slot, LOOK.FOR.FILE will jump to LOOK.ERR to loop and try the next slot/drive.  If it runs out of slot/drives the program returns with carry set to indicate FILE was not found.  Carry clear indicates that the last-used drive has FILE on it.

There are several routines you might want to copy as is to your program.  Calling them takes care of error trapping and reset trapping.

SETUP.DOS.TABLE:  copies MY.TABLE into DOS to jump to my program on any DOS error or RESET.  Unfortunately, at this point you can't tell them apart.

ON.ERROR:  sets the error address to the value in the A, Y (LO, HI) registers.  When a DOS error occurs after ON.ERROR has been called, DOS will jump to this address with the error number in the X register.  All other registers will have been changed.

OFF.ERROR:  turns off the error trapping and resets DOS to the state it was in before ON.ERROR was first called.  SAVE.AAB6 is used to keep track of which BASIC language DOS thinks was active.  Restoring AAB6 before exiting your program will help DOS keep things sorted out.  Calling OFF.ERROR restores AAB6.  (By the way, while ON.ERROR is active, DOS thinks that Applesoft is currently running a program and that there has been an ONERR statement.  Zero page locations $D8, $76, and $33 are used for this.)

CLEAR.ERROR:  call this the first thing in your program to set up the flags used by ON.ERROR and OFF.ERROR.

Note:  MY.RESET just reenters the program loop if someone types the RESET key.  That makes it a null key.  MY.ERROR should be looked at to see how the DOS error message comes back to you.  You can use the message to print various messages depending upon what is wrong.  Or, you can take various actions depending upon the error message.  Pages 114-115 of the DOS manual show what the various error numbers are that come back in the X register.

The program listing should show how most of these things are handled.
