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, IRXJCL, 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. IBM later released OREXX (Object REXX), a proprietary object-oriented extension of classic Rexx for OS/2, Windows, and AIX. Open Object Rexx (ooRexx), its open-source successor, is a widely-used, 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.
For Cowlishaw's own account of REXX's origins and evolution — his 1984 paper in IBM Systems Journal, a 2004 interview, and a forty-year retrospective — see RexxInfo.org's history links page.
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) both lack
CHANGESTR/COUNTSTR/QUALIFY —
neither mainframe interpreter has been brought up to ANSI-1996 level;
both remain at essentially the older TRL-2 function set, and z/VM is not
somehow ahead of z/OS here. 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 carve-out exists because stream I/O is
implemented via genuine (POSIX) syscalls, and issuing one requires the
enclosing task to be dubbed — established as a UNIX process by
z/OS UNIX System Services. Dubbing happens automatically inside the OMVS
shell; an ordinary TSO/E address space is not dubbed, so
LINEIN/LINEOUT/STREAM are
unavailable there regardless of whether TSO itself is interactive or
batch. This is unrelated to the link/attach family (LINK,
LINKMVS, LINKPGM, ATTACH,
ATTCHMVS, ATTCHPGM — see ADDRESS and the default environment below): those
call other MVS programs or subroutines directly and have nothing to do
with UNIX System Services or POSIX syscalls.
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.
Nor is this an exhaustive survey of every Rexx implementation that has existed. ooRexx, Regina, TSO/E REXX, and CMS REXX are covered because they are the ones in active, widespread use; smaller or historical implementations (Personal REXX, KEXX, uni-REXX, and others) are not discussed, and code should be tested against any of them directly rather than assumed to follow the patterns described 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,
IRXJCL, 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
z/OS), ADDRESS CMS (on z/VM), 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, ATTACH, ATTCHMVS,
ATTCHPGM — available to a REXX exec in any address
space, TSO or not. The APPC family: CPICOMM,
LU62 — 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 a TSO/E address space —
interactive TSO or batch TSO via PGM=IKJEFT01, both of
which establish TSO/E fully — not under PGM=IRXJCL, which
runs a REXX exec directly without establishing TSO/E at all ("batch"
alone is ambiguous between these two; they are not interchangeable).
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 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: only STREAM, STEM,
LIFO, and FIFO are valid resource types
(LIFO/FIFO are Regina's own extensions beyond
the ANSI baseline; USING is not among them). Neither
classic OS/2 REXX nor OREXX has it either — ADDRESS in both
is just ADDRESS [environment] [expression], no
WITH clause of any kind, 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.
An exec invoked from ISPF runs under the same default host-command
environment as any other TSO/E or CMS exec — TSO or
CMS, respectively, per the table above. ISPF itself adds
exactly one functional difference: the ISPEXEC environment,
for ISPF dialog services (panel display, dialog variable services, and
the rest of the ISPF service family), and, inside an active edit
session, ISREDIT, for edit-macro line commands. Nothing
else about writing REXX changes because ISPF is the caller.
ISPF reserves variable names beginning with Z for its
own dialog variables — ZSCREEN, ZUSER,
ZAPPLID, and the rest of the Z-prefixed pool
it shares across panels and services. Do not begin a variable name with
Z in code invoked from ISPF; doing so risks colliding with
one of ISPF's own variables.
System REXX starts automatically during Master Scheduler
Initialization and runs execs outside TSO/E and batch entirely — no job,
no logged-on user, no ISPF. An exec is submitted either from an
authorized program via the AXREXX macro or from the
operator console with MODIFY AXR,<exec name>; it runs
in its own AXR address space (TSO=NO) or, if
TSO=YES is specified, in one of up to eight dedicated TSO
server address spaces (AXR01–AXR08) so it can
allocate data sets without risking a DDNAME conflict with another exec
running concurrently — System REXX frees any allocations left open when
a TSO=YES exec ends. The default host-command environment
and its TSO=YES/TSO=NO differences are in the
table above; nothing below repeats that.
There is no terminal. Any SAY or TRACE
output goes straight to the console that invoked the exec — the
operator's console, or whatever console issued the triggering command —
not to a session only the exec's own user is looking at. Debug output
and status messages that would be harmless chatter under TSO/E become
console traffic here; keep both to a minimum, and never leave
TRACE on by default in a System REXX exec the way you might
for a moment during interactive TSO/E development.
Exec names have a platform-imposed naming restriction with the same
shape as ISPF's Z-prefix rule above: the
REXXLIB concatenation that System REXX searches must not
contain an exec beginning with the letters A through
I — that range is reserved for IBM's own execs, shipped in
SYS1.SAXREXEC, which is appended to the concatenation
automatically. Name a System REXX exec starting with
A–I and it risks colliding with (or simply
losing to search order against) an IBM-supplied exec of the same
name.
The AXR address space itself is non-cancelable — the
only way to stop it is the operator STOP AXR command, not a
normal task cancel. An exec that hangs or loops here is a harder problem
to walk back from than the equivalent mistake under TSO/E, where the
user's own session can simply be canceled or logged off.
eComStation and ArcaOS are direct continuations of OS/2 — the same Classic REXX (SAA REXX, IBM's Procedures Language 2/REXX) and the same OREXX (Object REXX) that shipped on OS/2 carry forward unchanged on both, and nothing below needs to distinguish among the three; "OS/2" covers all of them.
Both interpreters are installed concurrently, but only one is active
system-wide at a time. SWITCHRX.CMD switches by renaming
files — active is rexx.*, inactive is crexx.*
or orexx.* — and the switch takes effect only after a
reboot. Confirm which one is actually running with
PARSE VERSION (table above); it isn't a per-exec
choice.
The two interpreters keep separate online reference manuals, both
installed together: CREXX.INF — the name is short for
Classic REXX — remains the Classic REXX reference, distinct from OREXX's
own manual, so installing OREXX does not leave Classic REXX undocumented
or paper over which interpreter a given piece of reference material
actually describes. This OS/2 naming convention (CREXX.INF,
crexx.*) has nothing to do with Adrian Sutherland's
cREXX, an unrelated, independently-named open-source REXX
interpreter project written in C — the two "CREXX" names are
coincidental, not the same software or the same lineage.
The OREXX guidance in this edition is drawn from the Object REXX Reference manuals cited in the References section, not from direct testing against a live OREXX interpreter the way ooRexx and Regina claims elsewhere in this edition are — OREXX has been out of IBM support for many years, and no live copy was available to verify behavior against while preparing this edition.
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
.Stringclass provides acaseless-prefixed method family directly —caselessEquals,caselessCompare,caselessPos,caselessCountStr,caselessChangeStr,caselessAbbrev,caselessMatch,caselessStartsWith/caselessEndsWith,caselessWordPos,caselessContains/caselessContainsWord— rather than callingTRANSLATE()/~upperon both sides before every comparison.PARSEitself has aCASELESSmodifier too (alongsideLOWER) for case-independent template matching — but unlikePARSE UPPER, which is genuine ANSI X3.274-1996 syntax (spelled out in full; the standard documents no abbreviated form for it), neitherLOWERnorCASELESSappears 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:
.Streamis 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 same0-or-1-vs-exact-count behavior included) as their function-call equivalents..InputStream,.OutputStream, and.InputOutputStreamare abstract mixin classes underneath it, meant for building custom stream implementations, not for direct use on an ordinary file.A
.Streamcan be looped over two ways. Directly, since it supplies its own lines toDO ... OVER:s = .stream~new('data.txt') s~open('read') do ln over s say ln end s~closeOr explicitly, via its
~suppliermethod, which returns a.StreamSupplierobject (~available,~item,~index,~next, the same protocol as any other ooRexx Supplier):s = .stream~new('data.txt') s~open('read') sup = s~supplier do while sup~available say 'line' sup~index': ' sup~item sup~next end s~closeRequesting more than one supplier from the same stream is safe only if you account for this: creating a supplier consumes a line from the stream to prime its first item, so each one's starting position is fixed at creation time, not when you start looping over it. That consumed line isn't gone for everyone, though: every supplier draws from one shared recorded sequence of the lines already pulled off the stream, not its own independent copy of the file, so a line one supplier's creation consumed is still there for another supplier positioned to reach it:
s1 = mystream~supplier s2 = mystream~supplier do while s1~available -- reads every line in the file say s1~item s1~next end do while s2~available -- reads every line EXCEPT the first -- say s2~item -- s1's creation already consumed that one s2~next end
s1, created first, reads the entire file when looped.s2, created immediately after, already starts one line further in — whatevers1's creation alone consumed — so its loop silently skips that line, even thoughs2~nextis never called until afters1's loop finishes.
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: ooRexx's
PARSE VERSIONnamevalue follows its own pattern,REXX-ooRexx_<version>(MT)_<bits>-bit— for example:parse version v say von ooRexx 5.2.0 produces:
REXX-ooRexx_5.2.0(MT)_64-bit 6.06 18 Apr 2026If your
SELECTonPARSE VERSION'snamefield needs to distinguish ooRexx from classic implementations, match on anamethat begins withREXX-ooRexx, e.g.,when name~abbrev('REXX-ooRexx') then do ... end— do not assumenamewill be one of the two classic values above. The two classic values themselves are also not the whole story — the full set ofname/levelvalues you may actually meet:
| Implementation | name |
level |
Source |
|---|---|---|---|
| OS/2 classic REXX (Procedures Language 2/REXX) | REXXSAA |
4.00 |
OS/2 Procedures Language 2/REXX Reference, S10G-6268 |
| OREXX (IBM's Object REXX) | OBJREXX |
6.00 |
Object REXX Reference, OS/2 edition |
| CMS / TSO/E REXX (classic mainframe, "REXX370") | REXX370 |
4.00 |
z/OS TSO/E REXX Reference, SA32-0972, and z/VM REXX/VM Reference, SC24-6314 |
| Regina | REXX-Regina_<version> (e.g.,
REXX-Regina_3.9.6(MT)) |
5.00 |
The Regina Rexx Interpreter, Mark Hessling; 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 |
Open Object Rexx Reference, RexxLA |
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
(despite the name, these copy/move ordinary files everywhere; WPS-object
handling is an OS/2-only bonus, not the function's purpose) |
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
each behave 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.
REXX comments are delimited by /* and */,
and — unlike most C-family languages — they nest:
encountering another /* while already inside a comment
opens an additional level, and it takes an equal number of
*/ to close back out to real code. This is standard Rexx
behavior, deliberate language design documented since TRL-2 — not an
ooRexx extension or a parser bug. Verified identical on both ooRexx
5.2.0 and Regina 3.9.7 (a non-object-oriented classic interpreter): an
unbalanced /* outer /* inner still-in-outer */ raises the
same "Unmatched comment delimiter" error on both, rather than the first
*/ closing everything.
The trap is that ordinary prose inside a comment can contain
the two-character sequence /* incidentally, with no intent
to nest anything:
/* every *.rex/*.ps1/*.lua file under scripts\ gets copied */
Reading left to right, rex/*.ps1 and
ps1/*.lua are each an unintended /* that opens
another nesting level. With only the one closing */
actually written, two of the three opened levels stay unclosed —
everything from that comment onward, often dozens of otherwise-correct
lines, is silently absorbed as comment text until the parser exhausts
the file. The reported error is anchored at the original,
outermost /* ("unmatched comment delimiter"), not at
whatever later text actually broke the pairing, which makes the real
cause easy to miss on a first read of the diagnostic.
Avoid slash-asterisk sequences inside comment prose — write
".rex, .ps1, and .lua" rather
than ".rex/*.ps1/*.lua" — or, if separator-joined
extensions are unavoidable, use something other than /
immediately before a literal *.
-- as a line comment, running from the
-- to end of line, is not an ooRexx extension:
both ooRexx 5.2.0 and Regina 3.9.7 (a non-object-oriented,
ANSI-1996-level classic interpreter) support it, used throughout this
edition's ooRexx examples above (e.g., the
USE ARG/account example under Variable
references) without ever being formally introduced until now. Unlike
/* */, a line comment has no closing delimiter to get
wrong, so the nesting trap above doesn't apply to it.
It does create a different, genuinely dangerous trap of its
own, in both interpreters: -- is recognized as a comment
start unconditionally, taking priority over parsing the two characters
as two separate unary-minus operators — even with no intent to comment
anything out. Same result on both:
a = 5
b = 3
say a - -b /* 8 -- a real double-negation, spaces keep the two
"-" tokens separate */
say a-- b /* 5 on BOTH interpreters -- "-- b" is read as a
comment and discarded; this is NOT "a minus
negative b" on either one */
Subtracting a negative value therefore needs a space between the two
minus signs (a - -b) whenever there is any chance the two
signs could end up adjacent — a value substituted in from a variable or
expression can just as easily produce the adjacent-minus case as literal
source text can. No error is raised at all here on either interpreter:
the expression silently evaluates to the wrong number, which is what
makes it worth flagging specifically rather than trusting the parser to
catch it.
Scope of this trap: it applies to ooRexx and Regina
specifically — both are ANSI X3.274-1996-level implementations
(PARSE VERSION level 6.06/5.00
respectively, see the table above). TSO/E REXX and CMS REXX, the
pre-ANSI TRL-2-level (level 4.00) dialects per that same
table, support neither -- as a comment nor UTF-8 source at
all. A source file for either one is EBCDIC text, not Unicode of any
kind, which rules out -- being a portable assumption there
independent of whatever ANSI-1996 does or doesn't say about it. Code
that must also run on either mainframe dialect should not rely on
-- being recognized as a comment there, and should avoid
adjacent minus signs regardless of dialect.
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.
REXX has no ternary conditional operator.
cond ? a : b, familiar from C, Java, and JavaScript, is not
REXX syntax at all — the parser reads the ? and
: as part of the surrounding expression and fails with a
syntax error (ooRexx: "Incorrect expression detected at ':'") rather
than doing anything resembling a conditional selection. Write out the
equivalent IF/THEN/ELSE instead,
or, for a single variable assignment, compute both branches into a plain
variable beforehand and reference that — there is no expression-level
shorthand for it in any dialect covered here.
/* 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 classic Rexx's environment-variable selector */
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, andUSEgenuinely appear as bare words inside::METHOD/::ROUTINEbodies, andOVERappears as a bare word inDO var OVER collectionanywhere 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 */
This example genuinely diverges by implementation — checked directly on the two interpreters available while preparing this edition, and it's a three-way split, not two-way:
SIGNAL and the label are accepted; the error above surfaces
later, at the END statement, exactly as described.SIGNAL BELL statement itself —
Error 16.2: Cannot SIGNAL to label "BELL" because it is inside an IF, SELECT or DO group
— a different mechanism (objecting to the target label's location) and a
different, earlier failure point than TSO/E REXX's.SIGNAL —
Error 47.2: Labels are not allowed within a DO/LOOP block —
a static structural rule with nothing to do with control-stack flushing
at all.All three implementations agree the pattern is invalid; they disagree substantially on when and why. Don't assume a specific diagnostic, or even a specific point of failure, is portable across dialects for this pattern — only that some form of rejection is universal.
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: many of the built-in functions used alongside
PARSE—WORD,SUBWORD,WORDPOS,POS,SUBSTR,DELWORD, and others — invoke methods of theStringclass. For most of them the first argument becomes the receiver of the message, e.g.,SUBSTR("abcde", 3, 2)is"abcde"~substr(3, 2); forPOS,WORDPOS,LASTPOS,INSERT, andOVERLAY, it's the second argument instead, e.g.,POS("a", "Haystack", 3)is"Haystack"~pos("a", 3).
ooRexx note: for pattern matching that outgrows what a
PARSEtemplate can express cleanly — optional pieces, repetition, character classes, alternatives — ooRexx's.RegularExpressionclass 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::REQUIRESis needed:str = 'name=John' re = .RegularExpression~new('[:alpha:]+=[:alpha:]+') say re~match(str) -- 1: the whole string matches ::requires "rxregexp.cls"
matchreturns 1 or 0 for whetherstringmatches;poslocates 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 ordinaryPARSE.
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::CLASSbody 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 likeENDinside 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, with the same error number and wording on both ooRexx and
Regina. 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: there is an
EXPOSEclause and anEXPOSEinstruction, and they are easy to conflate. TheEXPOSEclause ofPROCEDURE(used inside a classic internal subroutine, as above) exposes the caller's local variables. TheEXPOSEinstruction, used as the first statement of a::METHODbody, exposes that object's instance variables — a completely different variable pool, private to the object, not the caller's locals. And theEXPOSEinstruction 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. The whole program parses and starts running normally; the failure only happens at the momentmyroutineis actually called, as an ordinary runtime execution error —say 'before call' -- this really does print call myroutine -- fails HERE, not at parse time say 'after call' -- never reached exit ::routine myroutine expose foo -- Error 98.992: "The EXPOSE instruction may say foo only be used from method invocations."and it is catchable in-process, contrary to what was claimed here before: a
SIGNAL ON SYNTAXtrap set in the caller fires normally,CONDITION('C')reportsSYNTAX— there is no parse-time failure to dodge the trap. If code invoking such a routine is launched as a child process with no trap set up, the ordinary consequence of any uncaught error applies (non-zero return code, diagnostic on stderr) — nothing special about this particular error in that respect.
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 anUNKNOWNmethod 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, orClass~enhancedat 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:
.Arrayand other collection objects are safer than stem-simulated arrays — they enforce real bounds instead of a compound variable's silent-anything-goes:arr = .Array~of('a', 'b', 'c') do item over arr say item endPrefer
do item over collectiontodo i = 1 to stem.0when position doesn't matter.
Indirect/computed stem access has more than one form, and reaching for the wrong one doesn't always error. A tail that's a single bare symbol substitutes its current value directly, no bracket needed:
mystem.1 = 'one'; mystem.2 = 'two'; mystem.3 = 'three'
i = 3
say mystem.i /* CORRECT: 'three' */
mystem.(i) looks like it should work the same way but is
a real pitfall in both dialects, with different failures: ooRexx parses
it as a call to a routine named MYSTEM. and fails with
Error 43.1; Regina instead falls through to an external
command lookup. Neither does array indexing — don't use this form.
ooRexx note:
mystem[i]andmystem.[i]are ooRexx-only (classic Rexx's lexer has no meaning for[/]at all — Regina fails at parse time withError 13.1). Bracket notation sends a[]message, and what it does depends on the receiver: on a.Stringit's character extraction ("abc"[2]is"b"); on a.Stemit's an alternate way to build a tail from comma-separated expressions (a.[1+2, 3+4]assignsa.3.7) — not positional indexing. Somystem[i](no trailing dot) hits a dropped simple variable, which evaluates to its own name"MYSTEM", and[i](3) silently returns"S"— a real value, just the wrong one.mystem.[i](trailing dot — the real Stem object) is the form that actually returns'three'.
A third, unrelated trap: a compound variable used as a tail component
isn't re-parsed as a compound reference — the tail is split on periods
first. orphans.orphans.0 = 'first' doesn't produce
orphans.1; it always clobbers the same literal tail
ORPHANS.ORPHANS.0. Same fix as above: copy the index into a
plain variable first, then use that
(n = orphans.0; orphans.n = value).
ooRexx note:
~itemsis a read-only count of a Stem's currently-set compound variables. However, it is still cleaner to use collection objects: a real.Arrayand its~appendmethod add an element directly, and~first/~last/~firstItem/~lastItemread the ends — no arithmetic, no assumption about how the stem was populated. Prefer.Arrayunless the data genuinely needs a Stem's string-keyed lookup; to visit every populated tail on a Stem anyway, usedo tail over orphans.~allIndexes, not a loop to~items.
ooRexx note:
.stem~newcreates 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) andrealStem.(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 thatrealStem.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 */
a. = b. is not portable between dialects — it's
an ordinary, correct assignment in each, just not the same
assignment. By analogy with arrays or any other bulk data
structure, a. = b. reads as "copy all of b's compound
variables into a." Neither dialect actually does that; each does
something else entirely, consistent with its own variable model, and
code written assuming one dialect's behavior will simply be wrong — not
broken, just running the other dialect's well-defined semantics
instead of the one the author had in mind.
In classic Rexx, a. and b. used bare (no
tail) are each simply the name of one ordinary variable — the same
"default value" variable the common a. = '' idiom
initializes. a. = b. reads b.'s current
value as that bare variable and assigns it to a., and
— this is the part that catches people out — assigning to the bare stem
name resets the entire stem as an ordinary consequence of how
that assignment is defined, replacing every previously-set tail
(a.1, a.2, ...), not merely supplying a
default for tails not yet set. If b.'s bare form was never
itself explicitly assigned (only individual tails like b.1
were), it is still a dropped symbol and evaluates to its own name — so
every tail of a. ends up holding the literal string
"B.", not any value b actually holds anywhere.
Verified against Regina 3.9.7:
a.1 = 'a-one'
a.2 = 'a-two'
b.1 = 'b-one' /* b.'s bare form itself was never set */
a. = b.
say a.1 /* 'B.' -- not 'a-one', not 'b-one' */
say a.2 /* 'B.' -- a.'s own prior data is gone too */
b.1 = 'CHANGED'
say a.1 /* still 'B.' -- a. and b. are fully
independent after the assignment */
If b.'s bare form had been explicitly set first
(b. = 'x'), that value — not the dropped-symbol artifact —
is what ends up assigned to every tail of a., but
a.'s own prior individually-set tails are still replaced
wholesale either way, and a./b. remain fully
independent afterward: mutating one never affects the other.
ooRexx note: ooRexx does something different again — genuine object aliasing, not a stem-wide replacement.
a.andb.are each a bare symbol naming one particular variable — the stem's own object, per the note above — anda. = b.is an ordinary single-variable assignment: it points the namea.at whatever objectb.currently names, replacinga.'s previous binding entirely rather than merging into it. Verified directly against ooRexx 5.2.0, same starting data as the classic-Rexx example above:a. = b. say a.1 -- 'b-one' -- not 'a-one': a.'s own prior data is gone, same as classic Rexx -- but here it shows b.1's REAL value, not a wipe artifact say a.2 -- 'B.2' -- not 'a-two': this is b.2's own dropped- symbol default, because a. now IS b. say (a. == b.) -- 1 -- the same object, not a copy b.1 = 'CHANGED' say a.1 -- 'CHANGED' -- mutating b. mutates a. too, since they're one object under two names -- classic Rexx's a. and b. stayed fully independent after the same assignmentFrom the point of assignment on,
a.andb.are the same object under two names: changing a tail through either name changes what the other name sees — the opposite of classic Rexx's replace-and-go-independent behavior above, but the same underlying lesson either way:a.'s previous binding is simply gone once the assignment runs, exactly as reassigning any other variable would discard its old value, and neither dialect is doing anything other than its own ordinary, correct thing. If an independent copy is actually wanted, copy tails individually (or via~allIndexes/~allItems, see above) into a fresh.stem~newrather than assigning one bare stem to another.
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:resultisn't only set byCALL. Any bare message-send statement — a whole clause, its return value not assigned to anything — is handled the same way as aCALLto a routine with noRETURNvalue: if the invoked method returns nothing at all (not even.nil— several Collection methods, e.g.,~put, are defined to return no result object),resultis 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 namedresultvia repeated bare sends to it. Verified directly against ooRexx 5.2.0, including the exact error text below: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~puttruly returns nothing; a single bareresult~put(...)statement reproducibly dropsresultimmediately 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 reusingresultas 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. A third argument, selector, names a variable pool
other than the program's own — dialect-specific
(OS2ENVIRONMENT on OS/2 classic Rexx, plain
ENVIRONMENT on ooRexx; unsupported under REXX/VM GCS, which
only takes the two-argument form) — and INTERPRET has no
equivalent for it at all. The difference matters the moment a variable
name isn't fully under your control. Same task, same malicious name,
both ways:
foo = 'bar=1; call SomeRoutine; x'
/* INTERPRET: foo'=7' concatenates first, then runs the result as
source */
interpret foo'=7'
say bar /* 1 */
say x /* 7 -- but "call SomeRoutine", hidden in foo's value,
also ran: injection succeeded */
/* VALUE(): foo's value is used only as a name, never as source */
baz = value(foo, 7) /* SYNTAX condition -- not a legal variable
name -- nothing executes */
INTERPRET builds a whole clause and runs it, so it
cannot tell "the part that's a name" from "the part that's code" — after
concatenation there's only one string. VALUE()'s first
argument is always and only a name, checked as one, so an illegal name
is refused rather than executed. Reserve INTERPRET for
genuinely dynamic code, not as a heavier substitute for an indirect
variable reference.
PROCEDURE hides all of the caller's variables except
those named in an EXPOSE clause. Each name there is either
a symbol — an ordinary variable name — or a parenthesized symbol, whose
value is read as a further, whitespace-separated list of names to
expose. So if you pass an argument containing the name of some other
variable, and that name isn't reached by either form, code inside the
procedure gets only a local variable of that name, not the caller's.
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 ARGstatement:::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 ARGis 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.
PARSE ARGis the inverse risk.PARSE(in every form —PARSE ARG,PARSE VAR,PARSE PULL,PARSE VALUE) operates on strings; handed anything else, it sends the object aSTRINGmessage and parses whatever comes back:call PassAnObject .directory~new exit PassAnObject: procedure parse arg d say d~class -- "The String class" -- not Directory say d -- "a Directory" -- not the object's contents d~put('x', 'k') -- Error 97.1: Object "a Directory" does not understand message "PUT"Reach for
USE ARG(orUSE STRICT ARG) instead whenever an argument is a genuine object rather than plain text — it binds the argument directly with no string coercion. This is an easy bug to introduce by accident:PARSE ARGis the classic-Rexx habit for plain-string arguments, correct in that case, and the failure only surfaces the moment a routine written that way receives something that isn't already a string.
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 and Regina's
CHARS() are both exact for disk files but their own
LINES() isn't: on a real 29-byte, 3-line disk file, both
report chars() as exactly 29, but
lines() as 1 on both — not the real line count
— regardless of how many lines actually remain unread:
/* 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 holds on both ooRexx
and Regina: two separate LINEOUT calls to the same file,
each followed by closing it, with no REPLACE in between,
leave both writes in the file rather than the second overwriting the
first. It's 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. Verified
directly against ooRexx 5.2.0, exact wording included:
Error 97.1: Object ".FOO" does not understand message "NEW".
Fix: declare the class PUBLIC — confirmed this resolves
it, .Foo~new then runs successfully from the other
package:
::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. Verified directly against ooRexx 5.2.0, exact
wording included:
/* 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
cREXX Level B, the language of the compiler-to-bytecode implementation of that name, is not Classic Rexx, and the recommendations elsewhere in this article do not carry over to it. Treat it as a separate dialect, and test any code meant to run under it with the cREXX compiler; the Release 1 line is in beta, so these recommendations may change.
Do not assume Classic Rexx code means the same
thing. cREXX accepts only a subset of Classic Rexx (Level C)
and rejects a construct outside that subset with an unsupported-shape
diagnostic. Keep code that must remain portable to Classic Rexx in a
.rexx file and cREXX code in a .crexx
file.
Assign a variable before a conditional if you read it
afterward. A variable first assigned inside a DO
block belongs to that block when no enclosing scope has it, so two
sibling DO blocks that assign x create two
variables, and a read after them refers to a third. The compiler warns
NOT_IN_SAME_SCOPE.
x = .string /* before the conditional */
if flag = "1" then do
x = "from-if"
end
else do
x = "from-else"
end
return xDo not use PROCEDURE EXPOSE to share a
caller's variables. In Level B it names module-global
variables, not the caller's. To let a procedure update a caller's
variable, declare the parameter arg expose name = type,
which passes it by reference; a plain arg name = type
passes by value. In code that must also run under another dialect, pass
and return values instead of sharing state.
Do not port TSO/E or CMS environment code
unchanged. The cREXX port to z/OS is experimental, and
integration with IBM REXX variable pools and general
ADDRESS TSO commands is not yet available in it.
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.
PARSE VERSION output and
ANSI compliance level), https://regina-rexx.sourceforge.io/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)AXREXX macro,
MODIFY AXR/SYSREXX operator commands, the
AXR/AXR01–AXR08 address-space
structure, the REXXLIB A–I exec-name reservation, and
console-directed SAY/TRACE output; a separate
manual from the REXX Reference above, which does not cover System REXX
at alldocs/books/, including the language-levels,
global-variables, and arguments pages, for the Release 1 beta line)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).