Exceptions: Difference between revisions

From m204wiki
Jump to navigation Jump to search
 
(27 intermediate revisions by 4 users not shown)
Line 1: Line 1:
Exceptions are a technique for handling unusual occurrences in the execution of a method call. This page discusses <var class="product">Janus SOAP</var> exception handling.
Exceptions are a technique for handling unusual occurrences in the execution of a method call. This page discusses <var class="product">SOUL</var> exception handling.
 
==Background==
==Background==
A wide variety of errors can occur inside a program.
A wide variety of errors can occur inside a program.
Many of these, such as syntax errors, or invalid variable or member
Many of these, such as syntax errors, or invalid variable or member
names, can be caught at compile-time, so they produce compile-time
names, can be caught at compile-time, so they produce compile-time errors.
errors.
Other errors can only be caught at run-time, so they produce run-time errors.
Other errors can only be caught at run-time, so they produce run-time
errors.
   
   
For example, a reference to a member of a null object variable will
For example, a reference to a member of a null object variable will
Line 20: Line 18:
program at the earliest point that the error was detected so that
program at the earliest point that the error was detected so that
the program does not do damage because of the error.
the program does not do damage because of the error.
In addition, if <var class="product">SirFact</var> is being used, the request cancelling error
In addition, if <var class="product">[[SirFact]]</var> is being used, the request cancelling error
will produce a <var class="product">SirFact</var> dump at the time the error was first detected
will produce a <var class="product">SirFact</var> dump at the time the error was first detected
(or at the very least, indicate the procedure and line number where the
(or at the very least, indicate the procedure and line number where the error was first detected).
error was first detected).
Such early detection of error greatly simplifies problem diagnosis.
Such early detection of error greatly simplifies problem diagnosis.
   
   
Still other errors are actually quite common and might not even be
Still other errors are actually quite common and might not even be considered errors in most contexts in which they occur.
considered errors in most contexts in which they occur.
For example, the <var>Stringlist</var> class <var>[[Locate (Stringlist function)|Locate]]</var> method might not locate any
For example, the <var>Stringlist</var> class <var>Locate</var> method might not locate any
items that match the search criterion.
items that match the search criterion.
Since this is likely not to be a true error, the <var>Locate</var> method simply
Since this is likely not to be a true error, the <var>Locate</var> method simply
Line 45: Line 41:
   
   
For example, a method that returns an object instance might return a
For example, a method that returns an object instance might return a
null under certain error conditions that are not necessarily indicative
<var>Null</var> object under certain error conditions that are not necessarily indicative
of a programming error.
of a programming error.
This allows the calling program to check for a null and perform
This allows the calling program to check for a <var>Null</var> and perform
appropriate processing if a null is returned.
appropriate processing if a <var>Null</var> is returned.
If the program doesn't expect this error case, it might not check for
If the program doesn't expect this error case, it might not check for a <var>Null</var>.
a null.
If the program is correct, and the error case cannot happen in the
If the program is correct, and the error case cannot happen in the
given context, the program behaves correctly with no unnecessary
given context, the program behaves correctly with no unnecessary error checking.
error checking.
If, however, it is incorrect, and a <var>Null</var> can be returned, it is likely
If, however, it is incorrect, and a null can be returned, it is likely
to get a <var>Null</var>-object request-cancelling error when it attempts to
to get a null-object request-cancelling error when it attempts to
use the <var>Null</var> as an object reference.
use the null as an object reference.
   
   
This is the best of all worlds &mdash; no error checking is required
This is the best of all worlds &mdash; no error checking is required
Line 69: Line 63:
For example, an error happens inside a subroutine or inside the <var>Set</var>
For example, an error happens inside a subroutine or inside the <var>Set</var>
method of a property.
method of a property.
<li>The return value has no invalid values available to be used to
indicate an unusual situation.
<li>The return value has no invalid values available to be used to indicate an unusual situation.
For example, if a function returns a string, and a null value is sometimes
For example, if a function returns a string, and a null value is sometimes
a valid output, there might be no reasonable string value that can be used
a valid output, there might be no reasonable string value that can be used
to indicate the error.
to indicate the error.
<li>Even if there is a reasonable value (such as a null string, null object
<li>Even if there is a reasonable value (such as a null string, <var>Null</var> object
reference, or zero or negative numeric value), such a value might not provide
reference, or zero or negative numeric value), such a value might not provide
enough information about the nature of the error for the calling program to
enough information about the nature of the error for the calling program to deal with the error.
deal with the error.
Obviously, a single string or numeric value, or a <var>Null</var> object reference, cannot
Obviously, a single string or numeric value, or a null object reference, cannot
provide a lot of information.
provide a lot of information.
This is especially important if a method might encounter more than one unusual
This is especially important if a method might encounter more than one unusual
Line 87: Line 81:
For example, a method places an order for a product, and given a product number,
For example, a method places an order for a product, and given a product number,
it wants to indicate an error if the product number is invalid.
it wants to indicate an error if the product number is invalid.
The number might be invalid because
The number might be invalid because the product number doesn't exist or because the product is out of stock.
the product number doesn't exist or because the product is out of stock.
Obviously, these are two very different errors, the former possibly being
Obviously, these are two very different errors, the former possibly being
indicative of a programming error, the latter less likely to be.
indicative of a programming error, the latter less likely to be.
Line 102: Line 95:
or might be set, might only think to look for assignment statements with
or might be set, might only think to look for assignment statements with
the variable on the left side of the assignment.
the variable on the left side of the assignment.
Certainly, life would be easier if that's all one needed to look for.
Certainly, life would be easier if that is all one needed to look for.
<li>Just because a variable is used for the output parameter, there is no
<li>Just because a variable is used for the output parameter, there is no
guarantee that the program actually examines the output parameter value.
guarantee that the program actually examines the output parameter value.
Line 108: Line 102:
certain error situation can't occur, the code might not bother to check the variable
certain error situation can't occur, the code might not bother to check the variable
used as an output parameter and continue on, oblivious to an error.
used as an output parameter and continue on, oblivious to an error.
<li>Output parameters cannot (currently) be optional.
<li>Output parameters cannot (currently) be optional.
This means that a variable has to be specified for the output parameter,
This means that a variable has to be specified for the output parameter,
Line 115: Line 110:
</ul>
</ul>
   
   
<var class="product">Sirius Mods</var> 7.2 introduces an alternative to output parameters to handle unusual
An alternative to output parameters to handle unusual
cases in method calls.
cases in method calls is called '''exception handling'''.
This alternative is called '''exception handling'''.
 
