Thoughts on Rexx portability
Here's my take on REXX portability, based on my own experiences.
CLASSIFICATION OF PORTABILITY ISSUES
There are three kinds of portability problems that I have encountered:
(1) OS platform portability -- operating system commands vary (from say
DOS to UNIX to MVS)
(2) user interface mode portability -- a console-mode interpreter
interacts with the user in a different way than a GUI interpreter does
(3) interpreter portability -- different interpreters (typically from
different vendors) running on the same platform. At one time both
Kilowatt REXX and Personal REXX were available for DOS, and they offered
different vendor extensions. Now the same thing is true of Personal
Rexx for Windows, Enterprise REXX, and IBM's Object Rexx.
SOLUTIONS TO PORTABILITY ISSUES --
The first task to address in writing portable code is to develop a
routine that enables you easily to figure out what platform (operating
system, user interface mode, interpreter) you are running on. I have
developed an external REXX routine that I call RUNNING.REX. Here it
is, in its current version. Obviously, anybody can customize it to
handle a particular set of available platforms. If I had it to do over
again (and I may try), I'd have it return values is UPPER case, which
would be more compatible with mainframe implementations.
/* RUNNING.REX -- find out what interpreter/environment we're running */
/* 1.0 1997/09/23 Steve Ferg (email@example.com) wrote it */
if translate(arg(1)) = "TEST" then do
parse source source
parse version version
say " source :" source
say " version:" version
parse source Environment .
parse version Interpreter .
if left(translate(arg(1)),3) = "ENV" then return environment
if Interpreter = "REXX/Enterprise" then RETURN "e~rexx"
if Interpreter = "OBJREXX" then RETURN "objrexx"
if Interpreter = "REXX/Personal" then do
if Environment = "PCDOS" then RETURN "prexx"
if Environment = "WIN" then RETURN "prexxwin"
In a program, you can invoke RUNNING() this way:
IF RUNNING() = "prexx" then do ... end
SOLUTIONS TO PORTABILITY ISSUES --
The next thing is to isolate your applications from functions that are
Such functions tend to be related to operations on files.
Unfortunately, there are no reliable standards for what kinds of
commands you can give to STREAM(), and some implementations can handle
wildcards on the STREAM() filespec while others can't.
I have developed some standard external routines that I use to do file
operations. They include
F_EXISTS(f) -- returns a boolean, tells whether a file named by "f"
F_DELETE(f) -- delete a file
F_CREATE(f) -- create an empty file
F_RENAME(old_f, new_f) -- rename old_f to new_f
F_COPY(source_f, target_f) -- copy file source_f to target_f
F_MOVE(source_f, target_f) -- move source_f target_f
F_SORT(f) -- do a simple ascending-sequence sort on file f
returns a string of blank delimited filenames that match the filespec.
(Now that long filenames can contain blanks, I may have to revise this)
GETDIRS(...) -- like getfiles(), but operates on directories rather than
As long as an application program uses these functions, it can be ported
from one platform to another -- all you have to do is to recode the
internals of these routines. It is even possible to make these
routines somewhat platform independent, by having them using the
RUNNING() function, and then to perform different OS-dependent actions
depending on which OS they're running on.
I have also developed routines to perform various functions on the NAMES
F_NAME(name_part, f) -- return a specified part of a filename
F_NAME32(function, long_filename) -- performs various functions (e.g.
CREATE, DELETE, return SHORTNAME) on files specified by a long filename,
and returns the short (DOS 8.3) filename of the file. This is for older
REXX interpreters (Personal REXX for DOS, for instance) that cannot
handle long filenames
F_UNC() -- performs various functions on filenames specified in
Universal Naming Convention format
(\\servername\sharename\directoryname\filename). This is for older REXX
interpreters that cannot handle UNC filenames.
I have developed some standard external routines interact with the user.
They rely on the RUNNING() function to tell them what user-interface
mode they're in, and then they behave appropriately. They include:
MSGBOX (message, title) --
displays a message to the user
GET_KEY(string_containing_list_of_acceptable_keys, message) --
displays a message to the user, and gets a reply. Will accept only keys
specified in arg(1).
In console mode, this routine writes a line to the screen and waits for
a key press.
In a windowed environment, if the acceptable keys are only Y, N, or
escape, it will present a "QuestionBox" or "CancelBox" window with
Yes/No or Yes/No/Cancel buttons, and the user clicks on his choice. If
the list of acceptable keys includes other keys, then the user will get
a listbox displaying the acceptable letters, and can choose one by
clicking on it.
GET_SEL(string_containing_list_of_acceptable_choices, message) --
A "get selection" routine. Like GetKey(), but the list of acceptable
choices consists of strings separated by "00"x characters. It drops
down a scrollable list of choices from which the user can make a
selection or CANCEL. (I had to write the routine that does this for
console mode, myself.)
Get a string (some input) from the user.
GET_NUM(lower_bound, upper_bound, default, message) --
Like Get_Str() , but will accept only whole numbers between lower_bound
edits a file using an appropriate editor (i.e. either DOS-based or
Windows-based) for the environment.
(3) Interpreter dependent
These routines rely on the RUNNING() function to tell them what
interpreter they're using, and then they behave appropriately.
Most of these routines also fit into category (2) but there are still a
few leftovers. For example, both Personal Rexx for Windows and
Enterprise Rexx support a RexxConsole() function to control the console
window, but one of them accepts letters(e.g. "x") and the other accepts
whole words (e.g. "Maximize"). So I have a function called CONSOLE().
It accepts whole words. It checks with RUNNING() and returns a whole
word if running under the interpreter that requires whole words,
otherwise it returns the appropriate letter.
So now, instead of coding CALL REXXCONSOLE "MAXIMIZE" or CALL
REXXCONSOLE "X", a program can do this: CALL REXXCONSOLE
CONSOLE("MAXIMIZE") and it will run under either interpreter.
I love REXX, and I love to play with it on different platforms. So far
I've played with (console mode and windows mode) versions of Kilowatt
Rexx (now defunct) Personal Rexx and Enterprise Rexx, as well as Regina
on Unix, and IBM's Object Rexx on Win95. No two of them ever worked
exactly the same.
-- Steve Ferg
firstname.lastname@example.org which (currently) goes to...
Return to the Rexx Anywhere! web page.