Difference between revisions of "Exception Handling"

From wiki.visual-prolog.com

(Update to Vip 7.2)
Line 1: Line 1:
{{IsOutdated}}
+
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.
  
An exception is a deviation of a program from its "normal" execution path. The PFC exception package contains predicates to deal with exceptions, which occur in a prolog program. This tutorial describes:
+
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.
  
* How to catch exceptions
+
The program will terminate with an error message if there is no exception handler to transfer the control to.
* How to raise exceptions
 
* How to continue another exception
 
* How to dump an exception
 
* How to use the <vp>errorPresentationDialog</vp> in GUI programs
 
  
== How to Catch Exceptions ==
+
== Introduction to exception handling  ==
  
Let us consider a program that reads a text file and prints its content to the console. The PFC supply the <vp>file::readString</vp> predicate that can fulfill the task. However, some cases can prevent to implement the task; for example, a specified file name can point to a non-existing file. When predicate <vp>file::readString</vp> cannot fulfill the task, it generates an exception.
+
There are many concepts and entities to understand when dealing with exceptionsThis section will give an introduction by means of an example.
  
Visual Prolog supplies a built-in <vp>trap/3</vp>  predicate to catch an exception and process it. Please, see Visual Prolog online Help ('''Language Reference -> Built-in Domains, Predicates and Constants-> Predicates ->trap topic''') for more details about the <vp>trap/3</vp> predicate.
+
A certain program needs to write some information to a file.  Therefore it has a predicate <vp>writeInfo</vp> that can write this information. <vp>writeInfo</vp> calls the PFC constructor <vp>outputStream_file::create</vp> to obtain a stream that it can write the information to:
  
Therefore the code to catch an exception will look like:
+
<vip>clauses
 +
    writeInfo(Filename) :-
 +
        S = outputStream_file::create(Filename),
 +
        …</vip>
 +
 
 +
But if the file already exists and is write-protected it is impossible to create the stream.
 +
 
 +
In <vp>outputStream_file::create</vp> 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 <vp>writeInfo</vp> 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 {{lang2|Terms|try-catch|try-catch}} language construction:
 +
 
 +
<vip>clauses
 +
    saveInfo(Filename) :-
 +
        try
 +
            writeInfo(Filename)
 +
        catch TraceId do
 +
              % <<handle situation>>
 +
        end try.</vip>
 +
 
 +
The code works like this:  <vp>writeInfo</vp> is called, if it succeeds the {{lang2|Terms|try-catch|try-catch}} construction will not do more and <vp>saveInfo</vp> will also succeed.
 +
 
 +
But in the write-protected case <vp>outputStream_file::create</vp> will raise an exception and therefore the following will take place:
 +
 
 +
# <vp>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)
 +
# The handler code (i.e. the code between <vp>do</vp> and <vp>end try</vp>) is evaluated
 +
# The result of the handler code will be the result of the {{lang2|Terms|try-catch|try-catch}} construction, and thus of <vp>saveInfo</vp>.
 +
 
 +
So for the "write-protected" exception the handler code that should write the 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 <vp>TraceId</vp> variable that is bound in the {{lang2|Terms|try-catch|try-catch}} 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.  <vp>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:
  
<vip>trap(MyTxtFileContent = file::readString(myFileName, _IsUnicode),
+
* It is raised
    ErrorCode, handleFileReadError(ErrorCode)),</vip>
+
* It is caught and continued
 +
* It is caught and continued
 +
* …
 +
* It is caught and handled (or the program terminates with an error message)
  
The most interesting part is the implementation of the handleFileReadError predicate:
+
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.
  
<vip>class predicates
+
== Defining an exception ==
     handleFileReadError : ( exception::traceID TraceId ) failure.
+
 
 +
An exception is a value of the type <vp>core::exception</vp>
 +
 
 +
<vip>domains
 +
    exception = (classInfo ClassInfo [out], string PredicateName [out], string Description [out]).</vip>
 +
 
 +
I.e. an exception is a predicate that returns three pieces of information:
 +
 
 +
; <vp>ClassInfo</vp>
 +