==Errors unsuitable for exception handling==
==Errors unsuitable for exception handling==
Exception handling consists of three parts:
Exception handling consists of three parts:
<ol>
<ol>
<li>The ability to define classes of exceptions and the information available
<li>The ability to define classes of exceptions and the information available for these classes.
for these classes.
Unsurprisingly, classes of exceptions are defined in almost the same way as other classes.
Unsurprisingly, classes of exceptions are defined in almost the same way as other
classes.
<li>The ability to indicate an unusual or exception situation.
<li>The ability to indicate an unusual or exception situation.
This is referred to as ''throwing'' an exception.
This is referred to as ''throwing'' an exception.
<li>The ability to detect a thrown exception and to perform special processing
<li>The ability to detect a thrown exception and to perform special processing
for the exception.
for the exception. This is known as ''catching'' an exception.
This is known as ''catching'' an exception.
</ol>
</ol>
   
   
The <var class="product">[[Janus SOAP User Language Interface|Janus SOAP ULI]]</var>
Exception handling in <var class="product">SOUL</var>
exception handling support is very similar to many other
is very similar to many other
object-oriented programming languages, such as Java or VB.Net.
object-oriented programming languages, such as Java or VB.Net.
However, each language's implementation of exception handling is unique, each
However, each language's implementation of exception handling is unique, each
with subtle or not so subtle differences from others.
with subtle or not so subtle differences from others.
<var class="product">Janus SOAP ULI</var> is no exception (no pun intended), and significant departures from the way
<var class="product">SOUL</var> is no exception (no pun intended), and significant departures from the way
other languages support exceptions is indicated where applicable.
other languages support exceptions is indicated where applicable.
   
   
A philosophical departure from the support of most other
A philosophical departure from the support of most other languages for exceptions is worth noting:
languages for exceptions is worth noting:
while many object-oriented languages make all (or almost all) errors exceptions,
while many object-oriented languages make all (or almost all) errors exceptions,
<var class="product">Janus SOAP ULI</var> does not.
<var class="product">SOUL</var> does not.
This is partially because many object-oriented languages are not, strictly speaking,
This is partially because many object-oriented languages are not, strictly speaking,
application languages &mdash; in addition
application languages &mdash; in addition
Line 152: Line 145:
facilitate this by making all errors catchable.
facilitate this by making all errors catchable.
   
   
<var class="product">User Language</var> is an application development language.
<var class="product">SOUL</var> is an application development language.
While it is theoretically possible to build a program environment in <var class="product">User Language</var>,
While it is theoretically possible to build a program environment in <var class="product">SOUL</var>,
or even an operating system, this is not really the focus of <var class="product">User Language</var>, and it is
or even an operating system, this is not really the focus of <var class="product">SOUL</var>, and it is
not likely anyone would ever do this.
not likely anyone would ever do this.
The <var class="product">User Language</var> programming environment (<var class="product">Model 204</var>, which is not, itself, written in <var class="product">User Language</var>)
The <var class="product">SOUL</var> programming environment (<var class="product">Model 204</var>, which is not itself written in <var class="product">SOUL</var>)
is responsible both for the ultimate catching of errors and the providing of
is responsible both for the ultimate catching of errors and the providing of information about the nature of the errors so that they can be corrected.
information about the nature of the errors so that they can be corrected.
   
   
The kinds of errors that <var class="product">Janus SOAP ULI</var> does ''not'' turn into exceptions,
The kinds of errors that <var class="product">SOUL</var> does ''not'' turn into exceptions,
and thus are not trappable, fall into two broad categories:
and thus are not trappable, fall into two broad categories:
<ol>
<ol>
<li>Pernicious environmental errors from which it would generally be
<li>Pernicious environmental errors from which it would generally be
almost impossible to recover gracefully on an application level.
almost impossible to recover gracefully on an application level.
<p>
For example, if <var>CCATEMP</var> fills up, it is exceedingly unlikely that a
For example, if <var>CCATEMP</var> fills up, it is exceedingly unlikely that a
request could recover gracefully.
request could recover gracefully.
Line 174: Line 167:
<var>CCATEMP</var> could fill.
<var>CCATEMP</var> could fill.
And even if it could anticipate the places, it would be very difficult to avoid
And even if it could anticipate the places, it would be very difficult to avoid
doing anything that might also require <var>CCATEMP</var>.
doing anything that might also require <var>CCATEMP</var>.</p>
<p>
Similarly, a <var>PDL</var> (push-down-list) overflow can be detected at almost any
Similarly, a <var>PDL</var> (push-down-list) overflow can be detected at almost any
method call, not to mention dozens of internal <var>PDL</var>-intensive routines
method call, not to mention dozens of internal <var>PDL</var>-intensive routines (such as, say, journal output).
(such as, say, journal output).
As such, it would be almost impossible for an application to anticipate
As such, it would be almost impossible for an application to anticipate
all the places where a <var>PDL</var> overflow might be detected.
all the places where a <var>PDL</var> overflow might be detected.
And even if it could anticipate the places, any method calls to handle the situation
And even if it could anticipate the places, any method calls to handle the situation
would likely also encounter a <var>PDL</var> overflow.
would likely also encounter a <var>PDL</var> overflow.</p>
<li>Errors that are indicative of logic errors in a program.
<li>Errors that are indicative of logic errors in a program.
<p>
For example, a class member reference via a null object variable is usually
For example, a class member reference via a <var>Null</var> object variable is usually indicative of a programming error.
indicative of a programming error.
Similarly, a reference to an invalid collection item number is also generally
Similarly, a reference to an invalid collection item number is also generally
indicative of a programming error.
indicative of a programming error.</p>
<p>
While there might be error-causing cases of "sloppy" references to
While there might be error-causing cases of "sloppy" references to
object variables or collection items that you would want to simply catch
object variables or collection items that you would want to simply catch
when they happen:
when they happen:</p>
<ul>
<ul>
<li>Most of these are handled well by existing facilities such as Auto New
<li>Most of these are handled well by existing facilities such as <var>[[Classes and Objects#Allow Auto and constructors|Auto New]]</var>
and also UseDefault processing for collections.
and also <var>[[UseDefault (GenericNamedArraylist property)|UseDefault]]</var> processing for collections.
<li>In the odd cases where such sloppy references are useful, it is trivial
<li>In the odd cases where such sloppy references are useful, it is trivial
and efficient to check for the sloppy references (null object variable,
and efficient to check for the sloppy references (<var>Null</var> object variable,
invalid collection item number), so an exception-handling paradigm
invalid collection item number), so an exception-handling paradigm adds little value.
adds little value.
<p>
Of course, there are always border-line cases where an error that is nearly
Of course, there are always border-line cases where an error that is nearly
always indicative of a programming error, might not be in certain
always indicative of a programming error, might not be in certain
Line 208: Line 200:
likely that many request cancelling errors in system methods will gradually
likely that many request cancelling errors in system methods will gradually
be converted into catchable exceptions over time.
be converted into catchable exceptions over time.
This is also quite likely to be the case for many errors in <var class="product">User Language</var> methods.
This is also quite likely to be the case for many errors in user-written methods.</p>
</ul>
</ul>
</ol>
</ol>
   
   
Note that <var class="product">Model 204</var> '''does''' provide <var class="product">User Language</var> facilities to catch even
Note that <var class="product">Model 204</var> '''does''' provide <var class="product">SOUL</var> facilities to catch even
these types of untrappable errors.
these types of untrappable errors.
These facilities include <var>On</var> units, especially <var>On Error</var> units, and <var>APSY</var>
These facilities include <var>On</var> units, especially <var>On Error</var> units, and <var>APSY</var>
error procedures.
error procedures.
These facilities make it possible for <var class="product">User Language</var> programs to, at least,
These facilities make it possible for <var class="product">SOUL</var> programs to, at least,
provide nicer notification of an error to a client (web browser, 3270
provide nicer notification of an error to a client (web browser, 3270
user, SOAP client) than a broken connection or a <var class="product">Model 204</var> error message.
user, SOAP client) than a broken connection or a <var class="product">Model 204</var> error message.
Line 227: Line 219:
constraints that caused the problem in the first place.
constraints that caused the problem in the first place.
This problem is not unique to <var class="product">Model 204</var> &mdash; severe environmental
This problem is not unique to <var class="product">Model 204</var> &mdash; severe environmental
constraints can wreak havoc with the best laid error recovery plans
constraints can wreak havoc with the best laid error recovery plans in any environment.
in any environment.
==Exception class definitions==
==Exception class definitions==
Most error conditions have information associated with them.
Most error conditions have information associated with them.
This information can be useful for correcting or recovering from
This information can be useful for correcting or recovering from the problem.
the problem.
For example, if an invalid character is detected in a stream of
For example, if an invalid character is detected in a stream of
data, it might be useful to know the character offset in the stream,
data, it might be useful to know the character offset in the stream,
the actual invalid character, and some indication of why the character
the actual invalid character, and some indication of why the character is invalid.
is invalid.
It might also be useful to have a text description of the nature of the error.
It might also be useful to have a text description of the nature of the
error.
   
   
The collection of data describing an error situation might be used
The collection of data describing an error situation might be used
immediately after the error is detected, or it might be examined
immediately after the error is detected, or it might be examined elsewhere.
elsewhere.
As such, this error data should be able to persist indefinitely.
As such, this error data should be able to persist indefinitely.
Given this requirement, it is clear that the logical place to
Given this requirement, it is clear that the logical place to
Line 249: Line 237:
would, of course, be a class.
would, of course, be a class.
   
   
Classes that describe trappable error situations are
Classes that describe trappable error situations are called '''exception classes'''.
called '''exception classes'''.
To a large degree, exception classes are no different from any other class:
To a large degree, exception classes are no different from any other
class:
<ul>
<ul>
<li>They can have <var>Public</var>, <var>Private</var>, and <var>Shared</var> blocks; they
<li>They can have <var>Public</var>, <var>Private</var>, and <var>Shared</var> blocks; they
can have variables, methods, and the same <var>Allow</var> and <var>Disallow</var> rules
can have variables, methods, and the same <var>Allow</var> and <var>Disallow</var> rules
as any other class.
as any other class.
<li>There can be system exception classes,
<li>There can be [[List of system exception classes|system exception classes]],
which are maintained by the <var class="product">Janus SOAP ULI</var> environment, and there can be <var class="product">User Language</var>
which are provided as part of the <var class="product">Model 204</var> installation, and there can be
exception classes, which are defined and maintained by <var class="product">User Language</var> code.
exception classes which are defined and maintained by <var class="product">SOUL</var> code.
<li>Exception classes can be used wherever non-exception classes are used,
<li>Exception classes can be used wherever non-exception classes are used,
although the reverse is not true.
although the reverse is not true.
Line 268: Line 255:
</ul>
</ul>
   
   
<var class="product">User Language</var> exception classes are denoted by using the <var>Exception</var>
User-defined exception classes are denoted by using the <var>Exception</var>
keyword in the class header:
keyword in the class header:
<p class="code">class tackyColor exception
<p class="code">class tackyColor exception
Line 280: Line 267:
an exception class.
an exception class.
   
   
If you want, you can put the Exception keyword on almost all class declarations;
If you want, you can put the <var>Exception</var> keyword on almost all class declarations;
specifying it removes no capabilities from the class.
specifying it removes no capabilities from the class.
Doing so, however, is misleading, since most classes are likely never to be used
Doing so, however, is misleading, since most classes are likely never to be used
to indicate error situations.
to indicate error situations.
In this regard, you can view the Exception keyword on a class declaration as
In this regard, you can view the <var>Exception</var> keyword on a class declaration as
documentation that a class can be used as an exception class.
documentation that a class can be used as an exception class.
While the compiler ''cannot'' ensure that a class with an Exception
While the compiler ''cannot'' ensure that a class with an <var>Exception</var>
declaration will be used as an exception, it ''does'' ensure that a
declaration will be used as an exception, it ''does'' ensure that a
class not declared as Exception is not used in exception contexts.
class not declared as <var>Exception</var> is not used in exception contexts.
   
   
The one case where the Exception keyword is not allowed on class declarations
The one case where the <var>Exception</var> keyword is not allowed on class declarations
is in declarations that extend non-exception classes &mdash; exception classes
is in declarations that extend non-exception classes &mdash; exception classes
can only extend other exception classes.
can only extend other exception classes.
Line 296: Line 283:
Some examples of system exception classes are:
Some examples of system exception classes are:
<dl>
<dl>
<dt<var>[[InvalidHexData class|InvalidHexData]]</var>
<dt><var>[[InvalidHexData class|InvalidHexData]]</var>
<dd>Describes an error in converting hexadecimal data to some other datatype.
<dd>Describes an error in converting hexadecimal data to some other datatype.
<dt<var>[[InvalidRegex class|InvalidRegex]]</var>
<dt><var>[[InvalidRegex class|InvalidRegex]]</var>
<dd>Describes an error processing a regular expression.
<dd>Describes an error processing a regular expression.
<dt<var>[[DaemonLost class|DaemonLost]]</var>
<dt><var>[[DaemonLost class|DaemonLost]]</var>
<dd>Describes a situation where the thread doing processing for a <var>Daemon</var> object
<dd>Describes a situation where the thread doing processing for a <var>Daemon</var> object was lost (logged off).
was lost (logged off).
</dl>
</dl>
   
   
Line 315: Line 301:
For links to the descriptions of the individual system exception classes, see the
For links to the descriptions of the individual system exception classes, see the
[[Lists of classes and methods#except|"Lists of classes and methods"]].
[[Lists of classes and methods#except|"Lists of classes and methods"]].
==Throwing exceptions==
==Throwing exceptions==
When an error situation occurs, the code that detects the situation can do
When an error situation occurs, the code that detects the situation can do
Line 320: Line 307:
<ul>
<ul>
<li>It can cancel the request.
<li>It can cancel the request.
In <var class="product">User Language</var> this is done with an <var>Assert</var> statement.
In <var class="product">SOUL</var> this is done with an <var>Assert</var> statement.
In system code, this is done by some equivalent of the <var>Assert</var> statement.
In system code, this is done by some equivalent of the <var>Assert</var> statement.
This is the correct response to an error if the error is clearly indicative
This is the correct response to an error if the error is clearly indicative
of a programming error or a severe environmental constraint that is likely
of a programming error or a severe environmental constraint that is likely
to make request continuation unproductive.
to make request continuation unproductive.
<li>It can ''throw'' an exception.
<li>It can ''throw'' an exception.
In <var class="product">User Language</var> this is done with a <var>Throw</var> statement.
In <var class="product">SOUL</var> this is done with a <var>Throw</var> statement.
In system code, this is done by some equivalent of the <var>Throw</var> statement.
In system code, this is done by some equivalent of the <var>Throw</var> statement.
This is the correct response to an error if the error might occur in the
This is the correct response to an error if the error might occur in the
normal course of processing and the code that called the method might be
normal course of processing and the code that called the method might be
able to recover from the error.
able to recover from the error.
<br>'''Note:'''
<p class="note">'''Note:'''
In the <var class="product">Janus SOAP ULI</var> implementation of exceptions, exceptions can only
In the <var class="product">SOUL</var> implementation of exceptions, exceptions can only
be thrown by methods.
be thrown by methods. </p>
</ul>
</ul>
   
   
In both system and <var class="product">User Language</var> methods, a thrown exception can only be handled
In both system and user-written methods, a thrown exception can only be handled
(caught) by the code that called the method.
(caught) by the code that called the method.
This is different from many other languages' implementations of exceptions
This is different from many other languages' implementations of exceptions
Line 345: Line 333:
This is because a method's exceptions are part of the method's interface to
This is because a method's exceptions are part of the method's interface to
its callers, as much as any input and output parameters, as much as output values.
its callers, as much as any input and output parameters, as much as output values.
===Specifying a Throws clause===
===Specifying a Throws clause===
The exceptions thrown by a method are indicated by the <var>Throws</var>
The exceptions thrown by a method are indicated by the <var>Throws</var>
clause in the method declaration and definition header.
clause in the method declaration and definition header.
For example, if the method Paint might throw a TackyColor and InvalidHexData
For example, if the method <code>Paint</code> might throw a <code>TackyColor</code> and <code>InvalidHexData</code>
exception, it should be declared and defined as:
exception, it should be declared and defined as:
<p class="code"> subroutine paint(%hexColor is string len 6) -
<p class="code">subroutine paint(%hexColor is string len 6) -
            throws tackyColor and invalidHexData
          throws tackyColor and invalidHexData
</p>
</p>
   
   
Line 358: Line 347:
at a time, and in most cases, it will not throw an exception at all.
at a time, and in most cases, it will not throw an exception at all.
   
   
The list of exceptions after a <var>Throws</var> keyword is the list of exception
The list of exceptions after a <var>Throws</var> keyword is the list of exception class names.
class names.
These class names could be user-defined exception class names, or they could be system
These class names could be <var class="product">User Language</var> exception class names, or they could be system
exception class names.
exception class names.
If the class is a system class name, it could be fully qualified with the
If the class is a system class name, it could be fully qualified with the <code>System:</code> namespace indicator.
<code>System:</code> namespace indicator.
For instance, the previous example could be written as
For instance, the previous example could be written as
<p class="code">subroutine paint(%hexColor is string len 6) -
<p class="code">subroutine paint(%hexColor is string len 6) -
           throws tackyColor and -
           throws tackyColor and system:invalidHexData
          system:invalidHexData
</p>
</p>
   
   
This would only be necessary if there was a <var class="product">User Language</var> class name with the same
This would only be necessary if there was a user-defined class name with the same
name as the system class name.
name as the system class name.
Of course, it's best to avoid such a situation as much as possible.
Of course, it is best to avoid such a situation as much as possible.
   
   
As with most method descriptions, the <var>Throws</var> clause on a method declaration
As with most method descriptions, the <var>Throws</var> clause on a method declaration
Line 400: Line 386:
Methods that implement an overridable method cannot throw any
Methods that implement an overridable method cannot throw any
exceptions not thrown by the implemented method; however, they do not
exceptions not thrown by the implemented method; however, they do not
necessarily have to throw all the exceptions thrown by the implemented
necessarily have to throw all the exceptions thrown by the implemented method.
method.
That is, the exceptions in a <var>Throws</var> list on an <var>Implements</var> method
That is, the exceptions in a <var>Throws</var> list on an <var>Implements</var> method
must be a subset of the <var>Throws</var> list on the corresponding
must be a subset of the <var>Throws</var> list on the corresponding
Line 411: Line 396:
</p>
</p>
   
   
then it is valid for an implementing method to indicate
then it is valid for an implementing method to indicate:
<p class="code">subroutine buy(%productCode is string len 8) -
<p class="code">subroutine buy(%productCode is string len 8) -
               implements buy in products -
               implements buy in products -
Line 417: Line 402:
</p>
</p>
   
   
or even
or even:
<p class="code">subroutine buy(%productCode is string len 8) -
<p class="code">subroutine buy(%productCode is string len 8) -
               implements buy in products
               implements buy in products
Line 428: Line 413:
It would be surprising, indeed, if the method threw some different
It would be surprising, indeed, if the method threw some different
exception, simply because it had been overridden.
exception, simply because it had been overridden.
 
===Using the Throw statement===
===Using the Throw statement===
Once a method declaration indicates the exceptions it might throw,
Once a method declaration indicates the exceptions it might throw,
Line 438: Line 423:
</p>
</p>
   
   
and there is a variable declaration in the method
and there is a variable declaration in the method:
<p class="code">%tacky    is object tackyColor
<p class="code">%tacky    is object tackyColor
</p>
</p>
   
   
the method could do
The method could do this:
<p class="code">throw %tacky
<p class="code">throw %tacky
</p>
</p>
Line 471: Line 456:
   
   
But, even this is not quite right.
But, even this is not quite right.
Usually, the exception objects will contain information to aid in problem
Usually, the exception objects will contain information to aid in problem determination and recovery:
determination and recovery:
<p class="code">if %color eq %yuckyGreen then
<p class="code">if %color eq %yuckyGreen then
   %tacky = new
   %tacky = new
Line 501: Line 485:
</p>
</p>
   
   
Of course, for code readability, named parameters are preferable on
Of course, for code readability, named parameters are preferable on such constructors:
such constructors:
<p class="code">if %color eq %yuckyGreen then
<p class="code">if %color eq %yuckyGreen then
   throw %(tackyColor):new(reason='Yucky', alternative='00FF00')
   throw %(tackyColor):new(reason='Yucky', alternative='00FF00')
Line 514: Line 497:
of the classes listed in the method declaration's <var>Throws</var> clause results
of the classes listed in the method declaration's <var>Throws</var> clause results
in a compilation error.
in a compilation error.
===Exception classes extending other exception classes===
===Exception classes extending other exception classes===
Exception classes can extend other exception classes.
Exception classes can extend other exception classes.
Line 523: Line 507:
match multiple classes in a method's <var>Throws</var> list.
match multiple classes in a method's <var>Throws</var> list.
   
   
The thrown (and so catchable) class is the first class in the <var>Throws</var>
The thrown (and therefore catchable) class is the first class in the <var>Throws</var>
list that matches the object in the <var>Throw</var>.
list that matches the object in the <var>Throw</var>.
That is, the first class in the <var>Throws</var> list that exactly matches the thrown
That is, the first class in the <var>Throws</var> list that exactly matches the thrown
object's class or that is a base class of the thrown object is used as the thrown
object's class or that is a base class of the thrown object is used as the thrown class for the method caller.
class for the method caller.
Because of this, a <var>Throws</var> list must always specify extension classes before
Because of this, a <var>Throws</var> list must always specify extension classes before
base classes.
base classes.
Otherwise, the base class in the <var>Throws</var> list would always match any object
Otherwise, the base class in the <var>Throws</var> list would always match any object
that would match an extension class, so the extension class would never be
that would match an extension class, so the extension class would never be used as the thrown class.
used as the thrown class.
   
   
For example, if exception class <code>ReallyNastyColor</code> extended exception class
For example, if exception class <code>ReallyNastyColor</code> extended exception class
Line 543: Line 525:
</p>
</p>
   
   
The new <code>ReallyNastyColor</code> exception object would be thrown as a
The new <code>ReallyNastyColor</code> exception object would be thrown as a <code>TackyColor</code> object.
<code>TackyColor</code> object.
That is, the callers of <code>Paint</code> would not be able to assign the thrown
That is, the callers of <code>Paint</code> would not be able to assign the thrown
exception to a <code>ReallyNastyColor</code> object without the use of a narrowing
exception to a <code>ReallyNastyColor</code> object without the use of a narrowing assignment.
assignment.
==Try and Catch==
==Try and Catch==
Without any action on a method caller's part, a thrown exception is,
Without any action on a method caller's part, a thrown exception is,
Line 555: Line 536:
   
   
A <var>Try/Catch</var> block consists of two parts.
A <var>Try/Catch</var> block consists of two parts.
The first, the <var>Try</var> section contains one or more <var class="product">User Language</var> statements that
The first, the <var>Try</var> section, contains one or more <var class="product">SOUL</var> statements that
might result in an exception.
might result in an exception.
The <var>Try</var> section is then followed by one or more <var>Catch</var> sections, each
The <var>Try</var> section is then followed by one or more <var>Catch</var> sections, each
one of them handling (catching) a particular class of exception.
one of them handling (catching) a particular class of exception.
   
   
The following fragment illustrates the use of a <var>Try/Catch</var> block to trap
The following fragment shows the use of a <var>Try/Catch</var> block to trap
an exception caused by invalid hexadecimal data:
an exception caused by invalid hexadecimal data:
<p class="code">try
<p class="code">try
Line 588: Line 569:
</ul>
</ul>
   
   
===Try block syntax===
<p class="syntax"><span class="literal">Try</span>
      <span class="term">tStmt1</span>              ; * Exceptions can
    <span class="squareb">[</span> <span class="term">tStmt2</span> <span class="squareb">]</span>            ; * be caught in
    <span class="squareb">[</span> ...    <span class="squareb">]</span>            ; * these statements
<span class="squareb">[</span> <span class="literal">Rethrow</span> <span class="term">rethClassA</span> <span class="squareb">[</span><span class="literal">And</span> <span class="term">rethClassB</span> <span class="squareb">]</span> ... <span class="squareb">]</span>
<nowiki>...</nowiki> any number of <var>Rethrow</var> statements
<span class="squareb">[</span> <span class="literal">Catch</span> <span class="term">catClassC</span> <span class="squareb">[</span><span class="literal">To</span> <span class="term">%objC</span><span class="squareb">]</span> <span class="squareb">[</span><span class="literal">And</span> <span class="term">catClassD</span> <span class="squareb">[</span><span class="literal">To</span> <span class="term">%objD</span><span class="squareb">]</span><span class="squareb">]</span> ...
    <span class="squareb">[</span> <span class="term">cStmtW</span> <span class="squareb">]</span>            ; * These are executed if any
    <span class="squareb">[</span> <span class="term">cStmtX</span> <span class="squareb">]</span>            ; * of the execptions on the
    <span class="squareb">[</span> ...    <span class="squareb">]</span>            ; * <var>Catch</var> statement are thrown
<span class="squareb">]</span>
<nowiki>...</nowiki> any number of <var>Catch</var> blocks
<span class="squareb">[</span> <span class="literal">Success</span>
    <span class="squareb">[</span> <span class="term">sStmtY</span> <span class="squareb">]</span>            ; * These are executed
    <span class="squareb">[</span> <span class="term">sStmtZ</span> <span class="squareb">]</span>            ; * if no execptions
    <span class="squareb">[</span> ...    <span class="squareb">]</span>            ; * are thrown
<span class="squareb">]</span>
  <span class="literal">End Try</span>
</p>
====Syntax terms====
The <var>Try</var> block is divided into two or more sections, which are separated by
<var>[[#Rethrow|Rethrow]]</var>, <var>Catch</var>, and <var>[[#Success blocks|Success]]</var> statements.  Multiple
<var>Rethrow</var> and <var>Catch</var> statements can be mixed
with each other and with the <var>Success</var> statement, in any order.
There must be either one <var>Rethrow</var> or <var>Catch</var> statement in a <var>Try</var> block.
<table>
<tr><th>tStmt1 [tStmt2...]</th>
<td>The first section in the <var>Try</var> block must contain one or more <var class="product">SOUL</var> statements.  These statements are executed in turn, and if an exception is thrown within them, execution resumes at the <var>Rethrow</var> or <var>Catch</var> statement specifying that exception; if there are none, the request is cancelled.  If no exceptions are thrown, execution resumes with the <var>Success</var> block statements, if any, and then after the <var>End Try</var> statement.</td></tr>
<tr><th nowrap><var>Rethrow</var> rethClassA ...</th>
<td>The <var>Rethrow</var> <b>statement</b> (not block) specifies one or more exception classes; if one of them is thrown, then the method containing the <var>Try</var> block throws that exception. Hence a <var>Rethrow</var> statement can only occur within a method which declares all of the <var class="term">rethClass</var> exceptions in its <var>Throws</var> clause.
<p></p>
Each <var class="term">rethClass</var> may not be the same as, nor an extension of, any exception class preceding it on the statement or on preceding <var>Rethrow</var> and <var>Catch</var> statements in the <var>Try</var> block.</td></tr>
<tr><th><var>Catch</var> catClassC ...</th>
<td>The <var>Catch</var> statement specifies one or more exception classes; if one of them is thrown, then the (optional) <var class="term">cStmt</var> statements in the <var>Catch</var> block are executed in turn, and then execution resumes after the <var>End Try</var> statement.
<p></p>
Each <var class="term">catClass</var> may not be the same as, nor an extension of, any exception class preceding it on the statement or on preceding <var>Rethrow</var> and <var>Catch</var> statements in the <var>Try</var> block.</td></tr>
<tr><th><var>Success</var></th>
<td>If no exceptions are thrown in the <var class="term">tStmt</var> statements, the (optional) <var class="term">sStmt</var> statements in the <var>Success</var> block are executed in turn, and then execution resumes after the <var>End Try</var> statement.</td></tr>
</table>
===Try block considerations===
A <var>Try</var> block can contain more than one statement:
A <var>Try</var> block can contain more than one statement:
<p class="code">try
<p class="code">try
Line 601: Line 636:
</p>
</p>
   
   
While valid, this example is intended to illustrate that it might not
While valid, this example is intended to show that it might not
be a good idea to put a lot of statements inside a <var>Try</var> block:
be a good idea to put a lot of statements inside a <var>Try</var> block:
<ul>
<ul>
Line 625: Line 660:
   
   
As a general rule of thumb, place as few statements inside
As a general rule of thumb, place as few statements inside
a try block as possible.
a <var>Try</var> block as possible.
To facilitate this, you can follow the <var>Try</var> statement by a <var class="product">User Language</var>
To facilitate this, you can follow the <var>Try</var> statement by a <var class="product">SOUL</var>
statement on the same line, as in:
statement on the same line, as in:
<p class="code">try %binary = %input:hexToString
<p class="code">try %binary = %input:hexToString
Line 634: Line 669:
</p>
</p>
   
   
===Referencing a thrown exception object===
As noted in [[#Using the Throw statement|"Using the Throw statement"]], what gets thrown with an exception is
As noted in [[#Using the Throw statement|"Using the Throw statement"]], what gets thrown with an exception is
an exception object that contains information about the nature of the
an exception object that contains information about the nature of the exception.
exception.
In the <var>Try/Catch</var> examples to this point, the thrown objects were ignored &mdash;
In the <var>Try/Catch</var> examples to this point, the thrown objects were ignored &mdash;
only the class of the thrown exception was used.
only the class of the thrown exception was used.
Line 642: Line 677:
<var>To</var> clause, followed by an object variable of the exception class being caught.
<var>To</var> clause, followed by an object variable of the exception class being caught.
   
   
For example, if <code>%daemon</code> is a <var>Daemon</var> object and <code>%daemonLost</code>
For example, if <code>%daemon</code> is a <var>[[Daemon class|Daemon]]</var> object and <code>%daemonLost</code>
is an object of the <var>DaemonLost</var> exception class, the following block
is an object of the <var>[[DaemonLost class|DaemonLost]]</var> exception class, the following block
catches the exception thrown if the daemon thread was logged
catches the exception thrown if the daemon thread was logged
off for some reason, and it displays the output of the last command
off for some reason, and it displays the output of the last command up to the point where it logged off:
up to the point where it logged off:.
<p class="code">try %daemon:run('INCLUDE NASTY')
<p class="code">try %daemon:run('INCLUDE NASTY')
catch daemonLost to %daemonLost
catch daemonLost to %daemonLost
Line 654: Line 688:
</p>
</p>
   
   
Presumably, this block would be useful in diagnosing the problem or even
Presumably, this block would be useful in diagnosing the problem or even correcting it.
correcting it.
===<b id="Rethrow statement"></b>Rethrow===
====Syntax====
{{template:Rethrow statement syntax}}
====Description====
<var class="product">Sirius Mods</var> 8.1 introduced the <var>Rethrow</var> clause in a <var>Try</var> block. <var>Rethrow</var> lets you propagate an exception without having to assign it to a local variable, probably eliminating the need to declare a local exception class object for the sole purpose of propagating an exception.
The <var>Rethrow</var> clause must occur at the same level as a <var>Catch</var> clause in a <var>Try</var> block. But, unlike the <var>Catch</var> clause, no code is allowed "inside" the <var>Rethrow</var> "block." Because <var>Rethrow</var> causes control to pass immediately out of the current method, no code after <var>Rethrow</var> would ever be executed.
A <var>Rethrow</var> clause can only be followed by an <var>End Try</var>, a <var>Catch</var> clause, or a <var>Success</var> block. Like the <var>Throw</var> statement:
<ul>
<li>The <var>Rethrow</var> clause must be invoked inside a method.
<li>The class or classes specified on a <var>Rethrow</var> clause must be declared as being thrown by that method.
</ul>
====Example====
The following local subroutine illustrates a <var>Rethrow</var> of an <var>InvalidHexdata</var> exception. This subroutine sends a string converted from hex to binary on a socket.
<p class="code">local subroutine (socket):sendHex(%hexdata is longstring) throws invalidHexData
%sendData is longstring
try %sendData = %hexdata:hexToString
rethrow invalidHexdata
end try
%this:send(%sendData)
end subroutine
</p>
<!--SOUL.DME.290.QA is an example.  It differs from the above
example.  My feeling is that it would be a better doc example,
although it should be simplified & just do a single (failing)
invocation of the local function (i.e., remove the For loop).
It's a bit more complete in that it shows the caller handling
the exception (strictly speaking, the reader should understand
this, but I think it's nice to be explicit).  Also, I think the
HexToString method is nice because it's so simple and the user
could clip and play with it, without the "baggage" of Sockets.
Dave Evans, 23 March 2020.
-->
 
===Mutiple classes on a Catch or Rethrow statement===
<var class="product">Sirius Mods</var> 8.0 introduced the ability to specify multiple exception classes on a single <var>Catch</var> statement. The class names are separated from each other by the <var>And</var> keyword. However, in <var class="product">Sirius Mods</var> 8.0, no <var>To</var> clause could be specified on a <var>Catch</var> statement with multiple classes.
<var class="product">Sirius Mods</var> 8.1 added support for <var>To</var> clauses on a <var>Catch</var> statement with multiple classes as well as including support for multiple classes on the <var>[[#Rethrow|Rethrow]]</var> statememt.
The following illustrates a <var>Catch</var> statement with multiple classes:
<p class="code">try %foo:doSomethingCrazy
catch invalidHexdata to %ivhexData and tooCrazy and notCrazyEnough to %notCrazyEnough
end try
</p>
If the above were inside a method, the exceptions could be rethrown:
<p class="code">try %foo:doSomethingCrazy
rethrow invalidHexdata and tooCrazy and notCrazyEnough
end try
</p>
Of course, if a <var>Catch</var> block catches multiple class exceptions, it might be important to determine which exception was actually thrown. In that case, a null test can be performed on the target objects:
<p class="code">try %foo:doSomethingCrazy
catch invalidHexdata to %ivhexData and and tooCrazy and notCrazyEnough to %notCrazyEnough
...
if %ivhexData is not null then
  ...
elseIf %notCrazyEnough is not null then
  ...
end if
...
end try
</p>
For this to work, one must be sure that the target catch objects are null before the <var>Try</var> block. And, unless there is a lot of common code for the classes in the <var>Catch</var> block, it probably makes more sense to just have a separate <var>Catch</var> block for each exception.
There is no difference between rethrowing exceptions in a single <var>Rethrow</var> statement and multiple <var>Rethrow</var> statements, so it is largely a matter of style as to which approach is used.
===Success blocks===
===Success blocks===
In cases where a <var>Try</var> block contains multiple statements, a
In cases where a <var>Try</var> block contains multiple statements, a
Line 668: Line 773:
   
   
You code your <var>Try</var>/<var>Catch</var> block like this:
You code your <var>Try</var>/<var>Catch</var> block like this:
<p class="code">try  
<p class="code">try
   <a>...
   <a>...
   &lt;b>...
   &lt;b>...
Line 696: Line 801:
resolves the problem by making it clear that the <var>Catch</var> statements do '''not''' apply to
resolves the problem by making it clear that the <var>Catch</var> statements do '''not''' apply to
statements <code><nowiki><b></nowiki></code>, <code><c></code>, <code><d></code>, and <code><e></code>:
statements <code><nowiki><b></nowiki></code>, <code><c></code>, <code><d></code>, and <code><e></code>:
<p class="code">try  
<p class="code">try
   <a>...
   <a>...
success
success
Line 714: Line 819:
The principle benefits of the <var>Success</var> statement are:
The principle benefits of the <var>Success</var> statement are:
<ul>
<ul>
<li>It makes it clear in the code which statement is expected to produce the exceptions being caught.  
<li>It makes it clear in the code which statement is expected to produce the exceptions being caught.
<li>It prevents a catch from accidentally catching an exception from a statement that didn't really expect that exception.
<li>It prevents a catch from accidentally catching an exception from a statement that didn't really expect that exception.
</ul>
</ul>
 
You can also reverse the order of the the <var>Success</var> and catches:
You can also reverse the order of the the <var>Success</var> and catches:
 
<p class="code">try  
<p class="code">try
   <a>...
   <a>...
catch foo
catch foo
Line 735: Line 840:
end try
end try
</p>
</p>
 
===Some differences with other languages===
===Some differences with other languages===
This section describes some differences between the <var class="product">Janus SOAP ULI</var> implementation
This section describes some differences between the <var class="product">SOUL</var> implementation
of <var>Try/Catch</var> and implementations in other languages.
of <var>Try/Catch</var> and implementations in other languages.
Outside of these differences, <var>Try/Catch</var>
Outside of these differences, <var>Try/Catch</var>
support in <var class="product">Janus SOAP ULI</var> is very similar to that in other languages.
support in <var class="product">SOUL</var> is very similar to that in other languages.
<ul>
<ul>
<li>Unlike Java, it is not necessary to provide a <var>Catch</var> for all exceptions
<li>Unlike Java, it is not necessary to provide a <var>Catch</var> for all exceptions
Line 748: Line 853:
It seems unnecessary to add code to deal with (or ignore) an error that
It seems unnecessary to add code to deal with (or ignore) an error that
cannot occur in a context.
cannot occur in a context.
<li>Unlike many other languages, uncaught exceptions are not automatically
<li>Unlike many other languages, uncaught exceptions are not automatically
propagated to higher level callers.
propagated to higher level callers.
Partially, this is because the <var class="product">User Language</var> environment is not written in <var class="product">User Language</var>, so
Partially, this is because the <var class="product">SOUL</var> environment is not written in <var class="product">SOUL</var>, so
there is no need to propagate exceptions to some outer <var class="product">User Language</var> environment
there is no need to propagate exceptions to some outer <var class="product">SOUL</var> environment
which, presumably, would clean up the failing request and possibly provide
which, presumably, would clean up the failing request and possibly provide
diagnostics about the error.
diagnostics about the error.
Instead, clean-up and diagnostics are provided by the assembler environment.
Instead, clean-up and diagnostics are provided by the assembler environment.
<p>  
<p>
Automatic propagation of errors is also a grand opportunity for bugs.
Automatic propagation of errors is also a grand opportunity for bugs.
Since an automatically propagated error can happen anywhere in a method,
Since an automatically propagated error can happen anywhere in a method,
Line 762: Line 868:
Updates to data-structures, including transactions that update files, might
Updates to data-structures, including transactions that update files, might
be half-done at the time of the occurrence of an exception that gets
be half-done at the time of the occurrence of an exception that gets
automatically propagated.</p>  
automatically propagated.</p>
<p>
<p>
While languages that provide automatic exception propagation usually also
While languages that provide automatic exception propagation usually also
provide a <code>Finally</code> clause to "ensure" that the method
provide a <code>Finally</code> clause to "ensure" that the method
Line 769: Line 875:
clause that indicates whether the programmer anticipated the particular
clause that indicates whether the programmer anticipated the particular
error being propagated.
error being propagated.
And the absence of a Finally clause does not prevent error propagation, anyway.</p>  
And the absence of a Finally clause does not prevent error propagation, anyway.</p>
<p>
<p>
If you want to propagate an exception in <var class="product">User Language</var>, you can simply catch it
If you want to propagate an exception in <var class="product">SOUL</var>, you can simply catch it
and re-<var>Throw</var> it:</p>  
and re-<var>Throw</var> it:</p>
<p class="code"> catch tackyColor to %yuck
<p class="code">catch tackyColor to %yuck
* let caller deal with this
&#42; let caller deal with this
throw %yuck
throw %yuck
</p>
</p>
<p>
<p>
This makes it clear which error is being propagated and under what circumstances.</p>  
This makes it clear which error is being propagated and under what circumstances.</p>
<li>The absence of automatic exception propagation eliminates the utility of a
<li>The absence of automatic exception propagation eliminates the utility of a
Finally clause, so no Finally clause is available in <var>Try/Catch</var> blocks in <var class="product">User Language</var>.
Finally clause, so no Finally clause is available in <var>Try/Catch</var> blocks in <var class="product">SOUL</var>.
<li><var>Catches</var> cannot catch locally thrown exceptions.
<li><var>Catch</var> statements cannot catch locally thrown exceptions.
That is, a <var>Throw</var> statement always results in immediate exit from the current
That is, a <var>Throw</var> statement always results in immediate exit from the current
method, regardless of whether or not the <var>Throw</var> is inside of a <var>Try</var> block and
method, regardless of whether or not the <var>Throw</var> is inside of a <var>Try</var> block and
whether or not there are <var>Catches</var> that correspond to the thrown exception.
whether or not there are <var>Catch</var> statements that correspond to the thrown exception.
The <var class="product">Janus SOAP ULI</var> view of exceptions is that they are part of the interface between
The <var class="product">SOUL</var> view of exceptions is that they are part of the interface between
a method and its callers, so they have no place in controlling local program flow.
a method and its callers, so they have no place in controlling local program flow.
</ul>
</ul>
 
===Nesting Try/Catch blocks===
===Nesting Try/Catch blocks===
Like in other languages, <var>Try/Catch</var> blocks can be nested.
Like in other languages, <var>Try/Catch</var> blocks can be nested.
Line 811: Line 919:
</p>
</p>
   
   
The catch that applies to a particular thrown exception is the first <var>Catch</var>
The <var>Catch</var> that applies to a particular thrown exception is the first <var>Catch</var>
that either exactly matches the class of the thrown exception, or is a base
that either exactly matches the class of the thrown exception, or that is a base
class of the thrown exception.
class of the thrown exception.
Because of this, it is possible to catch many different exceptions with a single
Because of this, it is possible to catch many different exceptions with a single
catch if all the exceptions are extensions of a specified base class.
<var>Catch</var> if all the exceptions are extensions of a specified base class.
For this reason, too, it is invalid to specify a <var>Catch</var> for an exception class
For this reason, too, it is invalid to specify a <var>Catch</var> for an exception class
after a <var>Catch</var> for a base class of that exception class, since the base class
after a <var>Catch</var> for a base class of that exception class, since the base class
<var>Catch</var> will always catch the exception class exception before the exception
<var>Catch</var> will always catch the exception class exception before the exception
class <var>Catch</var> is processed.
class <var>Catch</var> is processed.
If separate <var>Catches</var> are required for base and extension classes, specify the
If separate <var>Catch</var> statements are required for base and extension classes, specify the
extension class <var>Catches</var> first.
extension class <var>Catch</var> statements first.
   
   
Because there is no single base exception class for all exceptions
Because there is no single base exception class for all exceptions
Line 827: Line 935:
generically catch any and all exceptions with a single <var>Catch</var>.
generically catch any and all exceptions with a single <var>Catch</var>.
While this ability might be convenient, it is also likely to encourage sloppy
While this ability might be convenient, it is also likely to encourage sloppy
programming, where generic <var>Catches</var> obscure the possible errors in a context,
programming, where generic <var>Catch</var> statements obscure the possible errors in a context,
and make it all too easy to catch unanticipated errors and do the wrong thing.
and make it all too easy to catch unanticipated errors and do the wrong thing.
   
   
For <var>Try</var> blocks nested inside of other Try blocks, if no <var>Catches</var> that correspond
For <var>Try</var> blocks nested inside of other <var>Try</var> blocks, if no <var>Catch</var> statements that correspond
to a thrown exception are found for the inner-most <var>Try</var> block, then the outer-most
to a thrown exception are found for the inner-most <var>Try</var> block, then the outer-most
<var>Try</var> block's <var>Catches</var> are checked for a match, and they are used if a match is found.
<var>Try</var> block's <var>Catch</var> statements are checked for a match, and they are used if a match is found.
That is, when <var>Try</var> blocks are nested, all <var>Try</var> blocks are in effect for statements
That is, when <var>Try</var> blocks are nested, all <var>Try</var> blocks are in effect for statements
inside the inner-most <var>Try</var> block.
inside the inner-most <var>Try</var> block.
Note, however, that other <var>Catches</var> for a <var>Try</var> block are no longer in effect
Note, however, that other <var>Catch</var> statements for a <var>Try</var> block are no longer in effect
inside any of the <var>Catch</var> blocks for the <var>Try</var>.
inside any of the <var>Catch</var> blocks for the <var>Try</var>.
For example, if a request had a daemon object <code>%dmn</code>, in the following:
For example, if a request had a <var>Daemon</var> object <code>%dmn</code>, and if <code>%errorhex</code> had invalid hexadecimal data in it in the following block:
<p class="code">try
<p class="code">try
   %dmn:run('NASTY')
   %dmn:run('NASTY')
Line 847: Line 955:
end try
end try
</p>
</p>
The <code>Catch InvalidHexData</code> above would '''not''' catch the conversion error, because the conversion is not
inside the <var>Try</var> block associated with that <var>Catch</var> &mdash; it is in a <var>Catch</var> block.
   
   
if <code>%errorhex</code> had invalid hexadecimal data in it, the <code>Catch InvalidHexData</code>
would '''not''' catch the conversion error, because the conversion is not
inside the <var>Try</var> block associated with that <var>Catch</var> &mdash; it is in a <var>Catch</var> block.
==OnThrow and OnUncaught==
==OnThrow and OnUncaught==
An exception class might want to perform special processing at the time an
An exception class might want to perform special processing at the time an exception is thrown:
exception is thrown:
<ul>
<ul>
<li>It might want to make sure the exception object has valid data.
<li>It might want to make sure the exception object has valid data.
<li>It might want to record diagnostic information, perhaps to the audit
<li>It might want to record diagnostic information, perhaps to the audit
trail or perhaps to some <var class="product">Model 204</var> file.
trail or perhaps to some <var class="product">Model 204</var> file.
<li>It might want to derive some variable values that might not necessarily
<li>It might want to derive some variable values that might not necessarily
have been derivable in the constructor.
have been derivable in the <var>Constructor</var>.
</ul>
</ul>
   
   
To provide this capability, <var class="product">Janus SOAP ULI</var> special-cases two method names in a
To provide this capability, <var class="product">SOUL</var> special-cases two method names in a
<var class="product">User Language</var> exception class: <var>OnThrow</var> and <var>OnUncaught</var>.
user-defined exception class: <var>OnThrow</var> and <var>OnUncaught</var>.
Both of these methods must be <var>Subroutines</var> (as opposed to <var>Functions</var> or
Both of these methods must be <var>Subroutines</var> (as opposed to <var>Functions</var> or
<var>Properties</var>) and cannot have parameters.
<var>Properties</var>) and cannot have parameters.
Line 872: Line 979:
The <var>OnThrow</var> subroutine is automatically called whenever an exception
The <var>OnThrow</var> subroutine is automatically called whenever an exception
of the containing class is thrown, and either the exception will not be
of the containing class is thrown, and either the exception will not be
caught and there is no <var>OnUncaught</var> method in the class, or the exception
caught and there is no <var>OnUncaught</var> method in the class, or the exception will be caught.
will be caught.
   
   
These two method names have no meaning in non-exception classes.
These two method names have no meaning in non-exception classes.
Line 896: Line 1,002:
   
   
The following illustrates an <var>OnUncaught</var> subroutine that logs information
The following illustrates an <var>OnUncaught</var> subroutine that logs information
from the exception to the audit trail before allowing the request to be
from the exception to the audit trail before allowing the request to be cancelled:
cancelled:
<p class="code">class pratfall exception
<p class="code">class pratfall exception
   public
   public
Line 911: Line 1,016:
If <var class="product">SirFact</var> is available and capturing dumps for requesting cancelling
If <var class="product">SirFact</var> is available and capturing dumps for requesting cancelling
errors, all the information one would need is likely to be in the dump,
errors, all the information one would need is likely to be in the dump,
so there is probably little need to collect extra data in an <var>OnUncaught</var>
so there is probably little need to collect extra data in an <var>OnUncaught</var> subroutine.
subroutine.
   
   
There is no way for an <var>OnUncaught</var> or <var>OnThrow</var> subroutine to undo the
There is no way for an <var>OnUncaught</var> or <var>OnThrow</var> subroutine to undo the
Line 940: Line 1,044:
   
   
It does this, however, by using the subroutine name (qualified
It does this, however, by using the subroutine name (qualified
with the class name) rather than with the Super method (since
with the class name) rather than with the superclass method (since
no overriding is involved):
no overriding is involved):
<p class="code">subroutine onThrow
<p class="code">subroutine onThrow
Line 949: Line 1,053:
</p>
</p>
   
   
In this example, the extension exception class's base class is
In this example, the extension exception class's base class is called <code>FooError</code>.
called <code>FooError</code>.
Of course, an <var>OnUncaught</var> in an extension class can also call an
Of course, an <var>OnUncaught</var> in an extension class can also call an
<var>OnThrow</var> for a base class (or even for its own class).
<var>OnThrow</var> for a base class (or even for its own class).
Line 963: Line 1,066:
class, regardless of whether there are any <var>OnThrow</var> subroutines in the
class, regardless of whether there are any <var>OnThrow</var> subroutines in the
exception class or any of its base classes.
exception class or any of its base classes.
 
[[Category:Janus SOAP ULI topics]]
[[Category:SOUL object-oriented programming topics]]

Latest revision as of 21:58, 23 March 2020

Exceptions are a technique for handling unusual occurrences in the execution of a method call. This page discusses SOUL exception handling.

Background

A wide variety of errors can occur inside a program. Many of these, such as syntax errors, or invalid variable or member names, can be caught at compile-time, so they produce compile-time errors. Other errors can only be caught at run-time, so they produce run-time errors.

For example, a reference to a member of a null object variable will produce a request cancelling “reference to null object” error. Generally, such an error is indicative of a logic error in the program — most likely the program forgot to create an instance of the object, forgot to assign it to the object variable being used, or is mistakenly using the wrong object variable.

In any case, since such an error is indicative of a program logic error, the request cancellation is a benefit: it stops the program at the earliest point that the error was detected so that the program does not do damage because of the error. In addition, if SirFact is being used, the request cancelling error will produce a SirFact dump at the time the error was first detected (or at the very least, indicate the procedure and line number where the error was first detected). Such early detection of error greatly simplifies problem diagnosis.

Still other errors are actually quite common and might not even be considered errors in most contexts in which they occur. For example, the Stringlist class Locate method might not locate any items that match the search criterion. Since this is likely not to be a true error, the Locate method simply returns a 0 in such a case. The Locate method caller can then check for a zero value and do processing appropriate to the no-matching-item-found case.

If the Locate method caller assumes that the Locate always finds a matching item, it is likely to use the zero returned by the Locate method to reference a matched item, then get a request cancelling error when item zero is requested. This illustrates a general principle that methods that can produce unusual results or, at least, results that are significantly different from others, should try to set the unusual or result values to something that is likely to cause a request cancelling error if misused.

For example, a method that returns an object instance might return a Null object under certain error conditions that are not necessarily indicative of a programming error. This allows the calling program to check for a Null and perform appropriate processing if a Null is returned. If the program doesn't expect this error case, it might not check for a Null. If the program is correct, and the error case cannot happen in the given context, the program behaves correctly with no unnecessary error checking. If, however, it is incorrect, and a Null can be returned, it is likely to get a Null-object request-cancelling error when it attempts to use the Null as an object reference.

This is the best of all worlds — no error checking is required if the error is known not to occur in a context, but if that knowledge is incorrect, a request canceling error will quickly stop the program before any damage is done.

Unfortunately, there are some cases where the return value cannot be used to indicate an unusual situation:

  • There is no return value. For example, an error happens inside a subroutine or inside the Set method of a property.
  • The return value has no invalid values available to be used to indicate an unusual situation. For example, if a function returns a string, and a null value is sometimes a valid output, there might be no reasonable string value that can be used to indicate the error.
  • Even if there is a reasonable value (such as a null string, Null object reference, or zero or negative numeric value), such a value might not provide enough information about the nature of the error for the calling program to deal with the error. Obviously, a single string or numeric value, or a Null object reference, cannot provide a lot of information. This is especially important if a method might encounter more than one unusual situation, and the unusual situations are different enough that one might be expected in a particular context, and the other not; or the unusual situations require very different corrective action. For example, a method places an order for a product, and given a product number, it wants to indicate an error if the product number is invalid. The number might be invalid because the product number doesn't exist or because the product is out of stock. Obviously, these are two very different errors, the former possibly being indicative of a programming error, the latter less likely to be.

One solution to such cases is to have an output parameter on the method calls. Unfortunately, this has several problems:

  • Output parameters can be confusing in code. There's nothing that marks them as output parameters, so it's not obvious that's what they are. Also, someone looking for how a variable used as an output parameter was or might be set, might only think to look for assignment statements with the variable on the left side of the assignment. Certainly, life would be easier if that is all one needed to look for.
  • Just because a variable is used for the output parameter, there is no guarantee that the program actually examines the output parameter value. This could mean that where it is incorrectly believed that a certain error situation can't occur, the code might not bother to check the variable used as an output parameter and continue on, oblivious to an error.
  • Output parameters cannot (currently) be optional. This means that a variable has to be specified for the output parameter, even if the unusual situations indicated by the output parameter couldn't happen in a given context. This can be annoying, at best, and confusing, at worst.

An alternative to output parameters to handle unusual cases in method calls is called exception handling.

Errors unsuitable for exception handling

Exception handling consists of three parts:

  1. The ability to define classes of exceptions and the information available for these classes. Unsurprisingly, classes of exceptions are defined in almost the same way as other classes.
  2. The ability to indicate an unusual or exception situation. This is referred to as throwing an exception.
  3. The ability to detect a thrown exception and to perform special processing for the exception. This is known as catching an exception.

Exception handling in SOUL is very similar to many other object-oriented programming languages, such as Java or VB.Net. However, each language's implementation of exception handling is unique, each with subtle or not so subtle differences from others. SOUL is no exception (no pun intended), and significant departures from the way other languages support exceptions is indicated where applicable.

A philosophical departure from the support of most other languages for exceptions is worth noting: while many object-oriented languages make all (or almost all) errors exceptions, SOUL does not. This is partially because many object-oriented languages are not, strictly speaking, application languages — in addition to writing applications in some other object-oriented languages, one might write programming environments or even operating systems. As such, a programming environment or operating system must be able to intercept all errors and try to deal with them, so the object-oriented languages facilitate this by making all errors catchable.

SOUL is an application development language. While it is theoretically possible to build a program environment in SOUL, or even an operating system, this is not really the focus of SOUL, and it is not likely anyone would ever do this. The SOUL programming environment (Model 204, which is not itself written in SOUL) is responsible both for the ultimate catching of errors and the providing of information about the nature of the errors so that they can be corrected.

The kinds of errors that SOUL does not turn into exceptions, and thus are not trappable, fall into two broad categories:

  1. Pernicious environmental errors from which it would generally be almost impossible to recover gracefully on an application level.

    For example, if CCATEMP fills up, it is exceedingly unlikely that a request could recover gracefully. Since CCATEMP is used for so many different things (including record sets for finds, stringlists, collections, internal storage of objects swapped out of a server, transaction backout logs, and so on), it would be very difficult for an application to anticipate all the places that CCATEMP could fill. And even if it could anticipate the places, it would be very difficult to avoid doing anything that might also require CCATEMP.

    Similarly, a PDL (push-down-list) overflow can be detected at almost any method call, not to mention dozens of internal PDL-intensive routines (such as, say, journal output). As such, it would be almost impossible for an application to anticipate all the places where a PDL overflow might be detected. And even if it could anticipate the places, any method calls to handle the situation would likely also encounter a PDL overflow.

  2. Errors that are indicative of logic errors in a program.

    For example, a class member reference via a Null object variable is usually indicative of a programming error. Similarly, a reference to an invalid collection item number is also generally indicative of a programming error.

    While there might be error-causing cases of "sloppy" references to object variables or collection items that you would want to simply catch when they happen:

    • Most of these are handled well by existing facilities such as Auto New and also UseDefault processing for collections.
    • In the odd cases where such sloppy references are useful, it is trivial and efficient to check for the sloppy references (Null object variable, invalid collection item number), so an exception-handling paradigm adds little value.

      Of course, there are always border-line cases where an error that is nearly always indicative of a programming error, might not be in certain contexts. Since the initial Sirius Mods support for exceptions was not accompanied by a complete survey of all error returns by all system methods, it is quite likely that many request cancelling errors in system methods will gradually be converted into catchable exceptions over time. This is also quite likely to be the case for many errors in user-written methods.

Note that Model 204 does provide SOUL facilities to catch even these types of untrappable errors. These facilities include On units, especially On Error units, and APSY error procedures. These facilities make it possible for SOUL programs to, at least, provide nicer notification of an error to a client (web browser, 3270 user, SOAP client) than a broken connection or a Model 204 error message.

Even these facilities, however, are limited by severe environmental resource constraints. For example, if a CCATEMP or record-locking-table full error drives an APSY error procedure or an On Error unit, there might be little these error routines can do without bumping into the very same resource constraints that caused the problem in the first place. This problem is not unique to Model 204 — severe environmental constraints can wreak havoc with the best laid error recovery plans in any environment.

Exception class definitions

Most error conditions have information associated with them. This information can be useful for correcting or recovering from the problem. For example, if an invalid character is detected in a stream of data, it might be useful to know the character offset in the stream, the actual invalid character, and some indication of why the character is invalid. It might also be useful to have a text description of the nature of the error.

The collection of data describing an error situation might be used immediately after the error is detected, or it might be examined elsewhere. As such, this error data should be able to persist indefinitely. Given this requirement, it is clear that the logical place to hold error information is inside an object. And the description of the data associated with a particular error would, of course, be a class.

Classes that describe trappable error situations are called exception classes. To a large degree, exception classes are no different from any other class:

  • They can have Public, Private, and Shared blocks; they can have variables, methods, and the same Allow and Disallow rules as any other class.
  • There can be system exception classes, which are provided as part of the Model 204 installation, and there can be exception classes which are defined and maintained by SOUL code.
  • Exception classes can be used wherever non-exception classes are used, although the reverse is not true. That is, there are certain statements that require an exception class or an object of an exception class. This is largely to prevent accidental misuse of a non-exception class in an exception class context.

User-defined exception classes are denoted by using the Exception keyword in the class header:

class tackyColor exception

In many object-oriented languages, a class is indicated as an exception class by extending some special base class, often called "Exception." One can think of the Exception keyword on the Class declaration as indicating that the class extends some hidden base class. This neither adds nor detracts from an understanding of the basic concept of an exception class.

If you want, you can put the Exception keyword on almost all class declarations; specifying it removes no capabilities from the class. Doing so, however, is misleading, since most classes are likely never to be used to indicate error situations. In this regard, you can view the Exception keyword on a class declaration as documentation that a class can be used as an exception class. While the compiler cannot ensure that a class with an Exception declaration will be used as an exception, it does ensure that a class not declared as Exception is not used in exception contexts.

The one case where the Exception keyword is not allowed on class declarations is in declarations that extend non-exception classes — exception classes can only extend other exception classes.

Some examples of system exception classes are:

InvalidHexData
Describes an error in converting hexadecimal data to some other datatype.
InvalidRegex
Describes an error processing a regular expression.
DaemonLost
Describes a situation where the thread doing processing for a Daemon object was lost (logged off).

This list is far from exhaustive, but it illustrates that system exception classes might be associated with a specific system class. For example, the DaemonLost class is clearly associated with the Daemon class. On the other hand, an exception class might be associated with a facility that's used by many different classes. For example, the InvalidRegex class is not associated with any specific class, because regular expressions are used in Stringlist and Intrinsic methods.

For links to the descriptions of the individual system exception classes, see the "Lists of classes and methods".

Throwing exceptions

When an error situation occurs, the code that detects the situation can do one of two things:

  • It can cancel the request. In SOUL this is done with an Assert statement. In system code, this is done by some equivalent of the Assert statement. This is the correct response to an error if the error is clearly indicative of a programming error or a severe environmental constraint that is likely to make request continuation unproductive.
  • It can throw an exception. In SOUL this is done with a Throw statement. In system code, this is done by some equivalent of the Throw statement. This is the correct response to an error if the error might occur in the normal course of processing and the code that called the method might be able to recover from the error.

    Note: In the SOUL implementation of exceptions, exceptions can only be thrown by methods.

In both system and user-written methods, a thrown exception can only be handled (caught) by the code that called the method. This is different from many other languages' implementations of exceptions where exceptions can be handled locally, that is, inside the same method that threw the exception. Like many other languages, the exceptions that might be thrown by a method must be documented in the method header. This is because a method's exceptions are part of the method's interface to its callers, as much as any input and output parameters, as much as output values.

Specifying a Throws clause

The exceptions thrown by a method are indicated by the Throws clause in the method declaration and definition header. For example, if the method Paint might throw a TackyColor and InvalidHexData exception, it should be declared and defined as:

subroutine paint(%hexColor is string len 6) - throws tackyColor and invalidHexData

Of course, while the keyword And is used to separate the exceptions that might be thrown by a method, the method can only ever throw one exception at a time, and in most cases, it will not throw an exception at all.

The list of exceptions after a Throws keyword is the list of exception class names. These class names could be user-defined exception class names, or they could be system exception class names. If the class is a system class name, it could be fully qualified with the System: namespace indicator. For instance, the previous example could be written as

subroutine paint(%hexColor is string len 6) - throws tackyColor and system:invalidHexData

This would only be necessary if there was a user-defined class name with the same name as the system class name. Of course, it is best to avoid such a situation as much as possible.

As with most method descriptions, the Throws clause on a method declaration and definition must match exactly. That is, the mismatch in the following definition and declaration is invalid:

class house public ... subroutine paint(%color is string len 6) - throws tackyColor and outOfPaint ... end public ... **** The following produces a compile error **** subroutine paint(%color is string len 6) - throws tackyColor ... end subroutine ... end class

It is also invalid if, in the method definition, the list of thrown exceptions contains the same exceptions as the method declaration, but in a different order.

Methods that implement an overridable method cannot throw any exceptions not thrown by the implemented method; however, they do not necessarily have to throw all the exceptions thrown by the implemented method. That is, the exceptions in a Throws list on an Implements method must be a subset of the Throws list on the corresponding Overridable (or Abstract) method. For example, if an overridable method in class Products indicates:

subroutine buy(%productCode is string len 8) - overridable - throws tooExpensive and outOfStock

then it is valid for an implementing method to indicate:

subroutine buy(%productCode is string len 8) - implements buy in products - throws tooExpensive

or even:

subroutine buy(%productCode is string len 8) - implements buy in products

However, it is not valid for the implementing method to throw an exception other than TooExpensive or OutOfStock. This is because a caller of the base class only expects the Buy method to throw one of these two exceptions. It would be surprising, indeed, if the method threw some different exception, simply because it had been overridden.

Using the Throw statement

Once a method declaration indicates the exceptions it might throw, that method could then throw the exception with the Throw statement. The Throw statement must be followed by an instance of the exception class being thrown. For example, if the method header contains

subroutine paint(%color is string len 6) throws tackyColor

and there is a variable declaration in the method:

%tacky is object tackyColor

The method could do this:

throw %tacky

Of course, the Throw statement is likely to be inside an If clause, since exceptions are generally thrown in unusual situations, not common ones:

if %color eq %yuckyGreen or - %color eq %grottyOrange then throw %tacky auditText Threw an exception end if

Note that in the above example, the AuditText statement after the Throw will never be executed. This is because the Throw either returns immediately to the method caller (if the method caller catches the exception), or it cancels the request immediately.

Another problem with the above example is that %tacky was likely to never have been set to reference a TackyColor instance. So, more correct would be something like:

if %color eq %yuckyGreen or - %color eq %grottyOrange then %tacky = new throw %tacky end if

But, even this is not quite right. Usually, the exception objects will contain information to aid in problem determination and recovery:

if %color eq %yuckyGreen then %tacky = new %tacky:reason = 'Yucky' %tacky:alternative = '00FF00' throw %tacky end if if %color eq %grottyOrange then %tacky = new %tacky:reason = 'Grotty' %tacky:alternative = 'FFA500' throw %tacky end if

This example illustrates the point that it is common for any Throw of a particular exception class to always return more or less the same information. As such, exception classes often have constructors that can specify all the information provided by the class. Then, no variables of the exception class need to be defined; the result of the constructor call can simply be thrown:

if %color eq %yuckyGreen then throw %(tackyColor):new('Yucky', '00FF00') end if if %color eq %grottyOrange then throw %(tackyColor):new('Grotty', 'FFA500') end if

Of course, for code readability, named parameters are preferable on such constructors:

if %color eq %yuckyGreen then throw %(tackyColor):new(reason='Yucky', alternative='00FF00') end if if %color eq %grottyOrange then throw %(tackyColor):new(reason='Grotty', alternative='FFA500') end if

An attempt to throw an exception object whose class does not match one of the classes listed in the method declaration's Throws clause results in a compilation error.

Exception classes extending other exception classes

Exception classes can extend other exception classes. As such, the class of an object specified in a Throw statement does not have to match any class in the method's Throws list exactly — it can be of an extension class of one of the Throws list classes. Because an extension class can, itself, be extended, and because of multiple inheritance, this means that a Thrown exception object might match multiple classes in a method's Throws list.

The thrown (and therefore catchable) class is the first class in the Throws list that matches the object in the Throw. That is, the first class in the Throws list that exactly matches the thrown object's class or that is a base class of the thrown object is used as the thrown class for the method caller. Because of this, a Throws list must always specify extension classes before base classes. Otherwise, the base class in the Throws list would always match any object that would match an extension class, so the extension class would never be used as the thrown class.

For example, if exception class ReallyNastyColor extended exception class TackyColor, and method Paint had this header:

subroutine paint(%color is string len 6) throws tackyColor

And if the following statement appeared inside the Paint method;

throw %(reallyNastyColor):new(reason='Awful')

The new ReallyNastyColor exception object would be thrown as a TackyColor object. That is, the callers of Paint would not be able to assign the thrown exception to a ReallyNastyColor object without the use of a narrowing assignment.

Try and Catch

Without any action on a method caller's part, a thrown exception is, for all intents and purposes, a request cancelling error. To prevent the request cancellation, an exception must be "caught." This is achieved by the use of a Try/Catch block.

A Try/Catch block consists of two parts. The first, the Try section, contains one or more SOUL statements that might result in an exception. The Try section is then followed by one or more Catch sections, each one of them handling (catching) a particular class of exception.

The following fragment shows the use of a Try/Catch block to trap an exception caused by invalid hexadecimal data:

try %binary = %input:hexToString catch invalidHexData %binary = '???' catch invalidBase64Data %binary = '???' end try

This example illustrates a few points:

  • The end of the statements whose exceptions are being caught is indicated by the first Catch block.
  • The class of exceptions being caught follows the Catch statement.
  • A catch block is terminated by another Catch statement or an End Try.
  • One can catch multiple exception types, each within its own Catch block.
  • There is no validation that the type of exception being caught might actually be thrown inside the Try block. In the preceding example, there would be no chance for an InvalidBase64Data exception inside the Try block since the HexToString method will not throw such an exception. Having a block to catch an exception that's not possible is no different from having an If/Else If block for an impossible condition — it simply adds some dead code to a request.

Try block syntax

Try tStmt1  ; * Exceptions can [ tStmt2 ]  ; * be caught in [ ... ]  ; * these statements [ Rethrow rethClassA [And rethClassB ] ... ] ... any number of Rethrow statements [ Catch catClassC [To %objC] [And catClassD [To %objD]] ... [ cStmtW ]  ; * These are executed if any [ cStmtX ]  ; * of the execptions on the [ ... ]  ; * Catch statement are thrown ] ... any number of Catch blocks [ Success [ sStmtY ]  ; * These are executed [ sStmtZ ]  ; * if no execptions [ ... ]  ; * are thrown ] End Try

Syntax terms

The Try block is divided into two or more sections, which are separated by Rethrow, Catch, and Success statements. Multiple Rethrow and Catch statements can be mixed with each other and with the Success statement, in any order.

There must be either one Rethrow or Catch statement in a Try block.

tStmt1 [tStmt2...] The first section in the Try block must contain one or more SOUL statements. These statements are executed in turn, and if an exception is thrown within them, execution resumes at the Rethrow or Catch statement specifying that exception; if there are none, the request is cancelled. If no exceptions are thrown, execution resumes with the Success block statements, if any, and then after the End Try statement.
Rethrow rethClassA ... The Rethrow statement (not block) specifies one or more exception classes; if one of them is thrown, then the method containing the Try block throws that exception. Hence a Rethrow statement can only occur within a method which declares all of the rethClass exceptions in its Throws clause.

Each rethClass may not be the same as, nor an extension of, any exception class preceding it on the statement or on preceding Rethrow and Catch statements in the Try block.
Catch catClassC ... The Catch statement specifies one or more exception classes; if one of them is thrown, then the (optional) cStmt statements in the Catch block are executed in turn, and then execution resumes after the End Try statement.

Each catClass may not be the same as, nor an extension of, any exception class preceding it on the statement or on preceding Rethrow and Catch statements in the Try block.
Success If no exceptions are thrown in the tStmt statements, the (optional) sStmt statements in the Success block are executed in turn, and then execution resumes after the End Try statement.

Try block considerations

A Try block can contain more than one statement:

try %binary = %input:hexToString %bin = %input64:base64ToString %key = %inputKey:hexToString %foo:process(%binary, %bin, %key) catch invalidHexData %binary = '???' catch invalidBase64Data %bin = '???' end try

While valid, this example is intended to show that it might not be a good idea to put a lot of statements inside a Try block:

  • It obscures which statements might be throwing which exceptions, and this makes the code harder to read.
  • The more statements you have inside a Try block, the more likely it is that you will accidentally catch an exception in a statement in which you were not expecting an exception. For example, in the above code, the InvalidHexData Catch block is clearly fixing a problem with invalid hexadecimal data in %input. But, it will also catch an exception thrown by invalid hexadecimal data in %inputKey, and in this case, probably it will do the wrong thing.
  • All the code in the Try block after the method that threw the exception will not be executed. While, in some cases, this is what would be intended, there are many cases where this would not be intended. In the above example, it is likely that one would want to try to execute the Base64ToString method on %input64 after "correcting" errors in executing HexToString on %input.

As a general rule of thumb, place as few statements inside a Try block as possible. To facilitate this, you can follow the Try statement by a SOUL statement on the same line, as in:

try %binary = %input:hexToString catch invalidHexData %binary = '???' end try

Referencing a thrown exception object

As noted in "Using the Throw statement", what gets thrown with an exception is an exception object that contains information about the nature of the exception. In the Try/Catch examples to this point, the thrown objects were ignored — only the class of the thrown exception was used. If you want to reference the thrown exception object, you must specify a To clause, followed by an object variable of the exception class being caught.

For example, if %daemon is a Daemon object and %daemonLost is an object of the DaemonLost exception class, the following block catches the exception thrown if the daemon thread was logged off for some reason, and it displays the output of the last command up to the point where it logged off:

try %daemon:run('INCLUDE NASTY') catch daemonLost to %daemonLost printText Daemon died! Its last words were: %daemonLost:daemonOutput:print end try

Presumably, this block would be useful in diagnosing the problem or even correcting it.

Rethrow

Syntax

Rethrow exceptionName ...

Description

Sirius Mods 8.1 introduced the Rethrow clause in a Try block. Rethrow lets you propagate an exception without having to assign it to a local variable, probably eliminating the need to declare a local exception class object for the sole purpose of propagating an exception.

The Rethrow clause must occur at the same level as a Catch clause in a Try block. But, unlike the Catch clause, no code is allowed "inside" the Rethrow "block." Because Rethrow causes control to pass immediately out of the current method, no code after Rethrow would ever be executed.

A Rethrow clause can only be followed by an End Try, a Catch clause, or a Success block. Like the Throw statement:

  • The Rethrow clause must be invoked inside a method.
  • The class or classes specified on a Rethrow clause must be declared as being thrown by that method.

Example

The following local subroutine illustrates a Rethrow of an InvalidHexdata exception. This subroutine sends a string converted from hex to binary on a socket.

local subroutine (socket):sendHex(%hexdata is longstring) throws invalidHexData %sendData is longstring try %sendData = %hexdata:hexToString rethrow invalidHexdata end try %this:send(%sendData) end subroutine

Mutiple classes on a Catch or Rethrow statement

Sirius Mods 8.0 introduced the ability to specify multiple exception classes on a single Catch statement. The class names are separated from each other by the And keyword. However, in Sirius Mods 8.0, no To clause could be specified on a Catch statement with multiple classes.

Sirius Mods 8.1 added support for To clauses on a Catch statement with multiple classes as well as including support for multiple classes on the Rethrow statememt.

The following illustrates a Catch statement with multiple classes:

try %foo:doSomethingCrazy catch invalidHexdata to %ivhexData and tooCrazy and notCrazyEnough to %notCrazyEnough end try

If the above were inside a method, the exceptions could be rethrown:

try %foo:doSomethingCrazy rethrow invalidHexdata and tooCrazy and notCrazyEnough end try

Of course, if a Catch block catches multiple class exceptions, it might be important to determine which exception was actually thrown. In that case, a null test can be performed on the target objects:

try %foo:doSomethingCrazy catch invalidHexdata to %ivhexData and and tooCrazy and notCrazyEnough to %notCrazyEnough ... if %ivhexData is not null then ... elseIf %notCrazyEnough is not null then ... end if ... end try

For this to work, one must be sure that the target catch objects are null before the Try block. And, unless there is a lot of common code for the classes in the Catch block, it probably makes more sense to just have a separate Catch block for each exception.

There is no difference between rethrowing exceptions in a single Rethrow statement and multiple Rethrow statements, so it is largely a matter of style as to which approach is used.

Success blocks

In cases where a Try block contains multiple statements, a Success block makes it clear in the code which statement is expected to produce the exceptions that are being caught. These blocks also protect you from an inadvertent exception thrown in an unexpected context.

For example, consider the following scenario. You want to try statement <a> and, if no exceptions get Thrown, you want to do statements <b>, <c>, <d>, and <e>. Otherwise, if statement <a> throws an exception, you want to do statements <x>, <y>, or <z>, depending on the exception.

You code your Try/Catch block like this:

try <a>... <b>... <c>... <d>... <e>... catch foo <x>... catch bar <y>... catch another <z>... end try

If statement <a> does indeed throw an exception, statements <b> through <e> do not run, and the appropriate Catch statement takes effect. However, if statement <a> does not throw an exception, there might be no way to know that statement <b>, <c>, <d>, or <e> might throw an exception that is one of the exceptions in the subsequent Catch statements. Or you might be aware of their capacity to do so, but you might not expect an exception from any of them in this context. Prior to Version 7.8 of the Sirius Mods, there was no good way of preventing the catches to also be in effect for these statements as well as for statement <a>.

As of Sirius Mods 7.8, a Success block inside the Try block resolves the problem by making it clear that the Catch statements do not apply to statements <b>, <c>, <d>, and <e>:

try <a>... success <b>... <c>... <d>... <e>... catch foo <x>... catch bar <y>... catch another <z>... end try

The principle benefits of the Success statement are:

  • It makes it clear in the code which statement is expected to produce the exceptions being caught.
  • It prevents a catch from accidentally catching an exception from a statement that didn't really expect that exception.

You can also reverse the order of the the Success and catches:

try <a>... catch foo <x>... catch bar <y>... catch another <z>... success <b>... <c>... <d>... <e>... end try

Some differences with other languages

This section describes some differences between the SOUL implementation of Try/Catch and implementations in other languages. Outside of these differences, Try/Catch support in SOUL is very similar to that in other languages.

  • Unlike Java, it is not necessary to provide a Catch for all exceptions that a method might throw. This seems antithetical to the idea that exceptions are unusual conditions and, in many instances, are known to be impossible. It seems unnecessary to add code to deal with (or ignore) an error that cannot occur in a context.
  • Unlike many other languages, uncaught exceptions are not automatically propagated to higher level callers. Partially, this is because the SOUL environment is not written in SOUL, so there is no need to propagate exceptions to some outer SOUL environment which, presumably, would clean up the failing request and possibly provide diagnostics about the error. Instead, clean-up and diagnostics are provided by the assembler environment.

    Automatic propagation of errors is also a grand opportunity for bugs. Since an automatically propagated error can happen anywhere in a method, there is no indication that the writer of a method considered the possibility of the propagated error. Updates to data-structures, including transactions that update files, might be half-done at the time of the occurrence of an exception that gets automatically propagated.

    While languages that provide automatic exception propagation usually also provide a Finally clause to "ensure" that the method doesn't leave things in a half-done state, there is nothing in a Finally clause that indicates whether the programmer anticipated the particular error being propagated. And the absence of a Finally clause does not prevent error propagation, anyway.

    If you want to propagate an exception in SOUL, you can simply catch it and re-Throw it:

    catch tackyColor to %yuck * let caller deal with this throw %yuck

    This makes it clear which error is being propagated and under what circumstances.

  • The absence of automatic exception propagation eliminates the utility of a Finally clause, so no Finally clause is available in Try/Catch blocks in SOUL.
  • Catch statements cannot catch locally thrown exceptions. That is, a Throw statement always results in immediate exit from the current method, regardless of whether or not the Throw is inside of a Try block and whether or not there are Catch statements that correspond to the thrown exception. The SOUL view of exceptions is that they are part of the interface between a method and its callers, so they have no place in controlling local program flow.

Nesting Try/Catch blocks

Like in other languages, Try/Catch blocks can be nested. That is, a Try/Catch block can be inside the try or catch clause of another Try/Catch block:

try %dmn:run('I STEP1') try %str = %hex:hexToString %dmn:run('I STEP2 ' with %str) catch invalidHexdata %dmn:run('I STEP2 ???') end try catch daemonLost auditText My daemon's gone! try %str = %hex:hexToString catch invalidHexdata %str = '???' end try return %str end try

The Catch that applies to a particular thrown exception is the first Catch that either exactly matches the class of the thrown exception, or that is a base class of the thrown exception. Because of this, it is possible to catch many different exceptions with a single Catch if all the exceptions are extensions of a specified base class. For this reason, too, it is invalid to specify a Catch for an exception class after a Catch for a base class of that exception class, since the base class Catch will always catch the exception class exception before the exception class Catch is processed. If separate Catch statements are required for base and extension classes, specify the extension class Catch statements first.

Because there is no single base exception class for all exceptions (as there is in many other languages), however, it is not possible to generically catch any and all exceptions with a single Catch. While this ability might be convenient, it is also likely to encourage sloppy programming, where generic Catch statements obscure the possible errors in a context, and make it all too easy to catch unanticipated errors and do the wrong thing.

For Try blocks nested inside of other Try blocks, if no Catch statements that correspond to a thrown exception are found for the inner-most Try block, then the outer-most Try block's Catch statements are checked for a match, and they are used if a match is found. That is, when Try blocks are nested, all Try blocks are in effect for statements inside the inner-most Try block. Note, however, that other Catch statements for a Try block are no longer in effect inside any of the Catch blocks for the Try. For example, if a request had a Daemon object %dmn, and if %errorhex had invalid hexadecimal data in it in the following block:

try %dmn:run('NASTY') %str = %hex:hexToString catch daemonLost %str = %errorhex:hexToString catch invalidHexdata %str = '???' end try

The Catch InvalidHexData above would not catch the conversion error, because the conversion is not inside the Try block associated with that Catch — it is in a Catch block.

OnThrow and OnUncaught

An exception class might want to perform special processing at the time an exception is thrown:

  • It might want to make sure the exception object has valid data.
  • It might want to record diagnostic information, perhaps to the audit trail or perhaps to some Model 204 file.
  • It might want to derive some variable values that might not necessarily have been derivable in the Constructor.

To provide this capability, SOUL special-cases two method names in a user-defined exception class: OnThrow and OnUncaught. Both of these methods must be Subroutines (as opposed to Functions or Properties) and cannot have parameters.

The OnUncaught subroutine is automatically called whenever an exception of the containing class is thrown, and the exception will not be caught. The OnThrow subroutine is automatically called whenever an exception of the containing class is thrown, and either the exception will not be caught and there is no OnUncaught method in the class, or the exception will be caught.

These two method names have no meaning in non-exception classes.

These methods can be called explicitly, and they can be either Private or Public (though whether they are Public or Private is irrelevant for implicit calls when an exception is thrown).

The following illustrates an OnThrow subroutine that makes sure the exception data is valid at the time an exception is thrown:

class pratfall exception public variable sound is string len 32 subroutine onThrow end public subroutine onThrow assert %this:sound eq 'splat' or - %this:sound eq 'boing' end subroutine end class

The following illustrates an OnUncaught subroutine that logs information from the exception to the audit trail before allowing the request to be cancelled:

class pratfall exception public variable sound is string len 32 subroutine onUncaught end public subroutine OnUncaught auditText Taking a pratfall -- {%this:sound} end subroutine end class

If SirFact is available and capturing dumps for requesting cancelling errors, all the information one would need is likely to be in the dump, so there is probably little need to collect extra data in an OnUncaught subroutine.

There is no way for an OnUncaught or OnThrow subroutine to undo the effect of the exception, that is, to prevent a request cancellation if the exception is uncaught. Both routines, however, can force a request cancellation, perhaps by using an Assert statement, even if the exception would have been caught. If a request cancellation occurs inside on OnThrow subroutine for an exception that's about to be caught, the catching statements are not executed, because the request is cancelled before the OnThrow subroutine returns.

The Throws clause is invalid on an exception class's OnThrow and OnUncaught subroutines, so these subroutines cannot, themselves, throw an exception (for, hopefully, obvious reasons).

The Overridable and Implements clauses are also not valid in an exception class's OnThrow and OnUncaught subroutine declarations. However, their behavior is similar to overridable routines:

  • If an extension exception class contains an OnUncaught or OnThrow routine, that routine will be called, when appropriate, rather than the base class routine.
  • The extension class routine can call the corresponding base class routines as it deems fit. It does this, however, by using the subroutine name (qualified with the class name) rather than with the superclass method (since no overriding is involved):

    subroutine onThrow auditText Calling base class OnThrow %this:(fooError)onThrow auditText returned from base class OnThrow end subroutine

    In this example, the extension exception class's base class is called FooError. Of course, an OnUncaught in an extension class can also call an OnThrow for a base class (or even for its own class).

If an OnUncaught subroutine exists in a base class, but the extension class contains only an OnThrow subroutine, the base class OnUncaught routine will be called if an extension class object is thrown as an exception and the exception will not be caught. That is, an OnUncaught subroutine will always be called for uncaught exceptions if one is available in the exception's class or any base class, regardless of whether there are any OnThrow subroutines in the exception class or any of its base classes.