Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

[Inform] Adding exceptions to Inform (long)

0 views
Skip to first unread message

Marnix Klooster

unread,
Oct 14, 1996, 3:00:00 AM10/14/96
to

Hello all,

Here are some thoughts about adding exceptions to Inform. I don't
know whether people would find exceptions useful, but I found out that
they can be added at fairly low cost. The basic idea for the syntax
and semantics have shamelessly been stolen from Guido van Rossum's
Python; check out http://www.python.org. Let me hear your opinions on
this. (And excuse the length, but I thought it best to give some
detail, and an example.)

* * *

The essence of exceptions is that it is possible anywhere in a program
to 'raise' an exception, thereby passing control immediately to an
'exception handler' which is a program fragment that handles the
exceptional situation. Exceptions are typically used to indicate and
handle unusual situations, without having to resort to things like
special return values and flags. In my experience (with Python) they
allow for cleaner and clearer programs. (See the example program
below.)

I propose to introduce two new statements, "try" and "raise", and one
new declaration, "Exception". The Exception declaration simply
declares a new exception name. (This is not strictly necessary, but
it gives the compiler the opportunity for some more error-checking.)
There should be one built-in exception name, meaning informally "no
exception was raised". I propose to use "None" for this.

A "raise" instruction ("raise <exception name>") causes the statement
that contains it to be abnormally terminated, and control is passed to
the closest exception handler.

The most general form of the "try" statement is

try <statement>
finally <statement>
except {
<exception name>: <statement list>
<etc...>
default: <statement list>
}

Everything from "finally" onward is called the 'exception handler' of
this "try" statement. (A finally-clause that is left out defaults to
"finally ;", i.e., the statement is the empty statement. An
except-clause that is left out defaults to "except {}".)

A "try" statement is executed as follows. First execute the
try-clause. If this raises an exception, pass control to the handler.
If the try-clause terminates normally, behave as if the exception
"None" was raised.

On entering the handler first execute the finally-clause. Next
execute the part of the except-clause labelled with the raised
exception. The "default" part is used if it is a non-"None" exception
that is not explicitly mentioned. If the exception cannot be handled
by the except-clause (i.e., it is not mentioned, and either it is
"None" or there is no "default" part), the "try" statement as a whole
raises that exception.

Any exception raised during execution of the handler part of a "try"
statement causes the entire statement to raise that exception. (The
exception raised in the try-part is forgotten in that case.)

Note that I said nothing about jumping into and out of try-, finally-,
and except-clauses. It might be possible to find a sensible semantics
for such jumps, but it would be very difficult to implement; better
avoid them altogether.

* * *

As a simple example, consider the following, currently imaginary,
Inform program.

!----------------------------------------------------------------
Exception DivisionByZero;

[ Main;
print "Division";
print "An Exceptional Demonstration Of Mathematical Truths^^";

PrintDiv(32767,217);
PrintDiv(1,0);
];

! Print the result of a division.
[ PrintDiv a b
c;
print "Dividing ", a, " by ", b, " results in ";
try
c = DoDiv(a,b);
except {
None: print c;
DivisionByZero: print "failure";
}
print ".^";
];

! Return a/b, or raise DivisionByZero if b==0.
! We gleefully ignore signedness or overflow problems,
! but note that other exceptions could be used for these.
[ DoDiv a b;
if (b==0)
raise DivisionByZero;
return a/b;
]
!----------------------------------------------------------------

This program should produce the following output:

Division
An Exceptional Demonstration Of Mathematical Truths

Dividing 32767 by 217 results in 151.
Dividing 1 by 0 results in failure.

* * *

There are some extensions possible to the basic scheme described
above.

* The programmer may be given access to the 'current exception', by
reading a pre-defined global variable that contains it. (To be
precise, it contains a number that uniquely identifies the exception
name. This makes an exception name into a special kind of Constant.)
The current exception is the one which passed control on to the
active handler; the active handler is the last handler entered that
hasn't been left yet. (Here, 'left' means either by normal
termination, or by raising an exception.)
Note that this access to the current exception makes an
except-clause equivalent to a finally-clause containing a switch that
is similar to that except-clause.

* Sometimes it is useful to pass one or more values along with a
raised exception. (For instance, a parser routine might raise an
UnknownToken exception, passing the address of the unknown token as
well.) This can be implemented by allowing

raise <exception name>, <expression>, <expression>, <etc...>

