by Shmuel (Seymour J.) Metz (שמואל בן לייביש ולאה)
This is a merged and updated edition of two closely related articles by the same author: Safe REXX on the Desktop (© 1993, revised 1998, originally printed in OS/2 Magazine, February 1995) and Safe REXX in the Enterprise (© 1993, the earlier/base text, from which Desktop was adapted for a PC-focused readership). Both were updated for the web in January/February 2023. This edition merges them into one text, adds explicit ooRexx guidance throughout (both papers predate ooRexx and explicitly disclaimed direct experience with Object REXX), and extends the scope to the fuller range of platforms and dialects in current use: TSO/E REXX (the only REXX on z/OS, running under TSO, ISPF, the OMVS shell, batch, and System REXX), CMS, Classic REXX, Object REXX, ooRexx, Regina.
The current source of this document, including its revision history, is maintained at https://github.com/shmuelmetz/Safe-REXX.
Copyright 1993, 1998, 2023, 2026 by Shmuel (Seymour J.) Metz (שמואל בן לייביש ולאה). All rights reserved. Permission for reproduction in whole or in part is hereby granted to educational, non-profit and computer user groups for internal, non-profit use, provided credit is given and this notice is included. All other reproduction without the author's prior written permission is prohibited.
REXX is a language designed by Mike Cowlishaw, initially under the name REX, in 1979; REX saw internal use at IBM before being renamed REXX — gaining its second X by 1982, to avoid confusion with other products — and shipping for the first time in VM/SP Release 3, to replace the EXEC and EXEC2 command-macro languages in the CMS component of IBM's VM/SP. Since then it has spread to a large number of other platforms, including Unix, and has been designated by IBM as the SAA Procedures Language. REXX has been used to implement a wide variety of applications beyond its original problem domain, including many of substantial size. Open Object Rexx (ooRexx) is a widely-used, open-source, object-oriented extension of classic Rexx: it executes unmodified classic Rexx programs and adds classes, methods, and message-send syntax on top. Everything in this edition that applies to classic Rexx applies unchanged to ooRexx unless a specific note says otherwise.
The American National Standards Institute published a Rexx standard,
ANSI X3.274-1996, adding a number of enhancements beyond Cowlishaw's
original The Rexx Language 2nd-edition ("TRL-2") specification —
among them the CHANGESTR, COUNTSTR, and QUALIFY built-in
functions and the LOSTDIGITS condition. ooRexx and Regina both
implement these ANSI-1996 enhancements. IBM's mainframe classic-Rexx
interpreters do not: TSO/E REXX (z/OS) and CMS REXX (z/VM) are both
documented as lacking CHANGESTR/COUNTSTR/QUALIFY — per
rexxinfo.org's classic-Rexx function reference chart, which lists
both mainframe interpreters identically as "not supported" for these,
rather than treating z/VM as somehow upgraded past z/OS's level.
Neither mainframe interpreter has been
brought up to ANSI-1996 level; both remain at essentially the older
TRL-2 function set. This matters when porting code between
mainframe and non-mainframe Rexx: code that leans on CHANGESTR or
COUNTSTR for convenience will not run unmodified on TSO/E or CMS,
regardless of which one you started on.
TSO/E REXX does have one significant environment-specific carve-out
despite this: it supports stream I/O (LINEIN, LINEOUT, STREAM,
and the like) only when running in the UNIX System Services (OMVS)
shell, not in an ordinary TSO/E address space, which uses EXECIO
instead — see I/O model below for the full detail and a
worked example of both forms.
This document does not discuss NetRexx. NetRexx compiles a Rexx-derived syntax to Java bytecode (or Java source) rather than running under a classic-Rexx or ooRexx interpreter, and its typed variables and Java interop give it a different pitfall profile than the dialects covered here.
REXX has a number of features that can trap the unwary. This does not mean that REXX is a bad language, just that you need to understand it for what it is, as you must for any other programming language. Some of these features are just language glitches, while in other cases they were added as the necessary price for greater expressive power.
One of the easiest ways to run afoul of REXX is to be misled by superficial similarity with other languages, especially PL/I, TSO CLISTs, and languages derived from them. Make a conscious effort to learn REXX on its own terms, without relying on analogies with other languages. This applies with equal force to ooRexx: its message-send syntax and class model can look superficially like other object-oriented languages, but its scoping and activation rules have their own logic, covered below.
Other areas that may confuse the neophyte are the use of abutment for concatenation, the use of dropped symbols as constants, the rules for continuation, parsing, the block structure, and the way variable references are passed. Later sections go into detail on each.
You may write REXX code that must run on multiple platforms, or in different environments on the same platform. REXX has some language features that may impede portability. It also has some features that may be exploited to improve portability. Later sections give guidelines on how to ease migration between environments and between platforms.
Of course, there are many generic principles of defensive programming that apply just as much to REXX as to any other language. These include:
Although this edition discusses only issues and solutions specific to REXX and ooRexx, those generic principles are of equal importance in avoiding programming errors.
Sections marked ooRexx note cover behavior specific to Open
Object Rexx that does not apply to classic Rexx dialects (TSO/E
REXX — the same interpreter whether run under TSO, ISPF, the OMVS
shell, batch, or System REXX — CMS REXX, Regina, and the like). Note
that OREXX (IBM's original Object REXX — released for OS/2, Windows,
and AIX — the precursor ooRexx implements and succeeds as an
open-source project) is itself an
object-Rexx family member, not a classic-Rexx dialect — but an older,
more limited one: see the specific ~translate vs ~upper gap noted
below.
REXX has some specific features that you can exploit to make your programs more compatible across platforms, or between environments on the same platform. REXX also has some features that hinder compatibility.
If you write a command file that issues host commands — OS/2, CMS,
TSO, DOS commands — do not assume that the default environment is
that of the host itself. By including, e.g., ADDRESS TSO (on a
mainframe) or ADDRESS CMD (on OS/2/Windows), you enable the routine
for use from within other environments too, e.g., the ISPF/PDF editor
on a mainframe, or an editor that uses REXX as its macro language on
a PC.
A platform name alone is too coarse here — the invocation context, not just the OS, decides the default. Only environments actually documented for that context are listed; a blank cell means none:
| Invocation context | Default environment | Other environments |
|---|---|---|
| OS/2 command prompt (classic REXX) | CMD |
|
| PC-DOS command prompt (classic REXX) | COMMAND |
|
| Command prompt (OREXX, ooRexx) | CMD |
SYSTEM, PATH on ooRexx |
| Regina command prompt | SYSTEM |
COMMAND, REXX |
| TSO/E READY | TSO |
MVS, CONSOLE†, the link/attach family, the APPC family |
| ISPF on z/OS | TSO |
MVS, CONSOLE†, the link/attach family, the APPC family, ISPEXEC |
| ISPF/PDF EDIT on z/OS | TSO |
MVS, CONSOLE†, the link/attach family, the APPC family, ISPEXEC, ISREDIT |
| ISPF on z/VM | CMS |
ISPEXEC |
| ISPF/PDF EDIT on z/VM | CMS |
ISPEXEC, ISREDIT |
| OMVS shell | SH |
TSO, MVS, SYSCALL |
IRXJCL |
MVS |
the link/attach family, the APPC family |
| System REXX | MVS (TSO=NO) |
the link/attach family, APPCMVS, BCPii, the APPC family; TSO=YES adds TSO, ISPEXEC, ISREDIT |
| EDIT macro | EDIT |
none — TSO itself is unavailable until END terminates EDIT |
| TEST macro | TEST |
none — TSO itself is unavailable until END or RUN terminates TEST |
| IPCS macro | TSO |
IPCS — not available at all in the session's own separate TSO/E mode |
| CMS command line | CMS |
COMMAND, CP |
| GCS | GCS |
COMMAND |
| XEDIT macro | XEDIT |
falls through to CMS, then CP, automatically |
The link/attach family: LINK, LINKMVS, LINKPGM (link to an
unauthorized program on the same task level), ATTACH, ATTCHMVS,
ATTCHPGM (attach one on a different task level) — available to a
REXX exec in any address space, TSO/E or not. The APPC family:
CPICOMM (SAA CPI Communications calls), LU62 (APPC/MVS calls,
SNA LU 6.2) — likewise available in any MVS address space. † CONSOLE
needs an active extended MCS console session (started with the TSO/E
CONSOLE command) and console command authority; it's available only
in the TSO/E address space, not from batch. ISREDIT requires an
active edit session regardless of platform — attempting it outside one
fails at run time even where the environment is nominally available.
Standard Rexx (not an ooRexx-only extension) can capture a child process's stdout and stderr directly into stems, with no temp files or pipes needed:
address system 'some-command' with output stem out. error stem err.
cmdRc = rc
do i = 1 to out.0
say out.i
end
do i = 1 to err.0
say ' [stderr]' err.i
end
TSO/E REXX has no ADDRESS WITH at all to do this with — like classic
OS/2 REXX and OREXX, it predates the ANSI-1996 enhancement. Its own
mechanism for capturing host-command output is the OUTTRAP built-in
function instead, which traps subsequent command output into a stem
(or the program stack) until turned back off:
call outtrap 'mystem.' /* start trapping into mystem. */
'LISTC LEVEL(MY.DATA)'
call outtrap 'off' /* stop trapping */
do i = 1 to mystem.0
say mystem.i
end
See Continuation below (Figures 2 and 3) for the
continuation pitfalls specific to OUTTRAP's own argument list.
Valid I/O redirect types in the WITH clause are NORMAL, STEM,
STREAM, and USING — STRING is not a valid type. Of these, USING
— supplying the input value directly, input using (expr), with no
stem or stream needed — goes beyond ANSI X3.274-1996's own ADDRESS
WITH semantics, which define only STREAM and STEM as resource
types; NORMAL/STEM/STREAM are standard, but USING is an ooRexx
extension (ooRexx 5.2.0). Regina does not have it: its own reference
manual's ADDRESS WITH syntax diagram lists only STREAM, STEM,
LIFO, and FIFO as resource types — LIFO and FIFO are Regina's
own extensions beyond the ANSI baseline, but USING appears nowhere
in the manual. Neither classic OS/2 REXX nor OREXX has it either — per
IBM's own OS/2 reference documentation for both (Procedures Language
2/REXX Reference and Object REXX Reference), the ADDRESS instruction's
own syntax diagram in both is just ADDRESS [environment]
[expression], no WITH clause of any kind. Neither ever had any
form of ADDRESS WITH, USING included; the whole I/O redirection
clause is an enhancement neither IBM product picked up.
To supply empty stdin (preventing a child process from blocking
waiting for input), define an empty stem and pass it as INPUT STEM:
noIn.0 = 0
address system cmd with output stem out. error stem err. input stem noIn.
Do not wrap the command itself in cmd /c "..." on Windows, even
though it may seem like it should be needed — ADDRESS SYSTEM
already dispatches straight to the platform's native shell. An extra
cmd /c layer is redundant at best, and actively wrong once the
wrapped command itself contains its own quoted arguments (a path with
spaces, a commit message with spaces): cmd.exe's quote parser does
not reliably handle the resulting nested quoting.
REXX does not shield you from the underlying environment; in writing
a REXX program you must understand the behavior of your operating
system and user interface if you want to avoid nasty surprises. As an
example, if you invoke a REXX program in an OS/2 CMD file and scan
the argument looking for the string /Q, you will not find it,
because CMD.EXE will have taken the string /Q to be a "quiet"
option and removed it.
If you must use binary or hexadecimal constants for character data, be aware that character encoding varies among systems, and not just between EBCDIC and ASCII. CMS and TSO use EBCDIC. Most other systems use some combination of plain 7-bit ASCII, an 8-bit code page extending ASCII (e.g. Latin-1, Windows-1252 — the specific extension matters, since they disagree above code point 127), and Unicode (typically UTF-8 or UTF-16) — which one depends on the specific system, its locale/code-page configuration, and the file or stream in question, not just the OS family. Even within CMS and TSO there are national-language issues, and in many systems there are code-page issues. Be aware of the character sets used in each of your target systems, and program accordingly. Segregate system-dependent values and code-page-dependent values to make your code easier to maintain.
Rexx variable names and labels are case-insensitive on every platform, in every dialect — but two related things are not, and the platform matters:
VALUE() built-in for reading environment variables is
case-sensitive on Linux but case-insensitive on Windows.ooRexx note: for case-insensitive comparisons generally, not just
the platform-specific cases above, ooRexx's .String class provides a
caseless-prefixed method family directly — caselessEquals,
caselessCompare, caselessPos, caselessCountStr,
caselessChangeStr, caselessAbbrev, caselessMatch,
caselessStartsWith/caselessEndsWith, caselessWordPos,
caselessContains/caselessContainsWord — rather than calling
TRANSLATE()/~upper on both sides before every comparison. PARSE
itself has a CASELESS modifier too (alongside LOWER) for
case-independent template matching — but unlike PARSE UPPER, which
is genuine ANSI X3.274-1996 syntax (spelled out in full; the standard
documents no abbreviated form for it), neither LOWER nor CASELESS
appears anywhere in the ANSI text: both are extensions, present in
ooRexx and Regina alike but absent from TRL-2-level classic Rexx and
from the ANSI standard itself.
REXX's first shipped implementation, on CMS in VM/SP Release 3, used
the EXECIO command, later inherited by TSO/E and other platforms.
CMS's EXECIO reads and writes through three kinds of source or
target — the program stack (FIFO/LIFO), a stem (STEM stem.), or
a single plain variable (VAR name, but only for exactly one line at
a time; the count operand must be 1 with VAR) — the source for a
write, the target for a read. Of these, TSO/E REXX in MVS inherited
only a subset: the stack and STEM forms, not VAR. Some
interpreters still support EXECIO for compatibility with legacy
TSO/CMS code, but it is not the primary I/O model outside TSO/E and
CMS themselves.
Stream I/O came later. It's part of the language as defined in
Cowlishaw's TRL-2 (1990) and later formalized by ANSI X3.274-1996.
LINEIN(file)/LINEOUT(file, string) read and write whole lines;
CHARIN(file)/CHAROUT(file, string) do the same character by
character; LINES(file) and CHARS(file) report whether more data
remain, for use as a loop condition before the next read;
STREAM(file, option) queries or acts on a stream — 'State' reports
its overall status, 'Description' a fuller status string, 'Command'
executes an operation on it. See Figure 10.
/* In OS/2 SAA REXX */
do while lines(myfile) /= 0
myline = linein(myfile)
...
end
/* In CMS and TSO REXX */
'EXECIO *' name '(STEM MYSTEM. FINIS'
do i = 1 to mystem.0
parse var mystem.i myline
...
end
TSO/E REXX in MVS does not support stream I/O at all, except in the
UNIX System Services subsystem (originally called OpenEdition, or
Open MVS); code that must run in environments supporting stream I/O
and also in, e.g., TSO, can use conditional logic to select EXECIO
on the TSO side (see PARSE SOURCE and VERSION
below for how to detect which side you're on).
ooRexx note: I/O object types. Alongside the bare functions above,
ooRexx models stream I/O as a small class family: .Stream is the
concrete class most code uses, wrapping a file or other stream as an
object — aStream~lines, aStream~chars, aStream~linein,
aStream~lineout, aStream~charin, aStream~charout, and so on,
with identical semantics (the same 0-or-1-vs-exact-count behavior
included) as their function-call equivalents. .InputStream,
.OutputStream, and .InputOutputStream are abstract mixin classes
underneath it, meant for building custom stream implementations, not
for direct use on an ordinary file.
The safest thing is to encapsulate your input/output code and then
take advantage of whatever facilities may exist in each target
system, e.g., EXECIO with the STEM option, or a third-party
library such as REXXLIB. Any such code should be thoroughly
documented. Be aware that EXECIO in TSO/E supports only the stem
and stack forms, not the variable-name form; even in CMS, it is
usually best to use the stem form of EXECIO.
The PARSE SOURCE statement allows your code to determine the
operating system and file from which it was invoked, as well as the
type of invocation. You can take advantage of this in order to
maintain a single version of a REXX program for two different
systems, to detect inappropriate invocations, to select character
encoding, etc. If you have data files that, by default, should be in
the same directory as your code, you can use this statement to locate
them. See Figure 11.
The PARSE VERSION statement allows you to determine the language
level of REXX that your program has available. This allows you to
write code that exploits new features of REXX, yet include alternate
code that will be used when running on an older platform.
parse source system invocation origin
select
when system = 'OS/2' then do
...
end
when system = 'TSO' then do
...
end
otherwise do
say system 'is not supported by' origin
exit
end
end
parse version name level date1 date2 date3 .
select
when name = 'REXXSAA' then do
parse var level int '.' frac
if int > 3 then do
/* fast code for SAA level 4 goes here */
end
else do
/* slower code for older SAA level goes here */
end
end
when name = 'REXX370' then do
/* Code for CMS or TSO level of REXX goes here */
end
otherwise do
say name 'is an unsupported REXX implementation'
exit
end
end
ooRexx note: the classic REXXSAA/REXX370 name values above are
platform-specific classic-Rexx implementation names; ooRexx reports
neither.
parse version v
say v
on ooRexx 5.2.0 produces:
REXX-ooRexx_5.2.0(MT)_64-bit 6.06 18 Apr 2026
If your SELECT on PARSE VERSION's name field needs to
distinguish ooRexx from classic implementations, match on a name
that begins with REXX-ooRexx, e.g., when name~abbrev('REXX-ooRexx')
then do ... end — do not assume name will be one of the two classic
values above. The two classic values themselves are also not the
whole story — the full set of name/level values you may actually
meet:
| Implementation | name |
level |
Source |
|---|---|---|---|
| OS/2 classic REXX (Procedures Language 2/REXX) | REXXSAA |
4.00 |
IBM's own reference manual |
| OREXX (IBM's Object REXX) | OBJREXX |
6.00 |
IBM's own reference manual (OS/2 edition) |
| CMS / TSO/E REXX (classic mainframe, "REXX370") | REXX370 |
4.00 |
Widely documented |
| Regina | REXX-Regina_<version> (e.g. REXX-Regina_3.9.6(MT)) |
5.00 |
Regina's own reference manual; ANSI-compliant since Regina 3.1 |
| ooRexx | REXX-ooRexx_<version>(MT)_<bits>-bit (e.g. REXX-ooRexx_5.2.0(MT)_64-bit) |
6.06 |
ooRexx 5.2.0 |
Two things worth noticing in this table. First, level is not the
interpreter's own version number — it is the Rexx language level the
interpreter targets (4.00 is TRL-2, 5.00 is ANSI X3.274-1996), and
several implementations have historically conflated the two; look for
the interpreter's own version inside the name word instead (as
ooRexx and Regina both do) or in PARSE SOURCE. Second, REXXSAA and
REXX370 share the exact same level, 4.00 — despite one being a
PC/workstation implementation and the other a mainframe one — because
neither was ever brought up to the ANSI-1996 level; see
Platforms and standards conformance above.
Do not assume that an optional function library your code depends on
is actually loaded in every environment it might run in. The original
form of this advice, in both source papers, was framed around a
specific and by-2023 obsolete scenario: OS/2's RexxUtil requires
Presentation Manager, which was too large to fit on a 1.44MB emergency
boot floppy, so code meant to run from one had to avoid depending on
RexxUtil and fall back to a more primitive equivalent — one route
being BOOTOS2 to build a bootable maintenance partition or emergency
floppy still capable of loading RexxUtil, WPS, or PM sessions on a
minimum boot configuration. See Figure 12.
if REXXUTIL_loaded then do
stat = SysFileTree(filespec, 'filelist.', 'FSO')
do i = 1 to filespec.0
...
end
end
else do
'DIR' filespec '/F /O > WORK_FILE'
...
end
The emergency-boot-floppy scenario itself is long obsolete, but the
underlying principle is not: any function library your code treats as
"just there" — RexxUtil, an ooRexx package pulled in via ::REQUIRES,
a third-party library like REXXLIB — may genuinely not be loaded in
every environment your code might run in, and checking before you
depend on it is cheap insurance.
The modern equivalent check before using a RexxUtil function —
standard across classic Rexx and ooRexx alike, not an ooRexx-only
mechanism — is RxFuncQuery, and the standard way to load every
function in the package (one call registers them all; needed at least
once per process, since none of them is autoloaded the way some
built-ins are) is:
call RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'
call SysLoadFuncs
running this unconditionally at the top of a script is the practical,
idiomatic equivalent of Figure 12's availability check for the common
case where you'd rather just load the package than branch on whether
it's there — reach for the explicit RxFuncQuery branch only when a
genuine no-RexxUtil fallback path exists, the way the original boot-disk
scenario needed one.
Loading the package is not the same as every function in it being
present. The repertoire behind the name RexxUtil is not itself
standardized, and varies by implementation:
| Function(s) | OREXX | ooRexx 5.2.0 | Regina (RegUtil) |
|---|---|---|---|
SysFileCopy, SysFileMove |
No on Windows; not checked on AIX | Yes | No — SysCopyObject/SysMoveObject instead |
The SysIsFileXxx family (SysIsFile, SysIsFileDirectory, SysIsFileLink, and the Windows-only detail variants) |
No | Yes | No |
The Workplace-Shell family (SysCreateObject, SysDestroyObject, SysSetObjectData, SysQueryClassList, and related) |
Yes, but only in the OS/2 edition | No | No |
The semaphore family (SysCreateEventSem, SysCreateMutexSem, and related) |
Yes | Yes, but deprecated in favor of the .EventSemaphore/.MutexSemaphore classes |
Yes |
Unix process functions (SysFork, SysWait, SysCreatePipe) and SysGetMessage/SysGetMessageX (Unix message catalogs) |
Yes, in the AIX edition | Yes, on Unix-like platforms | No |
SysWinGetPrinters, SysWinGetDefaultPrinter, SysWinSetDefaultPrinter, SysFormatMessage, SysGetLongPathName, SysGetShortPathName, SysShutdownSystem |
No | Yes | No |
SysLoadFuncs/SysDropFuncs |
Required, to register the package | Deprecated no-ops since ooRexx 4.0.0 — the package is auto-registered | Required, to register the package |
Ordinary file/directory operations (SysFileTree, SysMkDir,
SysRmDir, SysSearchPath, SysTempFileName, SysGetFileDateTime,
SysSetFileDateTime, SysDriveInfo, SysDriveMap, SysVolumeLabel,
SysWaitNamedPipe), the macro-space family, console I/O (SysCls,
SysGetKey, RxMessageBox, and related — Windows-only in all three),
and SysQueryProcess are present in all three; SysFileTree and
SysQueryProcess are each documented as behaving differently across
platforms, so test them on each target rather than assuming identical
semantics.
A blanket "is RexxUtil loaded?" check, whether via Figure 12's flag or
RxFuncQuery('SysLoadFuncs'), only tells you the package itself
loaded — it says nothing about whether the specific function you're
about to call is part of that implementation's repertoire. Guard any
WPS-specific (or otherwise platform-specific) call with its own
RxFuncQuery on that function's own name, not just on the package.
If you use variable patterns in the templates of your PARSE
statements, be aware that some extremely old implementations of REXX
do not support all forms — e.g., in MVS/XA the form +(variable) is
not available. If you need to run on multiple platforms, check which
forms are supported on each and program accordingly.
Although REXX has a number of features that lend themselves to fast prototyping, it has a few pitfalls that can beset the unwary.
Although REXX has a conventional concatenation operator (||), it
also supports two other concatenation operators: abutment with white
space and abutment without white space (see Figure 1). With abutment
an expression is abutted against a second expression. If there is
white space (e.g., blanks, tabs) between the two, the resulting value
is formed by concatenating a single blank between the other two
values; otherwise the result is formed by simple concatenation. It is
a common beginner's error to add or remove a blank that appears to be
irrelevant to the program's semantics, only to change the output.
Another common error is to abut a literal string with a single
character variable name. If the variable name is a valid suffix for a
literal string, e.g., X (for hexadecimal) or B (for binary), it
will be treated as part of the literal string, not as a variable
reference. For this reason, among others, it is best not to use
one-character names for your variables.
It is so easy to misuse abutment that some recommend not to use it at all. That position is extreme, since abutment is so convenient and readable, but exercise caution and good judgement in its use.
/* Explicit (conventional) concatenation */
dog = "Peke"
say "Tom's " || dog || "s" /* output is "Tom's Pekes" */
/* Abutment */
dog = "Peke"
say "Dick's "dog"s" /* output is "Dick's Pekes" */
/* Abutment with white space */
dog = "Peke"
say "Harry's" dog "s" /* output is "Harry's Peke s" */
/* Incorrect abutment of X -- the risk depends on your platform's
native character encoding */
x = 'unknown'
say '41'X /* ASCII platforms (OS/2, Linux, Windows):
displays "A", not "41unknown" */
say 'C1'X /* EBCDIC platforms (CMS, TSO/E, System
REXX): displays "A", not "C1unknown" */
REXX's continuation rules are more nuanced than "an incomplete line continues." Implicit continuation happens at specific trailing constructs — a comma awaiting the next argument, a binary operator awaiting its right operand, an unclosed parenthesis or bracket — not merely because a line "looks incomplete" in some general sense. A line that ends in the middle of an unterminated quoted string is a different failure altogether (a lexical error), not a continuation case at all: REXX has no line-continuation mechanism for a string literal split across lines. Explicit continuation is requested with a trailing comma, which is itself context-sensitive — see the argument- separator pitfall just below, where a comma already meaningful in its own right (an argument separator) has to be followed by a second, purely continuation-marking comma. This presents two common pitfalls for the unwary.
If you break a procedure invocation after a comma, the trailing comma will be treated as an explicit continuation request rather than as an argument separator. In this situation you must add an additional comma as an explicit continuation request in order to allow the separator to be recognized. See Figure 2.
/* Example for CMS types */
say value('X',,'LASTING FOO') /* retrieves X with no side effects */
say value('X',, ,
'LASTING FOO') /* same as above */
say value('X',,
'LASTING FOO') /* displays current value of X and then
sets X to 'LASTING FOO' */
/* Example for TSO types */
say outtrap('mystem',,'CONCAT') /* retrieves output to stem mystem */
say outtrap('mystem',, ,
'CONCAT') /* same as above */
say outtrap('mystem',,
'CONCAT') /* error: parameter 2 is not numeric! */
/* Example for OS/2 (and other environment-variable-based) types */
say value('X',,'OS2ENVIRONMENT') /* retrieves X with no side effects */
say value('X',, ,
'OS2ENVIRONMENT') /* same as above */
say value('X',,
'OS2ENVIRONMENT') /* displays current value of X and
then sets X to OS2ENVIRONMENT */
If you break an expression after a literal or variable that is not enclosed in parentheses, the statement will be treated as complete and the next line will be treated as a new statement. In this situation you must supply a trailing comma as a continuation request. See Figure 3.
The ECHO command used in these examples is present in various PC
operating systems and in the Unix subsystems of MVS and VM.
/* Example for CMS types */
'ECHO' 'DIR' /* displays 'DIR' */
'ECHO' ,
'DIR' /* same as above */
'ECHO'
'DIR' /* displays directory */
/* Example for TSO types */
'HELP' 'LISTC' /* displays help for LISTC */
'HELP' ,
'LISTC' /* same as above */
'HELP' /* displays generic help */
'LISTC' /* displays catalog */
Note that although in some cases REXX will recognize a syntax error when you omit a required explicit continuation character, in other cases you will get incorrect results with no error message.
Avoid the use of variables with the same name as a REXX keyword. If you use such names you risk having statements misinterpreted or rejected as invalid. See Figure 4. This is similar to the problem of one-character variable names being misinterpreted when abutted to literal strings. Even if you are careful to write code that does what you want, use of those names will confuse whoever has to modify your code, possibly including yourself.
The words that most often cause real parsing trouble — because they
double as PARSE subkeywords, so the parser genuinely reads them
differently depending on position — are:
ARG PULL VAR
EXTERNAL SOURCE VERSION
NUMERIC VALUE WITH
Figures 5 through 7 below show exactly how VALUE, VAR, and WITH
can misparse. Beyond that narrower set, avoid the fuller list of
REXX instruction keywords, keyword clauses, and common subkeywords as
plain variable names too — legal in every case, and the parser
resolves each occurrence correctly by position, but it is a real
readability hazard for a human reader, not just the parser:
ADDRESS ARG CALL DO DROP ELSE
END EXIT EXPOSE IF INTERPRET ITERATE
LEAVE LOOP NOP NUMERIC OPTIONS OTHERWISE
PARSE PROCEDURE PULL PUSH QUEUE RETURN
SAY SELECT SIGNAL THEN TRACE UPPER
WHEN BY FOR FOREVER TO WHILE
UNTIL WITH ON OFF VALUE CONDITION
DIGITS FORM FUZZ
Also avoid REXX's three special variables — RC, RESULT, SIGL —
as names for your own ordinary variables, for the same reason:
they already carry a specific meaning the language sets on your
behalf (RC from host commands, RESULT from CALL/function
invocations, SIGL the line number of the most recent SIGNAL or
CALL), and naming your own variable the same thing invites exactly
the kind of silent confusion this section is about. See
Variable references below for the RC-vs-RESULT
distinction in more detail, and the ooRexx-specific RESULT trap
under Dropped symbols.
text = 'tom dick harry'
with = 'Ada Emmy Gracie Lise'
/* we want to parse 'tom dick harry Ada Emmy Gracie Lise'
with 'with first rest' */
parse value text with first rest /* wrong ! */
parse value text with with first rest /* also wrong ! */
Also, be careful about your use of the keywords VALUE, VAR, and
WITH. The code in Figures 5 and 6 will produce quite unexpected
results, and was probably meant to behave like the code in Figure 7.
In general, use VAR for simple parsing.
stg = abc
parse var stg with x +1 y +1 z
/* sets with='ABC' x='' y='' z='' */
stg = abc
parse value stg x +1 y +1 z
/* Error 38! */
stg = abc
parse value stg with x +1 y +1 z
/* sets x='A' y='B' z='C' */
/* Equivalent to, and better form in this case: */
parse var stg x +1 y +1 z
ooRexx note: EXPOSE, GUARD, FORWARD, and USE genuinely
appear as bare words inside ::METHOD/::ROUTINE bodies, and OVER
appears as a bare word in DO var OVER collection anywhere in ooRexx
code, not just inside a directive body — all belong on the
avoid-as-variable-name list alongside the classic-Rexx keywords above.
The SIGNAL statement in REXX looks very much like a GOTO in PL/I
and other block-structured languages, but its semantics are very
different. Do not attempt to use SIGNAL <labelname> as a substitute
GOTO or you will cause yourself serious difficulties. Although the
form SIGNAL <labelname> will cause a jump to the code with that
label, it also flushes the control stack. A subsequent END statement
will be detected as an error (see Figure 8). It is best to use
SIGNAL strictly for its intended purpose of indicating exceptional
conditions.
do forever
signal BELL
whatever
BELL:
end /* an error will be detected here
because the SIGNAL logically
terminated the DO */
SIGNAL ON <condition> and CALL ON <condition> both arm condition
traps, but they are not interchangeable, and this is standard ANSI
Rexx (X3.274-1996) behavior, not an ooRexx extension — CALL ON is
implemented by Regina too, and any claim that it's ooRexx-only is a
myth worth retiring. The two forms differ in resumability: SIGNAL
ON behaves like the unconditional SIGNAL above and flushes the
call stack when it fires, with no return to the point of the
condition. CALL ON calls the trap routine as a subroutine and
returns to the point right after the guarded instruction once the
trap routine finishes. If a guarded host command or block should be
able to resume afterward, use CALL ON, not SIGNAL ON.
ANSI supports both forms for ERROR, FAILURE, HALT, and
NOTREADY; SYNTAX and NOVALUE are SIGNAL ON-only conditions —
there is no CALL ON SYNTAX or CALL ON NOVALUE in the standard. Not
every platform implements every condition identically: TSO/E REXX
does not support NOTREADY at all, for instance. Check your target
dialect's own reference before assuming a given condition/form
combination is portable.
The parsing facilities of REXX have several features that may be confusing to the neophyte.
REXX has keywords for abbreviated forms of PARSE, e.g., ARG is
short for PARSE UPPER ARG. Beginners often forget that these
abbreviated forms will translate all data to upper case.
When using PARSE or its abbreviations, it is important that you
remember that the last variable or period (.) is treated
differently from all of the others; in general its value will include
leading and trailing blanks. Use the STRIP function or a
trailing period to remove these if they are unwanted.
In a parse template, a variable not enclosed in parentheses is a
receiver — it is assigned the next parsed token, and does not
match against the variable's current value. A variable enclosed in
parentheses, (foo), is a match pattern — Rexx uses foo's current
value as a literal string to scan for. Confusing the two is a silent
logic error, not a syntax error:
parse var line word rest /* word RECEIVES the first token */
parse var line (delim) rest /* Rexx SCANS for delim's current value */
When the parse source is already a plain variable, prefer PARSE VAR
over PARSE VALUE ... WITH. The two are not semantically different
here — the reason is that every extra WITH/VALUE token in the
source is one more chance to trip over exactly the keyword-confusion
pitfalls covered above (Figures 5-7), or to introduce a stray
keyword-shaped identifier while editing later. Reserve PARSE VALUE
... WITH for genuine expression sources, where the VALUE keyword is
actually doing something:
parse var foo template /* foo is a plain variable */
parse value foo || bar with template /* a genuine expression source */
ooRexx note: for pattern matching that outgrows what a PARSE
template can express cleanly — optional pieces, repetition, character
classes, alternatives — ooRexx's .RegularExpression class is an
alternative worth reaching for instead of contorting a template. It
uses its own pattern syntax (| for alternation, */+/{n} for
repetition, [...] for character sets, :alpha:/:digit:-style
named classes), not POSIX or PCRE syntax. It is not preloaded; a
::REQUIRES is needed:
str = 'name=John'
re = .RegularExpression~new('[:alpha:]+=[:alpha:]+')
say re~match(str) -- 1: the whole string matches
::requires "rxregexp.cls"
match returns 1 or 0 for whether string matches; pos locates a
match's starting position instead of requiring the whole string to
match. Reserve it for genuine pattern matching — plain fixed-position
or delimiter-based extraction is still clearer with ordinary PARSE.
Although superficially REXX appears to be a block-structured
language, it is actually a hybrid between dynamic and static scoping.
It is possible, although bad form, to call a label inside a DO from
code outside the DO. It is possible to invoke code at an arbitrary
label as both a call and as a function invocation. It is incumbent
upon the programmer to supply the discipline that the language omits.
The scope of a procedure is determined strictly dynamically; there is
no static terminator such as END.
ooRexx note: a ::METHOD, ::ROUTINE, or ::CLASS body is
closed by a static boundary — the next :: directive, or end of file.
This doesn't contradict the classic-Rexx rule above (there is still no
explicit terminator statement like END inside the body itself), but
it does mean a directive body's extent is fixed by the file's
directive structure, not purely by dynamic control flow the way an
internal-subroutine's scope is.
Pitfall — falling through a classic-style internal label into a
directive silently ends the whole program. This is specifically
about a plain CALLed label (not a ::ROUTINE or ::METHOD body,
which have their own ordinary no-RETURN-means-return-nothing
behavior, unaffected by any of this). When such a label's code has no
explicit RETURN, running out of code to execute — whether the next
thing in the file is a :: directive, or nothing at all (true
end-of-file) — does not return control to the caller the way falling
off the end into more ordinary code would; this is easy to get wrong
by analogy with the classic-Rexx case. Both terminate the entire
program cleanly instead — no error condition raised, nothing
returned to the caller, the same as an implicit EXIT. Code after
the CALL simply never runs, with no diagnostic pointing at why. This
is a real risk specifically in ooRexx files that mix classic-style
internal labels with directives, since a directive now sits exactly
where "just more code" used to be assumed.
Do not write code intended to serve as both inline and out-of-line
code; programs in which you both call and fall through into the same
code are notoriously error prone. Precede each internal subprocedure
with a statement that will prevent accidentally falling into it,
e.g., EXIT; if your logic permits, begin the procedure with a
PROCEDURE statement, which must be the first statement after the
label. See Figure 9.
This is not just a style preference — falling through into a label
that starts with PROCEDURE is a hard error, not silent
misbehavior. PROCEDURE must be the first instruction actually
executed immediately after its own label is reached via CALL (or
a function invocation); reaching it any other way — straight-line
fall-through from the code above it — raises Error 17: Unexpected
PROCEDURE. as a SYNTAX condition, at the PROCEDURE line itself.
This is standard Rexx behavior, not ooRexx-specific. It's the
mechanism behind the "notoriously error prone" warning above: an
unguarded fall-through into a PROCEDURE-led subprocedure doesn't
just risk exposing variables unexpectedly — it crashes outright the
moment it happens, which is at least easier to notice than either of
the other two silent failure modes above (a directive or end-of-file
boundary ending the program, or plain code silently running with the
wrong variable scope).
saytime: PROCEDURE /* here I can get away with hiding all variables */
say time
return
/* Note that there is no END statement ! */
exit /* In case of fallthrough, since I can't use PROCEDURE */
putdata:
parse arg name .
say name'='value(name)
return
/* Note that there is no END statement ! */
badstyle: PROCEDURE /* This entry has no access */
badform: /* This entry has access */
...
return
/* Don't ever do this; it is an extremely dangerous style */
The PROCEDURE statement hides all variables except those explicitly
listed in an EXPOSE clause. If your subroutine accesses the
caller's variables and constructs those variable names from its
arguments, then you must not use the PROCEDURE statement. This is
the only situation in which you should omit it. See Figure 9.
It is possible to write procedures with overlapping scope in which
one procedure hides variables with a PROCEDURE statement and the
other procedure leaves all variables exposed by default. This is a
dangerous practice, and should be avoided.
ooRexx note: EXPOSE has two genuinely different meanings
depending on where it appears, and they are easy to conflate.
PROCEDURE EXPOSE (used inside a classic internal subroutine, as
above) exposes the caller's local variables. EXPOSE used as the
first statement of a ::METHOD body exposes that object's instance
variables — a completely different variable pool, private to the
object, not the caller's locals. And EXPOSE is not legal at all
inside a ::ROUTINE — a routine has no access to any caller's
variable pool the way an internal subroutine does. Attempting it
raises a parse-time error (Error 27.1) before any instruction in the
calling scope executes at all — which means a SIGNAL ON SYNTAX
trap set up in the caller will never fire, because the whole program
fails to parse before execution ever begins. If code invoking such a
routine is launched as a child process, the symptom is a silent
failure with a non-zero return code and nothing in any in-process
trap; the real diagnostic is only in the child's captured stderr.
Unlike most other languages, REXX has neither variable typing nor arrays. Arrays are often simulated using compound variables. This leads to several possible types of undetected errors.
ooRexx note: ooRexx's plain variables are exactly as untyped as
classic Rexx's — this doesn't change what's above. Its objects are
a different matter: ooRexx has dynamic typing at the object level.
Sending an object a message it doesn't recognize is a real, enforced
error — the Error 97.1/"does not understand message" pattern seen
throughout this document — not silent misbehavior. It's late-bound
(checked when the message is sent, not before the program runs)
rather than static, but it is genuine type enforcement, absent a
couple of low-level but documented escape hatches. ("Recognizes,"
not "the class defines," is deliberate above — see the second point
below.) A class can define an UNKNOWN method to deliberately accept
and handle any message that would otherwise be rejected, receiving
the message name and its argument list. Separately, an individual
object's own recognized-message set isn't fixed by its class alone:
a method can be attached to one specific instance at run time (via
~setMethod, callable only from that object's own code, or
Class~enhanced at creation time), so two objects of the identical
class can end up recognizing different messages. Both are opt-in
mechanisms a program has to invoke deliberately, not a gap in the
checking.
When you assign a value to a variable, there is no check that the
value is consistent with the intended type. If your logic requires
any constraints on the values that can be assigned, it is your
responsibility to code explicit checks using, e.g., the DATATYPE
function.
When you use a variable name as part of a compound variable in order to simulate an access to an array element, REXX does not check that the index is within the array extents, or even that it is an integer. If your logic requires enforcing such constraints, you must code them explicitly. Note that even a dropped symbol can be used as an "index" for a compound variable.
ooRexx note: ooRexx provides real collection classes — .Array,
.Directory, .Table, .Set, .Bag, .Queue, .OrderedCollection,
and others — as a genuine alternative to stem-simulated arrays, with
actual bounds/type behavior rather than the silent-anything-goes
behavior of a compound variable:
arr = .Array~of('a', 'b', 'c') -- construct-and-populate in one call
do item over arr
say item
end
Prefer do item over collection to do i = 1 to stem.0 when the
data doesn't need positional indexing at all.
Indirect/computed stem access has more than one form, and reaching for the wrong one doesn't always error. Standard classic Rexx already handles the common case cleanly: a tail that is a single bare symbol substitutes that symbol's current value directly, with no bracket of any kind:
mystem.1 = 'one'; mystem.2 = 'two'; mystem.3 = 'three'
i = 3
say mystem.i /* CORRECT: 'three' -- bare-symbol substitution,
classic Rexx, no bracket needed at all */
One form that looks plausible by analogy is a genuine pitfall in any Rexx dialect:
say mystem.(i) /* WRONG in any Rexx dialect: Error 43 --
parsed as a call to a routine literally
named MYSTEM */
ooRexx note: two more forms that look plausible by analogy —
mystem[i] and mystem.[i] — are valid only in ooRexx; classic
Rexx's own lexer has no defined meaning for [/] at all, so a
classic-Rexx program can't reach for either by mistake in the first
place. In ooRexx, both parse and run: [] is genuinely one uniform
mechanism — bracket notation sends a message named [] to the
receiver, with whatever's inside the brackets passed as its argument
list — but what that list means is entirely up to the receiving
object's own [] method, and the two below interpret it very
differently:
.String, [] is character/substring extraction (ooRexx
Language Reference §5.1.7.22): "abc"[2] is "b"; with a second,
comma-separated argument, "abc"[2,4] is a substring, "bc"..Stem, [] is documented separately, under "Evaluated
Compound Variables" (§1.13.5.1), as an alternate way to construct a
compound-variable tail: each comma-separated expression is
evaluated to a string, and the results are joined with periods to
form the tail — a.[1+2, 3+4] assigns a.3.7, exactly as if you
had written that dotted tail yourself. It is not positional
"element N" indexing the way .Array's [] is.say mystem[i] /* NOT AN ERROR -- and that's the trap: this is
a perfectly legitimate character selection,
just not the one intended. 'mystem' with no
trailing dot is a plain simple variable; it
was never assigned, so it's a dropped
symbol and evaluates to its own name, the
string "MYSTEM"; [] on a String correctly
selects a character -- "S" (character 3 of
"MYSTEM"). Nothing here is wrong except the
programmer's expectation that this reaches
the stem element instead */
say mystem.[i] /* CORRECT: 'three'. 'mystem.' -- the stem
itself, trailing dot, no tail -- is always
already bound to a genuine Stem object
(ooRexx Language Reference §1.13.4); its []
method takes i, evaluates it, and uses the
result directly as the tail -- the single-
expression case of the same tail-building
mechanism as the two-expression example
above */
A third, unrelated trap: using another compound variable directly as a tail component looks like it should nest, but the tail is split on periods into independent pieces before any substitution happens — a piece is never itself re-parsed as a compound-variable reference. This is standard Rexx behavior in both dialects, no brackets involved:
orphans.0 = 0
orphans.0 = orphans.0 + 1
orphans.orphans.0 = 'first' /* WRONG: not "orphans.1" -- every
iteration clobbers the SAME fixed
derived tail ORPHANS.ORPHANS.0 */
The safe pattern here too: copy the index into a plain simple
variable first, then use that variable as the tail (n = orphans.0;
orphans.n = value).
ooRexx note: a stem's own item count sidesteps maintaining a manual counter tail altogether —
orphans.is always a genuine Stem object (as established above), and~itemsis a read-only query reporting how many of its compound variables are currently set.~itemsitself does not add, remove, or change anything — it's the tail assignment that changes the count;~itemsonly reports whatever that count happens to be at the moment you call it, with nothing for the program to track by hand:orphans.[orphans.~items] = 'first' -- items was 0; sets tail "0" orphans.[orphans.~items] = 'second' -- items is now 1; sets tail "1" orphans.[orphans.~items] = 'third' -- items is now 2; sets tail "2"The stem starts out empty, with
~itemsequal to0— so the very first element written this way lands in tail"0", not tail"1": the bracket expression evaluates~itemsfirst, then the assignment runs and is what makes that tail exist, bumping the count for next time. This differs from the classic convention, where tail0is reserved for a manually-maintained counter and data start at1. The two schemes are not interchangeable; pick one and stay consistent within a given stem. Add+1to get the classic 1-based numbering instead:orphans.[orphans.~items+1] = 'first' -- items was 0; sets tail "1" orphans.[orphans.~items+1] = 'second' -- items is now 1; sets tail "2" orphans.[orphans.~items+1] = 'third' -- items is now 2; sets tail "3"This still relies on the same discipline as the 0-based form: every tail has to come from this exact idiom, with nothing added or removed out of band, or the numbering silently stops meaning what you think it means.
A Stem is an associative array — a string-indexed map — which is not the same thing as an array, even when every tail happens to be a contiguous integer: that's a simulated, conventional usage built on top of a fundamentally different structure, the way a flight simulator imitates flying without being an airplane. Nothing stops a program from setting
orphans.fooororphans.17directly alongside the sequence above. Don't use~itemsas a loop bound (do i = 1 to orphans.~items) except in the narrow case where every tail was built by exactly this idiom and none was added or removed out of band. The general, safe way to visit every populated tail isdo tail over orphans.~allIndexes(the tail names) ordo value over orphans.~allItems(the values directly, when the tail names themselves don't matter):do tail over orphans.~allIndexes say tail':' orphans.[tail] endIf what's actually wanted is array-style append — add an element and let the collection work out the next position itself — a real
.Arrayand its own~appendmethod do that directly, with none of the discipline the~items/~items+1idioms above depend on.orphans.[orphans.~items] = valueonly imitates the effect, for a Stem built consistently one way or the other; it is not itself an append operation. An.Arrayalso comes with~first/~last(the index of the first/last item, or.nilif empty) and~firstItem/~lastItem(the item itself) — reading the ends of the collection this way needs no bracket arithmetic and no assumption about how it was populated, unlike reaching for tail"0"ororphans.~items - 1on a Stem. Unless the data genuinely needs a Stem's string-keyed lookup, prefer the array.
ooRexx note: .stem~new creates a fresh, otherwise-anonymous Stem
object, and it is a genuinely different object from the Stem
object automatically bound to a compound-variable stem of the same
name — assigning the new object to a variable doesn't connect the two,
even though both are ordinary Stem objects and both support the same
bracket notation. realStem (no trailing dot) and realStem.
(trailing dot, no tail) have always been different variables, even in
classic Rexx with no ooRexx involved at all; what's new here is only
that realStem. is bound to a genuine Stem object, not just a plain
default value:
realStem = .stem~new
realStem[9] = 'nine'
say realStem.9 /* still dropped -- "REALSTEM.9" -- the
compound-variable route uses its own,
separate Stem object, sharing nothing with
the one 'realStem' happens to hold */
say (realStem. == realStem) /* 0 -- confirms they're genuinely
different objects, not aliases */
ANSI X3.274-1996 distinguishes a symbol (§3.1.47: "a sequence of characters used as a name... [symbols] are used to name variables, functions, etc.") from the variable it may or may not currently name. A symbol that has never had a value assigned to it — what's often called "uninitialized" informally — has no variable behind it yet; the standard's own term for this state is dropped (§3.1.16: "a symbol which is in an uninitialized state, as opposed to having had a value assigned to it, is described as dropped"). When you refer to a dropped symbol, its value is by default its own name in upper case. This is frequently a convenient alternative to the use of literal strings. However, if you inadvertently assign a value to that same name elsewhere in the program — turning it into a real variable — you may get incorrect and apparently inexplicable results from code that still expected it dropped. It is best to adopt naming conventions that minimize the risk of such problems.
Some recommend always using explicit literal strings for constants.
Although well meant, this advice can lead to programs that are harder
to read. Use dropped symbols as constants, but judiciously. If you
choose to not exploit this default behavior, place a SIGNAL ON
NOVALUE at the beginning of your program to detect any reference to a
symbol that's still dropped when your logic expected a real variable.
ooRexx note: never name your own variable result. This is the
same "a dropped symbol reverts to its own name" behavior above, but
with a genuinely surprising trigger in ooRexx: result isn't only set
by CALL. Any bare message-send statement — a whole clause, its
return value not assigned to anything — is handled the same way as a
CALL to a routine with no RETURN value: if the invoked method
returns nothing at all (not even .nil — several Collection methods,
e.g. ~put, are defined to return no result object), result is
dropped again — the variable you just assigned reverts to being a bare
symbol. This breaks the moment any bare message-send whose method
returns nothing executes — including the ordinary case of building up
your own local variable named result via repeated bare sends to
it:
result = .Directory~new -- fine: plain assignment, RESULT is now
a real variable
result~put('', 'INTERPRETER') -- runs; but ~put returns no result
object, so RESULT is dropped again
right after this line
result~put('', 'DIALECT') -- Error 97.1: Object "RESULT" does not
understand message "PUT" -- the
PREVIOUS line already dropped it,
so RESULT is just the string "RESULT"
again by the time this line runs
putReturn = d~put('v','k') raises "Message did not return a
result," which is the proof that ~put truly returns nothing; a
single bare result~put(...) statement reproducibly drops result
immediately afterward, while an identically-shaped sequence using any
other variable name is never affected. Pick any other name (info,
found, outcome, ...) — there is no scope in which reusing result
as your own variable buys anything.
Classic Rexx does not allow passing parameters to procedures by name or by reference. However, you can often get similar results by passing constants and using them to construct names. This is an extremely common and powerful technique, especially in conjunction with compound variables. However, there are a few pitfalls.
When all you need is to read or set a variable by name, prefer
VALUE() to INTERPRET. VALUE(name) reads the variable named
name; VALUE(name, newvalue) sets it and returns the old value.
VALUE also takes an optional third argument, selector, naming a
variable pool other than the program's own — VALUE(name, newvalue,
'ENVIRONMENT') reads or sets an environment variable instead of a
Rexx variable, the mechanism behind the OS2ENVIRONMENT examples in
Figure 2 above — something INTERPRET has no direct equivalent for at
all. The third argument is not universal, though: REXX/VM under GCS
(see ADDRESS and the default environment above) does not
support the selector argument at all, only the two-argument form.
Building a string and running it through INTERPRET can achieve the
same thing, but INTERPRET executes whatever Rexx source text it is
handed — not just an assignment to one variable — which matters the
moment name is not a value you fully control. Hand the same crafted
string (a syntactically invalid variable name with a second clause
hidden after a semicolon) to each, and the difference shows up
immediately: VALUE() simply raises a SYNTAX condition on the
malformed name and executes nothing else, while INTERPRET begins
executing the hidden second clause (visibly compiling and invoking the
routine it named) before failing only because that call was missing a
required argument — not because the injection itself was ever blocked.
Reserve INTERPRET for
genuinely dynamic code — constructing a whole statement or expression
at run time — not as a heavier substitute for a single indirect
variable reference.
If you call a procedure that has an EXPOSE clause on the
PROCEDURE statement, it will only have access to the variables that
you exposed. If you pass an argument containing the name of some
other variable, the code will only be able to access a local version
of that variable.
If you call a procedure that requires a variable name as a parameter, and use a dropped symbol to represent its own name for that parameter, you will probably get incorrect results on your second time through.
ooRexx note: ooRexx closes the by-reference gap classic Rexx
disclaims above, with a real USE ARG statement:
::routine adjustBalance
use arg account -- account is a genuine mutable reference to
the caller's object, not a copy
account~balance = account~balance - fee
USE ARG is for mutable objects passed by reference — it does not
turn a plain string or number into a by-reference parameter the way
some other languages' reference parameters do, since Rexx strings and
numbers are themselves immutable values.
rc and result are set by different things, and conflating them is
a real, easy-to-make bug — in classic Rexx as much as ooRexx. rc is
set only by host commands — a bare host-command clause, ADDRESS
foo 'expr', or similar. It is not set by CALL or a
function/method invocation.
result is set by CALL and by any unassigned function invocation
(see the caution about naming your own variable result, above).
Reading rc after CALL SysFileCopy reads the previous host
command's return code (or the literal string 'RC' if none has run
yet), not the routine's own outcome:
call SysFileCopy src, dst
copyRc = result /* CORRECT -- SysFileCopy is a routine call */
address system 'some-command'
cmdRc = rc /* CORRECT -- a host command sets rc */
Don't assume CHARS()/LINES() give an exact count. ANSI Rexx
permits either to report only 0 or 1 (more data available, or
not) instead of a real number, and which one is exact, if either, is
a per-implementation choice, not a platform split — CMS's LINES()
is exact for disk files but its CHARS() never is; ooRexx's
CHARS() is exact for disk files but its own LINES() isn't:
/* WRONG -- assumes lines() gives an exact count */
do lines(myfile)
myline = linein(myfile)
...
end
/* on a target where lines() isn't exact for this stream, this would
only read one line */
/* CORRECT -- portable regardless of whether lines() is exact */
do while lines(myfile) /= 0
myline = linein(myfile)
...
end
Check your specific target's documented behavior rather than assuming
portability. LINES(file) = 0 (or CHARS(file) = 0 for
character-mode reads) is a reliable, portable end-of-file test
regardless of whether the count is exact. What is not reliable, in
any dialect, is using STREAM(file,"State") and checking for
"NOTREADY" — that state can also result from an I/O error or other
condition, not just end-of-file, and you only see it after already
reading past the end.
LINEOUT opens in append mode by default; a full-file overwrite
needs an explicit replace first. This is standard Rexx behavior, not
an ooRexx quirk, and a real bug pattern, not a hypothetical: a script
that deletes a file and then writes it fresh with repeated LINEOUT
calls will silently duplicate content the moment the delete step ever
fails (a locked file, a permission issue), because LINEOUT doesn't
know the delete was supposed to have happened:
/* WRONG -- if the delete silently fails, this appends instead of
replacing, duplicating old content underneath the new */
call SysFileDelete path
call lineout path, newContent
/* CORRECT -- explicit replace, independent of whether a prior
delete succeeded */
call stream path, 'C', 'OPEN WRITE REPLACE'
call lineout path, newContent
call stream path, 'C', 'CLOSE'
ooRexx also offers stream methods on a .Stream object as an
alternative to the classic built-in functions above, preferred in new
ooRexx code:
s = .Stream~new(path)
s~command('OPEN WRITE REPLACE')
s~lineout(newContent)
s~close()
The pitfalls in this section have no counterpart in classic Rexx — they arise only from ooRexx's package/class/object model, and are worth a dedicated section rather than a callout under an existing classic-Rexx topic.
::CLASS needs PUBLIC to be visible from another packageEvery file ooRexx parses — the program you invoke directly, or one
pulled in via ::REQUIRES — becomes its own .Package object. A
class defined with plain ::CLASS Foo parses cleanly and is usable by
its own package's mainline code, but is not visible via the
leading-dot environment-symbol lookup (.Foo~new) from a different
package that reaches it only through ::REQUIRES. The failure is
silent at ::REQUIRES time — no error until the first actual
reference, and it reads like the class doesn't exist at all:
Error 97.1: Object ".FOO" does not understand message "NEW".
Fix: declare the class PUBLIC:
::class Foo public -- required for .Foo~new to work from another package
PUBLIC is also not transitive across a chain of packages. If package
A ::REQUIRES both B and C, and B's code references a public class
from C, B must ::REQUIRES C itself — B does not inherit visibility
of C just because A happened to require both.
::REQUIRES with a relative path resolves against the current directory, not the file's own directory::REQUIRES '../lib/Foo.cls' works only when the current working
directory happens to make that relative path correct, and breaks the
moment the program is invoked from anywhere else. A bare filename
with no path prefix, by contrast, is resolved via the program search
path (PATH), independent of the current directory — this is how
multi-file ooRexx projects that must run from any invocation location
get away with ::REQUIRES 'Foo.cls': they ship a setenv script that
adds each directory containing a required file to PATH, and every
::REQUIRES uses a bare filename, never a relative path.
A program's non-directive ("mainline") statements must form one
contiguous block at the very start of the file, before the first
:: directive of any kind. Once a ::CLASS, ::ROUTINE, or
::REQUIRES directive has appeared, every subsequent top-level clause
must also be a directive — plain executable code cannot resume after
it, even just to call a routine defined below:
/* WRONG -- fails with Error 99.916, "Unrecognized directive
instruction" */
::requires 'Foo.cls'
say .Foo~new~greet
/* WRONG in a subtler way -- parses fine, but does nothing at all:
defining ::ROUTINE main does not call it */
::requires 'Foo.cls'
::routine main
say .Foo~new~greet
/* CORRECT -- mainline first, explicitly invoking the entry routine */
parse arg argLine
exit main(argLine)
::requires 'Foo.cls'
::routine main
use strict arg argLine
say .Foo~new~greet
Every ooRexx string is a .String object with methods. This isn't
reserved for "real" objects like .Array/.Directory — it applies
just as much to ordinary character-string manipulation, where
classic-Rexx habit reaches for nested built-in-function calls instead:
/* Classic-Rexx style -- reads inside-out */
text = translate(substr(str, 1, 5))
text = strip(space(translate(str)))
/* ooRexx idiom -- reads left-to-right, in actual execution order
(never name a variable `result` -- see the ooRexx note above) */
text = str~substr(1, 5)~translate
text = str~translate~space~strip
TRACE is standard Rexx, not an ooRexx feature, and the advice below
applies to any Rexx dialect. When a program's observed behavior
doesn't match what the source should do — especially anything
involving ADDRESS, string-building, or implicit operators — add
TRACE I (or TRACE ALL for more detail) near the top of the script
and run it again, rather than iterating on black-box hypotheses
(rewording the command, adding or removing quotes, trying alternate
constructs) and inferring the cause from outcomes alone. TRACE I
prints every clause as it executes, the intermediate result of each
sub-expression, and, critically, the exact string handed to ADDRESS
or any other target — which settles what string your code actually
built as a fact instead of a guess. If a second attempt at explaining
unexpected behavior from outputs alone would just be another guess,
that's the signal to add TRACE I instead of guessing a third time.
You can make your use of REXX more enjoyable and productive by
following a few basic rules. Learn REXX on its own terms. Be careful
and consistent in your use of abutment and continuation. Do not use
keywords or single letters as variable names. Use SIGNAL only for
error handling — reach for CALL ON instead when the guarded code
should be able to resume. Do not attempt to use the same lines as
both inline code and out-of-line code. Place a PROCEDURE at the
beginning of every subroutine, and carefully analyze which variables
to expose, especially if you will be passing the names of variables —
and remember that EXPOSE means something different again inside a
::METHOD, and is not legal at all inside a ::ROUTINE. Be careful
in your use of dropped symbols, and never name one of your own
result. Adopt a clear and consistent programming style. Prefer
ooRexx's real collection classes to stem-simulated arrays when the
data doesn't need positional-only indexing. Understand the vagaries
of REXX parsing. Try to make your code portable across platforms and
usable in multiple environments.
These rules will not, of course, eliminate all errors, but they will certainly eliminate many errors that would otherwise be highly likely. Good luck, and practice Safe REXX!
Note: the portability considerations in this edition are based on direct experience with REXX in CMS (VM/SP), DOS (Personal REXX), MVS (TSO/E), OS/2 (SAA REXX and OREXX/ArcaOS), and, since this merged edition, ooRexx on Windows and Linux. Comments on portability to or from AREXX or Regina are still welcome, as neither has been used directly by me.
EDIT and TEST as TSO commands, including the specific EXEC subcommand behavior each imposes on a REXX exec it launches — a separate manual from the REXX Reference above, which does not cover either command)ADDRESS IPCS instruction and its per-mode availability within an IPCS session — a separate manual from the REXX Reference above, which does not cover IPCS)IBM, MVS/ESA, OS/390, z/OS, OS/2, VM/SP, VM/ESA, and z/VM are trademarks of IBM Corporation. Unix is a trademark of The Open Group. Open Object Rexx (ooRexx) is an open-source project distributed under the Common Public License (CPL); it is not a trademark of IBM or any other single organization. Slightly different versions of the two source articles for this edition appeared in print in the 1990s; the 2023 web revisions were merged and updated with ooRexx guidance in 2026.
Shmuel (Seymour J.) Metz (שמואל בן לייביש ולאה). Mr. Metz is a Senior MVS Systems Programmer supporting a Federal Government contract. He has worked with computers for over half a century. He has been involved in the development of two different operating systems. He has experience on a wide variety of languages and platforms, and has used REXX on more than four of them. Mr. Metz has an MA in Mathematics from the State University of New York at Buffalo.
This merged edition was compiled in 2026 from the 2023 web revisions
of the two source articles described under Publication History above.
Its ooRexx-specific guidance was cross-referenced against a maintained
ooRexx-conventions reference. Where this edition's text depends on a
specific claim about interpreter behavior rather than an assumption by
analogy — a PARSE VERSION output string, an error number, whether a
given feature or environment exists in a given dialect — that claim
rests either on a running ooRexx 5.2.0 interpreter or on the specific
implementation's own primary reference manual, rather than on
secondary sources or assumption; see References for the full list of
manuals consulted, and which claims rest on secondary sources instead
where a primary manual could not be obtained.
Editorial and drafting assistance for this edition was provided by Claude (Anthropic).