REXX Inside and Out
Written by Joe Wyatt
Several years ago I wrote programs in the SAS language to interrogate the MVS operating system's resources and configuration. Because of the interpretive nature of SAS, it was possible for one program to write new sections of code based on elements defined to the system, place them in temporary data sets, and then include them later in the program. Now instead of needing to keep static tables of resource information that were manually updated, I was able to provide a maintenance free application that could adjust itself to changes in the operating environment. I named the program SAS007 (007 as in James Bond) because of its sneaky nature. "You mean your program writes itself, executes, and then goes away so that there is no chance to debug it or see what was going on?", a co-worker asked one day while examining my program and slowly shaking her red head. "Slick, huh?", I boasted. She handed my program back, and with a chuckle she added, "I never really did like dynamically self-violating code myself."
I've learned quite a bit since that time, but the old temptations return when dealing with REXX and automation. "REXX is an interpretive language, right?", I ask myself. "Why not write some of that self-violating stuff that will adapt to the variations in the system? I could stuff some dynamic code into a file and then... No WAIT! I could put a bunch of code into a buffer and invoke the INTERPRET instruction! Yeah. That's the ticket!" Usually it's about now that reality hits me and I realize that even though the language supports it, it might not be such a good idea to use it.
Although we will discuss the INTERPRET instruction, this column is dedicated to the avoidance of "dynamically self-violating" code.
INTERPRET is one of those statements that if you need it you have to have it, but you pray that you will never need it. At least that is what I have heard. In my seven years of REXX programming, although I have played with INTERPRET, I have never "needed" the instruction badly enough to use it in a production level program. The statement usually renders the program unmaintainable by anyone but its author unless the author has slept since the program was written. (if the author has slept since the program's conception there is a great chance that he/she will be unable to easily update the code) "Couldn't you just fully document the use of this instruction in your program so that its intended purpose is apparent?", you ask. "Well... I have to believe that anybody who would not find away around using this instruction would not bother to comment their code." (That last statement was placed in quotes because it is certain that there are people out there who have had a valid need for this instruction and have taken great pains to comment the reasons for its use. Call it a gross generality if you would like.) There is a further reason to refrain from the use of the INTERPRET instruction. The IBM compilers for REXX (no, they don't exist for OS/2 at this time and may never) do not support it.
So why am I spending time telling you about an instruction that is at least as wicked as a GOTO? Can you say "Quick and Dirty programming?" I thought you could. When developing one shot tools or tools for self use this can solve some problems.
While an entire program section can be placed in a buffer and used dynamically with the INTERPRET instruction, our example will only interpret one instruction at a time.
/* quick and dirty calculator */ x = 0 /* initialize displayed variables */ y = 0 z = 0 /* display variable content and availability */ Say "x =" x Say "y =" y Say "z =" z call charout ,"===> " /* input prompt */ pull stuff /* get equation */ do while (stuff <> "EXIT") INTERPRET stuff /* evaluate equation */ Say "x =" x Say "y =" y Say "z =" z call charout ,"===> " pull stuff end exitFigure 1: A simple example of INTERPRET.
The above program accepts user input such as "x = y + 56 / 8.3", evaluates the expressions with the INTERPRET instruction, displays the results, and gathers more input until the word "exit" is entered; or the word "barf" is entered; or the phrase "I'll bet you can't handle this input" is entered. This program would be quite a bit longer were it to take the time to evaluate the equation on its own instead of using INTERPRET. There is one other interesting feature of this bullet proof code that is a bit off of the topic that should be noted here.
Many people desire a prompt line before their "pull" instruction. This is accomplished with the use of charout() with a null parameter for the file name.
It was mentioned earlier that an entire program section could be placed into a variable and included as part of your program with the INTERPRET instruction. To accomplish this nasty habit, each statement must be delimited by a semicolon.
: buffer = "Var" || count "=" count"; say Var" || count INTERPRET buffer :
The above lines assign a value to a variable created by the text "Var" and the value of the variable "count" and display the contents of this new data. This example was included to show multiple program statements within a single INTERPRET, but also included to show a common misuse of this instruction. REXX contains a function that will perform this variable manipulation without the smoke and mirrors of INTERPRET.
The value function has three arguments by definition:
target = Value( variable_name, new_value, selector );
variable_name - since the name of a variable is an argument, variables can be created dynamically. The previous use of INTERPRET created a variable, "Var" || count. Value() could be used to create this variable by passing the same information in the first argument.
new_value - the variable created or addressed by the first argument is assigned whatever value is referenced by this argument.
selector - this is a special argument and for basic variable manipulation should contain a null value. As far as I can determine, the only valid non-null value for this argument is OS2ENVIRONMENT. When OS2ENVIRONMENT is used, the current session environment is interrogated to solve the value request.
current_path = Value("PATH", , "OS2ENVIRONMENT")
The above call to value() would retrieve the value of the session's current "PATH" environment variable but would not change its value because the new_value argument was left blank.
returns - value() returns the value of the variable referenced by the first argument before the new value is assigned.
The last INTERPRET example could have been replaced with the more efficient and compiler friendly
: call value "Var" || count, count Say value("Var" || count) :
A further example:
: /* get list of available DASD */ drives = SysDriveMap("C:", "USED") do words( drives ) /* loop through drive letters */ parse var drives singleton drives /* get disk drive information */ info = SysDriveInfo( singleton ) /* create var with disk name */ call value word(info, 4), word(info, 2) end :Figure 2: A simple example of Value().
The above program segment creates a variable for each disk drive starting with C: and assigns it the value of the number of freespace bytes left on the drive. This is a less contrived use of Value(), but it still is too complex.
While Value() is certainly a nifty little function, the only use of it that can be found in programs that leave my machine are to reference the OS/2 environment. REXX's compound variable structures are powerful enough to thwart any of my contrived "needs" for the value() function.
The previous Value() example could have avoided any confusing use of the Value() function by being coded as follows:
: drives = sysDriveMap("C:", "USED") do words( drives ) parse var drives singleton drives info = SysDriveInfo( singleton ) drive_name = word( info, 4 ) drive_free_space.drive_name = word( info, 2 ) end :Figure 3: Cleaning up the Value() example.
Four out of five programmers agree that this example is clearer than the last Value() example. "But gee, Uncle Bob, aren't compound variables slow?", you might ask.
REXX's variable pools are kept in efficient tree structures so that their manipulation does not slow down the program. If speed is really a concern it might be noted that this example also eliminates a function call!
REXX is a powerful language that can support all of the bad programming practices of my youth. "Dynamically self- violating code" is even easier to create in REXX than it was in the SAS language. So why not go let all of this power go to my head and just start including INTERPRET statements and Value() calls any place I think it might be "neat"?
Gather around, Kiddies, and let old Uncle Bob tell you a story. Remember SAS007? That slick piece of code ran daily for a couple of years after I left the company for which it was written. One day (ok, probably over a weekend) the version of MVS was upgraded a to a new version (MVS/XA). This version changed the location of some of the information that SAS007 was gathering. Instead of being able to make some simple code changes to update the program to run with the new operating system version it was labeled as spooky and tossed aside like a useless piece of garbage. So all of the time that the company had invested for me to write that program was wasted because it had to be written again.
As we've seen here, straight forward code can even be more efficient than "slick" code. Most programmers not only desire their work to be efficient, but long lived. Let's face it. Our programs are the main tools a programmer has to make a name for himself. Slick code will make a name for the programmer. What will that name change to when the code breaks and the company that paid for its development gets to keep both pieces?