Welcome new REXX Users
With the huge increase in new OS/2 users because of the excitement and popularity resulting from Warp, I have decided to discuss some of the basics in using REXX for the benefit of those users who have never had any exposure to the language. My apologies to you "seasoned REXX veterans". However, I have included a caveat at the end of the column covering an undocumented facility in Warp that may affect your REXX programs.
REXX is a very powerful interpreted language with its origin in the IBM mainframe world - specifically VM/CMS. It was created by Mike Cowlishaw of the IBM Hursley Labs and first released in the early 1980s. REXX has gained wide acceptance since then. REXX is now available on all IBM operating system platforms and, except for those functions designed uniquely for a given platform, will run on every IBM operating system including both PCs and mainframes.
OS/2 REXX programs have a file extension of .cmd as do OS/2 procedural command files (like .bat files on DOS). However, what distinguishes a .cmd file between the two is that a REXX program must begin with a slash asterisk pair of characters (/*) beginning in position one of line one of the file. The absence of the "slashterisk" pair will result in the command processor program (cmd.exe) processing the statements contained in the file as if they were simply command line instructions. When the .cmd file begins with a /*, cmd.exe starts the REXX interpreter and passes control to it. REXX programs may be run in either windowed or full-screen sessions. REXX programs may also be "started" or run "detached".
If REXX programs are to be run in a PM session, they must be started with the PMREXX program if they issue any I/O commands to or from the console (monitor or keyboard).
Because REXX is a free form language, you can write programs with it very quickly and easily. Fragment 1 contains the usual example that you see as an introductory REXX sample program.
- Fragment 1
/* */ say 'Hello!' exit
If you create a .cmd file with the three lines from Fragment 1 and then enter the name of that .cmd file on a command line, you will have written and executed your first REXX program.
Fragment 2 contains a more practical program whose function is to ask for the drive letter where the config.sys file will be found. It then proceeds to read each line in config.sys, increment a count of the number of lines that have been read and write that line to the printer attached to lpt1. Finally, it does the housekeeping of closing the two files that were used and writes the total of the number of lines which had been read to the console. While it is possible to use a REXX function to determine the drive that contains config.sys, I have not used it here intentionally.
- Fragment 2
/* 9503LS02.CMD - List CONFIG.SYS on LPT1 */ /* 0001 */ Say 'Listing CONFIG.SYS on LPT1' /* 0002 */ Say 'Please enter drive letter of CONFIG.SYS' /* 0003 */ pull drive_letter /* 0004 */ /* 0005 */ config_sys_file_name = drive_letter || ':\CONFIG.SYS' /* 0006 */ if STREAM( config_sys_file_name, 'C', 'QUERY EXISTS' ) = '' then /* 0007 */ do /* 0008 */ say 'Unable to locate' config_sys_file_name /* 0009 */ exit /* 0010 */ end /* 0011 */ /* 0012 */ form_feed = D2C(12) /* 0013 */ input_line_count = 0 /* 0014 */ do while LINES(config_sys_file_name) > 0 /* 0015 */ input_line = LINEIN(config_sys_file_name) /* 0016 */ input_line_count = input_line_count + 1 /* 0017 */ call LINEOUT 'LPT1', input_line /* 0018 */ end /* 0019 */ call CHAROUT 'LPT1', form_feed /* 0020 */ /* 0021 */ call STREAM config_sys_file_name, 'C', 'CLOSE' /* 0022 */ call STREAM 'LPT1', 'C', 'CLOSE' /* 0023 */ /* 0024 */ Say input_line_count || ' lines listed' /* 0025 */ exit /* 0026 */
- Figure 3 - An explanation of the instructions used in the program shown in Listing 2.
- The presence of a comment, beginning in position 1 of line 1 tells cmd.exe that this is a REXX program rather than a procedural .cmd batch command file.
- As in the Say Hello example, these two lines write messages to the console. The PULL instruction then waits for you to key in a letter and press <Enter>.
The value entered is "assigned" to the "variable" drive_letter. An assignment clause is one of the most commonly used type of REXX clauses. A variable is a symbol whose value may be changed during the course of execution of a REXX program.
- Another assignment is made, only this time it involves assigning two values together to the variable config_sys_file_name or "concatenating" the value contained in the variable drive_letter to the a value of ':\CONFIG.SYS' (a "literal").
If you had entered a lower or upper case C when the program asked for a response, the resulting value in config_sys_file_name would be 'C:\CONFIG.SYS'. The apostrophes, or loosely-called quotes, are not included in the actual value. The PULL instruction has an implied function of uppercasing the value when it is read from the keyboard.
- This group introduces a number of REXX instructions and clauses. Its purpose is to check to make sure that the file actually exists. If it does not, then the user is informed via the message "Unable to locate ?:\CONFIG.SYS", where ? represents the letter the user entered, and the program is terminated.
- REXX has a rich collection of "functions" which are used for different purposes. STREAM() is one such function and it has a number of options that make it perform differently. In this case, we are using the function in an "expression" and testing the evaluated expression to see if it is equal to a null - the value which is returned if the file does not exist.
This is similar to using IF ... EXISTS in a DOS or OS/2 batch command file. The test is made using the IF THEN combination and the group of instructions between the DO and END instructions is executed if the evaluation is equal or true.
- A message, indicating the reason the program is terminating, is written to the console and control is returned to CMD.EXE with the EXIT instruction.
- The two assignments place the value of a printer form feed and a numeric value of zero in the variables form_feed and input_line_count respectively.
D2C() is another built-in REXX function. It is used to convert a decimal value to its actual character value. In this case a decimal value of 12 results in the pre-defined form feed control character of '0C'x.
- This group of instructions, bounded by DO and END instructions similarly to the group in lines 0008 - 0011, reads a line from config.sys and assigns the value of that line to the variable input_line, increments a tally of the number of lines written and then writes that line to the printer.
The use of the LINES() function in the conditional statement in line 0015 causes this group of instructions to iterate until all of the lines in CONFIG.SYS have been processed. At that time, the DO loop is complete.
- To keep the listing neat, a form feed character is sent to the printer to cause a continuous forms printer to skip up to the beginning of a new page or a laser printer to eject the printed page.
Note that the actual printed line was sent to the printer with the LINEOUT() function whereas the form feed character is sent to the printer with the CHAROUT() function. The difference is that LINEOUT() cause the line to be written to the output file or device with a terminating CR/LF pair (carriage return / line feed) combination. CHAROUT() sends only the specified characters without appending anything to the data.
- The housekeeping function of "closing" both the input and output files (yes, the printer is handled just like a disk file except that a form feed will not cause your hard drive to skip to a new page).
- The number of lines that were processed is written to the console. Line 0025 uses "abuttal" concatenation to cause the two values, the variable input_line_count and the literal value, to be written to the console.
The use of the double bar or abuttal concatenation prevents a space from being inserted between two values which is what would occur if the || were not there.
The blank lines which contain just a sequence number in Figure 3 were inserted simply to make the program more readable.
As you can see, writing a simple REXX program is not a difficult task. The secret to making REXX really perform for you is knowing the available functions that are at your disposal. The on-line REXX information file contains the definition of most of the functions contained in OS/2 SAA REXX and its REXXUTIL external function module. A complete reference for all of the REXXUTIL functions can be found in the [../rrsh-4/welcome.htm REXX Reference Summary Handbook].
Because of its simplicity, REXX is very easy to abuse. I recommend to both new REXX programmers as well as veterans that they take the extra time and effort to write all of their REXX programs as if they were intended to be read by others. Use meaningful labels for variables rather than abbreviations that will be meaningful to you but that will be undecipherable by anyone else.
Use appropriate spacing and column alignment to make your program more pleasant to the eye. This makes it more comfortable for a stranger to follow the logic of your program. Don't skimp on comments that provide an overview for groups of related instructions.
In summary, your programming style can sometimes be as important as your program functionality.
Undocumented Warp function
A pair of environment variables were added to Warp but are not contained in the Warp online documentation. SET BeginLibPath= and SET EndLibPath= provide the capability to dynamically alter the current session's value of LIBPATH as it is defined in config.sys. Both SET statements are followed by one or more path names which are to be inserted into the beginning and end of the LIBPATH string for the current session, respectively.
These two environment variables are handled uniquely by cmd.exe. This fact has special significance to a REXX program that wants to either set, or retrieve and parse these two variables.
First, though both of the environment variables can be set using the VALUE() function, they will not serve their intended purpose since the VALUE() function was not updated to include the special use intended for these values. Therefore, contrary to the recommended use of a REXX function to set an external value (rather than passing the equivalent command to cmd.exe); there is no alternative if it is necessary to set either environment variable from within a REXX program other than to pass the SET command to cmd.exe.
Secondly, the string returned from SET BEGINLIBPATH will not contain any spaces before or after the equal sign (SET BEGINLIBPATH=?:\...) whereas the result returned by a SET command by itself will contain a space both before and after the equal sign (SET BEGINLIBPATH = ?:\...).
The incompatibility of the VALUE() function is a program defect which I have reported to the appropriate group in IBM.
In future columns I will cover many of the features that experienced REXX users take for granted as well as some of the lesser known capabilities of REXX and the external function packages available for OS/2 REXX use.