ONERR RESUME and such

I added ONERR GOTO linenum / RESUME / POKE 216,0 support to my Applesoft Interpreter.

It turned out to be pretty simple. ONERR sets a handler address. On an exception, check if it's cachable (i.e. the program caused it, it's not the interpreter reporting a bug). If so, store the current execution point and jump to the handler. If the handler calls RESUME, restore the previous execution point. The only wrinkle is that IF...THEN... is considered a single statement, so the IF part needs to be resumed and I was cheating an treating the bit after THEN as a separate statement. I don't twiddle the stack at all, so I'm not positive the behavior there is consistent with a real Apple.

This is not a feature I used when I programmed in BASIC. I didn't have very good examples to learn from. Also, initial experiments with RESUME made me wonder "WTF?". For example, consider this:


5 ONERR GOTO 10000
10 INPUT "Open file?";F$ : REM prompt user for file name
20 PRINT CHR$(4)"OPEN "F$ : REM open the file
...
10000 PRINT "Invalid file" : RESUME : REM this won't do what we want

That's useless - the RESUME will rerun the statement on line 20, so it will just happen again.

I fell into the trap of thinking of ONERR / RESUME as a mandatory pair, and of errors as some global condition ("error is bad! stop error from bothering user!"). Here are two more practical examples. First, let's correct the first sample:

10 INPUT "Open file?";F$ : REM prompt user for file name
20 ONERR GOTO 30 : PRINT CHR$(4)"OPEN "F$ : GOTO 40 : REM open the file; if it worked, keep going
30 PRINT "Invalid file" : GOTO 10 : REM oops, caught an error - go back to the right spot to try again
40 POKE 216,0 : REM clear onerr handler, and continue reading file

In other words, the onerr is used to trap the error result of the call. RESUME isn't used.

Another handy example:

10 PRINT CHR$(4)"OPEN DATAFILE" : REM open file
20 PRINT CHR$(4)"READ DATAFILE" : REM access file for reading
30 ONERR GOTO 50 : REM trap end-of-file error
40 INPUT A$: PRINT A$ : GOTO 40 : REM read lines from file
50 POKE 216,0 : REM clear onerr handler
60 PRINT CHR$(4)"CLOSE DATAFILE"

This treats the end-of-file as exceptional, and handles it with an ONERR trap. Very slick. I wish I'd known this back in the 1980's!

Instead of this, languages like C (on which UNIX and Windows are built) use error codes - any function that can fail will have some way to return a success or failure code. So you write a lot of code that looks like this:

FILE* f = NULL;
while( f == NULL )
{
    /* ask user for filename here */
    f = fopen("datafile", "r");
    if( f == NULL )
    {
        printf("Invalid file");
    }
}

In other words, after every action, check the results!

More modern languages provide this mechanism in a less spaghettified fashion, called exceptions. (If you're unlucky and calling into a library from another language, like from C++ into C, you need to fall back to the old mechanism.) The syntax usually takes the form of a "try <some code> catch <an exception and respond>", where the verb "throw" is used to indicate that an exception has occurred, e.g. "throw EOFError" for an end-of-file exception.

For reference, here's what the above might look like in more contemporary language - this is "pseudo-Python*":

while True:
    print "Open file? "
    filename = readline()
    try:
        file = open(filename,"r")
    except:
        print "Invalid file"
    else:
        break # yay, worked
# do stuff with file

And:

file = open("datafile","r")
try:
    while True:
        line = file.readline()
        print line
except EOFError:
    pass # we must be done!
file.close()

Note the similarities to the Applesoft ONERR examples. You can see how the exception syntax is not particularly well suited for interactive actions like prompting the user for a file which require the "success" case to break out of a loop. However, in the case where the exception is the rarity (the end-of-file condition), it lets the code doing the bulk of the work remain clean (the read/print loop).

* I've taken liberties with the syntax; in real Python, readline() returns None at EOF rather than raising an exception, and you can just write while line in file: ...

Comments