Exception Handling

An exception is a deviation from the expected. What is an exception in one context may be expected in another context, and thus not an exception in that context.

In Visual Prolog programs exceptions are used to deal with deviations from what is expected in the given context. When a certain program part gets into a situation it cannot deal with it can raise an exception. When an exception is raised the program control is transferred to the nearest exception handler. If the exception handler cannot handle the exception it can either raise a new exception or continue the original exception.

The program will terminate with an error message if there is no exception handler to transfer the control to.

Introduction to exception handling
There are many concepts and entities to understand when dealing with exceptions. This section will give an introduction by means of an example.

A certain program needs to write some information to a file. Therefore it has a predicate writeInfo that can write this information. writeInfo calls the PFC constructor outputStream_file::create to obtain a stream that it can write the information to:

clauses writeInfo(Filename) :- S = outputStream_file::create(Filename), …

But if the file already exists and is write-protected it is impossible to create the stream.

In outputStream_file::create this situation is considered to be exceptional/unexpected and therefore it raises an exception.

When an exception is raised the program control is transferred to the nearest exception handler, which can hopefully handle the situation and thus bring the program back on track again.

In this case writeInfo is called after the user have entered the file name in a dialog and pressed a Save-button. So the handling we want is simply to tell the user that the information was not written because the file was write-protected. The user can choose a different filename, remove the write protection, delete the file, cancel the operation entirely or something else. But from the program's point of view the exceptional situation is handled.

To set a handler we use the language construction:

clauses saveInfo(Filename) :- try writeInfo(Filename) catch TraceId do             % < > end try.

The code works like this: writeInfo is called, if it succeeds the  construction will not do more and saveInfo will also succeed.

But in the write-protected case outputStream_file::create will raise an exception and therefore the following will take place:


 * 1) TraceId</vp> will be bound to the (so called) trace id, which is a handle to information about the exception (described in more details below)
 * 2) The handler code (i.e. the code between do</vp> and end try</vp>) is evaluated
 * 3) The result of the handler code will be the result of the  construction, and thus of saveInfo</vp>.

So for the "write-protected" exception the handler code should write a message to the user.

If it is another kind of exception the handler will have to do something different. Especially, it may be an exception that the handler does not know how to handle. In this case the best thing the handler code can do is to continue the exception as an unknown exception (i.e. unknown to the handler).

When an exception is continued it consists of the original exception plus some continue information. If it has been continued by several handlers it will contain a lot of continue information in addition to the original exception information. The exception information describes a trace from the point of raise through all the handlers. The TraceId</vp> variable that is bound in the construction gives access information about the entire trace (hence the name).

As mentioned above the program will terminate with en error message, if an exception is raised and no handler can be found. Therefore programs normally setup an outermost default exception handler. mainExe::run</vp> setup such a handler in a console program this is the fall-back handler. In a GUI program there is also a default hander in the GUI event loop which will catch exceptions arising from the handling of an event.

All in all, the lifecycle of an exception is as follows:


 * It is raised
 * It is caught and continued
 * It is caught and continued
 * It is caught and handled (or the program terminates with an error message)
 * It is caught and handled (or the program terminates with an error message)

Each time the exception is caught the trace so far can be examined. When the exception is raised and each time it is continued extra information can be added to the exception trace. If nothing else handles an exception it will (normally) be handled by the fallback/default handler of the program.

Defining an exception
An exception is a value of the type core::exception</vp>

domains exception = exception(string ClassName, string ExceptionName, string Description).

I.e. an exception is a functor term that contains three pieces of information:


 * ClassName</vp>
 * The name of the class that defines the exception


 * ExceptionName</vp>
 * Then name of the exception typically the name of a constant in the program


 * Description</vp>
 * A description of the exception

Many/most programmers will never need to define exceptions, because in many/most cases the exceptions to use are already defined. An exception definition is only needed when the exception must be distinguished from other kinds of exceptions, such that exception handlers can deal specially with that kind of exception. Typically exception definitions are only necessary for exceptions that are raised by library software. In most cases programmers will raise the exception internal_error</vp>.