: The <vp>classInfo</vp> of the class that defines the exception
 +
; <vp>PredicateName</vp>
 +
: The name of the exception predicate itself
 +
; <vp>Description</vp>
 +
: A description of the exception
 +
 
 +
{{example| The <vp>cannotCreate</vp> exception from the example above is declared in the class <vp>fileSystem_api</vp> like this:
 +
<vip>class fileSystem_api
 +
 +
predicates
 +
     cannotCreate : exception.
 +
…</vip>
 +
 
 +
The implementation looks like this:
 +
<vip>impement fileSystem_api
 +
 
clauses
 
clauses
     handleFileReadError(TraceId):-
+
     cannotCreate(classInfo, predicate_name(), "Cannot create or open the specified file").
        Descriptor = exception::tryGetDescriptor(TraceId, fileSystem_api::cannotcreate),
+
…</vip>
        !, % file cannot be loaded
+
<vp>classInfo</vp> is the <vp>classInfo</vp> of the <vp>fileSystem_api</vp> class ; <vp>predicate_name</vp> is a built-in predicate  that returns the name of the predicate in which it is called (i.e. in this case it will return <vp>"cannotCreate"</vp>).  The last argument is the description of the exception.  
        exception::descriptor(_ErrorCode, % ErrorCode is used if user invoked errorExit himself.
+
}}
            _ClassInfo, % class information of the class, which raised the exception .
+
 
            _Exception, % actually it is fileSystem_api::cannotcreate,
+
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.  I most cases programmers will raise the exception <vp>internal_error</vp>.
            % but the parameter should not be compared by ' = ' .
+
 
            % See exceptionState::equals
+
== Raising an exception ==
            _Kind, % exception can be raised or continued
+
 
            ExtraInfo,
+
Exceptions are raised using the predicate <vp>exception::raise</vp>:
            _TraceId, % here is the TraceId, but it is not necessary to check once more
+
 
            _GMTTime, % the time of exception creation .
+
<vip>class exception
            ExceptionDescription,
+
            _ThreadId) = Descriptor,
+
predicates
         FileName = namedValue::mapLookUp(ExtraInfo,
+
    raise : (classInfo ClassInfo, exception Exception) erroneous.
        fileSystem_api::fileName_parameter, string("")),
+
    raise : (classInfo ClassInfo, exception Exception, namedValue_list ExtraInfo) erroneous.</vip>
        Reason = namedValue::mapLookUp(ExtraInfo,
+
 
        common_exception::errorDescription_parameter, string("")),
+
<vp>ClassInfo</vp>
        stdIO::write("Cannot load file due to: ",ExceptionDescription,
+
: The <vp>classInfo</vp> of the class that raise the excption
            "\nFileName: ", FileName,
+
<vp>Exception</vp>
            "\nReason: ", Reason ),
+
: The exception to raise
        fail.
+
<vp>ExtraInfo</vp>
    handleFileReadError(ErrorCode):-
+
: Extra information about the raised exception.
        isDebugMode = true,
+
 
        !,
+
{{example|The <vp>cannotCreate</vp> exception from above is raised by the predicate <vp>fileSystem_api::raise_cannotCreate</vp>. The code  looks like this:
        exceptionDump::dumpToStdOutput(ErrorCode),
+
<vip>clauses
        % dump to console for developer needs
+
    raise_cannotCreate(ClassInfo, FileName, LastError) :-
        fail.
+
        Desc = common_exception::getLastErrorDescription(LastError),
    handleFileReadError(_ErrorCode):-
+
         exception::raise(
        % program cannot handle the exception and it does not report about it .
+
            ClassInfo,
        fail.</vip>
+
            cannotCreate,
 +
            [namedValue(fileSystem_exception::fileName_parameter, string(FileName)),
 +
            namedValue(common_exception::errorCode_parameter, unsigned(LastError)),
 +
            namedValue(common_exception::errorDescription_parameter, string(Desc))]).</vip>
 +
 
 +
<vp>raise_cannotCreate</vp> is called with the <vp>ClassInfo</vp> of the raising class the <vp>FileName</vp> and the windows error code <vp>LastError</vp> that the relevant low level Windows API predicate caused.
 +
 
 +
<vp>common_exception::getLastErrorDescription</vp> obtains the description corresponding to <vp>LastError</vp> from Windows.
 +
 
 +
<vp>exception::raise</vp> is then used to raise the <vp>cannotCreate</vp> exception with the three pieces of extra information.  <vp>fileSystem_exception::fileName_parameter</vp>, <vp>common_exception::errorCode_parameter</vp> and <vp>common_exception::errorDescription_parameter</vp> are names (strings) used for the extra information.  
 +
}}
 +
 
 +
