Since previous work on C
cleanup functions I've been trying various means to implement try/finally in C using macros that allow a natural in-code expression of a typical form, like:
try {
...
... throw(123);
...
} finally or except {
...
}
It was not possible to use cleanup functions to implement the finally block for many reasons including difficulty of syntax construction, but ultimately because a cleanup function must return
normally and so cannot chain an exception:
It is undefined what happens if cleanup_function does not return normally. http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html
(Or can they? as I proceed with
setjmp I think they can exit with
longjmp but at the cost of missing out other cleanup handlers, so maybe I just contradicted myself there.)
I was reading about call-with-current-continuation for C and was re-reminded about setjmp/longjmp and wondered if these might work. (For
GENSYM see my
previous post).
jmp_buf GENSY(jmp_buf);
if((errno=setjmp(GENSYM(jmp_buf)))==0) {
...
... longjmp(????, 123);
...
} nothing or else { // nothing for finally, and else for except
...
}
the main advantage with setjmp/longjmp is the effective go-back-to consequence of longjmp which avoids the need to recall the names of any generated reference further on in the code. The main difficulty with throw is knowing the reference ???? to use in the
longjmp. It should be the most recent active jump_buf --- but how to tell which it is?
We could have a local variable in the try scope which stores the address of the most recent
jmp_buf, or in other words we don't need GENSYM macro after all so long as we start a new scope:
{ jmp_buf active_jmp_buf;
if ((errno=setjmp(active_jmp_buf))==0) {
...
... throw(123);
...
} } nothing or else {
...
}
which could be generated by these
#define try { jmp_buf active_jmp_buf; \
if ((errno=setjmp(active_jmp_buf))==0)
#define finally }
#define except } else
#define throw(thrown) longjmp(active_jmp_buf, thrown)
Note how the exception number is smuggled into the exception handler through use of
errno.
After looking at some exception handler
examples for C# and seeing how much like a
switch statement it appeared, I decided I wanted to be able to separate exception types with their own handler and realised that a case statement might do for this:
{ jmp_buf active_jmp_buf;
switch (setjmp(active_jmp_buf)) {
case 0:
{
...
... throw(123);
...
}
...
case 123:
{
...
}
}
}
Which, though incomplete (consider the lack of break statements) might be generated by something like this:
#define try { jmp_buf active_jmp_buf; \
switch (setjmp(active_jmp_buf)) { \
case 0:
#define finally }
#define except(exception) case exception:
#define throw(thrown) longjmp(active_jmp_buf, thrown)
and so expressed like this:
try {
...
... throw(123);
...
} except(123) {
...
} except(default) {
...
}
Except that we don't have enough close-braces to close the switch statement or the scope containing the
jmp_buf which now contains the entire switch statement. This is the sort of problem that plagued previous attempts based on cleanup functions.
With the last new keyword being the
finally or the
except, any special scopes must be constrained to the
try keyword that
finally or
except can clean them up. Therefore the
switch statement is off limits as it requires an extra close-brace, although
Francesco Nidito was not too proud to introduce
ETRY to solve this problem with his C exceptions (along with Duff's device).