Raising an exception
There are many predicates that explicitly raises exceptions, but most of them does it by calling a predicate in exception</vp>:

class exception … predicates raise_exception : (exception Exception, ...) erroneous [programPoint]. raiseDetailes : (exception Exception, namedValue* ExtraInfo) erroneous [programPoint]. raise_definiteUser : (exception Exception, string Msg, namedValue* ExtraInfo) erroneous [programPoint]. ...

In all cases the Exception</vp> is the exception to raise and the rest of the arguments provides additional information about the exception. See also programPoint in Language Reference for information about the programPoint attribute.

It is very common to create helper predicates like fileSystem_api::raise_cannotCreate</vp> to do the actual raising rather that calling one of the predicates in exception</vp> directly from the code that needs to raise the exception. That way it is certain that the extra info for this particular exception is handled in the same way in all cases.

Normally, the helper raise predicate is created by the same programmer that declares the exception. And as mentioned above this is normally only programmers that create library software.

Application programmers will in most cases raise exceptions by calling appropriate raiser predicates, typically the some defined in exception</vp>.

Catching and handling exceptions
Exceptions are caught with the construction:

try <Body> catch <TraceId> do <Handler> end try

If <Body> terminates with an exception <TraceId> is bound to the trace id and then <Handler> is evaluated.

<TraceId> is used to obtain information about the exception trace. This information can be used for at least two things:


 * Determine which kind of exception that is caught, so that it can be decided if and how to handle it
 * Obtain additional information to write to the user, in a log or use for something else

As explained above it is very common that an exception is continued one or more times after it has been raised, so an exception contains a complete trace of entries (raise, continue, continue, ---). Subsequently, you will need to search the trace to see if a certain exception is among the entries. This is done by calling <vp>exception::tryGetDescriptor</vp>:

predicates tryGetDescriptor : (traceId TraceID, exception Exception) -> descriptor Descriptor determ.

The exception trace corresponding to <vp>TraceId</vp> is searched for an exception of the kind <vp>Exception</vp>. If there is such an entry in the trace (raised or continued) the corresponding exception descriptor is returned in <vp>Descriptor</vp>.

The predicate fails if such an exception is not in the exception trace.

The <vp>Descriptor</vp> contains the extra information about that particular entry in the exception trace:

domains descriptor = descriptor(programPointDescriptor ProgramPoint, exception Exception,        kind Kind, namedValue* ExtraInfo, gmtTimeValue GMTTime, unsigned ThreadId).

<vp>ProgramPoint</vp>
 * A readable representation of the <vp>programPoint</vp> where the exception was raised/continued.

<vp>Exception</vp>
 * The <vp>exception</vp> in question

<vp>Kind</vp>
 * The entry kind (<vp>raise</vp> or <vp>continue</vp>)

<vp>ExtraInfo</vp>
 * The extra information provided with this entry in the trace

<vp>GMTTime</vp>
 * The time this exception trace entry was raised/continued

<vp>ExceptionDescription</vp>
 * The exception description of this entry (this information is also present in <vp>ExceptionInfo</vp>)

<vp>ThreadId</vp>
 * The thread id of the thread that raised/continued this entry

Much of this information is mainly interesting if the exception is not handled. The information that is most interesting when handling an exception is:


 * The exception has been determined to be the one we expected
 * The <vp>ExtraInfo</vp> for this entry is available for use in messages, etc

The predicate <vp>exception::tryGetExtraInfo</vp> is convenient for obtaining extra information:

predicates tryGetExtraInfo : (descriptor Descriptor, string Name) -> value Value determ.

<vp>Descriptor</vp>
 * Is the description whose extra info we want to get something from.

<vp>Name</vp>
 * The name of the extra info parameter we want to obtain.

<vp>Value</vp>
 * The value that the mentioned parameter has.

Exception dumps
The class <vp>exceptionDump</vp> contains predicates for dumping exception traces to <vp>stdio</vp>, some other <vp>outputStream</vp> or a <vp>string</vp>.

