There are two fairly gross hacks needed to make this go. One is a
means of taking over errors signaled using stop or the internal
error and errorcall functions and transferring them to the
exception mechanism. This is done by hijacking the error and
show.error.message options (i.e. user settings of those options
will be ignored when inside the exception handling mechanism). The
second hack is needed to notify the internal error handling code that
the exception handler mechanism has taken over and we are no longer in
internal error handling code (i.e. the static variable inError in
errors.c needs to be set to zero). This is accomplished by
calling Rf_resetStack, which sets inError, and undoing
everything else this routine does. (Which means this will break
spectacularly when the internals of that routine change, but hopefully
by then we may have a better way to do this.)
exception. The class simple.exception is the class currently
used by stop and all internal error signals. The constructor by
the same name takes a string describing the exception as argument and
returns a simple.exception object.
Uncaught exceptions will be processed by the generic function
default.exception.handler. The method for class exception
calls stop with the result of applying as.character to the
exception argument.
The function try.catch is used to catch exceptions. Its usage is
try.catch(expr, ..., finally = NULL)It evaluates its expression argument in a context where the handlers provided in the
... argument are available. The finally
expression is then evaluated in the context in which try.catch was
called; that is, the handlers supplied to the current try.catch
call are not active when the finally expression is evaluated.
Handlers provided in the ... argument to try.catch are
established for the duration of the evaluation of expr. If no
exception is raised when evaluating expr then try.catch
returns the value of the expression.
If an exception is raised while evaluating expr then established
handlers are checked, starting with the most recently established
ones, for one matching the class of the exception. If a handler is
found then control is transferred to the try.catch call that
established the handler, the handlers in that call are dis-established,
the handler is called with the exception as its argument, and the
result returned by the handler is returned as the value of the
try.catch call.
If an exception is raised and no handler is found, then the default
handler is called. The context of the call is either the outer-most
try.catch call or the raise.exception call if there is no
surrounding try.catch call.
When a try.catch call is on the stack, calls to stop and errors
signaled internally are converted into exceptions of type
simple.exception and raised by raise.exception.
The only form of non-local transfer of control that try.catch can
catch is raising of exceptions. It cannot capture the other three
possibilities: calls to return, break, and next (there are
actually one or two more, depending on how you count, that also are
not caught). Whether these should be made, conceptually at least,
into a kind of exception, or perhaps into something inheriting off a
superclass of exception (in the spirit of Java's Throwable class) is
not clear. try and restart currently also do not catch these
three, but perhaps they should.
The printed representation of converted exceptions produced for
stop calls and internal errors is less that ideal (as is the
method of trapping and converting them) but this should do for getting
a feel for how this might work.
try.catch does not raise an
exception, the value of the expression is returned. If a finally
expression is provided, it is evaluated before return:
> try.catch(1, finally=print("Hello"))
[1] "Hello"
[1] 1
A simple exception can be constructed and raised by explicitly calling
the constructor and passing the result to raise.exception:
> e<-simple.exception("test exception")
> raise.exception(e)
Error in default.exception.handler.exception(e) :
unhandled exception (simple.exception): test exception
Alternatively, raise.exception can be called with a string
argument:
> raise.exception("test exception")
Error in default.exception.handler.exception(e) :
unhandled exception (simple.exception): test exception
If an exception is raised in an expression protected by a
try.catch and the exception is not caught, then the finally
expression is evaluated before passing the exception to the default
handler:
> try.catch(raise.exception(e), finally=print("Hello"))
[1] "Hello"
Error in default.exception.handler.exception(e) :
unhandled exception (simple.exception): test exception
Internal errors and calls to stop are converted to simple exceptions:
> try.catch(stop("fred"), finally=print("Hello"))
[1] "Hello"
Error in default.exception.handler.exception(e) :
unhandled exception (simple.exception): Error in catch("__TRY_CATCH__", list(value = expr, throw = FALSE)) :
fred
The printed representation of converted exceptions leave something to
be desired.
If an exception is raised and a handler is provided, then the result of calling the handler function with the exception object as argument is returned:
> try.catch(raise.exception(e),
exception = function(e) e,
finally=print("Hello"))
[1] "Hello"
<simple.exception: test exception>
> try.catch(stop("fred"),
exception = function(e) e,
finally=print("Hello"))
[1] "Hello"
<simple.exception: Error in catch("__TRY_CATCH__", list(value = expr, throw = FALSE)) :
fred
There are (at least) five other forms of non-local exits
return, break, and next
Q command in the browser
quit or sending a user signal
Should we have a class throwable that is a superclass of
exception and can be used to implement all non-local exits?
Doing this could have the benefit of cleaning up some mechanisms we
currently have. For example, we could define a class exit with a
constructor of the same name that takes the same three arguments as
q and define q as
q <- function(save = "default", status = 0, runLast = TRUE)
raise.throwable(exit(save, status, runLast))
and have the R main loop defined something like
mainloop <- function()
repeat
try.catch(try.catch(repl(),
exception=default.exception.handler),
exception = function(e) cat("uncaught exception"),
exit = function(e) if (ask.about.exit()) break)
Here repl would be the actual read-eval-print loop. The outer
exception handler is intended to be be as fail-safe as possible since
the next stop would have to be an exit from mainloop.
[This is too simplistic even for the current level of concurrency we have and certainly would not work with threads, but the point is we could get clearer semantics for the shutdown process if we do things this way. At the moment, if you cancel a quit things don't seem to go back quite to normal.]
One other reason for explicitly allowing us to manage all non-local
exiting is embedding and callbacks. Suppose an R function foo
does .C("bar", ...) and bar calls back into R to a function
baz. If baz calls stop or executes a return from
foo a longjmp will occur that skips over the bar C level
call frames. A lot of C code is robust to this sort of thing but a
lot isn't. In cases where the C code isn't it would be nice to have a
means of trapping all transfers of control, something like
calling baz with
try.catch(list(value=baz(...), throw=FALSE),
throwable=function(e) list(value=e,throw=TRUE))
This would allow the C code to then be given an exit status and to do
whatever cleanup it needs to do before returning to foo. foo
can then either ignore the throwable it receives, or re-throw it.
Currently we only have try, which is based on restart, to do
this sort of thing at the R level. restart and hence try only
catch errors, not return, break, or next calls; leaving
browser with Q also goes through a restart. Whether this
should be changed to make restart more absorbent is not clear. At
the C level we currently have another option, which is to establish a
new toplevel context for the call. This is particularly appropriate
for things like running finalizers or processing events where we are
at least conceptually running in a concurrent thread (to support this
there are other stack examinations that should stop at the first
toplevel context they find as well). It may not be the right approach
for nested callbacks where we are conceptually nested deep in the call
stack.
stop and errorcall contain the call that raised the
error. This could be folded into the exception mechanism. All
internal error calls currently also save the traceback information.
It would probably make sense to provide this sort of information to
exceptions as well. Java has a somewhat peculiar approach to this.
The constructor of throwables calls a method called
fillInStackTrace (or something like that) that fills in a stack
trace based on where the constructor is called. This stack trace is
then part of the exception and can be printed by a handler. This
approach assumes that errors will always be signaled by creating an
exception object on the spot. This is not always feasible; for
out-of-memory errors it would be a good idea to allocate the exception
object in advance.
We should probably do something along these lines, but exactly what is not clear.
<simpexcept.R>=
<catch and throw>
<dynamically scoped variables>
<exceptions>
<raising and catching exceptions>
<converting internal exceptions>
.First.lib <- function(lib, pkg)
library.dynam(pkg, pkg, lib)
<simpexcept.c>=
#include "Rinternals.h"
<ResetErrorHandling definition>
catch and
throw, which are patterned after the Common Lisp special operators
with the same names.
Catch uses local variables with hopefully unique names to save its tag
argument, provide a means for throw to transfer control to the
catch call, and provide a place for throw to place a value.
(It would be better to have a more structured mechanism for
maintaining dynamic state, some form of dynamically scoped variables.)
The means for transferring control is a closure that captures a
promise containing a return expression. Calling the closure
evaluates the return expression which then causes a return from the
catch call.
<catch and throw>= (<-U) [D->]
catch<-function(tag, expr) {
"__CATCH_TAG__" <- as.character(tag)
"__CATCH_THROWER__"<- make.thrower(return(get("__CATCH_VALUE__")))
"__CATCH_VALUE__" <- NULL
expr
}
make.thrower <- function(expr) function() expr
Definescatch,make.thrower(links are to index).
The throw function evaluates and saves the value of the expr
argument and then searches for an active catch for the specified tag.
The search begins with the current frame and will find the most
recently established catch if there is more than one. An error is
signaled if no matching catch is found. The value of the expression
argument is placed in the value variable in the frame of the catch
call, the throwing closure created by make.thrower in the catch
call is obtained and called. This call evaluates the captured
return expression and causes a return from the catch
expression.
<catch and throw>+= (<-U) [<-D]
throw<-function(tag, expr, no.tag = tag.not.found) {
value <- expr # forces evaluation of expr
tag <- as.character(tag)
env <- get.target.frame(tag, "__CATCH_TAG__")
assign("__CATCH_VALUE__", value, env = env)
fun <- get("__CATCH_THROWER__", env = env)
fun()
}
##****parent.env with arg > 1 doesn't seem to work as advertised??
get.target.frame <- function(tag, name) {
n <- sys.nframe()
if (n > 1)
for (i in (n-1):1) {
env <- sys.frame(i)
if (exists(name, env = env) &&
get(name, env = env) == tag)
return(env)
}
stop(paste("no catch for tag \"", tag, "\"", sep = ""))
}
Definesget.target.frame,throw(links are to index).
Matching of tags is done with ==, so tags can be any sort of
object for which this makes sense.
Some simple tests:
<tests>= [D->]
catch("x", 1)
catch("x", { throw("x", 1); 2 })
catch("x", throw("y", 1))
try.catch call
on the stack without causing an error. This is done by having
try.catch create a binding for a variable with a reasonably unique
name and searching for the existence of such a binding in the frame
stack. The function dynamic.exists does this search.
<dynamically scoped variables>= (<-U)
dynamic.exists <- function(name) {
n <- sys.nframe()
if (n > 1)
for (i in (n-1):1)
if (exists(name, env = sys.frame(i)))
return(TRUE)
FALSE;
}
Definesdynamic.exists(links are to index).
This function could be viewed as part of a mechanism for managing
dynamically scoped variables. The rest of such a mechanism would
consist of dynamic.get for getting the value of the current
dynamic binding, dynamic.assign for changing the value, and either
a separate mechanism for creating new dynamic bindings or a mechanism
or a variant of dynamic.assign that does this. It might also be
useful to use a separate mechanism for holding the bindings, a
separate environment for example, to avoid the possibility of name
clashes. Proper interaction with name spaces would also need a look.
It may well be that MzScheme's parameter mechanism would work well
for us.
exception. They are assumed to have a method for as.character
that produces a printed representation describing the exception that
occurred. The print method for exceptions uses as.character to
produce a representation of the exception.
<exceptions>= (<-U) [D->]
print.exception <- function(e, ...)
cat("<", class(e)[1], ": ", as.character(e), ">\n", sep="")
Definesprint.exception(links are to index).
The exception class is a virtual class. It has no constructor, and
its as.character method warns that this method should be
overridden by non-virtual classes. I'm not sure this is really a good
idea, but it seemed useful during development.
<exceptions>+= (<-U) [<-D->]
as.character.exception <- function(e, ...) {
warning(paste("no as.character method for exception of class", class(e)))
as.character(unclass(e))
}
Definesas.character.exception(links are to index).
One concrete exception class is provided: simple.exception.
Simple exceptions contain a string slot that is used to provide
the printed representation.
<exceptions>+= (<-U) [<-D->]
simple.exception <- function(string) {
class <- c("simple.exception", "exception")
structure(list(string=as.character(string)), class=class)
}
as.character.simple.exception <- function(e, ...)
e$string
Definesas.character.simple.exception,simple.exception(links are to index).
When an exception occurs for which no handler is available, the
generic function default.exception.handler is called. The method
for the exception class just calls stop.
<exceptions>+= (<-U) [<-D]
default.exception.handler <- function(e)
UseMethod("default.exception.handler", e)
default.exception.handler.exception <- function(e)
stop(paste("unhandled exception (", class(e)[1],
"): ", as.character(e), sep = ""))
stop and with the internal
error and errorcall functions into the exception mechanism I
use a fairly gross device: I turn off error message printing with the
show.error.messages option and set the error option to call a
function that grabs the error string, creates a simple exception with
this string, and raises the exception. Since the internal variable
inError in error.c is set before the error option
expression is evaluated, I need to reset this if the error is handled;
this is where the ResetErrorHandling C routine comes in. With
this approach calls to stop and error or errorcall will
only be converted if a try.catch call is on the stack.
Error conversion is done by
<converting internal exceptions>= (<-U) [D->]
error.converter <- function() {
raise.exception(simple.exception(geterrmessage()))
}
Defineserror.converter(links are to index).
The initial setting of the two options is handled by
set.error.options. This function returns the previous settings of
these two options in a list. The code is a little convoluted for two
reasons:
error option is NULL it is not in the options
list, and options()$error matches the error.messages option,
which is a logical.
show.error.messages is set the first time
options()$show.error.messages returns NULL, which is not a
valid value to use when setting this option.
<converting internal exceptions>+= (<-U) [<-D->]
set.error.options <- function() {
op <- options()
options(show.error.messages = FALSE)
options(error = quote(error.converter()))
if (is.logical(op$show.error.messages)) show <- op$show.error.messages
else show <- TRUE
if (is.logical(op$error)) err <- NULL
else err <- op$error
list(show.error.messages = show, error = err)
}
Definesset.error.options(links are to index).
During the raising of an exception, the options are set back to default values to prevent infinite recursion. I'm not sure this is really necessary, but for now I'll just do it to be a little safer.
<converting internal exceptions>+= (<-U) [<-D->]
reset.error.options <- function() {
options(show.error.messages = TRUE)
options(error = NULL)
}
Definesreset.error.options(links are to index).
At the end of a try.catch the original values of the options are
restored by the function
<converting internal exceptions>+= (<-U) [<-D]
restore.error.options <- function(op) {
options(op)
}
Definesrestore.error.options(links are to index).
raise.exception takes a single argument representing
an exception and raises the exception. To avoid recursion it first
calls reset.error.options to turn off the calling handler and turn
on printing of error messages. This corresponds in the current
internal error code to setting inError to a non-zero value. Next
it converts its argument to a simple exception if it is not already an
exception. This allows raise.exception to be called with a string
argument, for example. Finally the exception is thrown to the
innermost try.catch, or the default handler is called if no
try.catch is established.
<raising and catching exceptions>= (<-U) [D->]
raise.exception <- function(e) {
reset.error.options() # turn off to avoid recursion
if (! inherits(e, "exception"))
e <- simple.exception(as.character(e))
if (exists.try.catch())
throw("__TRY_CATCH__", list(value = e, throw = TRUE))
else
default.exception.handler(e)
}
exists.try.catch <- function() dynamic.exists("__TRY_CATCH__")
Definesexists.try.catch,raise.exception(links are to index).
The try.catch function uses catch to establish a target for
raise.exception to throw an exception to. The catch
expression is contained in a locally call that also establishes a
marker used by exists.try.catch to determine whether a
try.catch form is on the stack. Leaving this local
dis-establishes the try.catch; it will not be seen during the
remaining processing.
The result returned by the catch call will be a list with two
named components. If the throw component is false, then no
exception was raised and the value component is the value of the
expression. If the throw component is true, then an exception did
occur, and the exception object is in the value component.
If an exception is caught, then the options used for converting
internal error need to be turned back on since they have been turned
off by raise.exception. In addition, if the exception was
converted from an internal error or a call to stop, then we need
to turn off the internal setting that indicates that we are in error
handling code. This is done by calling a little C routine with
.Call.
Next, we search the ... argument for a handler that matches the
exception. If one is found, it is called and the result is returned
as the value of the try.catch call. Otherwise, we need to raise
the exception again. Before raising the exception, we disable the
on.exit, restore the error options and evaluate the finally
expression. This needs to be done in case raise.exception calls
the default handler, since that call will occur from within the
try.catch call.
<raising and catching exceptions>+= (<-U) [<-D]
try.catch <- function(expr, ..., finally = NULL) {
op <- set.error.options()
on.exit({restore.error.options(op); finally })
result <- local({
"__TRY_CATCH__" <- NULL
catch("__TRY_CATCH__", list(value = expr, throw = FALSE))
})
if (result$throw) {
set.error.options() # turn back on because turned off in raise
.Call("ResetErrorHandling") # is this the right place for this?
handlers <- list(...)
names <- names(handlers)
e <- result$value
for (i in seq(along = names))
if (inherits(e, names[i]))
return(handlers[[i]](e))
on.exit() # to get
restore.error.options(op) # things reasonable
finally # for stop calls going to default handler
raise.exception(e)
}
else result$value
}
The C function used to reset inError is ResetErrorHandling.
This approach is very brittle, since it depends on the way a
particular function Rf_resetStack happens to be implemented. It
would be much better to provide a hook within error.c that is
intended for this purpose, but I wanted to get something going that
did not require changes to R, so this will have to do for now. It is
likely to break very soon as changes are made to the internal error
handling code, but I will try to make modifications as necessary to
keep it working.
<ResetErrorHandling definition>= (U->)
SEXP ResetErrorHandling(void)
{
extern SEXP R_Warnings;
extern int R_CollectWarnings;
extern int R_PPStackTop;
SEXP oldWarnings = R_Warnings;
int oldCollectWarnings = R_CollectWarnings;
int oldPPStackTop = R_PPStackTop;
Rf_resetStack(FALSE);
R_PPStackTop = oldPPStackTop;
R_Warnings = oldWarnings;
R_CollectWarnings = oldCollectWarnings;
return R_NilValue;
}
DefinesResetErrorHandling(links are to index).
<tests>+= [<-D]
try.catch(1, finally=print("Hello"))
e<-simple.exception("test exception")
raise.exception(e)
try.catch(raise.exception(e), finally=print("Hello"))
try.catch(stop("fred"), finally=print("Hello"))
try.catch(raise.exception(e), exception = function(e) e, finally=print("Hello"))
try.catch(stop("fred"), exception = function(e) e, finally=print("Hello"))
ResetErrorHandling definition>: U1, D2