|SAS/AF Software: Class Dictionary|
This section describes the semantics of the catch-throw exception handling mechanism that is built into SCL.
|Introduction to Catch-Throw Exception Handling|
Catch-throw exception handling is a programming concept that has been made popular by languages such as Lisp and Java. It allows the developer greater control over raising and recovering from exceptions than that offered by a typical signal-handling mechanism. In a signal-handler application, such as the SCL generic program halt handler, exceptions are processed by special "handler" routines, which can typically provide a message, perhaps save some information, and then either try to continue execution or halt the application.
Catch-throw performs a similar role, but it also provides a developer with greater flexibility and eliminates the need for a centralized exception handler routine. In particular, the exception-handling syntax is built into the SCL language, which allows the user to make error recovery code a natural part of the application itself. This implementation clarifies the types of exceptions that may be encountered in a particular piece of code. It also allows a natural stack-unwinding mechanism in which errors are processed in each stack frame and then passed up to the previous caller where a partial recovery may be effected.
|Basic Syntax and Semantics|
To "throw" (or raise) an exception, the developer creates a special class to represent the exception, and then uses the THROW syntax in SCL to pass the class back up the stack framework. For example,
/* Y Class */ import sashelp.classes; class y; m: method; /* Throw an exception. Set message via constructor. */ throw _new_ SCLException('Exception in method m'); endmethod; endclass;This examples throws an instance of the base exception class, sashelp.classes.SCLException.class.
The THROW statement halts execution of the current entry for Y.CLASS and passes control up to the calling entry. If that entry contains a CATCH statement for the thrown class, execution in that entry will continue at the location of that CATCH statement. If there is no corresponding CATCH statement, control will pass up to the next calling entry where the search for a matching CATCH statement will go on. This process will continue until a CATCH statement is found, or the stack is completely unwound, in which case the "uncaught" throw is treated the same as a program halt.
Since the exception is a class, it can be designed to contain any type of information the user may consider relevant to error recovery. The simplest piece of information may just be an error message, as in the above example.
An example of code that calls Y.CLASS's method m is
/* X.CLASS */ import sashelp.classes; class x; m: method; /* Catch blocks must reside in do-statements. */ do; /* Declare local exception variable. */ dcl SCLException scle; dcl y y = _new_ y(); /* Call method which throws exception. */ y.m(); /* Catch block for SCLException object. */ catch scle; /* Print exception information. */ put scle.getMessage(); call putlist(scle.traceback); endcatch; end; endmethod; endclass;Here, a local variable
scleis declared to contain an exception of type SCLException. Also, a CATCH block is placed after the call to
m. When theSCLException is thrown during the method call, control will pass to the CATCH block. In this example, the CATCH block will print the message that was passed to the constructor of the thrown object. It will also print the SCL entry traceback list, which is stored automatically in the thrown object by the SCL interpreter. Once a throw is caught in this way, execution will continue.
As noted in the comment in X.CLASS, CATCH blocks must always reside in an enclosed DO statement. Additionally, CATCH statements must always contain an exception variable to hold the thrown exception. The general syntax for a DO statement containing CATCH blocks is:
do; dcl e1 e1; dcl e2 e2; ..... dcl en en; catch e1; /* process exception e1. */ endcatch; catch e2; /* process exception e2. */ endcatch; .... catch en; /* process exception en. */ endcatch; end;If an exception is encountered in the DO statement, the SCL interpreter will search for a suitable CATCH block to execute. If such a CATCH block is found, the code within it will execute, and control will then transfer to the end of the DO statement block.
Nested Do Statements. If no such CATCH block is found in the current DO statement, any enclosed DO statements will be searched for a matching catch block. If one is found, it will execute and the application will continue normally from the end of that DO statement. For example, a DO statement may contain
do; dcl e1 e1; do; dcl e2 e2; y.m(); catch e2; /* process e2. */ endcatch; end; catch e1; /* process e1. */ endcatch; end;
If the method m throws the exception e1, control will transfer to the CATCH block for e1 in the outer DO statement. If no matching CATCH statement appears in the current entry, control will transfer to the previous entry in the stack frame, where the search for a matching CATCH statement will continue in the same manner.
Catch Statement Control: If no exception occurs, none of the CATCH blocks will execute. In other words, when the SCL interpreter executes a CATCH statement, it transfers control directly to the end of the loop, which prevents the execution of any statements located between CATCH blocks. Also, all CATCH blocks should appear at the end of the DO statement, otherwise, no statements in the DO statement will execute. For example, if the CATCH block in X is placed at the beginning of the DO statement:
do; dcl SCLException scle; dcl y y = _new_ y(); catch scle; /* Print exception information. */ put scle.getMessage(); call putlist(scle.traceback); endcatch; y.m(); end;The method call to m does not execute because control would have passed out of the DO statement when the CATCH statement was encountered.
Nested Catch Blocks: Catch blocks can be nested. For instance, if we had a class W.CLASS, and two exceptions e1 and e2:
/* W.CLASS */ class w; m: method n:num; do; dcl e1 e1; dcl e2 e2; do; if (n < 0) then throw _new_ e2(); else throw _new_ e1(); catch e2; put 'caught inner e2'; do; dcl e1 e1; if (n < 0) then throw _new_ e2(); else throw _new_ e1(); catch e1; put 'caught inner e1'; endcatch; end; endcatch; end; catch e1; put 'caught outer e1'; endcatch; catch e2; put 'caught outer e2'; endcatch; end; endmethod; endclass;and ran W's method m with a negative argument:
/* WW.SCL */ init: dcl w w = _new_ w(); w.m(-2); return;The output is
caught inner e2 caught outer e2Note that the throw of e2 in the inner CATCH block for e2 is passed to the outer CATCH block -- that is, the code does not enter an infinite loop by continually going to the inner CATCH block. But if there is a matching CATCH within the enclosing CATCH block, then the throw will be caught as normal. For instance, if, in the above example, we reversed the conditional in the inner CATCH block so that e1 is thrown:
catch e2; put 'caught inner e2'; do; dcl e1 e1; if (n > 0) then throw _new_ e2(); else throw _new_ e1(); catch e1; put 'caught inner e1'; endcatch; end; endcatch;then the output would be
caught inner e2 caught inner e1There is a general rule: If a throw takes place within an enclosing CATCH block, that CATCH block cannot catch the throw.
Different types of exceptions can be created by subclassing the SCLException class, and corresponding CATCH statements can be created to catch these exceptions using a class hierarchy look-up. For example, consider a new exception class:
class NewException extends SCLException dcl string SecondaryMessage; endclass;and the previous example included the following code:
/* Y.CLASS */ import sashelp.classes; class y; m: method; dcl NewException NewException = _new_('Exception in method m'); NewException.SecondaryMessage = "There's no code in m!"; throw NewException; endmethod; endclass;The CATCH statement in X.CLASS still catches this exception because NewException is a subclass of SCLException. The constructor message and traceback will be printed as before.
If you want to print the secondary message, however, there must be a change to the CATCH block to catch the NewException class:
do; dcl NewException ne; dcl y y = _new_ y(); /* Call method which throws exception. */ y.m(); /* Catch block for NewException object. */ catch ne; /* Print exception information. */ put ne.getMessage(); call putlist(ne.traceback); /* Print secondary message */ put ne.SecondaryMessage=; endcatch; end;If NewException is the only exception that may be thrown in the DO statement, there is no problem. If SCLException is also thrown, that throw will pass this entry. If that happens, and no other entry in the calling stack frame catches it, then the applica tion will halt - just as in the case of a typical program halt.
If you want to catch the exception in X.CLASS, you must add the SCLException back in. For example, suppose Y.CLASS threw both SCLException and NewException:
/* Y.CLASS */ import sashelp.classes; class y; m: method n: num; if (n >= 0) then do; dcl NewException NewException = _new_('Exception in method m'); NewException.SecondaryMessage = "There's no code in m!"; throw NewException; end; else throw _new_ SCLException('Exception in method m'); endmethod; endclass;and X.CLASS is modified to include:
do; dcl NewException ne; dcl SCLException scle; dcl y y = _new_ y(); y.m(x); /* Catch block for NewException object. */ catch ne; put ne.getMessage(); call putlist(ne.traceback); /* Print secondary message */ put ne.SecondaryMessage=; endcatch; /* Catch block for SCLException object. */ catch scle; put scle.getMessage(); call putlist(scle.traceback); endcatch; end;Depending on the value of
x, the program throws either one exception or the other. If the SCLException is thrown, control will pass to the SCLException CATCH block. Otherwise, if the NewException is thrown, control will pass to the NewException CATCH block because the CATCH block lookup will try to find the best class match.
|Localized Recovery and Stack Unwinding|
The catch-throw mechanism allows a developer to perform localized error recovery in each entry in a stack frame. This operation is performed by coding relevant CATCH blocks for an entry and then rethrowing the same or another exception. For example, suppose we create another class Z:
Z.CLASS import sashelp.classes; class z; n:method; throw _new_ SCLException('Exception from z'); endmethod; endclass;Y.CLASS is modified to include:
import sashelp.classes; class y; m: method; do; dcl z z = _new_ z(); dcl SCLException scle; z.n(); catch scle; put "Exception caught in y"; throw scle; endcatch; end; endmethod; endclass;In this case, Z.CLASS will throw an exception, which Y will catch. After processing the exception, Y will then pass it back up the stack frame by throwing it again. X.CLASS will catch that throw just as before. If you modify X.CLASS's CATCH block to include
catch scle; put 'Exception caught in x'; put scle.getMessage(); call putlist(scle.traceback); endcatch;You will see the output:
Exception caught in y Exception caught in x Exception from z (WORK.LL.Y.SCL=11 WORK.LL.X.SCL=12 WORK.LL.XX.SCL=3 )where XX.SCL is an SCL entry that creates in instance of X and calls the method m. Note that the message is still the original constructor message that was set in Z, but the traceback attribute indicates the most recent throw in Y.
Top of Page
Copyright 1999 by SAS Institute Inc., Cary, NC, USA. All rights reserved.