It is very common to create helper predicates like  <vp>fileSystem_api::raise_cannotCreate</vp> to do the actual raising rather that calling <vp>exception::raise</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 ones defined in <vp>common_exception</vp>.
 +
 
 +
== Catching and handling exceptions ==
 +
 
 +
Exceptions are caught with the {{lang2|Terms|try-catch|try-catch}} construction:
 +
 
 +
<vipbnf>try <Body> catch <TraceId> do <Handler> end try</vipbnf>
 +
 
 +
If <vpbnf><Body></vpbnf> terminates with an exception <vpbnf><TraceId></vpbnf> is bound to the ''trace id'' and then <vpbnf><Handler></vpbnf> is evaluated.
 +
 
 +
<vpbnf><TraceId></vpbnf> 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
  
When <vp>fileSystem_api::cannotcreate</vp> exception is expected, the predicate <vp>exception::tryGetDescriptor</vp> is invoked exactly with such parameter to catch and handle such exception. In our case we use the output to console:
+
You will call <vp>exception::tryGetDescriptor</vp> to determine if a certain kind of exception is caught:
  
<vip>stdIO::write("Cannot load file due to: ", ExceptionDescription,
+
<vip>predicates
     "\nFileName: ", FileName,
+
     tryGetDescriptor : (traceId TraceID, exception Exception) -> descriptor Descriptor determ.</vip>
    "\nReason: ", Reason ),</vip>
 
  
with an explanation of a reason of the exception.
+
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) a corresponding exception descriptor is returned in <vp>Descriptor</vp>.
  
Please notice that since Visual Prolog 7.0 it is not necessary to clear exceptions, as all necessary information is stored in TraceId. This fact actually increases the performance of your program.
+
The predicate fails if such an exception is not in the exception trace.
  
The full example presents in '''catchException\catchException.prj6''' project.
+
The <vp>Descriptor</vp> contains the extra information about that particular entry in the exception trace:
  
== How to Raise Own Exceptions ==
+
<vip>domains
 +
    descriptor = descriptor(classInfoDescriptor ClassInfo, exceptionDescriptor ExceptionInfo, kind Kind,
 +
        namedValue_list ExtraInfo, gmtTimeValue GMTTime, string ExceptionDescription, unsigned ThreadId).</vip>
  
Let us consider a program that checks the size of a specified file. The PFC supply the predicate file::getFileProperties that can return the size of the specified file. If the size is zero, then we can raise an exception by the predicate exception::raise. It is also necessary to create a predicate, which will specify the exception. We have created myExceptionZeroFileSize for this purpose. The code of raising an exception will look like:
+
; <vp>ClassInfo</vp>
 +
: The <vp>classInfo</vp> of the class that raised the exception (in functor form)
 +
; <vp>ExceptionInfo</vp>
 +
: The <vp>exception</vp> in question (in functor form)
 +
; <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 kind 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:
 +
 
 +
<vip>predicates
 +
    tryGetExtraInfo : (descriptor Descriptor, string Name) -> value Value determ.</vip>
 +
 
 +
