Trapping for one of the conditions is enabled by using the CATCH keyword, followed by the condition name. After this instruction, then you put all of the instructions you wish to handle the condition.
Note: Your CATCH instruction, and its following instructions, must appear at the bottom of your script (ie, where you normally would put your subroutines, but it must appear after all of those as well, and before any FINALLY). Don't put a colon after CATCH SYNTAX. Just like FINALLY, it isn't a label. It's an instruction.
For example, assume you put the following lines at the bottom of your script:
CATCH SYNTAX SAY "SYNTAX condition was raised!"Now, when Reginald encounters a syntax error in your script, Reginald will not abort your script at that line and display an error message (ie, the default handling for SYNTAX). Instead, Reginald jumps to the instructions after your CATCH SYNTAX instruction. In other words, Reginald lets your script handle that SYNTAX condition instead of doing its default handling for SYNTAX.
Note that the above REXX instructions (sort of) do what REXX's default SYNTAX handling would do. (ie, They display a message to the console window and end the script right there). But, you could do something different, for example, maybe ask the user if he wants to continue with the script execution, and then jump (ie, SIGNAL) to some other (safe) label in the script.
You can have numerous CATCH instructions in your script, to handle various conditions. For example, here we have some instructions to handle SYNTAX, and also some instructions to handle HALT:
CATCH SYNTAX SAY "SYNTAX condition was raised!" CATCH HALT SAY "HALT condition was raised!"Note that, unlike with subroutines, there is no need to put a RETURN instruction at the end of your group of instructions. In other words, the above instructions for SYNTAX will not drop down into the instructions for HALT (and therefore will not display HALT condition was raised!).
Above, if a HALT condition happens, then Reginald will jump to the instruction that SAYs HALT condition was raised!. If a SYNTAX condition happens, then Reginald will jump to the instruction that SAYs SYNTAX condition was raised!. Reginald will not abort your script and display some error message, in either case.
After doing the appropriate group of CATCH instructions, then Reginald does any FINALLY instructions you have at the end of your script (if your CATCH didn't SIGNAL to some other place in the script).
Let's take another example to demonstrate how CATCH and FINALLY instructions work together to simplify and bullet-proof your error handling and cleanup. Here we have the main body of our script CATCH SYNTAX. We then call a subroutine named Sub1 that gets some handle that it needs to free before it returns. Sub1 also calls Sub2 which also gets some handle that it needs to free. We're going to purposely stick an instruction inside of Sub2 that causes a SYNTAX error. We're going to call the function TIME() and pass it an option of 'Z', which is not a legal option.
Sub1() SAY "Back from Sub1" RETURN Sub1: DO MyHandle = GetAHandle() /* Descend into Sub2 */ Sub2() SAY "Back from Sub2" FINALLY IF EXISTS('MyHandle') THEN FreeHandle(MyHandle) RETURN END SAY "Still in Sub1" RETURN Sub2: DO MyOtherHandle = GetAHandle() /* Let's deliberately raise a SYNTAX error here by passing * an illegal argument to TIME(). */ TIME('Z') FINALLY IF EXISTS('MyOtherHandle') THEN FreeHandle(MyOtherHandle) RETURN END SAY "Still in Sub2" RETURN CATCH SYNTAX SAY 'SYNTAX raised' FINALLY SAY 'Done with script'Sub1 is called, and it gets MyHandle. Then Sub1 calls Sub2. Sub2 gets MyOtherHandle, and then causes a SYNTAX error. Now, Reginald must unwind to some CATCH SYNTAX (which appears at the bottom of the script for the main body of the script). First, Reginald does the FINALLY in Sub2, which frees MyOtherHandle and then RETURNs. You do not see the message Still in Sub2. Next, Reginald does the FINALLY in Sub1, which also RETURNs. You do not see the messages Back from Sub2 nor Still in Sub1. Reginald then jumps to the CATCH SYNTAX for the main body. You do not see the message Back from Sub1. You will see the message SYNTAX raised. And because we haven't put any SIGNAL to some place, Reginald will end the script, but first call the FINALLY for main which displays the message Done with script. And then the script ends. Study this example to get a feel for how Reginald automatically unwinds out of any DO/END's, calling their FINALLYs, until it gets to a CATCH SYNTAX. By becoming familiar with how this works, you can write scripts that always clean up their resources, and you can put all of your error handling in one particular place for simplicity -- here, in the main body of the script.
CATCH within a DO/END
Sometimes it is useful to have different handling for a particular condition in different places in your script. For example, in a particular subroutine or loop, you may want to handle NOTREADY condition differently than in another subroutine or loop. For this reason, you can put CATCH instructions inbetween any DO/END instructions, and those CATCH instructions will be in effect only within that particular DO/END.
Each CATCH instruction (and the instructions following it) must appear right before the END instruction (ie, at the bottom of the loop).
If a CATCH's condition is raised within the DO/END, then Reginald jumps to the instructions for your CATCH. After those instructions are executed, Reginald does any FINALLY part of the loop (unless you use an EXIT in your handler to abort completely or a SIGNAL to jump somewhere), and then the loop ends. Reginald will then carry on with whatever instructions are after the loop (unless your handler uses a RETURN to return to whomever called you, or a RAISE instruction to set off another condition, to be discussed later).
For example, here we have two different handlers for NOTREADY (although only one is effective at any given moment). Note that LINEIN() and LINEOUT() will raise the NOTREADY condition if the operating system has a problem accessing the file/device.
/* Put the call to LINEIN around a DO/END, so that we can * place a CATCH NOTREADY instruction for it. */ DO /* Read a line. If an error, then Reginald will jump to the * CATCH NOTREADY in this DO/END. */ chars = LINEIN(,,1) CATCH NOTREADY SAY "NOTREADY condition was raised from my call to LINEIN!" /* Return to whomever called me instead of continuing on after this loop */ RETURN END /* Put the call to LINEOUT around a DO/END, so that we can * place a CATCH NOTREADY instruction for it. */ DO /* Write a line. If an error, then Reginald will jump to the * CATCH NOTREADY in this DO/END. */ err = LINEOUT(,chars) CATCH NOTREADY SAY "NOTREADY condition was raised from my call to LINEOUT!" /* Return to whomever called me instead of continuing on after this loop */ RETURN ENDYou can trap any or all conditions within a DO/END, each with its own CATCH instruction. For example, here we trap any SYNTAX or NOTREADY condition that could be raised as a result of our call to LINEOUT(). (LINEOUT could also raise SYNTAX, for example, if you pass the wrong arguments).
/* Put the call to LINEOUT around a DO/END, so that we can * place CATCH NOTREADY and CATCH SYNTAX instructions in it. */ DO /* Write a line. Reginald will jump to the CATCH NOTREADY in this DO/END. */ err = LINEOUT(,chars) CATCH NOTREADY SAY "NOTREADY condition was raised from my call to LINEOUT!" RETURN CATCH SYNTAX SAY "SYNTAX condition was raised from my call to LINEOUT!" RETURN ENDThe CATCH instructions in your DO/END supercede any similiar CATCH instructions for your script itself. For example, assume the following script:
MySub() RETURN MySub: DO err = LINEOUT(,chars) /* Here's NOTREADY for this loop inside MySub */ CATCH NOTREADY SAY "NOTREADY condition within MySub!" END SAY "Done with MySub!" RETURN /* Here's NOTREADY for our script */ CATCH NOTREADY SAY "NOTREADY condition within main body of this script!"If a NOTREADY condition occurs on the call to LINEOUT (within MySub's DO loop), then Reginald jumps to the CATCH NOTREADY instructions (and displays NOTREADY condition within MySub!). Reginald does not jump to the CATCH NOTREADY for the main script itself (and does not display NOTREADY condition within main body of this script!). The CATCH instructions in the DO loop come first, and they handle the condition. Then the loop ends, and continues on to display Done with MySub! (because we didn't bother to put a RETURN or RAISE at the end of the CATCH NOTREADY).
CONDITION() built-in function
The CONDITION() built-in function gives a lot of information about the currently raised condition. The CONDITION() function is primarily called from within your own handler.
For example, CONDITION('D') may return a descriptive error message that you can display to the person running your script to tell him why the condition was raised. (Or it may return some other string that ultimately can be used to create such an error message).
Note:
Reginald's CONDITION() function has an additional option. CONDITION('M') will present a pop-up message box containing the error message, the name of the script in which the condition was raised, the source line and line number upon which the condition was raised, and a Help button to bring up a help page for that error. This is preferable to trying to create your own message to present to the person running your script.CONDITION('E') can return an error number associated with that instance of the condition. This number may help you deduce exactly why the condition was raised. The format and meaning of the error number can depend upon which condition was raised, so this will be discussed more when we examine each particular type of condition.
Note: CONDITION() keeps track of only the currently raised condition, so if another condition happens to be raised within your handler, then CONDITION() will return information about that second condition. Furthermore, calling a subroutine may reset the information that CONDITION() returns.
CATCH conditions in scripts you call
The CATCH instruction works across scripts too. Let's take the example above where we have Sub1 and Sub2. But let's make Sub2 its own script. Here's our first script (which calls Sub2):
Sub1() SAY "Back from Sub1" RETURN Sub1: DO MyHandle = GetAHandle() /* Call another script named Sub2.rex */ Sub2.rex() SAY "Back from Sub2.rex" FINALLY IF EXISTS('MyHandle') THEN FreeHandle(MyHandle) RETURN END SAY "Still in Sub1" RETURN CATCH SYNTAX SAY 'SYNTAX raised' FINALLY SAY 'Done with script'And here is Sub2.rex (which deliberately raises a SYNTAX condition, but has no CATCH SYNTAX instruction in it to handle that):
MyOtherHandle = GetAHandle() /* Let's deliberately raise a SYNTAX error here by passing * an illegal argument to TIME(). */ TIME('Z') FINALLY IF EXISTS('MyOtherHandle') THEN FreeHandle(MyOtherHandle)The unwinding above works exactly the same way as in the example where Sub2 was part of our main script. All of the FINALLYs get done, and Reginald unwinds to the CATCH SYNTAX in our first script.
Here's another example. Consider the following script named abort.rex:
/* Here's a REXX script we'll be calling. Its name is abort. * All it does is loop forever until the user initiates a HALT. And * since HALT is not trapped here, this script aborts then. */ SAY "Press CTRL-C to abort..." DO FOREVER END RETURNNow consider if we run the following script which calls abort:
/* Call the child script "abort". */ abort() /* We should not get here if HALT was raised in the * child, but the child didn't trap HALT for itself. */ SAY "ERROR: Parent did not trap the untrapped HALT in child" RETURN CATCH HALT SAY "Parent successfully trapped the untrapped HALT in child"The "abort" script does not display any information to the command prompt window when it is aborted. Instead, since the script that called "abort" is CATCHing HALT, then "abort" terminates, and then Reginald jumps to the CATCH HALT in the parent script. You therefore never see the message ERROR: Parent did not trap the untrapped HALT in child. You do see the message Parent successfully trapped the untrapped HALT in child.
When a condition is raised in some child script that you call, the error number and message returned by CONDITION('E') and CONDITION('D') may be slightly different than if that condition was raised by your own script. We'll discuss the particulars for each individual condition later.
But if desired, each script can have its own CATCH instructions for any conditions. What this means is that, for example, if you have a CATCH NOTREADY in a parent script, and then you call a child that also does CATCH NOTREADY, the child will handle NOTREADY itself, and the parent's CATCH NOTREADY will never be done. In fact, the parent will never even know about the NOTREADY in the child (unless the child uses a RAISE instruction to abort itself and inform the parent).
Errata
The FINALLY and CATCH instructions can greatly simplify and bulletproof your cleanup and error handling, without any need for SIGNAL statements and labels. And they can allow you to put all of your error handling and/or cleanup in one place in the parent script, if desired.
Warning: Using a SIGNAL statement to jump somewhere will defeat any FINALLY instructions you have set up, and will also result in Reginald aborting any unwinding to a CATCH statement. For this reason, you should avoid using any SIGNAL statements unless you do not place them inside of any DO/END that has a CATCH or FINALLY, nor use any SIGNAL in your FINALLY or CATCH instructions.
The next sections contain details about each condition, and how the CONDITION() 'D' and 'E' options can be used in your CATCH instructions for that condition.