PROCEDURE h()
TRY
g();
EXCEPT
E2 => <S2>
END
PROCEDURE g()
IF cond1 THEN
RAISE
E1;
ELSE IF cond2 THEN
RAISE
E2;
ELSE
<S4>
PROCEDURE Main()
TRY
f();
h();
EXCEPT
ELSE => <S3>
END
In the above code, don't worry about checked versus unchecked exceptions. (a) Show the exception tables for the above code. (b) Consider the following call sequence: Main->h->g and g raises exception E1. Using the exception table, show how this exception is handled.
Answer:
(a) at the call to g in procedure f, the exception table is: (E1, <S1>),
(*, Reraise)
at the call to g in procedure
h, the exception table is: (E2, <S2>), (*, Reraise)
at the RAISEs in procedure
g, the exception table is: (*, Reraise)
at the two calls in
Main, the exception table is: (*, <S3>)
(b) When g raises exception E1, it finds table (*, Reraise).
This pops off g's activation record and reraises exception E1 in its caller
h
The table there is (E2, <S2>),
(*, Reraise), which causes h's activation record to be popped off the stack
(due to the Reraise handler) and reraises the exception in caller Main
According to Main's table,
all exceptions are handled by <S3> so code <S3> is executed.
2. (20 points) In class I presented a scheme for implementing
exception handling for Java and Modula-3 (it's the same scheme you used
in question 1 above). Can this scheme be used if exceptions handlers
have a dynamic scope as in PL/1. Explain and give a supporting example.
Ans: the scheme presented in class cannot be used for exception handling
with dynamic scopes since it assumes that one knows statically all the
exceptions that can be raised at a program point. For example, consider
the PL/1 like code below:
on condition E1 print "Hi"
if b is true then
on condition E1 print "Bye"
end
<S1>
In code <S1> we don't know statically what the handler of E1 is:
it could be either the "Hi" or the "Bye" prints. Thus, we cannot
generate a table statically to map exceptions to exception handlers.
3. (20 points) Closures are difficult to implement efficiently
when nested functions can escape. Consider a language that supports
nested functions but wants to keep their implementation efficient by prohibiting
nested functions from escaping. Describe the compile-time and/or
run-time checks that are necessary in order to prohibit nested functions
from escaping. For each check you describe indicate clearly if it
is a compile time or a run-time check.
Ans: At run-time, we need to check that the code does not store a nested
function in a variable or return it from a routine. This check is
easy (and at compile time) if the assignment or return is explicit e.g.
:
x := nested_function;
or RETURN nested_function.
But if the return or assignment is indirect, then more work is
needed. Assume that all functions are passed as closures.
The Environment component of closures of global functions is empty and
of nested functions is non-empty.
Then if we see x := p, where x is a procedure typed variable,
then we need to ensure at run-time that the closure pointed to by p has
an empty "Environment" component (i.e., p->env is NIL). Similarly,
if we see RETURN p, where the enclosing function is declared to return
a function, we need to ensure at run-time that the environment component
of the closure pointed to by p is empty.
4. (20 points) Write the following function:
compare: ''a list * ''a list
-> bool
"compare" takes a pair of lists, and returns
true if the lists are identical and false otherwise. Use continuations
(callcc) in your code so that compare exits immediately if finds that the
lists are not identical.
Ans:
fun compare_lists (l1, l2) =
callcc(fn k =>
let fun comp_aux (nil, nil) = throw k(true)
| comp_aux(h::t, nil) = throw k(false)
| comp_aux(nil, h::t) = throw k(false)
| comp_aux(h1::t1, h2::t2) = if h1 = h2 then comp_aux(t1, t2) else
throw k(false)
in comp_aux(l1, l2) end)