The Joy of REXX

From EDM2
Jump to: navigation, search

A Programming Article/Book Review by Dan Bridges

This article originally appeared in the September 1995 issue of Significant Bits, the monthly magazine of the Brisbug PC User Group Inc.

The Problem

Some users only use computers to run a set of commercial applications. Other users start down the road to programming by writing batch files, using batch-enhancement programs, writing macros for commercial applications or look further "under the hood" in the process of experimenting with QBASIC/Visual BASIC/DEBUG. When I switched over to OS/2 v2 a few years ago, I was initially disappointed by the lack of an easy route to further knowledge.

It irks me that I've got a fair understanding of DOS and its internals but am almost completely ignorant of what's going on under OS/2's hood. There seems to be a significant gap between OS/2 introductory "kiddie" books and OS/2 programming books. Works like "OS/2 2.1 Unleashed" try to go a little further but leave out so much detailed info it's maddening.

I thought I'd take the plunge into OS/2 programming so I bought CA-Realizer for Windows & OS/2 and C Set++ First Step for OS/2. Now the OS/2 side of CA-Realizer appears to be a mirrors port of the Windows version so it's slow and limited (it doesn't support OS/2 features like threads), while I found that C Set++ First Step "assumes you have a working knowledge of the C or C++ programming language and the OS/2 Operating System" which I don't have yet.

The Solution

An obvious route for the OS/2 enthusiast to take is REXX programming. REXX is a multi-platform interpreted programming language that is appropriate for writing batch command procedures. It can also be used as a macro language for many editors, databases, word processors and communications packages. It was written by Mike Cowlishaw of IBM UK in 1979 for use on the VM mainframe operating system. The REXX language was created as a successor for the existing VM command languages, EXEC and EXEC2, hence the name: REXX (REstructured eXtended eXecutor language). Mark Hessling (creator of the THE editor) recently told me that REXX is one of the 18 languages that has achieved ANSI certification. Both SAA (IBM) REXX and ANSI REXX are based on Mike Cowlishaw's book and I believe that the differences between them are minor.

REXX programs have a .CMD extension, just like regular OS/2 batch files. The way that the command processor (normally CMD.COM) knows that it is dealing with REXX commands is the presence of a comment (/* This is a REXX comment */) in column 1, row 1 of the .CMD file. Note that the comment can be empty (/**/). When a comment is seen in this position the command processor invokes the REXX interpreter. When this interpreter parses an instruction it first looks for an internal keyword match (Figure 1)

Figure 1.

/* FIG1.CMD - internal REXX instruction example */

SAY "Hello"		/* Hello */
myName = "      Fred Nerk  "
SAY myName              /*      Fred Nerk     */
SAY			/* Produces a blank line */
SAY "Hello" myName      /* Hello    Fred Nerk     */
               /* Strip() is an in-built function */
SAY "Hello" Strip(myName)    /* Hello Fred Nerk */
SAY Strip("00123.40100",,0)  /* 123.401 */

and then for a matching "internal" OS command or for a matching external executable program (Figure 2).

Figure 2.

/* FIG2.CMD - Running a OS2/DOS command or ext. program */ 

'@DIR /OD'      /* "@" is the standard DOS/OS2 way of 
                   not echoing a command to the screen */ 

                /* The inverted commas avoid misinterpretation. 
                   Just DIR will also work but an unquoted @DIR 
                   causes an error */

PAUSE		/* Unquoted OS2/DOS command */ 

When a procedure name (a subroutine or user-defined function) is used the interpreter first looks for a matching label in the current program (Figure 3).

Figure 3.

/* FIG3.CMD - example of a user-defined function */ 

SAY Square(10)  /* An argument of 10 is passed to the function */
EXIT            /* Stop execution here so it won't continue on and run
                   through the function again */

Square:         /* A label name */
ARG Parm1	/* Assign the argument to the Parm1 var */
RETURN Parm1 * Parm1  /* Return this calculated value */

If this search fails it next looks for a built-in function (Strip() in Figure 1). This search order means you can override internal functions with your own creations, if you wish. If this is still no success in the search REXX looks at external functions whose presence it has been made aware of (Figure 4).

Figure 4.

/* FIG4.CMD - Running a procedure in an external 
   DLL Dynamic Link Library) */ 

CALL RxFuncAdd 'SysTextScreenSize', 'RexxUtil', 'SysTextScreenSize'
/* Adds the SysTextScreenSize() external functions situated in
   C:\OS2\DLL\REXXUTIL.DLL to the list of available REXX funcs */

SAY  SysTextScreenSize()              /* In this example: 43 80 */

PARSE VALUE SysTextScreenSize() WITH rows columns
/* Use the powerful PARSE instruction to split up the returned 
   string into two variables we've chosen to call rows & columns */

SAY "Columns:" columns "        Rows:" rows
/* Output: Columns: 80         Rows: 43 */

These external functions can either be part of the REXXUTIL.DLL library that comes with OS/2 and IBM PC-DOS v7 (due to the multi-platform support you should have little trouble running these programs under this version of DOS) or part of a third-party commercial, shareware or freeware REXX routine library. Finally, before reporting an error, REXX searches for an external .CMD with the same name as the procedure (Figures 5a & 5b).

Figure 5a.

/* FIG5A.CMD - Demonstrates the use of a separate REXX 
   cmdfile to house an external procedure */

SAY Fig5b('thIs is a test STRING')
SAY Fig5b.cmd('thIs is a test STRING')
SAY 'Fig5b.cmd'('thIs is a test STRING')
SAY 'D:\Joy of REXX\Fig5b.cmd'('thIs is a test STRING') 

/* All of the above produce: 'This Is A Test String' */

Figure 5b.

/* FIG5B.CMD - Converts a string to first letter uppercase */

ARG origString
/* There aren't in-built UpperCase() & LowerCase() functions.
   Here is a method to use an in-built function to convert
   to upper case. Included here for demo purposes

x = Translate(origString)  /* THIS IS A TEST STRING */ 

/* To convert to lowercase we need to create a string containing all
   uppercase letters, another string containing all lowercase letters
   and then substitute one for the other. Uses in-built functions. Note:
   there is no need to convert the chars that are already lowercase

upperCaseList = XRange('A', 'Z')  /* 26 uppercase letters */
lowerCaseList = XRange('a', 'z')  /* 26 lowercase letters */
convertedList = Translate(origString, lowerCaseList, upperCaseList)
/* this is a test string */

outputString =        /* Ensure initial work string is empty */ 

/* In the following, Words(string) returns the number of words in the
string. Word(string, n) picks the nth word in the string. Left(string,
1) returns the first letter and Translate(string) uppercases it.
Substr(string, 2) returns the rest of the word (from position 2
onwards). The comma at the end of a line indicates that the expression
is continued on the next line.

There are two types of string concatenation. Say you have two string
variables A ("A") and B ("test") . A B becomes "A test" with a single
intervening space, as would: A   B.

Concatentation is possible without a space by using either abuttal:
   AB  becomes "Atest"

or by using the concatenation (||) operator:
   A || B becomes "Atest"

There will be a space at the beginning of the resultant outputString due
to the first type of concatenation operation. This space will be needed
to separate the remaining words. 

Note: the algorithm used in this routine results in only a single space
between words, regardless of the initial number of spaces. For most
purposes this will be acceptable.

DO i = 1 TO Words(convertedList)
  currentWord = Word(convertedList, i)
  outputString = outputString Translate(Left(currentWord,1)) ||,

outputString = Strip(outputString)  /* Remove initial space */
RETURN outputString      /* Return this string to calling cmdfile */

The emphasis with REXX is on flexibility, convenience and speed of creation, rather than on blinding execution speed. However, with OS/2, some attempt is made to improve matters by storing a tokenised version in extended attributes that are associated with the cmd file. The first time you run a cmd file (or after modifying it) there will be a slight delay while the tokenisation takes places. This will speed up subsequent interpretation speed. You can see that EAs are used by doing a DIR on a already-run cmd file. (If you're operating on a FAT partition use DIR /N.)

The Book

I initially found REXX difficult to learn in OS/2 due to a lack of a guided tutorial. There is a good REXX .INF reference included but this is not the best way to pick up a new language. I've just finished "Teach Yourself REXX in 21 Days", William F. Schindler & Esther Schindler, Sams 1994, ISBN 0-672-30529-1. It has 527 well-filled Quarto-sized pages and costs $65.95 (Australian) before discount. I bought my copy from McGills Technical Books, 201 Elizabeth St, Brisbane, Australia. They offer a 10% discount off all computer books to members of a computer club.

This book is a good blend of the simple and the advanced. From Day 15 onwards some quite advanced topics are covered and it is evident that the authors are talented OS/2 REXX enthusiasts. Many interesting and useful programs are presented.

Advanced topics covered include: extended attribute reading and writing; interprocess communications; a look at three commercial visual REXX packages; the use of REXX as a macro language for other programs; the creation of new menu items and functions, using REXX, in OS/2's Enhanced Editor (EPM); creating and manipulating Workplace Shell objects.

The book is well organised, sticks to the topic-a-day paradigm, and the authors write in a educational manner. Recommended as a worthwhile purchase.

As an example of the both the power of REXX and what the book has to offer I'll present a program to dump a binary file like DEBUG does. The aim will not be to teach you all about REXX but rather to discuss an interesting REXX program. If, like me, you find it rather intriguing, it's hoped that it will stimulate you to explore REXX further . The program is based on a Day 15 program (LIST1503.CMD). I've simplified, improved and extended it but what shines through is the smartness and economy of the Schindler's original design.

Figure 6 presents the program, FILEDUMP.CMD and Figure 7

Figure 7.A dump of part of CMD.EXE on a 25-line screen. Produced by the FileDump.cmd REXX program.

0D00:  21 00 50 00 50 00 52 00 52 00 57 00 FF FF 6E 00  !.P.P.R.R.W...n.
0D10:  6E 00 70 00 70 00 00 00 00 00 4F 53 4F 30 30 31  n.p.p.....OSO001
0D20:  2E 4D 53 47 00 2B 3A 0A 0D 20 00 26 3C 7C 3E 00  .MSG.+:...& <|>.
0D30:  4F 53 4F 30 30 31 2E 4D 53 47 00 00 2E 00 2E 2E  OSO001.MSG......
0D40:  00 00 00 00 01 10 00 01 00 00 01 00 11 01 26 00  ..............&.
0D50:  7C 7C 00 26 26 00 7C 00 40 00 28 00 29 00 3D 3D  ||.&&.|.@.(.).==
0D60:  20 00 20 49 46 00 20 46 4F 52 00 44 49 52 43 4D   . IF. FOR.DIRCM
0D70:  44 00 44 49 52 43 4D 44 00 25 31 30 6C 75 00 25  D.DIRCMD.%10lu.%
0D80:  39 6C 75 00 25 38 6C 75 20 4B 00 25 39 6C 75 00  9lu.%8lu K.%9lu.
0D90:  25 32 36 6C 75 20 4B 00 25 32 38 6C 75 00 2E 00  %26lu K.%28lu...
0DA0:  2E 2E 00 20 00 5B 25 73 5D 25 2D 2A 73 00 25 2D  ... .[%s]%-*s.%-
0DB0:  2A 73 00 25 38 6C 75 20 4B 00 25 39 6C 75 00 25  *s.%8lu K.%9lu.%
0DC0:  31 30 6C 75 00 25 39 6C 75 00 5C 00 5C 00 25 32  10lu.%9lu.\.\.%2
0DD0:  36 6C 75 20 4B 00 25 32 38 6C 75 00 2A 00 2A 00  6lu K.%28lu.*.*.
0DE0:  20 20 20 25 73 00 25 31 30 6C 75 00 5C 00 2A 00	%s.%10lu.\.*.
0DF0:  2A 00 2E 2A 00 5C 00 2A 00 2E 00 2E 2E 00 46 41  *..*.\.*......FA
0E00:  54 00 00 2A 3F 00 2A 3F 2E 00 5C 2A 00 25 39 64  T..*?.*?..\*.%9d
0E10:  00 25 30 32 64 00 25 64 00 25 30 33 64 00 25 31  .%02d.%d.%03d.%1
0E20:  64 00 25 73 00 25 63 00 25 63 00 25 73 00 00 25  d.%s.%c.%c.%s..%
0E30:  39 64 00 00 00 FF 00 00 00 50 52 4E 00 5C 70 69  9d.......PRN.\pi
0E40:  70 65 5C 00 25 39 64 00 00 4E 65 73 74 65 64 20  pe\.%9d..Nested
0E50:  73 69 67 6E 61 6C 20 68 61 6E 64 6C 69 6E 67 08  signal handling.
0E60:  00 01 00 00 01 00 EE 00 55 6E 6B 6E 6F 77 6E 20  ........Unknown
--- Press any key for more ---

shows its output for a 25-line screen-height session. The program is commented much more heavily than would normally be the practice; so much so that it's interfering with readability. I've done this to assist new chums. Type in the ungrayed part of Figure 6, namely lines 1, 5-6, 17-34, 35-37, 39-48. These 34 lines incorporate the guts of the program and a lot is achieved for such a relatively small number of lines of code.

I've included the first comment in the line numbering since its presence in column 1, row 1 is vital for the operation of the program. The SIGNAL keyword instruction in line 6 is the REXX equivalent of a GOTO/JUMP. Line 16 demonstrates the wraparound behaviour of the XRange(starting point, ending point) function when the start is a higher ASCII character than the end. By the way, a printable sequence of characters could be quickly generated with printableChars = XRange(" ", "~").

The Investigation

To experiment with the value of expressions such as the one in line 17, type them in either as command-line parameters of, or at the interactive prompt of, the REXXTRY.CMD program that comes with OS/2. This is a multi-platform program that can be used to evaluate REXX expressions. For example: REXXTRY SAY XRange(" ", "~"). See Figure 8 for the result.

Figure 8. How to evaluate an expression from the command line using REXXTRY.CMD

[D:\JOY OF REXX] REXXTRY SAY XRange(" ", "~")

 !"#$%&'()*+,-./123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ [\]^_`
...................................... REXXTRY.CMD on OS/2

And for a very simple alternative to REXXTRY.CMD try LIST1602.CMD from the Schindler's book (only 13 lines!).

Another way to delve into what's going on it is to invoke tracing within the program itself. This is large topic but try adding a TRACE ?R line after, say, line 23. Execution will enter interactive mode. Each press of Enter on a blank line will execute another clause, showing the result. You can also issue commands like SAY spacedList. See Figure 9 for an example.

Figure 9. A TRACE ?R session. This session was ended by pressing Ctrl-C.

  47 *-*	offset = 0;
	>>>	"0"
	+++	Interactive trace. "Trace Off" to end debug, ENTER to Continue.
SAY spacedList

  48 *-* lines = 0;
   >>>	"0"

	53 *-*	Do While Chars(file) > 0;
	>>>	"1"

	54 *-*	extendedAsciiData = Charin(file,, 16);
	>>>	"MZ??????????__??"

	55 *-*	hexData = C2X(extendedAsciiData);
	>>>	"4D5A00000500000004000000FFFF0800"

	57 *-*	printableAsciiData=Translate(extendedAsciiData,,unprintableCh..
  >>>	"MZ.............."

	63 *-*	spacedHexData = Translate(spacedList, hexData, list);
	>>>	" 4D 5A 00 00 05 00 00 00 04 00 00 00 FF FF 08 00"

	73 *-*	CALL LineOut, D2X(offset, 4)':' spacedHexData'  'printableAsci..
0000:  4D 5A 00 00 05 00 00 00 04 00 00 00 FF FF 08 00  MZ..............
	>>>	"0"

	73 +++	CALL LineOut, D2X(offset, 4)':' spacedHexData'  'printableAsci..
REX0004: Err 4 running D:\JOY OF REXX\FD3.CMD, line 73: Program interrup..

Here *-* signifies the clause under investigation, >>> shows the result and +++ is a trace message. After line 48 (its numbering) I typed SAY spacedList. Otherwise I just repeatedly pressed Enter until I finally pressed Ctrl-C to break out.

For another type of interactive tracing try TRACE ?I after line 25. See Figure 10.

Figure 10. A TRACE ?I display.

 34  *-* Do While Chars(file) > 0;
 	 >V>	"c:\os2\cmd.exe"
 	 >F>	"91136"
 	 >L>	"0"
 	 >O>	"1"
 	 >>>	"1"
     +++  Interactive trace. "Trace Off" to end debug, ENTER to Continue.

>V> is the contents of a variable (here file); >F> is the result of a function call (since this is originally at the beginning of reading CMD.EXE this is currently equivalent to the filesize of CMD.EXE); >L> is a literal (here the RHS of the comparison); >O> is the result of a 2-term operation (the LHS is greater than the RHS - since this is true, the result is 1); >>> is the overall result (in this case, the same as the >O> result).

As regards indentation style, lines 21-23 are indented since they're part of a DO loop whereas lines 36-39 are not indented because they are just a multiple expression block occurring after an IF... THEN expression.

The Program Extender

Once you're familiar with the operation of the basic program, add in both the Section 1 parts. You will also need to comment out line 34, replace it with line 34alt. This process will enable the program to adjust the more prompt to different screen heights. (You can use MODE 80, nn to set different screen heights where nn is commonly 25, 43, 50 and 60 but you should be able to use 1-102.)

What's happening here is that we are using a function, SysTextScreenSize(), which is located in the external library, REXXUTIL.DLL. We could have just registered this one function with the REXX interpreter. Instead we register all functions in this library by first registering the SysLoadFuncs() function and then invoking it to register the rest. This uses up very little extra memory. Once the RexxUtil functions are loaded they will remain registered until you reboot.

SysTextScreenSize() is used to accurately determine the current screen height. The powerful PARSE instruction is used to extract only the first word from the returned string (the rows part). The dot indicates that any other words after this one will be discarded.

The Validity Check

An invalid filename will not cause the program to explode. Instead it will silently end. It is usually desirable to inform the user that the reason there is no screen output is due to an unlocatable filename being supplied. To do this, add in Section 2. The Stream() function is very versatile means of checking or working with I/O streams. Here we are using it in 'C' ('Command') mode. The commands we're using are to check if the file exists and, secondly, does it contain anything? Either condition will lead to an appropriate message and then the program will terminate.

The Better "Press any key" Routine

Up until this stage we've been using PULL. Normally, PULL is used to input something into a variable but we're using it here to just for it ability to wait for a press of the Enter key to go on. So we use a dot in place of the variable name to discard any user input. To modify the program to accept any keystroke we'll use another RexxUtil function: SysGetKey('NOECHO'). Comment out line 37 and add lines 37alt and 38. Also modify the message in line 36 to indicate that any key can be pressed. To investigate the matter of returned key codes you should look at LIST1601.CMD in the book. Note: some special key combinations can not be used such as Ctrl-Alt-Del, Ctrl-Esc, Alt-Tab etc.

The End

By now you should have some idea of the features that OS/2's or PC-DOS 7's REXX offers to both the casual and the serious programmer. I've not had the space to cover other important features such as stem variables (similar to arrays). OS/2 users wishing to learn more will find "Teach Yourself REXX in 21 Days" a sound investment. Another source of information is Paul Marwick's interesting series on REXX, SigBits May-Aug '94. In my opinion, OS/2 REXX programming is a good way of providing "power to the people".