and having a pre-defined global array that always contains the
sequence of values passed with the current exception.

* Another extension is to allow "raise" without arguments, to raise
the current exception, with its associated values, again.

* A fourth extension is to interpret a "break" which textually occurs
within a try-clause as "raise None". (Of course, this should not be
done for a "break" within a loop (or some such) within a try-clause.)

* * *

Finally, is it possible to implement the proposed instructions on the
Z-machine? This turns to be not too difficult for version 5 and
higher, which has CATCH and THROW instructions; for versions 1 to 4 it
appears to be very difficult. If anyone is interested in the details,
let me know and I'll e-mail them.

Those are my ideas for now. Are exceptions useful in Inform? What do
you think about the proposed syntax and semantics? Or perhaps you see
better ways to achieve the same effects?

Groetjes,

<><

Marnix
--
Marnix Klooster
kloo...@dutiba.twi.tudelft.nl // mar...@worldonline.nl


FMDesignPE

unread,
Oct 14, 1996, 3:00:00 AM10/14/96
to

[begin quote]

Are exceptions useful in Inform?
[end quote]

It seems to me at first glance that this might be a nice way of
implementing death of the player and winning the game. I've always
thought that the global `deadflag' was a bit kludgey.


Paul Evans in Chicago "Wisdom is an abomination to them that feareth
FMDes...@aol.com favor of the fool shall fall himself." - Dog-Matic

Matthew T. Russotto

unread,
Oct 14, 1996, 3:00:00 AM10/14/96
to

In article <erkyrathD...@netcom.com>,
Andrew Plotkin <erky...@netcom.com> wrote:

}In your V5 @CATCH/@THROW implementation, is there any overhead for a
}program that doesn't use exceptions? (I.e., old code.) What about a
}function that doesn't use exceptions in a program that does? If there's
}no cost in these cases, I can't think of any reason not to add the stuff
}to Inform. (Aside from the effort involved, of course.)

Without VM support (which the Z-machine doesn't have), overhead
for a function in any program which uses exceptions seems inevitable.
All divisions, for instance, would have to be checked.

}Dirty question: Are @CATCH and @THROW well-tested in current interpreters?
}Like, are any game files known to use them?

No V1-V5 game file uses them. Some V6 game files do. ZIP 2.0 is
broken and will fail with a fatal error if you use them.
--
Matthew T. Russotto russ...@pond.com
"Extremism in defense of liberty is no vice, and moderation in pursuit
of justice is no virtue."

Andrew Plotkin

unread,
Oct 14, 1996, 3:00:00 AM10/14/96
to

Marnix Klooster (mar...@worldonline.nl) wrote:
> Here are some thoughts about adding exceptions to Inform. I don't
> know whether people would find exceptions useful, but I found out that
> they can be added at fairly low cost. The basic idea for the syntax
> and semantics have shamelessly been stolen from Guido van Rossum's
> Python; check out http://www.python.org. Let me hear your opinions on
> this. (And excuse the length, but I thought it best to give some
> detail, and an example.)

[Post deleted; go back and read it]

Neat! I've always thought exceptions were useful. (Not so much because I
use exceptions a lot, but because I write a lot of code that uses small
utility procedures and special return values to *fake* exceptions. :-)

Comments:

I don't know if I see the usefulness of "None", but then I don't know if
I see the usefulness of "finally", and I can leave both of them out of
my games if I don't want to use them.

Since Inform is closest in syntax to C / C++, it might be better to use
the C++ argot of "try, catch, throw" instead of "try, except, raise."
(Also consistent with Java, for those of you banging your heads against
the bleeding edge of Internet marketing hype.)

Inform already has a "nothing" and a "NULL", which are different. Sanity
would be improved if we didn't add a "None" as well. Sanity also slaps me
hard on the forehead when I suggest that the best word to mean "no
exceptions" is "Rule." :-) Maybe "NoExcept"?

In your V5 @CATCH/@THROW implementation, is there any overhead for a
program that doesn't use exceptions? (I.e., old code.) What about a
function that doesn't use exceptions in a program that does? If there's
no cost in these cases, I can't think of any reason not to add the stuff
to Inform. (Aside from the effort involved, of course.)

Actually, could you just post your implementation details?

I would think it quite reasonable if the compiler said "Error: 'try' or
'raise' statements may only be used in V5 and above."

Dirty question: Are @CATCH and @THROW well-tested in current interpreters?

Like, are any game files known to use them? If not, I'll cheerfully bet
that at least one popular interpreter implements them wrong. (In fact,
even if some Infocom game *does* use them, I'll cheerfully bet...)

--Z

--

"And Aholibamah bare Jeush, and Jaalam, and Korah: these were the
borogoves..."

Andrew Plotkin

unread,
Oct 14, 1996, 3:00:00 AM10/14/96
to

Matthew T. Russotto (russ...@wanda.vf.pond.com) wrote:
> In article <erkyrathD...@netcom.com>,
> Andrew Plotkin <erky...@netcom.com> wrote:

> }In your V5 @CATCH/@THROW implementation, is there any overhead for a
> }program that doesn't use exceptions? (I.e., old code.) What about a
> }function that doesn't use exceptions in a program that does? If there's
> }no cost in these cases, I can't think of any reason not to add the stuff
> }to Inform. (Aside from the effort involved, of course.)
>

> Without VM support (which the Z-machine doesn't have), overhead
> for a function in any program which uses exceptions seems inevitable.
> All divisions, for instance, would have to be checked.

Well, most functions don't include division. And maybe we don't want
Z-machine division to throw an exception. (That would be a modification
to the Z-machine, right? Which is different from adding exception to
Inform.) If you want that effect in Inform-plus-exceptions, you'd write a
"divide" function like:
[ division a b;
if (b == 0)
throw DivideZero;
return a / b;
];

The question is, does this have any overhead for a function which *calls*
divide but does not try to catch the exception? I would guess "no", but
this is why I want to see the implementation.

> }Dirty question: Are @CATCH and @THROW well-tested in current interpreters?
> }Like, are any game files known to use them?

> No V1-V5 game file uses them. Some V6 game files do. ZIP 2.0 is


> broken and will fail with a fatal error if you use them.

Hm. Problematic. We'll see where Marnix's proposal for CATCH/THROW
implementation goes.

Marnix Klooster

unread,
Oct 15, 1996, 3:00:00 AM10/15/96
to

Matthew T. Russotto (russ...@wanda.vf.pond.com) wrote:

> In article <erkyrathD...@netcom.com>,
> Andrew Plotkin <erky...@netcom.com> wrote:

> }In your V5 @CATCH/@THROW implementation, is there any overhead for a
> }program that doesn't use exceptions? (I.e., old code.) What about a
> }function that doesn't use exceptions in a program that does? If there's
> }no cost in these cases, I can't think of any reason not to add the stuff
> }to Inform. (Aside from the effort involved, of course.)

> Without VM support (which the Z-machine doesn't have), overhead
> for a function in any program which uses exceptions seems inevitable.

Overhead is inevitable if you use exceptions in any case. And since
the Z-machine doesn't support exceptions directly, the overhead
consists of more Z-code instructions being executed.

> All divisions, for instance, would have to be checked.

But this is not true. I am not suggesting a modification of the
Z-machine so that exceptions are raised when e.g. "@div 27183 0 x" is
executed. Rather, I suggest an extension of the Inform language,
which can be compiled to standard V5+ Z-code that uses @catch and
@throw. (For further implementation details see my separate post
"[Inform] Implementing exceptions".)

Thus dividing by zero at the Z-machine level, as in "@div 31415 0 x",
still makes the interpreter halt with a 'fatal error'. (Which, by the
way, implies an interpreter check anyway.) But the Inform programmer
has the option of checking for zero before attempting a division, and
raising and handling an exception in that case. This is done by using
the proposed "try" and "raise" statements.

This means that if you don't use exceptions, you don't have any
overhead. And if you do, you do. Fair?

(On a related note, I can imagine an extended version of the Inform
compiler with a compilation option --runtime-checks, so that every
attempt to divide by zero raises an exception. Essentially, if the
programmer writes

c = a/b;

"inform --runtime-checks" would automatically insert a check, reading
this as

if (b==0) raise DivisionByZero;
c = a/b;

In this case, there *would* be overhead on every (Inform-level)
division. Which is why --runtime-checks should be an option, to be
used for debugging purposes.

All this, however, is independent of my proposal. It is an idea that
might be used later, when exceptions have successfully been added to
Inform.)

> }Dirty question: Are @CATCH and @THROW well-tested in current interpreters?
> }Like, are any game files known to use them?

> No V1-V5 game file uses them. Some V6 game files do. ZIP 2.0 is
> broken and will fail with a fatal error if you use them.

Oops. I thought ZIP 2.0 was correct; please disregard this comment in
my post "[Z-machine] Catch & throw". At least Frotz implements them
correctly.

> Matthew T. Russotto russ...@pond.com

Groetjes,

<><

Marnix
--
Marnix Klooster
mar...@worldonline.nl


Marnix Klooster

unread,
Oct 15, 1996, 3:00:00 AM10/15/96
to

[Repost: previous post was incomplete and has been cancelled.]

[This is a reply to Andrew Plotkin's comments on my Inform exceptions
proposal. I have read his article, but my newsserver doesn't have it
(yet?), so I'm replying to my own article --MK]

Andrew managed to bring up so many questions that I decided to break
my answers down into three posts. One of them discusses the Z-machine
@catch and @throw instructions, another gives an example of how the
implementation of exceptions on the Z-machine could be done, and this
is the third: it discusses the Inform-level syntax and semantics of
exceptions. (The discussion might benefit if we try to keep these
three things separated, as much as possible.)

About the Inform-level Andrew raises three points: the usefulness of
"finally" and "None", and the syntax to be used. Let me discuss them
in turn. (In examples I use my originally proposed syntax, for the
reasons outlined below.)

USEFULNESS OF FINALLY

As Steven Howard pointed out, the finally-clause can be useful to
specify a clean-up action. As a simple example, imagine that you
first mangle the state in some way, then execute some statements that
may raise exceptions, and finally de-mangle the state. In that case
you would put the de-mangling in a "finally" clause. A very silly
Inform example derived from a previous one:

!-------------------------------------------------------------------

! Print the result of the division b/a, and
! add a to global variable TotalA.

[ PrintInverseDiv a b
tmp;
tmp = a; a = b; b = tmp; ! exchange a and b


print "Dividing ", a, " by ", b, " results in ";
try

print DoDiv(a,b);
finally {
! this is executed even when DoDiv raises an exception
tmp = a; a = b; b = tmp; ! exchange a and b again
} except {


DivisionByZero: print "failure";
}
print ".^";

! Here we need to use the original a, regardless of what
! happened in the try statement.
TotalA = TotalA + a;
];

!-------------------------------------------------------------------

It is very well possible that situations like this occur in practice
(for another example see below). And since it is optional, and
doesn't cost much extra to put in, why not add it?

USEFULNESS OF 'NO EXCEPTION RAISED'

If only the try...except form is used, None is not necessary: all code
in the None case can be moved to the end of the try-clause.
(Exercise: remove the local variable 'c' from my original PrintDiv
routine by doing just that. Hint: see the above example.) Still,
using None may give rise to more 'symmetric' code (as witnessed by my
original PrintDiv routine).

However, in a finally-clause the programmer might like to know whether
or not an exception occurred in the try-clause. That is the reason
why succesful termination of the try-clause implicitly raises None,
and why the programmer should be able to test whether the 'current
exception' (stored in some global variable, say) is equal to None.

As an example, the following finally-clause can usefully be added to a
try...except statement when debugging:

finally
if (ExcName != None)
print "Exception raised: ", (exc) ExcName, "^";

where ExcName stands for the exception raised in the try-clause, and
'exc' would be a pre-defined Inform 6 function printing the name of
the given exception.

SYNTAX

Two syntax issues have been brought up: having other keywords, and the
syntax of the handler.

For the handler, I proposed

try <statement>
finally <statement>
except {
<exception name>: <statement list>
<etc...>
default: <statement list>
}

but Andrew argued that we do things the C++ way, because Inform is so
C-like. I agree that we should try to, but I'd say we either support
exactly the same syntax, or a markedly different one; we shouldn't
choose an almost-the-same syntax. That would bring even more
confusion.

So what is the C++ (and Java) way? If I remember correctly, it is

try <statement>
catch (<exception name>) <statement>
catch (<exception name>) <statement>
<etc...>
catch (...) <statement>

Leaving aside the problem that C++ does not have a finally clause, I
find this syntax has a number of disadvantages. (1) It gives a longer
program text, containing more brackets and braces, making it more
difficult to read and write. (2) It introduces new syntax "..." for
the default case. (We could use "default" instead of "...", but that
leads to the almost-but-not-exactly-the-same-as-C++ pitfall.) (3) The
handler is in fact a kind of 'switch,' so I feel we should use a
switch-like syntax here.

For those reasons, I propose to use my original switch-like syntax for
the handler. And to avoid confusion with the C++ syntax, I also
propose to use the keyword "except" instead of "catch". (We could of
course decide to have the C++ syntax (exactly) as an alternative
syntax. But that would only mess things up horribly, in my
not-so-humble opinion.)

Now for the keywords. Above I argued to use "catch" if we decide on
C++ syntax, and "except" if we use switch-like syntax. For "raise"
vs. "throw", my linguistic intuition says to use "throw" with "catch",
and "raise" with "except". Finally, I agree that it is probably not
wise to introduce "None" in addition to "NULL", with a different
meaning. The choice is therefore between re-using NULL as 'no
exception raised', and introducing a new keyword like "NoExcept" for
this. I prefer the former for its simplicity. (I think C++ and Java
can give us no guidance here, or can they?)

[As a separate issue, I think that "None" would be a good replacement
keyword for "NULL" in Inform. It describes more intuitively what that
special value is used for. E-mail Graham and/or me with your thoughts
on this.]

Yet another reason not to choose "catch" and "throw" as keywords is
that those are also used for Z-code instructions. These instructions
are used to achieve a similar effect, but they have a semantics that
differs completely from the high-level try statement we're discussing
here. This argument is not too strong, however, since Z-code
instructions in Inform are always distinguished by their leading "@".

SUMMARY

To summarize, "finally" and "None" (or "NULL") are useful, and the
choices to be made are as follows:

C++ syntax (exactly) | switch-like syntax
catch | except
throw | raise

and

NoExcept (or some such) | NULL

I strongly prefer the right hand side options, because they are easier
to read and write, and introduce less new syntax. Clarity is higher
on my priority list than C++ syntax compatibility.

Groetjes,

<><

Marnix
--
Marnix Klooster
mar...@worldonline.nl


Andrew Plotkin

unread,
Oct 16, 1996, 3:00:00 AM10/16/96
to

The reason for C++'s catch-catch-catch syntax is that exceptions are
class objects, which can inherit from each other. So you have to catch
them with (effectively) "instanceof" tests, which have to be done
sequentially, in a fixed order. (The order matters because an exception
may be in more than one tested class, because of inheritance. Or -- worse
-- multiple inheritance.)

Since Inform has real classes now, we *could* do exceptions that way, but
it would be Reely Dumm(tm). So as long as exceptions are simple numeric
constants, a switch statement is nicer.

> Now for the keywords. Above I argued to use "catch" if we decide on
> C++ syntax, and "except" if we use switch-like syntax.

I have no strong objection to this, although if there's a consistency to
Inform's design it's "almost C, but not quite." :-)

> For "raise"
> vs. "throw", my linguistic intuition says to use "throw" with "catch",
> and "raise" with "except".

Definitely.

> Finally, I agree that it is probably not
> wise to introduce "None" in addition to "NULL", with a different
> meaning. The choice is therefore between re-using NULL as 'no
> exception raised', and introducing a new keyword like "NoExcept" for
> this. I prefer the former for its simplicity. (I think C++ and Java
> can give us no guidance here, or can they?)

Nope.

> [As a separate issue, I think that "None" would be a good replacement
> keyword for "NULL" in Inform. It describes more intuitively what that
> special value is used for. E-mail Graham and/or me with your thoughts
> on this.]

Remember that NULL is a definition of the standard Inform library, not
the language. The obvious compromise here is to make "None" be a
language-defined constant, equal to -1, same as NULL. Then None and NULL
will be interchangeable, and we can deprecate NULL in favor of None without
breaking any existing code.

Paranoia inspires me to say that 0 should never be an exception constant
(because "nothing" is 0.) Further paranoia inspires me to suggest that
exception constants be numbered from -2 downwards. That way they won't
collide with object numbers. I know there's no reason for them *not* to
collide with object numbers, or "nothing" for that matter, but it reduces
the possibility of confusion by some tiny degree.

Other questions: In C++ and Java, "throw;" by itself re-throws the
current exception. (This can only be done in an exception handler.) This
is useful enough that I'd like to keep it. (If not, it can be simulated
with "raise ExcNum;" or whatever the variable was.)

I'd rather not have "break" acquire additional meaning. "raise None;"
does what you were saying "break" might, right?

Exception arguments are pretty cool. I'll lobby for them.

raise GameOver("You have drowned");

Yes, the exception mechanism variables should go in a small array, *not*
in global variables. Global variables are something we're actually in
short supply of.

0 new messages