OK, I wrote it, now what?
The simple tasks that many OS/2 users began doing in REXX have shown them how easy it is to use the language. Now programmers and non-programmers alike are relying on REXX as their primary language for OS/2. As programs increase in size and complexity, making them do what you want them to do, rather than what you told them to do, becomes a more difficult task. This process is known as debugging and I'm going to discuss some techniques that should help you find out what's going wrong when your programs don't work. Also I have some tips on things you can do to make it easier to both prevent careless errors from causing you hours of grief and make it easier to find errors when they occur.
The first kind of error you have probably run into is seeing your program scroll by on the screen producing error messages like:
C:\REXXPROG end_1 = TIME('E') SYS1041: The name specified is not recognized as an internal or external command, operable program or batch file.
It probably didn't take you too long to realize that the REXX interpreter never saw your program because the first two positions of the first line did not contain the slash-asterisk pair (/*) that defines a .cmd file as being a REXX program. The key here is that the error message begins with SYS. All REXX error messages begin with REX followed by a numeric value that specifies the error number. Very often the cryptic message that accompanies the error is very much help in telling you what's wrong other than identifying the line number where the error occurred; at least most of the time.
The first thing to do when you see a REX error that you don't understand is to invoke HELP for that error. The command, issued from any OS/2 session command line, is:
where n is the numeric value following the REX error prefix. Leading, or high order, zeroes are not necessary. Entering help rex6 is the same as help rex0006. Since the numbers assigned to the REXX error messages are reasonably standard across all platforms where REXX is used, some of these messages can be vague when applied to an OS/2 environment.
These error messages are contained in the *.msg files that are generally found in your \os2\system directory. REXXUTIL, the Application Program Interface (API) that is a standard part of OS/2 contains the SysGetMessage() function that allows you to extract both the message data and an explanation of the message. At the same time, you can replace variable data in the message with successive strings specified in the call to SysGetMessage(). The easiest way to see how this works is using the REXXTRY program on your system.
If you are not familiar with REXXTRY, it is a REXX program located in your \os2 directory that allows you to enter single or multiple REXX commands on a single line. Multiple commands must be separated by a semicolon. The commands are carried out immediately. Values resulting from the command can be displayed with the SAY instruction. The value returned by a function can be seen by entering:
REX0006: %1Unmatched "/*" or quote%2
which isn't quite what you see when you have an unpaired quote in your program. The error message you are accustomed to seeing is:
REX0006: Error 6 running J:\REXXPROG\TEMP.CMD, line 2: Unmatched "/*" or quote
This results from the substitution capability of the message facility referenced with SysGetMessage(). Each of the fields which begins with a % in the message is successively replaced with a string provided with the call to the function. That is how the raw message is transformed into the message you actually see. The \os2\system\rex.msg file which contains the actual error messages has a corresponding rexh.msg file which contains the explanations you see when you invoke help for the error message as I described at the beginning. The rexh.msg file can also be accessed with the SysGetMessage() function. [#Fragment 1 Fragment 1] contains a program that will create an ASCII file with all of the messages and corresponding explanations from the rex.msg and rexh.msg files. The substitution fields will not be replaced.
Because of the way that the REXX interpreter works, as soon as an error is encountered, you are out of your program and back to the command line (assuming you started your program from the command line) with very little information beyond the error message and sometimes an explanation. What you do at this point is the main purpose of this column. My comments and suggestions are aimed at those problems which are REXX-related rather than logic errors within your programs.
Since REXX is an unstructured language which does not require you to declare (define) the variables you use in advance of using them, you can be more productive than with a structured language; however you can also get into some troublesome situations because of this flexibility. The primary method of overcoming this dichotomy is the use of the NOVALUE condition handler built into REXX.
The condition handlers in REXX are enabled with the SIGNAL instruction. Enabling the NOVALUE condition handlers results in the transfer of program flow to a routine named NOVALUE:, or a routine name of your choice if the NAME option of the signal instruction is used, when reference is made to a variable that has not been initialized. What you do in your condition handler routine is up to you. When the condition handler routine gets control, the SIGL special variable contains the line number of the instruction that caused the event. In my own programs, when a NOVALUE event occurs, I want to know the source line that caused the event along with a list of all of the variables used in the program. This objective isn't very different from the information that I want in the event that any of the other capturable conditions: ERROR, FAILURE, or SYNTAX occur. While the source lines and a variable dump aren't necessarily enough to determine the error, it certainly is a start.
The source line data is retrieved using the SOURCELINE() built-in REXX function and passing it the line number obtained from the SIGL special variable. Capturing the contents of all of the variables in your program requires an additional API. REXXLIB, from Quercus Systems, contains the VARDUMP() function. [www.softouch.com REXX SuperSet/2], from GammaTech, contains the GrxDumpVarPool() function, and Dave Boll's [../pub/welcome.htm#rxu YDBAUTIL (RXU)] contains the RxVList() . All three of these APIs allow you to retrieve the contents of the variable pool, which you can then write to a file.
Since finding a bug in a bug handling routine is very frustrating, I recommend that you create your own skeleton routine, or adopt someone else's, and use that skeleton as the beginning of every REXX program you write. That way you can be confident of not having a bug within the bug handler and gain some comfort level in using it. Many people have made their own versions of these skeleton programs available on BBSs and other electronic repositories. My own personal routine that I use to start every new REXX program I write is too large to include in this column. However, it is available via anonymous FTP or with your Web browser This file happens to use REXXLIB but could easily be changed to use any of the other APIs mentioned here. My own convention for dealing with the output of the variable pool is to write it to a file with the same name as the program and a file extension of .dmp.
One drawback to writing the variable pool to a file when the NOVALUE condition occurs is that you will only see those variables that are exposed at that point in the program. If your program contains separate procedures, resulting from using the PROCEDURE instruction, the unexposed variables are not available. One method of circumventing this is to make all of the common variables part of a compound, or stem, variable and exposing the stem to all of the procedures. In my own skeleton, I use the stem name of GBL. and expose GBL. in all of my separate procedures.
In many cases tracing your program is necessary to see what is actually has occurred. I'll leave it to you to read about the details of the TRACE instruction in whatever REXX reference you rely on. There is even an environment variable, RXTRACE, that when set to ON will make your program act as if a TRACE ?R instruction was the first instruction of the program. If STDERR is redirected for a program started with the RXTRACE=ON environment variable (the program is started with 2>file_name), the effect is as if TRACE R was the first instruction in your program. This works well for short programs. However, you certainly don't want to trace through hundreds or thousands of instructions to get to the failure. Even though you can insert a TRACE instruction at a critical point in your program, you then have to edit the program source to remove the trace instruction.
By using a trace instruction that gets its settings from your own environment variable, you can eliminate this hassle. I use an environment variable of TRACE in my own programs and have a group of instructions, all written as a single line with the instructions separated with semicolons, that I move around in the program as I need it. When I want it out of the way, I move the entire line to a position in the program where program flow will never reach it. I call this instruction my "magic" trace instruction and it lies dormant in skeleton.cmd until I need it. I can then move it to the point where I want it and run the program with an environment variable of SET TRACE=?R to cause interactive tracing. I then reset the environment variable with SET TRACE= to negate the trace.
/* 9607ls01.cmd */ call RxFuncAdd 'SysLoadFuncs', 'REXXUTIL', 'SysLoadFuncs' call SysLoadFuncs rex_msg_file = 'rex.msg' rexh_msg_file = 'rexh.msg' output_file = '9607ls01.txt' call SysFileDelete output_file do i = 1 to 125 msg_data = SysGetMessage( i, rex_msg_file ) msgh_data = SysGetMessage( i, rexh_msg_file ) call LINEOUT output_file, msg_data call LINEOUT output_file, msgh_data end call STREAM output_file, 'C','CLOSE' exit