An exception dump contains three major pieces of information:


 * A dump of the call stack (with file names and line numbers) from the point where the exception was raised
 * A dump of the exception-trace with all the information from the exception descriptors
 * Information about the operating system that the program ran on

<vp>exceptionDump</vp> have predicates both for dumping exception traces both from a <vp>traceId</vp> and from a <vp>traceInfo</vp> obtained with <vp>exception::getTraceInfo</vp> (see below).

Dumps for a certain program are in general only useful to the programmers of that program; the users of the program will find dumps cryptic and rather uninteresting. I.e. the purpose of dumps is to give the programmer means for improving the program.

Default handling
A program should handle all exceptions, because if an exception is continued or raised at a point where there is no handler the program will terminate with a rather bad error message. The error message is bad because most exception handling is done in PFC; the language itself knows nothing about exception traces and the like.

To be sure that all exceptions are handled it is normal to set up a default (or fallback) exception handler.

<vp>mainExe::run</vp> (and <vp>mainExe::runCom</vp>) which is normally called in the goal setup such a default exception handler: It runs the <vp>main::run</vp> predicate inside a construction that will handle any exception.

The handler in <vp>mainExe::run</vp> (and <vp>mainExe::runCom</vp>) dumps the entire exception trace to output stream in <vp>stdio</vp>. And then the program will terminate. The idea is that:


 * Perhaps the user can see something from the dump (which may for example say that access is denied to xxx.txt)
 * Alternatively, the developer of the program may use the dump to improve the program

In addition to the handler in <vp>mainExe::run</vp> a GUI program also set a default handler in the event loop. This handler is set by calling <vp>applicationWindow::setErrorResponder</vp>:

domains errorResponder = (applicationWindow Source, exception::traceId TraceId).

predicates setErrorResponder : (errorResponder Responder).

By default the error responder <vp>errorPresentationDialog::present</vp> is used. The functioning of the <pv>errorPresentationDialog</vp> is closely related to a categorization of exceptions described in the next section.

PFC place exceptions in three major categories based on who is (believed to be) responsible:


 * Internal errors
 * An exceptional state which the programmer is responsible to deal with


 * User exception
 * An exception which can perhaps be solved by the end user of the program, but which may also call for a programmer solution. User exceptions carries a message for the end user of the program.


 * Definite user errors
 * An exception which is definitely one the end user should deal with, because the programmer cannot do anything about it anyway

You may notice that "error" is used to signal that a person is (believed to be) responsible for the problem, where as "exception" also covers situations with looser relation to specific persons (such as a network failure).

Internal errors
Internal errors are caused and/or should be prevented by the programmer of the program.

User exceptions
User exceptions typically deal with problems related to external resources such as files, network, databases, web servers, etc. Often the user will become wiser by being informed about the problem and often the user may even solve the problem.

A user exception is distinguished by having extra information in a field with name <vp>common_exception::userMessage_parameter</vp>. This extra info should be a message with relevance for an end user of the program. The message can reference other extra info parameters using the format %PARAM. The preferred way to raise user exceptions is by means of the predicate <vp>exception::raise_user</vp>. Likewise you can continue exceptions with a user message using <vp>exception::continue_user</vp>.

The predicate <vp>exceptionDump::tryGetUserMessage</vp> will return a user message from a <vp>traceId</vp> if possible. The message will consist of all user messages from the entire trace and will have parameters substituted.

Definite user errors
Definite user errors are exceptions that are solely the responsibility of the user of the program. For example wrong use of the program.

A definite user exception is distinguished by adding <vp>true</vp> as extra info for the parameter <vp>exception::definiteUserError_parameter</vp>. The preferred way to do this is by using the predicates:


 * <vp>exception::raise_definiteUser</vp>
 * <vp>exception::continue_definiteUser</vp>

The use of these predicates is the same as the user of <vp>common_exception::raise_user</vp> and <vp>common_exception::continue_user</vp>.

An exception trace is considered a definite user error if one (or more) entries in the trace carries the <vp>exception::definiteUserError_parameter</vp>. The predicate <vp>exception::isDefiniteUserError</vp> will succeed for definite user errors.