; <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.
 +
 
 +
{{example|  Code for catching and handling the <vp>fileSystem_api::cannotCreate</vp> exception can look like this:
  
 
<vip>clauses
 
<vip>clauses
     run():-
+
     saveInfo(Filename) :-
         console::init(),
+
         try
        trap(file::getFileProperties(myFileName, _Attributes, Size, _Creation, _LastAccess, _LastChange),
+
            writeInfo(Filename)
             ErrorCode,
+
        catch TraceId do
            handleFileGetPropertiesError(ErrorCode)),
+
              if D = exception::tryGetDescriptor(TraceId, fileSystem_api::cannotCreate) then
        Size = unsigned64(0, 0), % file size is zero
+
                stdio::write("Cannot write to the file: ", Filename),
        !,
+
                if string(Description) = exception::tryGetExtraInfo(D, common_exception::errorDescription_parameter) then
        exception::raise(classInfo,myExceptionZeroFileSize,
+
                    stdio::writef("; %", Description)
            [namedValue(fileSystem_api::fileName_parameter, string(myFileName))]).
+
                end if,
     run().</vip>
+
                stdio::write("\n")
 +
            else
 +
                common_exception::continue_unknown(TraceId, classInfo, predicate_name(), "Filename = ", Filename)
 +
             end if
 +
        end try.</vip>
 +
 
 +
The code here don't need to obtain the <vp>fileSystem_exception::fileName_parameter</vp>, because the file name is already known in the <vp>Filename</vp> variable.
 +
 
 +
But we can obtain Window's description of the problem querying for <vp>common_exception::errorDescription_parameter</vp>
 +
 
 +
We '''continue''' the exception as unknown if it is not the <vp>cannotCreate</vp> kind .  For the continue call we add the filename as.  <vp>common_exception::continue_unknown</vp> will create a string of the extra arguments and add it as an <vp>common_exception::errorArguments_parameter</vp>.  Such information is mainly for use in the default exception handler to be discussed below.
 +
}}
 +
 
 +
== 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>:
 +
 
 +
<vip>predicates
 +
    getTraceInfo : (traceId TraceId) -> traceInfo TraceInfo.</vip>
 +
 
 +
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.
 +
 
 +
== 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>.
 +
 
 +
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 {{lang2|Terms|try-catch|try-catch}} 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>:
 +
 
 +
<vip>domains
 +
    errorResponder = (applicationWindow Source, exception::traceId TraceId).
 +
 
 +
predicates
 +
     setErrorResponder : (errorResponder Responder).</vip>
 +
 
 +
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 exceptions
 +
: 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 ===
  
The first parameter of the predicate exception::raise is the predicate classInfo, which is generated by the IDE for any new class. It is, of course, a good idea to specify extra information, when an exception is generated. In our case the file name can be helpful, as a zero size belongs to the specified file.
+
Internal errors are caused and/or should be prevented by the programmer of the program.
  
The predicate myExceptionZeroFileSize is created by the template:
+
{{Example| A predicate takes two lists as argument and pair the elements of the lists.  So the lists are expected to have the same length.  If the lists have different lengths it is appropriate to raise an <vp>internal_error</vp>, because it is clearly the responsibility of the programmer to ensure that the lists have same length; the end-user of the program can at most influence this indirectly.
 +
}}
  
<vip>clauses
+
=== User exceptions ===
  myExceptionZeroFileSize(classInfo, predicate_Name(),
 
    "File size cannot be zero").</vip>
 
  
That is, the first and the second parameters for exception predicates are always classInfo and predicate_Name() respectively, and the third parameter contains a text explanation of the exception reason.
+
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.
  
The full example presents in '''raiseException\raiseException.prj6''' project.
+
{{Example|  The read-only file problem discussed above is a typical example of a user exceptionIt is the user rather than the programmer that can solve the problem with the read-only file.
 +
}}
  
== How to Continue Another Exception ==
+
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>common_exception::raise_user</vp>.  Likewise you can continue exceptions with a user message using <vp>common_exception::continue_user</vp>.
  
