REXX is billed as a user-friendly language that is as easy as possible to read and write. Part of the foundation for this claim is that REXX syntax was carefully designed to “do the right” thing in the most common usage situations – the “principle of least surprise”. Unfortunately, this objective isn’t achieved 100% – sometimes surprising things happen in REXX, particularly from the point of view of people who have used other programming languages. Sometimes these surprises are actually the by-product of decisions that were intended to make REXX easier in some way or other.
Whatever the reason, we list here some of the language pitfalls that have been revealed by the experience of thousands of users over the years – things that show up again and again in customer support calls and messages in forums where REXX is discussed.
The NOP instruction
Although the NOP instruction does nothing, it has several uses.
First, it is required in the syntax of IF and SELECT statements. For instance, when the first branch of an IF statement is null, you must use NOP:
if some_condition() then nop; else say /* this is correct */ "Condition was false." if some_condition() then; else say /* this is NOT correct */ "Condition was false."
Of course, this is not a natural example, since you would ordinarily make the only branch of an IF immediately follow THEN. However, when you have nested IF statements, you sometimes need to have an ELSE clause which does nothing, just so that the conditions match:
if x >= 100 then if y >= 200 then say "X is >= 100 and Y is >= 200." else nop else say "X is < 100."
Another case in which NOP is useful is in tracing. The specification of REXX says that during interactive tracing REXX will stop only after a statement is executed. You can use NOP to pause just before a statement:
nop /* about to invoke erase command */ 'erase' name_of_something
Also, REXX will not pause at all during interactive tracing for certain types of statements, such as CALL and SIGNAL. You can use NOP to force a pause:
do forever if something then do nop /* about to signal out of loop */ signal something_handler end ... end
Uninitialized variables, quoting of literals
By far the most frequent mistake that both beginning and experienced REXX users make is neglecting to quote all literal strings. It is tempting to leave off quotes around system commands, but this easily leads to mysterious syntax errors, e. g.:
/* the following actually causes division */ ipfc /inf foo.ipc /* and this implies multiplication */ erase *.bak /* the following are correct */ 'ipfc /inf foo.ipc' 'erase *.bak'
Even worse, REXX will perform substitution on unquoted strings that are valid symbols. Depending on the names you use for variables you can produce some very unintended effects:
copy = 'erase' ... copy "*.*"
There is a more general problem with not quoting literal strings even when the direct results are harmless. One of the most frequent types of programming mistakes that both beginners and experience REXX users make is the use of uninitialized variables. Such errors can go completely undetected except for the fact that the program does not behave as expected:
w = 42 call subroutine ... subroutine: procedure expose x y z /* we forgot to expose w */ if w = 42 then say "W is the answer." ...
Since REXX provides a "default" value of uninitialized variables which is the (uppercase) name of the variable, the comparison in the above is false ("W" does not equal 42). There is a very easy way to prevent errors of this kind: always put the statement
signal on novalue
at the start of any REXX program. Then every attempted usage of an uninitialized variable will cause an error (which will be "label not found" unless you actually include a handler for the NOVALUE condition). Of course, this won't work if you have even one "innocuous" use of an unquoted literal.
So the best recommendation is: Never use unquoted literals or uninitialized variables, and always start a program with SIGNAL ON NOVALUE in order to catch uninitialized variable errors.
Variable scoping
Scoping problems with variables tend to cause many subtle errors in REXX programs. "Scope" refers to the part of a program in which a given variable is "visible". Normally, a variable is visible from the point it is created until entering a subroutine that begins with PROCEDURE. Even then the variable can be added to scope of the subroutine by naming it after EXPOSE.
If you have a program that has many nested subroutines, the typical problem is that a variable used in one of the higher routines must be exposed in all intermediate routines before it can be used in low-level routines:
x = 50 call first ... first: procedure call second ... second: procedure expose x say "X-squared is" x**2
In the above example, the variable X is not available (or rather, is not initialized) in the subroutine called SECOND because it was not exposed in FIRST.
This problem most often arises when certain data variables need to be available globally throughout the whole program. However, the use of PROCEDURE statements is also a good thing, since it promotes "encapsulation" and prevents subroutines from having unintended side effects.
There are several techniques that can be used to provide "global" data easily. One is to place all such data into a single compound variable:
glbl.screen_height = 25 glbl.screen_width = 80 glbl.attributes = 31
Then you need only be sure the stem GLBL. is exposed everywhere. An alternative is to list the names of all required variables in a string and then do a special kind of expose:
globals = 'height width attr' height = 25 width = 80 attr = 31 ... subroutine: procedure expose (globals)
Placing the variable name in parentheses after EXPOSE tells REXX that the variable itself, and all variable names contained in the value should be exposed.
The PARSE instruction
The PARSE instruction is a very powerful REXX feature, but it can take quite a bit of experience to use it effectively.
It's also very easy to create subtle errors which can be very hard to find if you don't know some of the details of how PARSE operates.
1. Incorrect use of WITH
The keyword WITH is used only with PARSE VALUE. In all other forms of PARSE, WITH is not a keyword and will not raise any error condition, since it will be interpreted as a variable name:
/* the following is correct */ parse value date('u') with month '/' day '/' year /* the following is a very hard to find error */ x = "When in the course of human events" parse var x with a b c say a b c /* says "in the course" */
2. Unexpected blanks in parsed results
The rules of PARSE provide that the last variable to be assigned just before a literal subpattern or before the end of the whole pattern will contain all remaining characters. Frequently this includes some leading or trailing blanks. This can present subtle problems, since REXX ignores such blanks in ordinary comparisons and in numbers, but not in other contexts:
call subroutine "one ( two 40 ) three" ... subroutine: parse arg a '(' b c ')' d /* a = "one " b = "two" c = "40 " d = " three" */ if a == "one" then /* strict comparison fails */ say "Not this" if datatype(c, 'x') = 1 then /* not valid hex because */ say "Nor this" /* of trailing blank */ if abbrev("three", d) then /* not abbreviation because */ say "Nor this" /* of leading blank */
The most general way to deal with this is to use the STRIP() function where extraneous leading or trailing blanks could be a problem, since STRIP() removes the leading and trailing blanks.
In some cases you can use the special "." notation in a PARSE pattern to send blanks to the bit-bucket:
call subroutine "one ( two 40 ) three" ... subroutine: parse arg a . '(' b c . ')' d . /* a = "one" b = "two" c = "40" d = "three" */
3. Mismatched arguments and PARSE pattern
One of the most common uses of PARSE is in a PARSE ARGS statement that is used to access the arguments of a subroutine. Normally, arguments are passed to a subroutine as a list of values separated by commas. It is easy to forget that the commas must also be used in the PARSE ARGS statement:
call subroutine 'carl', 'friedrich', 'gauss' ... subroutine: /* this is probably wrong: */ parse args first second third /* first = 'carl' second = third = */ /* this is probably what is intended: */ parse args first, second, third /* first = 'carl' second = 'friedrich' third = 'gauss' */
The reason that the first PARSE ARGS statement doesn't work is that only the first argument (which consists of only a single word) has been accessed, since there are no commas separating subpatterns of the template.
In general, it would be a good rule of thumb to always use the same number of commas in the PARSE ARGS statement as are used in the corresponding procedure invocation.
4. Problems accessing command-line arguments
A very common problem that is the inverse of the one just discussed occurs in receiving command line arguments. When CMD.EXE invokes a REXX program, it places the entire string following the program name into a single argument. This is true even if the string contains embedded commas.
For instance, if you enter
parrot hello, world
on the command line, then the parrot.cmd program should be something like this:
/* a general parrot program */ parse args my_