Serializing exceptions
In some situations the best way to handle an exception is by sending all the information about the exception somewhere else. A server might for example want to send the information to the client program that caused the exception, because then there is a user that can take action.

A <vp>traceId</vp> only have a meaning in the process that has created it, but the predicate <vp>exception::getTraceInfo</vp> can create a <vp>traceInfo</vp> structure (<vp>TraceInfo</vp>) from the <vp>traceId TraceId</vp>:

predicates getTraceInfo : (traceId TraceId) -> traceInfo TraceInfo.

Such a structure is a functor structure that can be serialized/deserialized with <vp>toString</vp>/<vp>toTerm</vp>; <vp>write</vp>/<vp>read</vp>; nested in a fact database using <vp>save</vp>/<vp>consult</vp>; etc.

Packed Exceptions
Sometimes, e.g., in a client/server application, you catch an exception on the server which you want to handle on the client. In that case you can serialize the exception on the server send it to the client and reraise it as a packed exception.

On the server try ... catch E do  sendExceptionToClient(exception::getTraceInfo) end try, ...

On the client ... Rtn = remoteCall(...), if error(TraceInfo) = Rtn then exception::raise_packed(TraceInfo, "remoteCall returned an exception") end if ...

The predicate <vp>raise_packed</vp> works like a <vp>continue_unknown</vp> but continues a serialized exception instead of normal exception. It is meant to be used when the exception stem from another program and thus only is available in serialized form.

The error presentation dialog
The error presentation dialog (in the class <vp>errorPresentationDialog</vp>) is by default used by the default exception handling in a GUI program. Its behavior is closely related to the classification of exceptions described above, as described by these two rules:


 * If the exception trace carries a user message, this message will be presented to the user.
 * If the exception trace is not a definite user error, there will be information for the programmer

So if the exception is an internal error there will only be information for the programmer; if it is a user exception there will both be information for the user and for the programmer; and if it is a user error there will only be information for the user.

If there is information to the programmer the exception dialog will have a details/less details button that can show/hide the programmer information. It will also have a "report error" button, which by default will copy the information to the clipboard so that the user can easily send it to the programmer.



The dialog is customizable by means of various properties:

properties title : string. % := "Error". callBackBtnText : string. % := "&Copy". dontCallBackBtnText : string. % := "&OK". showDetailsBtnText : string. % := "Show &Details". hideDetailsBtnText : string. % := "Hide &Details". closeApplicationBtnText : string. % := "Close &Application". commentTextPromt : string. % := "Please describe what you were doing when the error occured:" internalErrorMessage : string. % := "An internal error has occurred.\nPlease send a bug report to your vendor.". feedbackMessage : string. % := "Please send your feedback.". definiteUserErrorIcon : vpiDomains::resid. userErrorIcon : vpiDomains::resid. internalErrorIcon : vpiDomains::resid. reportErrorDelegate : reportErrorDelegate. extraInfoWriter : writer.

All texts and labels are controlled by properties.

The <vp>extraInfoWriter</vp> is a callback that can be set to include extra info for the programmer. This information will both be presented in the details window and reported back to the programmer if the user press the "report error" button (i.e. the button which is by default labeled Copy).

The <vp>extraInfoWriter</vp> callback predicate must have this type:

domains writer = (outputStream Stream).

The dialog will invoke the predicate and then it should simply write extra information to the received stream.

The action performed by the "report error" button is customizable using the property <vp>reportErrorDelegate</vp>, which is a callback predicate of this type:

domains reportErrorDelegate = (window Parent, string Comment, optional{exception::traceInfo} TraceInfo, string ExtraInfo).


 * <vp>Parent</vp>
 * The error presentation dialog which can be used as parent for additional dialogs.


 * <vp>Comment</vp>
 * The users comment from the comment box.


 * <vp>TraceInfo</vp>
 * If the dialog is invoked on an exception the trace info is here, if it is invoked for feedback this value is <vp>none</vp>


 * <vp>ExtraInfo</vp>
 * The extra info written by the <vp>extraInfoWriter</vp> callback