Let us consider a DLL program that reads a text file and returns its content as a return parameter. If an exception occurs, then the DLL continues it and adds extra information about DLL's version. The code to continue the exception will look like:
+
{{Example| This predicate will raise a '''''user''''' <vp>fileSystem_api::cannotCreate</vp> exception  
  
<vip>constants
+
<vip>class predicates
     dllVersion = "1.20.0.0".
+
     raise_cannotCreate : (string Filename) erroneous.
 
clauses
 
clauses
     loadFile(FileName) = FileContent :-
+
     raise_cannotCreate(Filename) :-
         trap(FileContent = file::readString(FileName, _IsUnicode),
+
         common_exception::raise_user(classInfo, fileSystem_api::cannotCreate, predicate_name(),
          ErrorCode,
+
            "It is not possible to create the file %Filename",
          exception::continue(ErrorCode, classInfo, continuedFromDll,
+
            [namedValue("Filename", string(Filename))]).</vip>
                [namedValue(version_Parameter,string(dllVersion))])).</vip>
 
  
The predicate continuedFromDll is declared as:
+
The use of <vp>"%Filename"</vp> in the message corresponds to the extra info <vp>"Filename"</vp>.
 +
}}
  
<vip>predicates
+
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.
    continuedFromDll : core::exception as "_ContinuedFromDll@12"</vip>
 
  
and it is exported from the DLL. The implementation of the continuedFromDll predicate is created by a template ( the same as myExceptionZeroFileSizeabove):
+
{{Example|  Given <vp>raise_cannotCreate</vp> from above and calling <vp>test</vp>:
  
 
<vip>clauses
 
<vip>clauses
     continuedFromDll(classInfo, predicate_Name(),
+
     test() :-
          "Exception continued from continueException.DLL").</vip>
+
        try
 +
            raise_cannotCreate("test.xxx")
 +
        catch TraceId do
 +
            if UserMsg = exceptionDump::tryGetUserMessage(TraceId) then
 +
                stdio::write(UserMsg)
 +
            end if
 +
        end try.</vip>
 +
 
 +
The following message is written to <vp>stdio</vp>:
 +
 
 +
<source lang="text">It is not possible to create the file test.xxx</source>
 +
}}
 +
 
 +
=== 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.
 +
 
 +
{{Example| A program can write out some information, but only if the user has specified which information to write.  So if the user invoke the functionality without having specified which information to write, it is appropriate to raise a definite user error.
 +
}}
 +
 
 +
A definite user exception is distinguished by adding <vp>true</vp> as extra info for the parameter <vp>common_exception::definiteUserError_parameter</vp>.  The preferred way to do this is by using the predicates:
 +
 
 +
* <vp>common_exception::raise_definiteUser</vp>
 +
* <vp>common_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>common_exception::definiteUserError_parameter</vp>.  The predicate <vp>common_exception::isDefiniteUserError</vp> will succeed for definite user errors.
 +
 
 +
== 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.
  
When the predicate exception::continue continues the exception, it adds extra information about dllVersion.
+
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 full example presents in '''continueException\continueException.prj6'''  project.
+
[[Image:ErrorPresentationDialogDefault.png]]
  
Let us look how to catch such continued exception. The code is similar to the code discussed above and we will focus on the differences here.
+
The dialog is customizable by means of various properties:
  
<vip>constants
+
<vip>properties
     myFileName = "my.txt".
+
     title : string. % := "Error".
clauses
+
    callBackBtnText : string. % := "&Copy".
     run():-
+
    dontCallBackBtnText : string. % := "&OK".
        console::init(),
+
    showDetailsBtnText : string. % := "Show &Details".
        initExceptionState(exception::getExceptionState()),
+
    hideDetailsBtnText : string. % := "Hide &Details".
        trap(MyTxtFileContent = loadFile(myFileName),
+
    closeApplicationBtnText : string. % := "Close &Application".
            ErrorCode,
+
    commentTextPromt : string. % := "Please describe what you were doing when the error occured:"
            handleFileReadError(ErrorCode)),
+
    internalErrorMessage : string. %  := "An internal error has occurred.\nPlease send a bug report to your vendor.".
        !,
+
    feedbackMessage : string. %  := "Please send your feedback.".
        stdIO::write("The content of ",myFileName,"is:\n",MyTxtFileContent).
+
    definiteUserErrorIcon : vpiDomains::resid.
     run().
+
     userErrorIcon : vpiDomains::resid.
 +
    internalErrorIcon : vpiDomains::resid.
 +
    reportErrorDelegate : reportErrorDelegate.
 +
    extraInfoWriter : writer.</vip>
 +
 
 +
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:
 +
 
 +
