@catch and @throw - What are they for?

I read about these opcodes and even did the exercise about them in DM4, but I don’t think I understand what their practical use is intended to be.

Has anybody out there had occasion to make use of these? If so, for what purpose(s)?

Catch & throw is typically used for exception handling. Say routine A calls routine B which in turn calls routine C which calls routine D. And if an error occurs in routine A, B, C or D, you want to handle it in one place, so you set up an error handler in routine A using a catch statement. This means when routine B calls routine C it doesn’t have to consider that the routine may return different normal results or an error code - it only has to consider the normal results. If there’s an error, the whole chain of calls on the stack will just be discarded and control will go back to the error handler in routine A.

Catch & throw instructions are part of the Z-machine specification.

In typical everyday Inform 6 code, you don’t need it. It may definitely be useful in a complex library extension though.

Dialog uses it extensively.

I can only see that Infocom used them in a couple of places in the V6 games. As for practical applications… error handling, perhaps? My Inform 6 is a bit rusty, but consider the following program:

Global stackFrame;

[ Main;
    print "Testing catch and throw.^";
    if (TestCatchAndThrow() == 42) {
        print "You sure left in a hurry!^";
    }
    print "All done!^";
];

[ TestCatchAndThrow;
    print "Entering TestCatchAndThrow()^";
    @catch -> stackFrame;
    TestCatchAndThrow2();
    print "Leaving TestCatchAndThrow()^";
    return 1;
];

[ TestCatchAndThrow2 c;
    print "Entering TestCatchAndThrow2(). Press 'X'^";
    @read_char 1 -> c;
    if (c ~= 88 or 120) {
        print "Oops, you pressed the wrong key!^";
        @throw 42 stackFrame;
    }
    print "Correct! Leaving TestCatchAndThrow2()^";
];

Which will either print

Testing catch and throw.
Entering TestCatchAndThrow()
Entering TestCatchAndThrow2(). Press 'X'
Correct! Leaving TestCatchAndThrow2()
Leaving TestCatchAndThrow()
All done!

Or, if you press anything other than ‘X’:

Testing catch and throw.
Entering TestCatchAndThrow()
Entering TestCatchAndThrow2(). Press 'X'
Oops, you pressed the wrong key!
You sure left in a hurry!
All done!

So it did not reach the end print of TestCatchAndThrow2(), but it also did not reach the end print of TestCatchAndThrow(). Instead it acted as if TestCatchAndThrow() returned 42.

@fredrik and @eriktorbjorn, I appreciate the replies. What you’ve said is similar to the basic idea behind it as described briefly in DM4: It’s a tool for when you want to “short-circuit” some layers of function calls. I did understand that in the abstract, but I’m still having trouble imagining when that behavior would be desirable. (A strong form of exception handling such as the ability to react after the fact to a divide-by-zero or the like would make sense, but it seems like this functionality is somewhat weaker.)

I appreciate the reference to Dialog as an example, @fredrik. I’ll check it out.

Let’s say you’re writing a recursive parser, to parse what the player has typed. You iterate over a number of grammar lines. For each line, you call a routine to check if the sentence matches this line. This routine calls different routines for different grammar tokens. In one case, it calls a routine for a token to match a list of objects. This routine does this by trying to match one object and then look for a comma or “and” and call itself to match the rest of the list. After having matched several objects, this routine finds that the sentence ends with “and”, and this means that error message #5 should be printed. It sets a a global to say that the error number is 5, and throws back to the catch in the first routine. There, you print the error message and ask the player for a new command.

The Inform parser has a lot of lines of the form

if (deadflag) return;

The idea is that if anything sets deadflag (ending the game), the turn should end immediately, short-circuiting the normal followups of after, every-turn daemons, backdrop adjustments, and so on. This is handled by checking deadflag all over the place. But a @throw pattern might have handled this more cleanly.

(Although, of course, there is no truly clean IF parser code.)

2 Likes

Thank you, @fredrik and @zarf. These examples are very helpful.

Given this, it sounds like the handling of deadflag was solidified before @catch and @throw were completely understood.

The Inform parser was designed when v3 support was still important.

1 Like