Trap processing
In last month's column, I touched briefly on trapping the NOVALUE condition to detect and process uninitialized variables. Trap means to alter the program flow when a specified event occurs. Trap processing provides the program with the information necessary to identify the event.
Each trap is associated with an event having occurred. The specific particular event was raised as a result of a REXX instruction within the program or an external subroutine or command having notified the REXX program that an unusual condition occurred. The six events that may be trapped include: ERROR, FAILURE, HALT, NOTREADY, NOVALUE, and SYNTAX.
All condition traps are initialized as off in a REXX program until specifically set on with either the CALL or SIGNAL keyword instructions. Information about the trapped event may then be interrogated with the CONDITION() built-in function. The definition of these keyword instructions is shown in Figure 1 and the definition of the CONDITION() built-in function is shown in Figure 2.
Figure 1
- CALL name [expression][, [expression]]...
- Calls the internal routine, built-in function, or external routine name, passing each expression as an argument. The special variable RESULT will be set by the called routine if an expression is present on the RETURN instruction. If no expression is returned by the called routine, RESULT will become uninitialized.
- SIGNAL label_name
SIGNAL [VALUE] expression - Transfers control to the instruction labeled label_name; or evaluates expression and transfers control to the instruction labeled with that value.
When control reaches the specified label, the line number of the SIGNAL instruction is assigned to the special variable SIGL. This can aid debugging because the SIGL value can be used to determine the source of a jump to a label.
Figure 2
- CONDITION( [option] )
- Returns a word from the list shown below (option C, I, or S), or a descriptive string (option D) associated with the current trapped condition indicated by option. Option can be C (condition), D (description), I (instruction), or S (status). Possible combination of values (one from each column) returned for all options but D are:
Returns: If: C I S CALL X DELAY X ERROR X FAILURE X HALT X NOVALUE X NOTREADY X OFF X ON X SIGNAL X SYNTAX X
In the instance of this function being issued following a NOVALUE trap for condition D, the value returned is the uninitialized variable.
When a condition trap is disabled (off) and the condition event occurs, the default action that is taken depends on the condition. The NOVALUE and SYNTAX conditions are raised by the code within the REXX program itself or by a return code set by an external subroutine or function. The other four trap conditions are raised by external events. Unfortunately there is no documentation from IBM indicating which external conditions raise the ERROR condition and which external conditions raise the FAILURE condition. Therefore, I suggest that whenever you enable one, you enable both and service the trap event with common code.
For HALT and SYNTAX, the execution of the program is terminated and the system displays a message indicating that the event occurred. Stopping a running program with <Ctrl+Break> would result in: REX0004: Error 4 running program.CMD, line ?: Program interrupted. A syntactical error can occur from calling a non-existent function and might produce the following: REX0040: Error 40 running program.CMD, line ?: Incorrect call to routine. For all of the other events, the condition is ignored and the state of the condition remains off.
When a condition trap is currently enabled (on) and an event that raises the condition occurs, then the program flow is altered as if a CALL trap_processing_routine or SIGNAL trap_processing_routine had been issued. The name associated with trap_processing_routine may be specified with the label following the NAME sub-keyword shown for both the CALL and SIGNAL instructions in [#Figure 1 Figure 1]. If the NAME option is not used, the trap_processing_routine name is the same as the condition name. For example, the routine name for processing a SYNTAX event trap is SYNTAX and the routine name for processing a NOVALUE event trap is NOVALUE. [#Figure 3 Figure 3] contains an example of setting up a routine to process NOVALUE traps with the default name and [#Figure 4 Figure 4] contains an example of setting up a named routine for processing NOVALUE traps.
Figure 3
CALL ON HALT SIGNAL ON NOVALUE MAIN_LINE: ... exit HALT: say 'Ctrl-Break is being ignored' return NOVALUE: /* process NOVALUE trap */ exit
Figure 4
CALL ON HALT NAME HALT_ROUTINE SIGNAL ON NOVALUE NAME NOVALUE_ROUTINE MAIN_LINE: ... exit HALT_ROUTINE: say 'Ctrl-Break is being ignored' return NOVALUE_ROUTINE: /* process NOVALUE trap */ exit
Both the CALL ON and SIGNAL ON instructions enable (set on) the event trapping facility. Notice that the CALL ON instruction may not be used with either the SYNTAX or NOVALUE conditions.
The sequence of events that occur after one of the trapped events occur is dependent on whether a CALL or a SIGNAL is to be made to the trap processing routine.
If SIGNAL was used to enable the trap processing, then execution of the instruction that raised the trap condition is halted, the condition is disabled (set to off), and control is passed to the routine designated for processing that particular trap condition. Because the condition is disabled, a new CALL ON or SIGNAL ON instruction would be necessary to re-enable the condition within the first trap processing routine. This prevents a non-interruptable loop from occurring unintentionally.
If CALL was used to enable the trap processing, then the trap processing routine is given control as if the CALL instruction existed immediately following the clause that raised the condition. The only difference is that the special variable RESULT is not affected by execution of the call. Similarly, any expression specified in the RETURN instruction, at the end of the trap processing routine, is ignored. The value of RESULT in the "calling" routine remains unaffected.
When the trap event occurs, the matching condition is marked as delayed (before the actual CALL takes place) and remains in that state until return from the called routine. When return is made to the routine that caused the trapped event to occur, the condition state (on or off) is restored. Therefore, the trap processing routine can be used repeatedly.
The CONDITION() built-in function may be used to interrogate the current trap event. CONDITION(), using one of its four options, will return a string that indicates the name of the condition ('C'), the name of the unitialized variable that raised the NOVALUE event ('D'), the instruction used to transfer control to the trap event routine - CALL or SIGNAL ('I'), or the status of the condition ('S'). [#Figure 2 Figure 2] summarizes all of these options.
Let's take a look at a couple of examples. The ERROR condition is raised by an external command that returns an error code. An OS/2 COPY command issued from within a REXX program, running from an OS/2 command line, that species a non-existent file name serves as an example of an event that will raise the ERROR condition. [#Figure 5 Figure 5] shows a working example of a REXX program that will raise the ERROR condition and display the status of the error on the console. The screen output from the program is included at the end of the program as a comment.
- Figure 5
mcopy a_b_c_d nul a_b_c_d SYS0002: The system cannot find the file specified. 0 file(s) copied. ERROR routine entered Condition(C) = ERROR Condition(I) = CALL Condition(S) = DELAY Main routine RC = 1
If your program was trapping the ERROR condition, you more than likely would want to either redirect or suppress the error message (shown in the shaded lines) from appearing. The error lines cold be redirected to: a) a REXX data queue, b) a file, or c) sent to the big bit bucket in the sky (ignored). [#Figure 6 Figure 6] shows the respective changes necessary to the COPY command to effect the redirection / piping of the data sent to STDERR as a result of the erroneous COPY command.
- Figure 6
/* a */ 'copy a_b_c_d nul 2>&1 | RXQUEUE' /* b */ 'copy a_b_c_d nul 2>COPY.OUT' /* c */ 'copy a_b_c_d nul 2>nul'
Also, to prevent the COPY command itself from echoing, precede it with an at sign (@) or redirect STDOUT (1>) similar to the example shown in [#Figure 6 Figure 6] for STDERR.
One of the most useful purposes of setting up trap routines is to identify uninitialized variables that result in a NOVALUE trap. Two external REXX APIs (Application Program Interfaces) available - REXXLIB (from Quercus Systems) and SuperSet/2 (from GammaTech) provide the facility to write all or some of the contents of the REXX variable pool (the variables you create in your program) to a file. When one of these APIs is used in conjunction with the trap processing routine shown in Listing 1, a file with the same name as the REXX program and a file extension of .dmp is created which contains the name and the contents of all of the initialized variables along with other information related to the trap. The .dmp file is created in the same directory where the program resides. In the event that the program is running from the REXX macrospace, the .dmp file is created in a directory pointed to by a temp environment variable. If the temp environment variable does not exist, the .dmp file will be created in the current directory of the current drive.
Program debugging is much more simplified with this information available to you after your program traps. Figure 7 shows the screen output from the routine shown in Listing 1.
- Figure 7
copy a_b_c_d nul a_b_c_d SYS0002: The system cannot find the file specified. 0 file(s) copied. -------------------------------------------------- | | | 9411LS01.CMD | | Error line = 12; ERROR error. Return code = 1 | | | | See: C:\os2-mag\9411LS01.DMP | | | -------------------------------------------------- Source line at time of trap: 'copy a_b_c_d nul' /* 0012 */
Listing 1 contains a CALL to the REXXLIB function VARDUMP() at line 107. However, this could be replaced with a call to any routine capable of dumping the REXX variable pool to a file. It is presumed that the DLL containing the variable pool dump facility has already been registered with the RxFuncAdd() built-in function.
This same technique can be very useful with a program that is looping. By making a comment of the assignment statement at line 53, the dump file will be created when the program is halted with <Ctrl-C>. The DROP instruction at line 106 precludes the variables used to setup the screen output, which are extraneous to your program, from showing in the dump file.
Creation of a .dmp file, as I described here, is also extremely handy when used with a series of REXX programs and external functions which are inter-related. In the event of a failure in any of the REXX programs, .dmp files will document where the error(s) occurred. You may chose any other file extension of your choice for these variable dump files. However, I have not found any conflict with .dmp with any other programs.
- Listing 1
/*------------------------------------------------------------------------*\ | | | 9411LS01.CMD - Generic Trap Processing Routine | | | \*------------------------------------------------------------------------*/ SIGNAL ON ERROR /* trap object time errors */ /* 0006 */ SIGNAL ON FAILURE /* trap object time errors */ /* 0007 */ SIGNAL ON HALT /* trap object time errors */ /* 0008 */ SIGNAL ON NOVALUE /* trap object time errors */ /* 0009 */ SIGNAL ON SYNTAX /* trap object time errors */ /* 0010 */ /* 0011 */ 'copy a_b_c_d nul' /* 0012 */ exit /* 0013 */ /* 0014 */ /*------------------------------------------------------------------------*\ | | | Trap Routines | | | \*------------------------------------------------------------------------*/ ERROR: call TRAP_PROCESSING SIGL, 'ERROR', RC /* 0020 */ FAILURE: call TRAP_PROCESSING SIGL, 'FAILURE', RC /* 0021 */ HALT: call TRAP_PROCESSING SIGL, 'HALT', '' /* 0022 */ NOVALUE: call TRAP_PROCESSING SIGL, 'NOVALUE', '' /* 0023 */ SYNTAX: call TRAP_PROCESSING SIGL, 'SYNTAX', RC /* 0024 */ /* 0025 */ /* Rev. 94/06/14 */ /* 0026 */ TRAP_PROCESSING: /* 0027 */ parse Source . . TRAP.path_and_program /* 0028 */ if POS( ':', TRAP.path_and_program ) > 0 then /* 0029 */ /* get source line if it is available */ /* 0030 */ do /* 0031 */ trap_source_line = STRIP( SOURCELINE(ARG(1)) ) /* 0032 */ end /* 0033 */ else /* 0034 */ /* program is running in macrospace */ /* 0035 */ do /* 0036 */ TRAP.path_and_program = VALUE( 'TEMP',, 'OS2ENVIRONMENT' ) ||, TRAP.path_and_program /* 0038 */ trap_source_line = 'Source line is not available.' /* 0039 */ end /* 0040 */ /* 0041 */ parse value FILESPEC( 'N', TRAP.path_and_program ) with, /* 0042 */ TRAP.fn '.' TRAP.fe /* 0043 */ trap_file_name = FILESPEC( 'D', TRAP.path_and_program ) ||, /* 0044 */ FILESPEC( 'P', TRAP.path_and_program ) ||, /* 0045 */ TRAP.fn || '.' || 'DMP' /* 0046 */ /* 0047 */ /*------------------------------------------*\ /* 0048 */ | check for reason not to create .DMP file | /* 0049 */ \*------------------------------------------*/ /* 0050 */ if ARG(2) = 'HALT' then /* 0051 */ do /* 0052 */ trap_file_name = '' /* 0053 */ end /* 0054 */ if RxFuncQuery( 'VARDUMP' ) <> 0 then /* 0055 */ do /* 0056 */ trap_file_name = '' /* 0057 */ end /* 0058 */ if POS( ':', trap_file_name ) = 0 then /* 0059 */ do /* 0060 */ trap_file_name = '' /* 0061 */ end /* 0062 */ /* 0063 */ /*------------------------*\ /* 0064 */ | Build trap message box | /* 0065 */ \*------------------------*/ /* 0066 */ dbl.h = 'CD'x /* double line - horizontal */ dbl.v = 'BA'x /* double line - vertical */ dbl.bl = 'C8'x /* double line - bottom left */ dbl.br = 'BC'x /* double line - bottom right */ dbl.tl = 'C9'x /* double line - top left */ dbl.tr = 'BB'x /* double line - top right */ say ' ' /* 0073 */ trap_error_description =, /* 0074 */ 'Error line = ' || ARG(1) ||, /* 0075 */ '; ' ||, /* 0076 */ ARG(2) ||, /* 0077 */ ' error.' /* 0078 */ if ARG(3) <> '' then /* 0079 */ trap_error_description = trap_error_description ||, /* 0080 */ ' Return code = ' || ARG(3) /* 0081 */ say dbl.tl || COPIES( dbl.h, LENGTH(trap_error_description) + 2 ) || dbl.tr say dbl.v || COPIES( ' ', LENGTH(trap_error_description) + 2 ) || dbl.v say dbl.v CENTER( TRAP.fn || '.CMD', LENGTH(trap_error_description)) dbl.v say dbl.v trap_error_description dbl.v if trap_file_name <> '' then /* 0086 */ do /* 0087 */ say dbl.v || COPIES( ' ', LENGTH(trap_error_description) + 2 ) || dbl.v say dbl.v CENTER( 'See: ' || trap_file_name,, /* 0089 */ LENGTH(trap_error_description) ) dbl.v end /* 0091 */ say dbl.v || COPIES( ' ', LENGTH(trap_error_description) + 2 ) || dbl.v say dbl.bl || COPIES( dbl.h, LENGTH(trap_error_description) + 2 ) || dbl.br say ' ' /* 0094 */ say 'Source line at time of trap:' /* 0095 */ say ' ' || trap_source_line /* 0096 */ say ' ' /* 0097 */ /* 0098 */ /*---------------------------------*\ /* 0099 */ | Create .DMP file if appropriate | /* 0100 */ \*---------------------------------*/ /* 0101 */ if trap_file_name <> '' then /* 0102 */ do /* 0103 */ call SysFileDelete trap_file_name /* 0104 */ /* remove meaningless labels from dump for clarity */ /* 0105 */ drop dbl. TRAP. RC RESULT SIGL !tr! /* 0106 */ call VARDUMP trap_file_name /* write variables to program.DMP file */ end /* 0108 */ exit 254