<vip>domains
 +
    writer = (outputStream Stream).</vip>
 +
 
 +
The dialog will invoke the predicate and then it should simply write extra information to the received stream.
 +
 
 +
{{Example| The Visual Prolog IDE uses the <vp>extraInfoWriter</vp> to write version information, information about the open project and information about the last actions carried out in the IDE.  This information will help the programmers to analyze the problem.
 +
}}
 +
 
 +
The action performed by the "report error" button is customizable using the property <vp>reportErrorDelegate</vp>, which is a callback predicate of this type:
 +
 
 +
<vip>domains
 +
     reportErrorDelegate = (window Parent, string Comment, optional{exception::traceInfo} TraceInfo, string ExtraInfo).</vip>
  
class predicates
+
; <vp>Parent</vp>
    handleFileReadError : ( exception::traceID TraceId )failure .
+
: The error presentation dialog which can be used as parent for additional dialogs.
clauses
+
; <vp>Comment</vp>
    handleFileReadError(TraceId):-
+
: The users comment from the comment box.
        DescriptorContinued = exception::tryGetDescriptor(TraceId, continuedFromDll),
+
; <vp>TraceInfo</vp>
        ErrorDescriptorInformation = exception::tryGetErrorDescriptorInformation(TraceId),
+
: 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>
        ContinueException = ErrorDescriptorInformation:tryGetContinueException(),
+
; <vp>ExtraInfo</vp>
        !, %file cannot be loaded
+
: The extra info written by the <vp>extraInfoWriter</vp> callback
        exception::descriptor(_ErrorCodeContinued,
 
            _ClassInfoContinued,
 
            _ExceptionContinued,
 
            _KindContinued,
 
            ExtraInfoContinued,
 
            _,
 
            _GMTTimeContinued,
 
            ExceptionDescriptionContinued,
 
            _ThreadIdContinued) = DescriptorContinued,
 
        Version = namedValue::mapLookUp(ExtraInfoContinued, version_Parameter, string("")),
 
        stdIO::write("Exception continued : ",ExceptionDescriptionContinued,
 
            "\nDllVersion: ", Version,"\n"),
 
        ExtraInfo = ContinueException:getExtraInfo(),
 
        ExceptionDescription = ContinueException:getExceptionDescription(),
 
        FileName = namedValue::mapLookUp(ExtraInfo,
 
        fileSystem_api::fileName_parameter, string("")),
 
        Reason = namedValue::mapLookUp(ExtraInfo,
 
            common_exception::errorDescription_parameter, string("")),
 
        stdIO::write("Cannot load file due to: ",ExceptionDescription,
 
            "\nFile Name: ", FileName,
 
            "\nReason: ", Reason ),
 
        fail.
 
    handleFileReadError(TraceId):-
 
        isDebugMode = true,
 
        !,
 
        exceptionDump::dumpToStdOutput(TraceId),
 
        fail.
 
    handleFileReadError(_TraceId):-
 
        fail.</vip>
 
  
In the code we try to find <vp>continuedFromDll</vp> exception and we retrieve the continued exception by the predicate <vp>tryGetContinueException</vp>. This clearly shows that if an exception was continued, then we can gain more information about the exception.
+
{{Example| In the Visual Prolog IDE the <vp>reportErrorDelegate</vp> will send the exception information to a WEB service which will insert the notification in a database. The notifications in the database will then be used to improve the IDE.
 +
}}
  
The full example presents in '''continueException\testContinuedException\testContinuedException.prj6''' project. It is necessary to build '''continueException\continueException.prj6''' before running testContunedException executable.
+
[[Category:Tutorials]]

Revision as of 11:57, 6 November 2008

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 try-catch language construction:

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

The code works like this: writeInfo is called, if it succeeds the try-catch 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 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 and end try) is evaluated
  3. The result of the handler code will be the result of the try-catch construction, and thus of saveInfo.

So for the "write-protected" exception the handler code that should write the 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 variable that is bound in the try-catch 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 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)

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

domains
    exception = (classInfo ClassInfo [out], string PredicateName [out], string Description [out]).

I.e. an exception is a predicate that returns three pieces of information:

ClassInfo
The classInfo of the class that defines the exception
PredicateName
The name of the exception predicate itself
Description
A description of the exception
Example The cannotCreate exception from the example above is declared in the class fileSystem_api like this:
class fileSystem_api
…
predicates
    cannotCreate : exception.
…

The implementation looks like this:

impement	 fileSystem_api
…
clauses
    cannotCreate(classInfo, predicate_name(), "Cannot create or open the specified file").
…

classInfo is the classInfo of the fileSystem_api class ; predicate_name is a built-in predicate that returns the name of the predicate in which it is called (i.e. in this case it will return "cannotCreate"). The last argument is the 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. I most cases programmers will raise the exception internal_error.

Raising an exception

Exceptions are raised using the predicate exception::raise:

class exception
…
predicates
    raise : (classInfo ClassInfo, exception Exception) erroneous. 
    raise : (classInfo ClassInfo, exception Exception, namedValue_list ExtraInfo) erroneous.

ClassInfo

The classInfo of the class that raise the excption

Exception

The exception to raise

ExtraInfo

Extra information about the raised exception.
Example The cannotCreate exception from above is raised by the predicate fileSystem_api::raise_cannotCreate. The code looks like this:
clauses
    raise_cannotCreate(ClassInfo, FileName, LastError) :-
        Desc = common_exception::getLastErrorDescription(LastError),
        exception::raise(
            ClassInfo,
            cannotCreate,
            [namedValue(fileSystem_exception::fileName_parameter, string(FileName)),
             namedValue(common_exception::errorCode_parameter, unsigned(LastError)),
             namedValue(common_exception::errorDescription_parameter, string(Desc))]).

raise_cannotCreate is called with the ClassInfo of the raising class the FileName and the windows error code LastError that the relevant low level Windows API predicate caused.

common_exception::getLastErrorDescription obtains the description corresponding to LastError from Windows.

exception::raise is then used to raise the cannotCreate exception with the three pieces of extra information. fileSystem_exception::fileName_parameter, common_exception::errorCode_parameter and common_exception::errorDescription_parameter are names (strings) used for the extra information.

It is very common to create helper predicates like fileSystem_api::raise_cannotCreate to do the actual raising rather that calling exception::raise 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 ones defined in common_exception.

Catching and handling exceptions

Exceptions are caught with the try-catch 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

You will call exception::tryGetDescriptor to determine if a certain kind of exception is caught:

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

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

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

The Descriptor contains the extra information about that particular entry in the exception trace:

domains
    descriptor = descriptor(classInfoDescriptor ClassInfo, exceptionDescriptor ExceptionInfo, kind Kind,
        namedValue_list ExtraInfo, gmtTimeValue GMTTime, string ExceptionDescription, unsigned ThreadId).
ClassInfo
The classInfo of the class that raised the exception (in functor form)
ExceptionInfo
The exception in question (in functor form)
Kind
The entry kind (raise or continue)
ExtraInfo
The extra information provided with this entry in the trace
GMTTime
The time this exception trace entry was raised/continued
ExceptionDescription
The exception description of this entry (this information is also present in ExceptionInfo)
ThreadId
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 kind has been determined to be the one we expected
  • The ExtraInfo for this entry is available for use in messages, etc

The predicate exception::tryGetExtraInfo is convenient for obtaining extra information:

predicates
    tryGetExtraInfo : (descriptor Descriptor, string Name) -> value Value determ.
Descriptor
Is the description whose extra info we want to get something from.
Name
The name of the extra info parameter we want to obtain.
Value
The value that the mentioned parameter has.
Example Code for catching and handling the fileSystem_api::cannotCreate exception can look like this:
clauses
    saveInfo(Filename) :-
        try
            writeInfo(Filename)
         catch TraceId do
              if D = exception::tryGetDescriptor(TraceId, fileSystem_api::cannotCreate) then
                stdio::write("Cannot write to the file: ", Filename),
                if string(Description) = exception::tryGetExtraInfo(D, common_exception::errorDescription_parameter) then
                    stdio::writef("; %", Description)
                end if,
                stdio::write("\n")
            else
                common_exception::continue_unknown(TraceId, classInfo, predicate_name(), "Filename = ", Filename)
            end if
         end try.

The code here don't need to obtain the fileSystem_exception::fileName_parameter, because the file name is already known in the Filename variable.

But we can obtain Window's description of the problem querying for common_exception::errorDescription_parameter

We continue the exception as unknown if it is not the cannotCreate kind . For the continue call we add the filename as. common_exception::continue_unknown will create a string of the extra arguments and add it as an common_exception::errorArguments_parameter. Such information is mainly for use in the default exception handler to be discussed below.

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 traceId only have a meaning in the process that has created it, but the predicate exception::getTraceInfo can create a traceInfo structure (TraceInfo) from the traceId TraceId:

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

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

Exception dumps

The class exceptionDump contains predicates for dumping exception traces to stdio, some other outputStream or a string.

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

exceptionDump have predicates both for dumping exception traces both from a traceId and from a traceInfo obtained with exception::getTraceInfo.

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.

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

The handler in mainExe::run (and mainExe::runCom) dumps the entire exception trace to output stream in stdio. 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 mainExe::run a GUI program also set a default handler in the event loop. This handler is set by calling applicationWindow::setErrorResponder:

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

By default the error responder errorPresentationDialog::present 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 exceptions
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.

Example A predicate takes two lists as argument and pair the elements of the lists. So the lists are expected to have the same length. If the lists have different lengths it is appropriate to raise an internal_error, because it is clearly the responsibility of the programmer to ensure that the lists have same length; the end-user of the program can at most influence this indirectly.

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.

Example The read-only file problem discussed above is a typical example of a user exception. It is the user rather than the programmer that can solve the problem with the read-only file.

A user exception is distinguished by having extra information in a field with name common_exception::userMessage_parameter. 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 common_exception::raise_user. Likewise you can continue exceptions with a user message using common_exception::continue_user.

Example This predicate will raise a user fileSystem_api::cannotCreate exception
class predicates
    raise_cannotCreate : (string Filename) erroneous.
clauses
    raise_cannotCreate(Filename) :-
        common_exception::raise_user(classInfo, fileSystem_api::cannotCreate, predicate_name(),
            "It is not possible to create the file %Filename",
            [namedValue("Filename", string(Filename))]).

The use of "%Filename" in the message corresponds to the extra info "Filename".

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

Example Given raise_cannotCreate from above and calling test:
clauses
    test() :-
        try
            raise_cannotCreate("test.xxx")
         catch TraceId do
            if UserMsg = exceptionDump::tryGetUserMessage(TraceId) then
                stdio::write(UserMsg)
            end if
         end try.

The following message is written to stdio:

It is not possible to create the file test.xxx

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.

Example A program can write out some information, but only if the user has specified which information to write. So if the user invoke the functionality without having specified which information to write, it is appropriate to raise a definite user error.

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

  • common_exception::raise_definiteUser
  • common_exception::continue_definiteUser

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

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

The error presentation dialog

The error presentation dialog (in the class errorPresentationDialog) 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.

ErrorPresentationDialogDefault.png

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 extraInfoWriter 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 extraInfoWriter 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.

Example The Visual Prolog IDE uses the extraInfoWriter to write version information, information about the open project and information about the last actions carried out in the IDE. This information will help the programmers to analyze the problem.

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

domains
    reportErrorDelegate = (window Parent, string Comment, optional{exception::traceInfo} TraceInfo, string ExtraInfo).
Parent
The error presentation dialog which can be used as parent for additional dialogs.
Comment
The users comment from the comment box.
TraceInfo
If the dialog is invoked on an exception the trace info is here, if it is invoked for feedback this value is none
ExtraInfo
The extra info written by the extraInfoWriter callback
Example In the Visual Prolog IDE the reportErrorDelegate will send the exception information to a WEB service which will insert the notification in a database. The notifications in the database will then be used to improve the IDE.