home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 14 Text
/
14-Text.zip
/
rexxbook.zip
/
REXXBOOK.INF
(
.txt
)
Wrap
OS/2 Help File
|
1995-09-27
|
80KB
|
2,608 lines
ΓòÉΓòÉΓòÉ 1. Introduction ΓòÉΓòÉΓòÉ
Learn REXX Programming in 56,479 Easy Steps
So you want to learn how to write a REXX program, but you haven't bothered ever
cracking open any kind of programming textbook? What's the matter -- you've
finally gotten so bored with playing Doom that you're desperately searching for
something else to fruitlessly twiddle away all of your waking hours (and some
of your sleeping hours too, as many a REXX programmer has had frightening
nightmares upon starting a REXX programming project)? Well then, reading this
book is your first step in that direction.
This book is not intended to touch upon every aspect of REXX programming, but
rather, simply provide some of the most important concepts, and leave you to
pull out your own hair figuring out some esoteric technique you need in order
to complete your REXX script that analyzes your collection of smutty GIFs and
organizes them by breast size.
This OS/2 Online Book was created by Jeff Glatt, who pilfered a great many
other sources, but is not afraid of being sued for doing it because he doesn't
own anything that some lawyer-weasel would want. It is freely redistributable.
If you have suggestions, comments, criticisms, and anything else other than
dollar bills, then send them to someone else because you got it for free, and
you know what you get for nothing? But, if you do need to contact the author,
then either phone some of the more prominent psychiatrict clinics in central
New York state, or try this:
Jeff Glatt
6 Sycamore Drive East
New Hartford, NY 13413
(315) 735-5350
Example REXX code in this tutorial in displayed in colored text. Cheap,
obnoxious attempts at humor are not highlighted in any particular way, so
you'll have to be prepared for the worst at all times.
ΓòÉΓòÉΓòÉ 2. REXX ΓòÉΓòÉΓòÉ
REXX is a computer programming language. It's interpreted, like BASIC, which
means that you simply write your program as an ordinary text (ascii) file
containing instructions to tell the computer what to do, and some interpreter
software executes that text file directly, performing operations based upon
your instructions. There's no need for you to compile and link your source
code into an executable program. Your text file is your program. So, REXX is
a script language. But, it has lots of features, including variables, string
processing, looping, etc.
Note: From here on, a REXX program is referred to as a script, as opposed to
binary executables which are referred to as programs.
The REXX interpreter is built into OS/2 (if you elected to install the REXX
features of OS/2). That's why you can run a REXX script (ie, text file
containing REXX instructions) just by typing the name of the script from an
OS/2 Command Prompt (Window or Screen). The OS/2 Command Prompt can detect
that you're telling it to run a REXX script, and will invoke the REXX
interpreter upon that script.
A REXX script can run any executable (ie, a program written in another
language), including MS-DOS and Windows programs. For example, a REXX script
can run OS/2's DISKCOPY.COM program, and even receive back an error code
telling whether DISKCOPY performed its operation successfully. A REXX script
can also run another REXX script (which causes the interpreter to launch that
other script too).
OS/2 executables (ie, programs written in other languages) may be written to
also run REXX scripts, as well as allow REXX scripts to control various
features of the program, perhaps allowing as much control as an enduser has by
manually using every aspect of the program's user interface. The program must
be specifically written to have its own REXX Environment (or Interface), and
the REXX script (which controls the program) must "address" its REXX
instructions to that program's environment. In fact, the ability of OS/2
applications to use REXX as a fancy, standardized script language is one of the
most useful features of REXX which distinguishes it from other languages which
have no such clearly defined "interface" between the interpreted script and
applications written in other languages.
ΓòÉΓòÉΓòÉ 3. Creating and running a REXX script ΓòÉΓòÉΓòÉ
Most programming books begin by showing you a program which displays the
message Hello world! I find this approach to be a boring cliche with no
practical application. So, here's how to write that script in REXX.
(Remember, you got this book for free, so just shut up and bear it).
1. Write a text file called hello.cmd containing the example text below (ie, 2
lines). Use any text editor (the OS/2 System Editor or Enhanced Editor
will work) that saves plain (ascii) text without any special formatting
characters like what some word processors embed in the file.
/* This script displays Hello world! */
SAY "Hello world!"
2. Save the text file to disk as hello.cmd.
3. Open an OS/2 Command Prompt Window or Screen, and at the prompt, type hello
(ie, the name of the script), followed by the ENTER key. (Make sure that
you're in the same directory where the script was saved, or else type the
script's full path name).
The above script consists of a comment indicating what the script does,
followed by an instruction which does just what the comment indicates. SAY is
the REXX instruction which sends data to the standard output (ie, defaults to
displaying that data upon the computer's monitor). The data that SAY displays
is whatever has been listed after SAY. In this example, that's the literal
string Hello world!. We would refer to Hello world! as the argument for this
SAY instruction, since that's what this particular SAY instruction operates
upon. We refer to SAY itself as a keyword, as that's the word which indicates
what the instruction does.
When you run this script, you should see the message Hello world! appear upon
the screen.
To run a REXX script under OS/2, you just type the script's name at a command
prompt. Alternately, you can create a Desktop object for the script by
dragging a Program Object from the Templates folder, and typing the script's
full path name into its Setting's Program name field. Then, you can run the
script by double-clicking upon its icon.
Note: OS/2 requires that a REXX script be named with a cmd extension on it,
although you don't need to type the extension when you specify this
script to be run.
ΓòÉΓòÉΓòÉ 4. REXX Comment ΓòÉΓòÉΓòÉ
An OS/2 REXX script must always start with a comment. A comment is simply a /*
followed by a space, and then */. You can optionally put any desired text
inbetween the /* and */. This opening comment must start at the very beginning
of the text file (ie, column 1 of line 1). The opening comment is used by OS/2
to verify that this is indeed a REXX script, and not just some arbitrary text
file.
A comment can span any number of lines, and have any amount of text inside of
it. The OS/2 interpreter simply ignores everything inbetween the /* and */.
But, you can't have nested comments, so never put a /* or */ inside of another
comment, or REXX will undoubtably misinterpret part of the comment as if it
were REXX instructions, and post some sort of error message.
Here's an example of a comment:
/* The interpreter ignores this here text */
ΓòÉΓòÉΓòÉ 5. Literal Strings (and Concatenation) ΓòÉΓòÉΓòÉ
A Literal String is any text enclosed within single or double quotation marks
(ie, ' or "). It makes no difference whether you use single or double
quotation marks, as long as the opening quotation mark is the same as the
ending mark. The interpreter uses all of the text enclosed within the
quotation marks exactly as you typed it (ie, upper and lower case, as well as
blank space, is preserved). The interpreter doesn't ignore the text like with
a comment, but neither does the interpreter try to evaluate the text (ie,
replace it with some "value") like with a variable or mathematical expression.
If you need to use an apostrophe (single quotation mark) or double quotation
mark within the text itself, use the other quotation mark around the whole
string. For example, here we need to use an apostrophe within the word Here's,
so we use double quotes to enclose the entire string:
"Here's a REXX Literal String."
Here we need to use double quotation marks within the text, so we use single
quotes to enclose the entire string:
'He replied "I understand now" as he read this online book.'
You also can use a pair of quotation marks (to represent one mark) when you
need to utilize the same quotation mark as was used to mark the entire string.
For example, here we use double quotes to mark the entire string, but we also
want to use double quotes within the string. So, we need to use a pair of
double quotes within the string wherever we'd normally place only one.
"He replied ""I understand now"" as he read this online book."
Literal strings can be concatenated (ie, placed one after the next) with the
concatenation operator, || (ie, two | characters). For example, here we place
the string Jeff after the string My name is. Note that I leave an extra blank
space at the end of My name is literal string, so that there will be a blank
space between the the last word of the first string (ie, is) and the first word
of the second string (ie, Jeff) after they are combined.
"My name is " || "Jeff"
So if we use the above literal string as an argument for the SAY instruction,
as so:
SAY "My name is " || "Jeff"
...then My name is Jeff is displayed upon the screen.
It's also possible to concatenate strings by merely leaving a space inbetween
each string. For example:
SAY "My name is" "Jeff"
...will achieve the same result as using the concatenation operator above. The
space inbetween the two strings is retained by the interpreter. So, the end
result is that when you append strings by leaving a blank space inbetween them,
you always end up with a combined string that has that extra blank space there.
(But, even if you put more than one space character inbetween the strings, the
interpreter will retain only 1 space character). On the other hand, the
concatenation operator allows you to combine two strings without that extra
blank space inbetween them. If you wanted to append the string .exe to the
string program without any blank space inbetween the two strings, then you'd
need to use the concatenation operator as so:
"program" || ".exe"
Of course, you're wondering, "Why not just specify one literal string
consisting of 'program.exe'"? Well, as you'll see in the discussion of
variables, when you wish to combine the value of a variable and a literal
string (or the values of two variables) into a single string without spaces
inbetween the two pieces, then the concatenation operator is essential.
Note: When using the concatenation operator, you can eliminate all blank space
inbetween the strings. For example, the following two instructions are
really the same thing:
SAY "program" || ".exe"
SAY "program"||".exe"
But, the first one is easier for a human to read.
ΓòÉΓòÉΓòÉ 6. Variables (and assigning them values) ΓòÉΓòÉΓòÉ
A variable is a place to store some data. You can change the data assigned to
a variable (ie, change its value). Variables are therefore very useful for
keeping track of data that changes during the script's execution.
Each variable must have a unique name, and you access its value by using that
name. When you choose a name for a variable, the first character must be one
of the letters A through Z, a !, a ?, or a _. The remaining characters of the
variable's name can be any of the preceding characters as well as 0 to 9 and
the characters @, #, and $. The case of letters is unimportant, so that for
example, Hello, HELLO, and hellO all are the same variable name.
Note: You can't name a variable the same as any REXX keyword, REXX function
name, or subroutine in your script. Otherwise, the interpreter will
misinterpret the variable name as that preceding item. Also, don't use
the names RESULT, RC, or SIGL as these are special variables maintained
by the interpreter for your script's use.
You can assign a value to a variable simply by writing a REXX instruction that
consists of the variable name, followed by an equal sign, and then the desired
value for the variable. We call this an assignment instruction. Here we have
a variable named My_Variable. We will set it to the value of 5.
My_Variable = 5
Every time that the interpreter encounters what it regards to be a variable
name in some instruction, the interpreter replaces the variable name with its
value when carrying out that instruction (except for a variable whose value is
being set in an assignment instruction). For example, if you subsequently use
the above variable name as an argument for the SAY instruction, the interpreter
will substitute its value.
SAY My_Variable
The above would print out 5 upon the screen. It would not print out
My_Variable. Since My_Variable is not a literal string enclosed in quotes (nor
a keyword, nor a label, nor a function, nor anything else that the interpreter
recognizes), then the interpreter regards it as a variable name. Therefore,
the interpreter substitutes the value of My_Variable as the real argument here.
We already told the interpreter that My_Variable's value is 5 with the
preceding assignment instruction.
A variable can be assigned a value consisting of some literal string. For
example, here we set My_Variable to the string This is some text.
My_Variable = 'This is some text'
Now if you subsequently use this variable name as an argument for the SAY
instruction, the interpreter will substitute its new value. For example:
SAY My_Variable
...will print out This is some text upon the screen. It would not print out
My_Variable (since My_Variable is not a literal string enclosed in quotes), but
instead, print the value of My_Variable which happens to be the string This is
some text.
When assigning a value to a variable, that value can be any mathematical
expression. For example:
My_Variable = 5 + 3
...sets the value of My_Variable to 8 (ie, the addition of 5 and 3). Since
quotes weren't placed around 5 + 3, it's not a literal string and the
interpreter therefore evaluates the mathematical expression 5 + 3 rather than
literally accepting it as you typed it. Of course, if you did really want to
set My_Variable to the string 5 + 3, you'd specify a literal string by
enclosing in quotes, as so:
My_Variable = '5 + 3'
Furthermore, a variable's value can be set to a concatenation of strings, as
so:
My_Variable = "My name is " || "Jeff"
If you subsequently use My_Variable above as an argument for the SAY
instruction, SAY will print the combined string of My name is Jeff, which is
the current value of My_Variable.
You can set a variable to the value of another variable, as so:
My_Variable = 5
My_Other_Variable = My_Variable
Above, both variables have the value of 5.
You can set a variable to the value of a mathematical expression where another
variable is used in the expression, as so:
My_Variable = 5
My_Other_Variable = My_Variable + 3
Above, My_Other_Variable has the value of 8.
Note: You can assign a variable a new value by also using its old value in an
expression. For example, here's how to increment a variable by 1.
My_Variable = My_Variable + 1
The value of My_Variable on the right side of the equal sign is the value
before 1 is added to it, and this new value is now assigned back to My_Variable.
You can even set a variable's value to a string that is the combination of
several literal strings and mathematical expressions.
My_Variable = 5
My_Answer = "The answer to" My_Variable "+ 3 is" My_Variable + 3
The above results in My_Answer's value being set to the string The answer to 5
+ 3 is 8. First of all, The answer to is a literal string, so that is retained
verbatim. Next, the interpreter regards My_Variable as a variable name (ie, it
isn't enclosed in quotes, nor is it a keyword, label, function name, etc.), so
the interpreter substitutes its value there, which is simply 5. Note that
because a space was left inbetween the literal string The answer to and
My_Variable, this space is retained in My_Answer's value. Next, + 3 is is
another literal string. Finally, the interpreter sees another variable name,
and notes that 3 is being added to it. Since this is not enclosed within
quotes (ie, is not a literal string), the interpreter deems this to be a
mathematical expression, substitutes the variable's value, and evaluates this
mathematical expression to 8. Again, leaving a space inbetween + 3 is and that
mathematical expression results in the space being retained in My_Answer's
value.
We could have used the concatenation operator to combine the above pieces as
so:
My_Variable = 5
My_Answer = "The answer to " || My_Variable || " + 3 is " || My_Variable + 3
Note that I added extra spaces to those literal strings because the
concatenation operator appends the pieces together without otherwise retaining
spaces inbetween. If you run into a situation where you don't want a space
inbetween a variable's value and a literal string, then you must use the
concatenation operator. For example, assume that the variable My_Program is
already set to a string describing the name of some executable, and you wish to
append the literal string .exe to it without a space inbetween, and assign this
final string to the variable Full_Name:
Full_Name = My_Program || ".cmd"
Furthermore, if you wish to combine the values of two variables without a space
in between, then you must use the concatenation operator.
Full_Name = My_Program || My_Extension
You can even concatenate variables whose values are numeric, as if they were
strings.
My_Variable = 1
My_Fraction = '.5'
My_Result = My_Variable||My_Fraction
SAY My_Result /* Prints 1.5 */
If you use a variable in an expression, but that variable has not been
previously given a value, this does not cause an error (unless "SIGNAL ON
NOVALUE" is set, explained later). Instead, the interpreter just uses the
variable's own name (translated into upper case) as its value.
/* The variable My_Variable has not yet been assigned a value */
SAY My_Variable
The above results in the string MY_VARIABLE being displayed.
As you can see, variables can be assigned values that are numeric or strings.
So, how does the interpreter know whether a variable represents a string or
numeric value? The interpreter automatically evaluates the value of a variable
each time that it is used in an instruction to determine whether it's numeric
or a string. For example, if you assign the value 506 to a variable, the
interpreter figures out that it's a numeric value whenever you use the
variable. If you assign Hello to that same variable, the interpreter figures
out that it's a string value whenever you use the variable. You can change a
variable's value from numeric to string while the script executes, and the
interpreter will follow along. This is why REXX variables are described as
untyped -- because you can change their values from numeric to string and back
"on the fly". But if a variable's value happens to be a string, then you can't
use the variable in a mathematical expression (which we'll cover later). For
example, you can't multiple the value of the variable with some other numeric
value. Math operators only work upon a variable when its value is numeric (ie,
its value consists only of the characters 0 through 9 -- numeric digits).
Note: Some REXX documentation refers to variables as "symbols".
ΓòÉΓòÉΓòÉ 7. Getting input ΓòÉΓòÉΓòÉ
The PULL instruction can be used to get input from the person running the
script, and store that input data in a variable. You supply the name of the
variable where you want the data stored after the PULL keyword.
The script stops executing at that point and waits for the user to type in some
text (within the OS/2 command prompt window). When the user presses the ENTER
key, that line of text is stored in the desired variable, and the script
resumes executing after the PULL instruction. (PULL therefore retrieves only
one line of text at a time).
Here we get some input from the user, store it in the variable named
My_Variable, and then echo whatever the user typed back to the screen.
PULL My_Variable
SAY My_Variable
If you wish to display a message to the user telling him what to do, you'll use
a SAY instruction (or other instruction that prints to the screen) to print
that message before the PULL instruction. Here we tell the user to type in his
name.
/* This script asks the user to type his name, and echoes it */
SAY "Enter your name"
PULL My_Variable
SAY My_Variable
When running the above script, you'll note that the PULL instruction converts
all letters to upper case. If you wish to retain the case as typed by the
user, use PARSE PULL instead.
PARSE PULL My_Variable
For more information about PARSE PULL, see Parsing.
ΓòÉΓòÉΓòÉ 8. Math Operations ΓòÉΓòÉΓòÉ
With mathematical expressions, you can use whole numbers and decimal fractions.
A whole number is one which does not contain a decimal part. A whole number
can be positive, negative, or zero. Examples of whole numbers are 3, 569, -30,
0. A decimal fraction is a number containing a decimal point. Examples of
decimal fractions are 1.45, 0.6, -40.03333.
REXX has numerous math operators. There are operators to perform addition,
subtraction, multiplication, division, raise to a power, etc.
When the interpreter encounters some instruction containing a mathematical
expression not enclosed within quotes (ie, isn't a literal string), then the
interpreter evaluates that expression to its resulting value and substitutes
that value when carrying out the instruction. In other words, if the
interpreter sees the following instruction:
SAY 5 + 3
...it doesn't print 5 + 3, but rather, evaluates this expression as 8, and
subsequently prints out 8.
Addition
The operator for addition is the plus sign +. Here's an example of adding the
two numbers 2 and 14:
2 + 14
The interpreter evaluates the above to be 16.
Subtraction
The operator for subtraction is the minus sign -. Here's an example of
subtracting 8 from 230:
230 - 8
The interpreter evaluates the above to be 222.
Multiplication
The operator for multiplication is the asterisk *. Here's an example of
multiplying two numbers, 2 times 6:
2 * 6
The interpreter evaluates the above to be 11.999998. You suddenly realize that
you have a buggy Intel Pentium chip. After replacing it, the interpreter
evaluates the above correctly as 12.
Division
For division, there are several operators you can use, depending upon what kind
of answer you want. For a simple division, the operator is a single slash /.
Here's an example of dividing 7 by 2:
7 / 2
The interpreter evaluates the above to be 3.5 (ie, 3 and a half). In other
words, 2 goes into 7 three times and there's a remainder of one half of 2, or
1.
To divide for the purpose of getting only the remainder, the operator is two
slashes //. Here's an example of dividing 7 by 2, in order to get the
remainder:
7 // 2
The interpreter evaluates the above to be 1 (ie, after 2 goes into 7 three
times, there is a remainder of 1).
To divide, and return only the whole number portion of an answer and no
remainder, the operator is the percent sign %.
7 % 2
The interpreter evaluates the above to be 3 (ie, the decimal portion of the
answer is discarded, so the answer is rounded down to a whole number).
Note: To get the reciprocal of a number, divide it into 1 (ie, the reciprocal
of 20 is 1 / 20).
To raise a number to a power, the operator is two asterisks **. Here's an
example of raising 3 to the second power (ie, 3 squared):
3 ** 2
The interpreter evaluates the above to be 9.
Note: You don't need to place blank space inbetween numbers and operators.
For example, the following 2 expressions are the same.
2 + 55
2+55
Variables can be used in any mathematical expression. The interpreter simply
substitutes the variable's value, and then performs the math. The variable's
value must be numeric (as opposed to being comprised of characters other than
the digits 0 through 9) or the interpreter will report a "Bad arithmetic
conversion" error, or some such math related error.
For example, here we multiply two variables and then add 6.
My_Variable * My_Other_Variable + 6
ΓòÉΓòÉΓòÉ 8.1. Precedence ΓòÉΓòÉΓòÉ
Mathematical expressions are normally evaluated from left to right. Suppose
you had this expression:
9 - 5 + 2
The 9 - 5 would be evaluated first. The answer, 4, would be added to 2 for a
final value of 6.
Some math operations are given priority over others. In general, the rules of
algebra apply to mathematical expressions. In the following expression, the
division is handled before the addition. So 8 is divided by 2 first, and then
the result is added to 10, for a final value of 14.
10 + 8 / 2
If you use parentheses in an expression, the interpreter evaluates what is in
the parentheses first. For example, here we put parentheses around the
addition operation within this same expression. Therefore, 10 is added to 8
first, and then the result is divided by 2, for a final value of 9.
(10 + 8) / 2
Here is the order of precedence for math operations (ie, which math operations
get carried out first if you don't use parentheses to force a different order).
Raise to a power (the highest precedence)
Multiplication and Division
Addition and Subtraction (both the lowest precedence)
If in doubt about the order that operations are calculated within an
expression, then use parentheses to force your desired precedence. You can use
as many sets of parentheses as you want, and it's OK to use them even in
expressions where they're not really necessary. (But remember that REXX is an
interpreted language, so try not to do things that aren't necessary).
ΓòÉΓòÉΓòÉ 8.2. Math Example ΓòÉΓòÉΓòÉ
Here is an example of a script which does math. Make a file called arith.cmd
containing the following text.
/* This script inputs a number and does some calculations with it */
/* Get the original number from the user and put it into the variable named "a"
*/
SAY 'Enter a number'
PULL a
/* Calculate the square, and put it into a variable named "b". We could also
have done a**2 */
b=a*a
/* Calculate the reciprocal, and put it into a variable named "c" */
c=1/a
/* Calculate 3 + original number, and put it into a variable named "d" */
d=3+a
/* Calculate two to the power of one less than the number, and put it into a
variable named "e" */
e=2**(a-1)
/* Display those variables' values */
SAY 'Results are:' a b c d e
Run this REXX script. It will wait for you to type in a positive integer. Do
that, and then press ENTER. Here is a sample run:
Type a number >
5
Results are: 5 25 0.2 8 16
The results you see are the original number typed by the user (5), its square
(25), its reciprocal (0.2), the number plus three (8), and two to the power of
one less than the number (16).
If you supply the number 6 as input to the script, you should notice that the
value of 1/6 is given to nine significant figures. You can easily change this.
Edit the script and insert the following after the opening comment:
NUMERIC DIGITS 25
This tells the interpreter that you wish any mathematical expressions to be
calculated out to 25 significant digits. You can substitute 25 for however
many significant digits you desire. In this way, you can calculate numbers to
whatever accuracy you require, within the limits of the computer.
ΓòÉΓòÉΓòÉ 9. Formatting Instructions ΓòÉΓòÉΓòÉ
REXX is rather flexible with formatting instructions.
Instructions do not have to begin in a particular column. You can use as much
or as little indentation as desired.
Instructions can be typed in uppercase, lowercase, or a combination of both.
The interpreter always "sees" everything as uppercase anyway when it analyzes
your instructions, except for anything that you've enclosed in quotes (ie,
literal string).
The interpreter is rather liberal with blank space. You can separate the parts
of an instruction by as many or few blank spaces as desired. The interpreter
always regards several, adjacent space characters as just one space anyway,
except when those spaces are found within a literal string. When it sees a
concatenation operator, the interpreter ignores all blank spaces around that
operator, and sees the two adjacent pieces of the instruction as being directly
adjacent to each other.
The interpreter ignores blank lines.
Usually, a single REXX instruction is placed upon one line. But, you can put
more than one instruction upon a line. You simply place a semicolon between
the instructions. For example, here we put 3 instructions upon the same line:
/* This script inputs your name and prints it out */
SAY 'Type your name'; PARSE PULL name; SAY 'Your name is' name
If you want an instruction to span more than one line, you must put a comma (,)
at the end of the line to indicate that the instruction continues upon the next
line. But you can't split a literal string across more than one line. (You'd
have to split the string into 2 separate strings joined by a concatenation
operator followed by a comma).
The instructions in a REXX script are executed in the order that they appear in
the text file. (ie, The first instruction, at the top of the file, is executed
first. Then, the next instruction below it is executed. Etc). Instructions
do not have to be numbered or otherwise labeled (but they can be labeled for
the purposes of jumping from one instruction to another via the SIGNAL
keyword).
ΓòÉΓòÉΓòÉ 10. Conditionals (ie, Making a Decision or Comparisons) ΓòÉΓòÉΓòÉ
You use a Conditional instruction whenever you want to check if a certain
condition is true, and then execute other REXX instructions depending upon
whether that condition is true.
A Conditional instruction consists of a REXX conditional keyword, followed by
an expression, and then the rest of the conditional (which is usually another
instruction -- a "sub-instruction" if you will). The expression must evaluate
to either true (ie, 1) or false (0). REXX automatically evaluates the
expression following any conditional keyword, and if the expression is true,
then the rest of the conditional is executed. Otherwise, the rest of the
conditional is skipped.
An expression often consists of a comparison of two items. Those items may be
variables or literal strings or even the returned value of some REXX function
or subroutine. For example, the following script checks if the value of a
variable named My_Variable is greater than 10, and if so, then the literal
string It's greater than 10 is printed to the screen.
/* Check if My_Variable is greater than 10, and then say so if true */
IF My_Variable > 10 THEN SAY "It's greater than 10."
The above instruction uses two REXX conditional keywords which work together;
IF and THEN. IF is placed immediately before the expression (which in this
case is the comparison of My_Variable to 10). THEN is placed before the
sub-instruction that you want executed (which in this case is the SAY
instruction) if the expression is true.
You'll note that I use the comparison operator > to represent "is greater
than". There are several operators that you can use when comparing two items,
as so:
= "equal to"
< "less than"
> "greater than"
<= "less than or equal to"
>= "greater than or equal to"
<> "greater than or less than"
\= "not equal to"
\> "not greater than"
\< "not less than"
For example, let's say that I want to check if My_Variable is not equal to 10,
and SAY "Not equal to 10" if that's true.
/* Check if My_Variable is not equal to 10, and then say so if true */
IF My_Variable \= 10 THEN SAY "Not equal to 10."
Here I check whether the value of the variable named My_Variable is greater
than 5 plus the value of the variable named My_Other_Variable. If true, then I
set My_Variable to 10 (ie, I perform an assignment instruction).
/* If My_Variable is greater than My_Other_Variable + 5, then set My_Variable
to 10 */
IF My_Variable > My_Other_Variable + 5 THEN My_Variable = 10
REXX compares strings (including variables that have string values rather than
numeric values) on a character by character basis. Consider the following:
/* If My_Variable is greater than "hello", then say "It's true" */
My_Variable = "hell"
IF My_Variable >= "hello" THEN SAY "It's true."
First, REXX compares the first character of the literal string "hello" with the
first character of the value of My_Variable. Both characters are the letter
"h", so the expression is true so far (ie, My_Variable is greater than or equal
to "hello"). Next, the second characters of both strings are compared. These
are both "e". The expression is still true. By the time that REXX gets around
to comparing the "o" in "hello", it has run out of characters in the value of
My_Variable, so it substitutes a blank space as the next character. Therefore,
My_Variable is "less than" the literal string "hello" (ie, a blank space is
considered to be less than the letter "o"), and the expression is ultimately
false. The SAY instruction is not executed.
Let's look at another variation of the above.
/* If My_Variable is greater than "help", then say "It's true" */
My_Variable = "hell"
IF My_Variable >= "help" THEN SAY "It's true."
Again, REXX compares the strings on a character by character basis. The
expression is true until REXX compares the "p" in "help" to the second "l" in
"hell". It finds that "p" is greater than "l" (ie, "p" is the 16th letter in
the alphabet whereas "l" is only the 12th letter), and therefore My_Variable is
"less than" the literal string "help".
Upper case letters are considered to be less than lower case letters (ie, "A"
is less than "a"), and numeric digits are less than letters (ie, "3" is less
than "A"). A blank space is less than letters or numeric characters.
In conclusion, the expression in a Conditional instruction can involve
variables, literal strings, and/or returned values from functions or
subroutines, but ultimately, REXX will evaluate the expression to be true (or
false), and execute (or skip) the remainder of the Conditional accordingly.
ΓòÉΓòÉΓòÉ 10.1. ELSE keyword ΓòÉΓòÉΓòÉ
Besides IF/THEN, there are other REXX keywords used in Conditional
instructions.
ELSE is used in conjunction with IF/THEN. After an IF/THEN, you can use an
ELSE Conditional instruction. What happens is that, if the expression in the
IF/THEN instruction is not true (and therefore the remainder of the IF/THEN is
not executed), then REXX will execute the ELSE instruction instead. On the
other hand, if the IF/THEN expression is true (and the remainder of the IF/THEN
is executed), then REXX will skip any following ELSE instruction. In other
words, an ELSE instruction is only executed when the IF/THEN expression is
false.
/* If My_Variable equals "hello", then say "It's true". Otherwise, say "It's
not true". */
My_Variable = "hello"
IF My_Variable = "hello" THEN SAY "It's true."
ELSE SAY "It's not true."
Above, the expression in the IF/THEN is true (ie, My_Variable's value happens
to be equal to the literal string "hello"), so the SAY instruction after THEN
is executed (ie, "It's true" is printed to the screen). The following ELSE
instruction is skipped.
/* If My_Variable is greater than 10, then say "It's true". Otherwise, say
"It's not true". */
My_Variable = 5 + 2
IF My_Variable > 10 THEN SAY "It's true."
ELSE SAY "It's not true."
Above, the expression in the IF/THEN is false (ie, My_Variable's value is not
greater than 10), so the SAY instruction after THEN is skipped. The following
ELSE instrcution is therefore executed (ie, "It's not true" is printed to the
screen).
Another IF/THEN Conditional instruction could follow an ELSE keyword. You
could then follow this with yet another ELSE instruction.
IF My_Variable > 10 THEN SAY "It's greater than 10."
ELSE IF My_Variable = 10 THEN SAY "It's 10."
ELSE SAY "It's less than 10."
In the above, only one of the SAY instructions is ever executed for any given
value of My_Variable.
ΓòÉΓòÉΓòÉ 10.2. SELECT keyword ΓòÉΓòÉΓòÉ
The SELECT keyword is used when you want to select one of several possible
Conditional instructions to execute (and skip all other conditional
instructions within the group). The keywords WHEN and THEN are used for each
Conditional instruction much the same way that IF/THEN is used to form a
Conditional. In other words, the WHEN goes before the expression to be tested,
and the THEN goes before the instruction to be executed if the expression is
true. The SELECT keyword gets placed before all of the Conditional
instructions in the group. An END keyword is placed after the last Conditional
instruction (ie, it marks the end of the SELECT group).
SELECT is very useful if you wish to check some variable for a large number of
conditions, and execute instructions for only one of those conditions.
Consider the following:
/* Check My_Variable for a variety of conditions, but execute instructions for
only 1 condition. */
SAY "Enter a number"
PULL My_Variable
SELECT
WHEN My_Variable = 10 THEN SAY "It's equal to 10."
WHEN My_Variable < 10 THEN SAY "It's less than 10."
WHEN My_Variable < 20 THEN SAY "It's less than 20."
END
Assume that My_Variable's value is 5. REXX checks the first WHEN's expression
(ie, Is My_Variable equal to 10?). This is false, so the remainder of that
WHEN Conditional instruction is skipped (ie, It's equal to 10 is not printed to
the screen). Because the first Conditional was false, REXX proceeds to the
next Conditional. It checks if the second WHEN's expression is true (ie, Is
My_Variable < 10?). This is true. Therefore, REXX executes the instruction
after that WHEN's respective THEN (ie, It's less than 10 is printed to the
screen). REXX then immediately jumps to END. It skips the remaining
conditional instructions (ie, the third WHEN's expression is never even tested,
and therefore It's less than 20 is not printed to the screen).
Assume that My_Variable's value is 10. REXX checks the first WHEN conditional
expression (ie, Is My_Variable equal to 10?). This is true. Therefore, REXX
executes the instruction after that WHEN's respective THEN (ie, It's equal to
10 is printed to the screen). REXX then immediately jumps to END. It skips
the remaining conditional instructions (ie, the second and third WHEN's
expressions are never even tested).
Assume that My_Variable's value is 25. REXX checks the first WHEN conditional
expression (ie, Is My_Variable equal to 10?). This is false, so the remainder
of that WHEN conditional is skipped (ie, It's equal to 10 is not printed to the
screen). Because the first conditional was false, REXX proceeds to the next
conditional. It checks if the second WHEN's expression is true (ie, Is
My_Variable < 10?). This is also false. Therefore, REXX skips the respective
THEN for that WHEN. Because the second conditional was false, REXX proceeds to
the next conditional. It checks if the third WHEN's expression is true (ie, Is
My_Variable < 20?). This is also false. There are no more conditional
instructions. At this point, REXX will terminate the script with a syntax
error message. You must always make sure that every time a SELECT group is
executed, one of its conditionals turns out to be true, otherwise REXX will
generate a syntax error message. If you don't want REXX to generate a syntax
error message for any instances where all of the conditionals turn out to be
false, you can use the OTHERWISE keyword after the last conditional (ie, before
the END keyword), and optionally follow it with some REXX instructions. This
OTHERWISE and its associated instructions are executed only when all of the
conditionals in the SELECT turn out to be false.
/* Check My_Variable for a variety of conditions, but execute instructions for
only 1 condition. */
SAY "Enter a number"
PULL My_Variable
SELECT
WHEN My_Variable = 10 THEN SAY "It's equal to 10."
WHEN My_Variable < 10 THEN SAY "It's less than 10."
WHEN My_Variable < 20 THEN SAY "It's less than 20."
OTHERWISE
SAY "It must be > 19."
END
Now when My_Variable is 25, the above OTHERWISE gets executed (ie, the SAY
instruction printing It must be 19 is executed). By contrast, when using
IF/THEN with ELSE keywords, it's OK if the IF/THEN as well as all of its
subsequent ELSE instructions turn out to be false.
ΓòÉΓòÉΓòÉ 10.3. Multiple instructions within a conditional (ie, DO/END) ΓòÉΓòÉΓòÉ
Only one instruction may be placed after a THEN, ELSE, or WHEN keyword, but
REXX provides a way of bracketing instructions together so that they can be
treated as a single instruction. To do this, place the keyword DO before the
group of instructions, and the keyword END after the instructions.
/* If My_Variable is greater than 10, then say "It's true" and set it to 10.
Otherwise, say "It's not true" and print My_Variable's value. */
My_Variable = 5 + 2
IF My_Variable > 10 THEN DO
SAY "It's true."
My_Variable = 10
END
ELSE DO
SAY "It's not true."
SAY 'My_Variable =' My_Variable
END
Here's an example of using DO/END to enclose multiple instructions inside one
of the WHEN conditionals in a SELECT group. Note the DO/END in the second WHEN
instruction.
/* Check My_Variable for a variety of conditions, but execute instructions for
only 1 condition. */
SELECT
WHEN My_Variable = 10 THEN SAY "It's equal to 10."
WHEN My_Variable < 10 THEN DO
SAY "It's less than 10."
My_Variable = My_Variable + 1 /* Increment it */
END
WHEN My_Variable < 20 THEN SAY "It's less than 20."
OTHERWISE
SAY "It must be > 19."
END
The DO can be placed immediately after the THEN, ELSE, or WHEN, or the DO
keyword can be placed upon a separate line. It's your preference which is
easier to read. The following two examples are identical. (Remember that
indenting is also optional).
/* If My_Variable is greater than 10, then say "It's true" and set it to 10. */
IF My_Variable > 10 THEN DO
SAY "It's true."
My_Variable = 10
END
/* If My_Variable is greater than 10, then say "It's true" and set it to 10. */
IF My_Variable > 10 THEN
DO
SAY "It's true."
My_Variable = 10
END
ΓòÉΓòÉΓòÉ 10.4. Nested conditionals ΓòÉΓòÉΓòÉ
You can put conditionals inside of other conditionals, for example, put an
IF/THEN instruction inside of another IF/THEN, or put a SELECT inside of an
IF/THEN which itself is inside of an IF/THEN. But, you'll need to use DO/END
to help REXX distinguish nested conditionals (ie, which instructions go with
which Conditional instruction).
Here we put an IF/THEN inside of another IF/THEN.
IF My_Variable > 10 THEN DO
IF My_Other_Variable = 10 THEN SAY "Both are at least 10."
END
Here we put an IF/THEN inside one of the WHEN/THEN Conditionals within a
SELECT. Within this IF/THEN, we also put another IF/THEN.
SELECT
WHEN My_Variable = 10 THEN DO
SAY "It's equal to 10."
IF My_Other_Variable = 10 THEN DO
IF print_me = 'YES' THEN SAY "And so is My_Other_Variable."
END
END
WHEN My_Variable < 10 THEN SAY "It's less than 10."
WHEN My_Variable < 20 THEN SAY "It's less than 20."
OTHERWISE
SAY "It must be > 19."
END
ΓòÉΓòÉΓòÉ 10.5. NOP (no operation) keyword ΓòÉΓòÉΓòÉ
If you want one of the conditional instructions to do "nothing", then you must
use the keyword NOP (for "no operation"). Simply placing no instructions after
a THEN, ELSE, or WHEN will generate an error.
For example, here we specifically want to do nothing when My_Variable equals
10, and we also don't want to execute the OTHERWISE instructions in that case.
/* Check My_Variable for a variety of conditions, but execute instructions for
only 1 condition. */
SAY "Enter a number"
PULL My_Variable
SELECT
WHEN My_Variable = 10 THEN NOP
WHEN My_Variable < 10 THEN SAY "It's less than 10."
WHEN My_Variable < 20 THEN SAY "It's less than 20."
OTHERWISE
SAY "It must be > 19."
END
ΓòÉΓòÉΓòÉ 10.6. Strict Comparison Operators ΓòÉΓòÉΓòÉ
When comparing variables whose values are strings (ie, not numeric values),
leading and trailing spaces are stripped from the strings before the
comparison. For example,
My_Variable = ' Hello '
IF My_Variable = 'Hello' THEN SAY 'It matches.'
...results in the expression being true, and the SAY instruction is executed.
If you don't want leading and trailing blanks to be stripped before the
comparison, then you can use the "strict" versions of the comparison operators.
== "equal to"
<< "less than"
>> "greater than"
<<= "less than or equal to"
>>= "greater than or equal to"
\== "not equal to"
\>> "not greater than"
\<< "not less than"
For example,
My_Variable = ' Hello '
IF My_Variable == 'Hello' THEN SAY 'It matches.'
...results in the expression being false, and the SAY instruction is not
executed.
The strict operators are also useful if you wish to compare a variable with a
numeric value as if it were a string. For example, consider the following two
conditionals.
My_Variable = '0.0'
IF My_Variable = 0 THEN SAY "It matches."
IF My_Variable == 0 THEN SAY "It matches again."
The first conditional doesn't use a strict operator when it compares
My_Variable to 0. Therefore, the expression ends up being true. The second
conditional uses the strict operator ==, and therefore the expression ends up
being false. After all, although 0.0 is numerically the same as 0, the string
'0.0' is not the same as '0'. There's a decimal point and second 0 in '0.0'
which does not appear in '0', so as literal strings, these two aren't equal.
ΓòÉΓòÉΓòÉ 10.7. Boolean Operators in Comparisons (ie, combining expressions) ΓòÉΓòÉΓòÉ
Conditional expressions may be combined with the boolean operators: & (and), |
(or) and && (xor). They may also be reversed with the \ (not) operator.
The & operator causes the combined expression to be regarded as true only if
all of the individual expressions are true. If any one (or more) of the
individual expressions is false, then the combined expression is false.
The | operator causes the combined expression to be regarded as true if one (or
more) of the individual expressions is true. If all of the individual
expressions are false, then the combined expression is false.
The && operator causes the combined expression to be regarded as true when one
and only one of the individual expressions is true. If more than one
expression is true, or none of the expressions are true, then the combined
expression is false.
The \ operator reverses the result of any expression after it. An expression
that REXX normally evaluates as true is changed to false when the \ operator
preceeds it. Similarly, an expression that normally evaluates as false is
changed to true when preceded by a \ operator.
/* Decide what range My_Variable's value is in, and print out that range */
SAY "Enter a number"
PULL My_Variable
IF My_Variable>0 & My_Variable<10 THEN SAY "1-9"
IF My_Variable\<10 & My_Variable<20 THEN SAY "10-19"
IF \ (My_Variable<20 | My_Variable>=30) THEN SAY "20-29"
IF My_Variable<=0 | My_Variable>=30 THEN SAY "Out of range"
Note that we didn't put ELSE keywords before the second, third, and fourth IF
keywords. Therefore, REXX tests the expression of each of those IF
conditionals, executing whichever ones happen to be true. (Of course, due to
the particular expressions that we've used, only 1 will ever be true for any
given value of My_Variable).
The above script may also be written using SELECT.
/* Decide what range My_Variable's value is in, and print out that range */
SAY "Enter a number"
PULL My_Variable
SELECT
WHEN My_Variable>0 & My_Variable<10 THEN SAY "1-9"
WHEN My_Variable\<10 & My_Variable<20 THEN SAY "10-19"
WHEN \ (My_Variable<20 | My_Variable>=30) THEN SAY "20-29"
OTHERWISE SAY "Out of range"
END
Of course, only 1 of the WHEN instructions ever gets executed when this SELECT
is executed.
ΓòÉΓòÉΓòÉ 11. Variable Arrays (ie, Stem or compound variables) ΓòÉΓòÉΓòÉ
As well as "simple variables", REXX has arrays. Any ordinary variable name can
also be used as the name of an array. An element (ie, field) of an array is
accessed by typing the array name, a dot, and the element number or name. The
array name and the dot are together known as the "stem" of the array. The
element name is called a "compound symbol".
For example, you could have an array with the variable name Days. The first
element in the array could be Days.1. The second element in the array could be
Days.2. The third element in the array could be Days.3, etc.
In fact not only numbers, but strings and variable names may be used as element
names. For example, the first element in the array could be Days.Monday. The
second element in the array could be Days.Tuesday. The third element in the
array could be Days.Wednesday, etc.
Like with simple variables, an array does not have to be declared before it is
used.
/* This script uses an array named array with 3 elements named 1, 2, and 3 */
PULL val
array.1 = val
array.2 = val*val
array.3 = val**3
SAY array.1 array.2 array.3 array.1+array.2+array.3
Additional element names can be appended to create 2-dimensional (or more)
arrays. For example, you could have an array with the variable name Months.
The first element in the array could be Months.January.1. The second element
in the array could be Months.January.2, etc. The last element in the array
could be Months.December.31.
The following script uses a 2-dimensional array named book. Each book entry
has 3 elements named author, title, and pub. There can be an unlimited number
of book entries, so element numbers are used for the first compound symbol.
Therefore, book.1.author contains the author of the first book. book.1.title
contains the title of the first book. book.1.pub contains the publisher of the
first book. book.2.author contains the author of the second book. Etc. The
script initializes 2 book entries, asks the user what book number he'd like to
see the information for, and prints out that book's author, title, and
publisher.
/* This program uses a 2-dimensional array named book with various elements. */
book.1.author="M. F. Cowlishaw"
book.1.title="The REXX Language, a practical approach to programming"
book.1.pub="Englewood Cliffs 1985"
book.2.author="A. S. Rudd"
book.2.title="Practical Usage of REXX"
book.2.pub="Ellis Horwood 1990"
/* Look up the book that the user indicates. Use a variable to get the element
number. Note that we use that variable name when accessing the element number.
*/
SAY "Input a book number"
PULL i
SAY "Author: " book.i.author
SAY "Title: " book.i.title
SAY "Publisher:" book.i.pub
As with simple variables, if a particular array element has not been given a
value, then its name is used instead. If you enter a 3 for the desired book
number, then the following is displayed:
Author: BOOK.3.AUTHOR
Title: BOOK.3.TITLE
Publisher: BOOK.3.PUB
This is because these 3 elements were never initialized.
You can easily initialize every element of an array using just one instruction
by assigning a value to the stem itself. Edit the above script and insert the
following instruction after the opening comment:
book.="Undefined"
This gives every possible element of the array the value "Undefined", so that
if you again type 3 for the desired book number, the following is displayed:
Author: Undefined
Title: Undefined
Publisher: Undefined
Because the element can be a variable name, this means that you could assign a
new value to that variable, and thus access various fields of the array. For
example, assume that you want to create a list of the ages of various people.
You want to use one array called Age to store all of these ages, and for a
given person, you want to use his last name as the element name. Let's
identify 3 people whose last names are Baker, Jones, and Smith, and their ages
are 30, 16, and 45. Here we initialize the array.
/* Initialize our Age array for 3 people. */
Age.Baker = 30
Age.Jones = 16
Age.Smith = 45
Now, if you wanted to allow a user to look up a person's age by entering the
last name of that person, you could do this as so:
SAY 'Enter the last name of the person:'
PARSE PULL name
SAY 'The age of' name 'is' Age.name 'years.'
ΓòÉΓòÉΓòÉ 12. Loops ΓòÉΓòÉΓòÉ
REXX has many ways to implement loops using the keywords DO and END. DO
indicates the start of the loop, and is placed before the first instruction in
the loop. END indicates the end of the loop, and is placed after the last
instruction in the loop.
ΓòÉΓòÉΓòÉ 12.1. Loops that repeat a set number of times ΓòÉΓòÉΓòÉ
You can specify how many times to repeat the instructions within the loop by
putting a numeric expression after the DO keyword. For example, here we say
"Hello" 10 times.
/* Say "Hello" ten times */
DO 10
SAY "Hello"
END
Alternately, the numeric expression could be represented by a variable's value
(or any mathematical expression).
/* Say "Hello" ten times */
My_Variable = 10
DO My_Variable
SAY "Hello"
END
Note: Even if you were to change the value of My_Variable within the loop (ie,
put an assignment instruction inside of the loop to set My_Variable to a
new value), the loop would still execute 10 times. This usage of DO
copies the initial value of My_Variable to an internal REXX variable
which is then used to count how many times the loop repeats.
If instead of a numeric expression, you place the keyword FOREVER after DO,
then the loop will continue to repeat indefinitely (unless the user presses the
CTRL and BREAK keys to abort the script, or the ITERATE or LEAVE keywords are
used within the loop).
/* This script keeps saying "Hello" indefinitely */
DO FOREVER
SAY "Hello"
END
Control loops use a variable (called the "control variable") as a counter. You
specify the range over which the variable's (numeric) value is to be
incremented, by specifying the initial (ie, start) value of the variable as
well as the end value (ie, at what value the loop will stop repeating). REXX
automatically increments the variable's value upon each repeat of the loop,
terminating the loop when the variable's value reaches the end value. (REXX
also initializes the variable to the start value upon the first iteration of
the loop). By default, the control variable increments by 1. The start value
is specified after the = sign following the variable name. The end value is
specified after the TO keyword. The end value must be greater than or equal to
the start value in order for the loop to be executed. (If the start value is
equal to the end value, then the loop is executed once only). If the end value
is less than the start value, then the loop will be skipped. You may use the
variable in some instruction within the loop, and this is extremely useful when
you need to step through some array, for example.
/* Count from 1 to 20, inclusive, saying each incremented value of the variable
*/
DO My_Variable=1 TO 20
SAY My_Variable
END
Here we print out the values of the array elements Array.1, Array.2, Array.3
and Array.4.
/* Print values of array elements Array.1, Array.2, Array.3 and Array.4 */
DO My_Variable=1 TO 4
SAY Array.My_Variable
END
You can use a variable's value (or any mathematical expression) to specify the
start or end values. Here we use the value of the variable count to specify
the end value.
/* Count from 1 to the value of count, inclusive, saying each incremented value
of the variable */
DO My_Variable=1 TO count
SAY My_Variable
END
You can specify that the variable's value is to be incremented in steps other
than by 1. Use the BY keyword, followed by a numeric expression (ie, could be
a variable or mathematical expression too) representing the increment amount.
For example, here My_Variable is incremented by 2.3 upon each repeat of the
loop.
/* Print all multiples of 2.3 not more than 20 */
DO My_Variable=0 TO 20 BY 2.3
SAY My_Variable
END
You can even setup a loop such that the variable's value is decremented. When
doing this, the start value should be higher than the end value, and you should
specify a negative increment amount (ie, a decrement amount). Here we count
down from 20 to 1 (ie, decrementing by 1 upon each repeat of the loop).
/* Count from 20 to 1, inclusive, saying each decremented value of the variable
*/
DO My_Variable=20 TO 1 BY -1
SAY My_Variable
END
In fact, you could also use negative values for the start and end values.
/* Count from -5 to -1, inclusive, saying each incremented value of the
variable */
DO My_Variable=-5 TO -1
SAY My_Variable
END
Instead of specifying a start and end value, you can specify a start value and,
using the FOR keyword, specify how many times the loop is to repeat. The
repeat count is the numeric expression after FOR. Again, this can also be
specified using a variable's value (or a mathematical expression).
/* Print the first five multiples of 5.7 (ie, 0, 5.7, 11.4, 17.1, 22.8) */
DO My_Variable=0 FOR 5 BY 5.7
SAY My_Variable
END
If you only specify a start value, but no end value or repeat count, then the
loop will repeat forever, incrementing the variable's value by the increment
amount (unless the user presses CTRL and BREAK, or the ITERATE or LEAVE
keywords are used within the loop).
/* Print all the natural numbers */
DO My_Variable=0
SAY My_Variable
END My_Variable
The My_Variable at the end of the preceding example is optional. At the end of
any control loop, the name of the control variable may be placed after the END
keyword, where it will be checked to see if it matches the control variable.
Note: Changing the value of My_Variable within any of the above control loops
(ie, put an assignment instruction inside of the loop to set My_Variable
to a new value), will affect how many times the loop repeats. This
usage of DO checks the value of My_Variable each time the loop repeats,
incrementing its current value accordingly. In fact, after the loop has
finished, the value of My_Variable will then be one more than the repeat count.
ΓòÉΓòÉΓòÉ 12.2. Loops that repeat until a condition is met ΓòÉΓòÉΓòÉ
A set of instructions may be executed repeatedly until some condition is true,
by placing the UNTIL keyword after DO, and then following it with some
conditional expression (as are used in conditional instructions). For example,
here we repeat the loop until the variable answer does not have the value "NO".
/* I won't take no for an answer */
DO UNTIL answer \= "NO"
PULL answer
END
Alternatively, a loop can be executed as long as some condition is true by
placing the WHILE keyword after DO.
/* This loop repeats as long as there is no error */
DO WHILE error=0
PULL a
IF a="ERROR" THEN error=1
ELSE SAY a
END
Using WHILE, if the expression is true to start with, then the set of
instructions within the loop will not be executed at all. Using UNTIL, the
instructions within the loop will always be executed at least once. This is
because UNTIL tests to see if the expression is true at the end of each loop's
iteration, whereas WHILE tests to see if the expression is true at the start of
each iteration.
Another important distinction is that with WHILE, the loop continues executing
as long as the expression is true (ie, a false expression ends the loop),
whereas with UNTIL, the loop continues as long as the expression is false (ie,
a true expression ends the loop).
You can place a numeric value after DO in order to specify how many times to
repeat the loop, and then also use the UNTIL or WHILE keywords to specify some
condition that may prematurely end the loop. Of course, you can use a
variable's value (or mathematical expression) to specify the repeat count.
/* I won't take NO for an answer unless it is typed three times */
DO 3 UNTIL answer \= "NO"
PULL answer
END
You can also use a control variable along with the UNTIL or WHILE keywords.
/* Input upto 10 answers, but stop when empty string is entered */
DO n=1 TO 10 UNTIL answer==""
PULL answer
answers.n=answer
END
ΓòÉΓòÉΓòÉ 12.3. ITERATE and LEAVE keywords ΓòÉΓòÉΓòÉ
The ITERATE and LEAVE keywords allow you to skip to the next iteration of the
loop, or to break out of (ie, end) the loop respectively. For example:
/* Input ten answers in an array, but stop when empty string is entered */
DO n=1 TO 10
PULL My_Variable.n
IF My_Variable.n == "" THEN LEAVE
END
/* Print all integers from 1-10 except 3 */
DO n=1 TO 10
IF n=3 THEN ITERATE
SAY n
END
If a variable name is placed after the keywords ITERATE or LEAVE, then you can
iterate or leave the loop whose control variable is that variable name. This
is how, when you have nested loops, you can jump out of an inner loop and skip
some of the outer loops -- simply LEAVE or ITERATE to the desired outer loop by
placing that outer loop's control variable's name after the LEAVE or ITERATE.
/* Jumping out of an inner loop to an outer loop */
DO loop1=1 TO 3
SAY "I'm in loop1. loop1=" loop1
DO loop2=1 TO 3
SAY "I'm in loop2. loop2=" loop2
DO loop3=1 TO 3
SAY "I'm in loop3. loop3=" loop3
/* Jump out to loop1, thereby skipping loop2's remaining iterations. We
jump back to the very first line in this example. */
ITERATE loop1
END
END
END
/* Print pairs (i,j) where 1 <= i,j <= 5, except (2,j) if j>=3 */
DO i=1 TO 5
DO j=1 TO 5
IF i=2 & j=3 THEN ITERATE i /* or "LEAVE j" would work, or just "LEAVE" */
SAY "("i","j")"
END
END
ΓòÉΓòÉΓòÉ 13. Functions ΓòÉΓòÉΓòÉ
A REXX Function is a routine (supplied by the REXX interpreter or contained
within some add-on, binary executable known as a DLL) that your REXX script can
call, perhaps passing some data to it upon which the Function operates. A
Function performs its work, and then returns a value (which may be an error
number or error string, or some other data). When a REXX script calls a
Function, the script halts until the Function has completed its work and
returned its value.
There are numerous Functions built into the REXX interpreter, and these are
automatically available to be called by a script. A listing of each built-in
Function's name, what data (ie, arguments) it allows to be passed to it, and
what data (value) it returns, can be found in the REXX Procedures Language
2/Rexx book by IBM. Functions contained within add-on DLLs can also be called
by your script, but first you usually must issue the interpreter's RXFUNCADD
Function to let the interpreter know where to find the DLL containing the
desired function, as well as what the function's name is.
Just like Instruction Keywords such as SAY, a Function has a name which you use
to call the Function. For example, the interpreter has a built-in Function
named TIME. A Function is called by typing its name (case doesn't matter)
immediately followed by ( (ie, there must be no space inbetween the name and
open parenthesis). After the (, you put any data that you wish passed to the
Function, and finally a closing parenthesis ). Here's an example of calling
the TIME Function:
TIME() /* Actually, the CALL instruction needs to precede this */
You'll note that we didn't place any data inbetween the parentheses, because we
aren't passing any arguments to TIME.
The TIME Function happens to return a string containing the hours, minutes, and
seconds of the current time (in 24 hour, military time), each separated by a
colon. For example, the time may be reported as 01:30:44 (ie, AM).
Since a Function returns some data, you can say that the Function has a value
just like a variable. Therefore, you can treat a Function exactly like a
variable that has a value, except that this is a read-only variable (ie, you
can't assign a new value to a Function's name). For example, just like you can
assign the value of one variable to another variable, you can assign the value
of a Function to a variable.
My_Time = TIME()
The preceding instruction assigns the value of the TIME Function (ie, the
current time) to the variable My_Time. For example, after executing that
instruction, My_Time's value may be the string 01:30:44.
You can also use a Function anywhere you would use a variable's value. For
example, here we use the value of TIME in the Conditional IF/THEN instruction.
We check to see if TIME's value is 10:00:00 (ie, 10AM), and if so, we SAY "It's
time".
/* Check for 10AM */
IF TIME() == '10:00:00' THEN SAY "It's time"
So here you see a primary difference between a REXX Keyword and a Function. A
Function has a value, and be used as if it were a read-only variable. REXX
Keywords do not have values, and also are used only at specific places within
an instruction, sometimes only in conjunction with other keywords.
TIME also allows one argument. Sometimes, arguments can be optionally omitted,
as we did previously with TIME. TIME allows a string to be passed which must
be one of the following, depending upon how you wish TIME to format the
returned time string: CIVIL, ELAPSED, HOURS, LONG, MINUTES, SECONDS, NORMAL, or
RESET. If we specify the CIVIL option, then the returned time string takes the
format of the hour and minutes in 12 hour format, followed by AM or PM, for
example, 10:00:AM. Here we assign that return to My_Time.
My_Time = TIME('CIVIL')
Note that CIVIL was placed in quotes to make sure that the interpreter regards
it as a literal string, rather than a variable name whose value should be
passed to TIME. You can alternately specify variable names as arguments to a
Function. In that case, the interpreter, as usual, replaces the variable name
with its value, and that value is what gets passed to the Function as the
argument. Here, we pass the string CIVIL using a variable.
My_Option = 'CIVIL'
My_Time = TIME(My_Option)
Here are some more examples of using a Function's (returned) value.
/* Use TIME's value in a conditional loop (ie, loop until 10 o'clock) */
DO UNTIL TIME() == '10:00:00'
SAY "It's not 10 o'clock yet"
END
/* Use TIME's value as an argument for the SAY instruction (ie, print the time)
*/
SAY TIME()
You can even use a Function's value as an argument to another Function. Here
we use the returned value of TIME as an argument to the LENGTH Function. The
LENGTH Function allows one argument, any string or numeric value, and it
returns the number of characters in that string. Then, we use the returned
value of LENGTH as an argument for the SAY instruction.
/* Use TIME's value as an argument for the LENGTH Function which in turn
supplies an arg for SAY (ie, print number of characters in the time) */
SAY LENGTH(TIME('CIVIL'))
In this case, the interpreter executes the lowest nested function first. Here,
that's TIME. TIME is called with its argument "CIVIL'. The returned value of
TIME (let's assume that it's the string 10:00AM) is then passed as an argument
to LENGTH. LENGTH would return the value 7 (because there are 7 characters in
the string 10:00AM). This 7 is then the argument for SAY, and therefore a 7
gets printed to the screen.
If you wish to not use the Function's value in any way (ie, you're not
assigning it to a variable, or using its value in a conditional expression, or
in a conditional loop, or as an argument itself), then you must place the
keyword CALL before the Function name to toss the returned value away, or REXX
will report an error. For example, here we call the DIRECTORY Function,
supplying an argument that is the directory we desire to be made the current
one. DIRECTORY returns a string telling what is the current directory before
the change (ie, the previously current directory). We just throw away that
return value.
CALL DIRECTORY( 'C:\TEMP' )
Note that I added blank spaces around the argument for readability. This is
always optional.
Actually, the return value is not thrown away when you use call, but rather,
the interpreter stores it in the special variable named RESULT.
When a Function allows more than one argument, each argument must be separated
by a comma. For example, the LINEOUT Function allows 3 arguments. The first
arg is the name of the file to which data is written. The second arg is the
actual data to be written (ie, a string, numeric value, variable's value, or
Function's value). The third arg is a 1 if you want to overwrite any existing
file, or omit the arg if you wish to append the data to the end of any existing
file. For example, here we write the string more data to the file C:\DATAFILE,
overwriting any existing file.
error = LINEOUT('C:\DATAFILE', 'more data', 1)
Once again, note that blank space can be put between args to improve
readability.
Note: If you place CALL before the preceding instruction (in order to throw
away the return value), the REXX interpreter will cause an error. It
seems that, unless a function only takes one argument, when you want to
use CALL with it, you need to remove the parentheses around the
arguments. I consider this to be an unintuitive aberration of OS/2
REXX. So, in fact, if you want to use CALL in the above example, the
instruction will be:
CALL LINEOUT 'C:\DATAFILE', 'more data', 1
In the above, I'm throwing away the return value of LINEOUT, which would be an
error number (ie, 0 if there was no error writing the line, otherwise 1).
You'd normally want to check that return.
To omit an arg, simply put its comma, with nothing before the comma. For
example, if you omit the first arg to LINEOUT, then the Function defaults to
writing the line to the display (ie, same thing as the SAY command). Here I
omit the first arg.
error = LINEOUT(, 'more data', 1)
Omitting the last arg is even easier. You just omit it, and then you can omit
the comma of the previous arg too (ie, which separates the two args).
CALL LINEOUT , 'more data'
In fact, if you wish to omit several args, and they are all at the end of the
calling template, then you just omit those args, as well as any trailing comma
(after the last arg that isn't omitted).
ΓòÉΓòÉΓòÉ 14. Parsing (ie, breaking apart data into separate pieces) ΓòÉΓòÉΓòÉ
Parsing is the act of breaking apart one piece of data into separate pieces.
For example, let's say that the value of the variable My_Variable is the string
This is some data. You wish to break this apart so that each word is assigned
to a different variable. You use the PARSE instruction to do this.
By default, PARSE breaks apart data wherever there is at least one blank space.
So, the first piece of data that PARSE breaks off of My_Variable's value is the
word "This". This process is referred to as "tokenization", and so "This" is
the first token. The second token (ie, second piece of data that PARSE breaks
off) is the word "is". The third token is "some", and the fourth (and last
token) is "data". PARSE trims any leading and trailing spaces from each token
that is broken off, except for the last token. (One leading space upon the
last token is eaten by the parsing process).
One of the following keywords must follow the PARSE keyword: ARG, PULL, VAR,
SOURCE, VERSION, LINEIN, or VALUE. Which one you use depends upon where you're
getting the data being broken apart, and how you want it broken apart. You can
also use the keyword UPPER in conjunction with any of the preceding keywords in
order to tell REXX to change all lower case letters to upper case in any tokens
that are broken off.
ΓòÉΓòÉΓòÉ 14.1. VAR ΓòÉΓòÉΓòÉ
If you wish to break apart a variable's value into separate pieces, you'll use
the VAR (or VALUE) keyword. After the VAR keyword, you specify the variable
name that contains the data to be broken apart (ie, the source). Then, you
specify the names of other variables into which each token is placed. For
example, here we specify that My_Variable's value is to be broken apart, and
the first token is placed into the variable token.1, the second token is placed
into the variable token.2, the third token is placed into the variable token.3,
and the fourth token is placed into the variable token.4.
/* Parse My_Variable into 4 tokens */
My_Variable = "This is some data"
PARSE VAR My_Variable token.1 token.2 token.3 token.4
DO i=1 TO 4
SAY "token."i "= '"token.i"'"
END
If you supply less token variables than there are actual tokens within the
source, then the last token variable will contain all remaining tokens. For
example, consider the following line:
PARSE VAR My_Variable token.1 token.2 token.3
"This" is broken off and assigned to token.1, "is" is broken off and assigned
to token.2, and since token.3 is the last supplied token variable, the
remainder of My_Variable's value, "some data" is assigned to token.3. So, the
last token always gets whatever is left over of the value after preceding
tokens have been broken off. Also, remember that leading (except for one
space) and trailing spaces are not removed from the last token variable.
It's also acceptable to specify more token variables than there actually are
tokens within the source. For example:
PARSE VAR My_Variable token.1 token.2 token.3 token.4 token.5
"This" is broken off and assigned to token.1, "is" is broken off and assigned
to token.2, "some" is broken off and assigned to token.3, "data" is broken off
and assigned to token.4. There are no more tokens within My_Variable, so
token.5 is assigned to be an empty (ie, null) string containing no characters
at all. (Actually, if there were more than one trailing space after "data",
these would be assigned to token.5).
PARSE does not alter the source variable (ie, being parsed). So after the
PARSE instructions above, My_Variable is still "This is some data". On the
other hand, you can specify My_Variable as one of the token variables as well,
in which case it will be altered. This is very handy if you wish to break off
the first word of a variable, and then reassign the remaining tokens back to
that variable. Here we break off the first word of My_Variable, assigning that
token to token.1, and then the remaining tokens are reassigned to My_Variable.
PARSE VAR My_Variable token.1 My_Variable
After the above line, token.1's value is "This" and My_Variable's new value is
"is some data".
/* Parse My_Variable into all of its tokens */
SAY "Input a line"
PULL My_Variable
count = 0
DO WHILE My_Variable \== ""
PARSE VAR My_Variable token.count My_Variable
SAY "'"token.count"'"
count = count + 1
END
SAY "There are" count "tokens."
If you use PARSE VAR without specifying any token variable names, then the
specified source variable is accessed. If it does not have a value, the
NOVALUE condition is raised, if enabled.
ΓòÉΓòÉΓòÉ 14.2. Using search strings (to break apart tokens) ΓòÉΓòÉΓòÉ
PARSE defaults to breaking off tokens at blank spaces.
If you wish the value to be tokenized at characters other than a blank space,
you can specify that as a literal string (we'll call the search string) after
the token variable name. For example, assume that My_Variable's value is the
string "This is some data; separated by a semi-colon, and then a comma". You
wish to break this up into 3 tokens, separating the first token at the
semi-colon (ie, "This is some data"), and the second token at the comma (ie,
"separated by a semi-colon"), with the last token being "and then a comma".
Here's how we do that:
/* Parse My_Variable into 3 tokens, broken at ; and , */
My_Variable = "This is some data; separated by a semi-colon, and then a
comma"
PARSE VAR My_Variable token.1 ';' token.2 ',' token.3
DO i=1 TO 3
SAY "token."i "= '"token.i"'"
END
Note that since we aren't tokenizing by blank space, the interpreter doesn't
trim the leading spaces off of the second token. You'd have to use the TRIM
Function to subsequently do that.
You can token by entire strings (ie, patterns). For example, here we break
apart the first token at the string "some", and the second token at the string
"more".
/* Parse My_Variable at the string "some" and "more" */
My_Variable = "This is some data; and this is more"
PARSE VAR My_Variable token.1 "some" token.2 "more" token.3
DO i=1 TO 3
SAY "token."i "= '"token.i"'"
END
Note that the search strings "some" and "more" are completely removed from the
tokens. If "some" did not appear in My_Variable at all, then the first token
would contain the entire string, and the second and third tokens would be null
strings. If "some" appeared, but "more" did not, then the first token would be
"This is ", the second token would be the remaining data, and the third token
would be a null string.
It's possible to tokenise the source data appearing inbetween search strings.
For example,
/* Parse My_Variable between "some" and "more" */
My_Variable = "This is some data; and this is more"
PARSE VAR My_Variable token.1 "some" token.1 token.2 token.3 "more"
DO i=1 TO 3
SAY "token."i "= '"token.i"'"
END
The above tokenises everything between "some" and "more" and places the tokens
in token.1 (ie, "data;"), token.2 (ie, "and"), and token.3 (ie, "this is ").
You can also use a variable containing the desired search string. Put the
variable name containing the search string in parentheses when you use it with
PARSE. Here we use 2 variables to specify 2 search strings.
/* Parse My_Variable using search strings */
My_Variable = "This is some data; separated by a semi-colon, and then a
comma"
searchstr1 = ';'
searchstr2 = ','
PARSE VAR My_Variable token.1 (searchstr1) token.2 (searchstr2) token.3
DO i=1 TO 3
SAY "token."i "= '"token.i"'"
END
ΓòÉΓòÉΓòÉ 14.3. Throwing away tokens ΓòÉΓòÉΓòÉ
Placing a dot instead of a token variable name causes that token to be thrown
away. For example, here we throw away the second and fourth tokens of
My_Variable (ie, "is" and "data").
/* Parse My_Variable, tossing away 2nd and 4th tokens */
My_Variable = "This is some data"
PARSE VAR My_Variable token.1 . token.2 .
It's often a good idea to place a dot after the last token variable name. Not
only does this throw away any extra tokens that you don't want, but it also
ensures that none of the tokens that you do break off contain spaces.
(Remember, only the last token may contain spaces, and that's one that you're
throwing away).
ΓòÉΓòÉΓòÉ 14.4. Parse at particular character positions ΓòÉΓòÉΓòÉ
It's possible to parse by character (numeric) position instead of by searching
for a string. Character positions start at 1 for the first character, and
range upwards for further characters (ie, the second character in the source is
2, etc). You can indicate at which character to start parsing a token by
placing this character position before the token variable name. Then, you
place how many characters you wish to parse into that token after the token
variable name, with a + sign immediately before the count.
/* Parse My_Variable by character position */
My_Variable = "This is some data"
PARSE VAR My_Variable 4 token.1 +6 token.2 +5 3 token.3
DO i=1 TO 3
SAY "token."i "= '"token.i"'"
END
token.1 will be the 6 characters of My_Variable's value starting at character 4
(ie, "s is s"). token.2 will be the next 5 characters (ie, "ome d"). Note
that there is no character position specified for the second token, so that is
why it starts where the previous token ended. token.3 will be the all data
starting at character 3 (ie, "is is some data").
In above example, the position is absolute. That is, when you specify 4 for
the position, you mean the fourth character in the source string. But, you can
also specify relative position, by putting a + or - sign before the position
number. This means that you want a position relative to the next character
that PARSE is set to operate upon (after breaking off any previous token).
/* Parse My_Variable by relative character position */
My_Variable = "This is some data"
PARSE VAR My_Variable token.1 +6 -3 token.2
SAY "token.1 = '"token.1"'"
SAY "token.2 = '"token.2"'"
END
Above, we chop of the first 6 characters of the source string and place it into
token.1. After doing this, PARSE will be set to start breaking off the source
string at the seventh character. But, since we used a - sign before the
position number preceding the next token variable name, we move to a relative
position. In this case, we move 3 characters back, so PARSE starts breaking
off the second token at the fourth character in the source. So token.2 is "s
is some data".
Obviously, when using relative positions, you'll have to be careful that REXX
doesn't mistake a relative position for a given token as the character count
for the preceding token. This could happen if you omitted the character count
for the preceding token.
ΓòÉΓòÉΓòÉ 14.5. VALUE ΓòÉΓòÉΓòÉ
The VALUE keyword is similiar to the VAR keyword except that VALUE can be used
with Function returns and expressions whereas VAR needs to operate upon a
variable's value (ie, you must supply a variable name as the source). You need
to put the WITH keyword after the source to be parsed (ie, before the token
variable names).
Tokens are broken off at blank spaces, unless you specify a different search
string after a token variable name.
Tokens can be thrown away by specifying a dot instead of token variable name.
Tokens can be parsed by character position.
Here we get the returned time from the TIME Function, and parse it apart at the
colon characters into three token variables.
/* Parse TIME into 3 tokens, broken at : */
PARSE VALUE TIME() WITH token.1 ':' token.2 ':' token.3
DO i=1 TO 3
SAY "token."i "= '"token.i"'"
END
ΓòÉΓòÉΓòÉ 14.6. ARG ΓòÉΓòÉΓòÉ
The ARG keyword is used to retrieve arguments that the user typed on the OS/2
command line when he started the script (ie, any text that he typed after the
name of the REXX script).
Tokens are broken off at blank spaces, unless you specify a different search
string after a token variable name.
Tokens can be thrown away by specifying a dot instead of token variable name.
Tokens can be parsed by character position.
The following script parses its arguments:
/* Parse the arguments */
PARSE ARG token.1 token.2 token.3 token.4 .
DO i=1 TO 4
SAY "Argument" i "is:" token.i
END
Run the above script from the OS/2 command line, and type "alpha beta gamma
delta" after the script name. The script should print out:
Argument 1 is: alpha
Argument 2 is: beta
Argument 3 is: gamma
Argument 4 is: delta
The argument "alpha beta gamma delta" has been parsed into 4 tokens. The
tokens were split up at the spaces in the input. If you experiment with the
script, you should see that if you do not type 4 words as arguments, then the
last tokens printed out are empty, and that if you type more than four words
then the last token contains all the extra data. Also, even if multiple spaces
appear between the words, only the last token contains spaces.
Replace PARSE with PARSE UPPER in the program. Now, when you supply arguments,
they will be tokenized as uppercase. You can simply use ARG as an abbreviation
for PARSE UPPER ARG, so for example, the following 2 lines are identical:
PARSE UPPER ARG token.1
ARG token.1
ΓòÉΓòÉΓòÉ 14.7. SOURCE ΓòÉΓòÉΓòÉ
The SOURCE keyword parses information about how the script was invoked, and
what the script's name is. The source string contains the characters OS/2,
followed by either COMMAND, FUNCTION, or SUBROUTINE, depending on whether the
script was invoked as a host command (ie, directly launched by typing the name
at an OS/2 command prompt, or launched by an OS/2 program that can start REXX
scripts) or from a function call in an expression or using the CALL
instruction. These two strings are followed by the complete path specification
of the script.
Tokens are broken off at blank spaces, unless you specify a different search
string after a token variable name.
Tokens can be thrown away by specifying a dot instead of token variable name.
Tokens can be parsed by character position.
For example, if you start a script called "TEST.CMD" in the C:\OS2 directory,
from an OS/2 command line, and the script contains this line:
PARSE SOURCE token.1 token.2 token.3
then token.1 is "OS/2", token.2 is "COMMAND", and token.3 is "C:\OS2\TEST.CMD".
ΓòÉΓòÉΓòÉ 14.8. PULL ΓòÉΓòÉΓòÉ
The PULL keyword parses a line of text from the default input stream, which is
usually the user entering text at an OS/2 command prompt. (Actually, PULL
first tries to remove the next data item in the script's data stack, and uses
that for the source if there is an item there. If no item is on the stack,
then it reads from standard in, which is usually the keyboard. The data stack
will be discussed later).
You can simply use PULL as an abbreviation for PARSE UPPER PULL, so for
example, the following 2 lines are identical:
PARSE UPPER PULL token.1
PULL token.1
If no line is available, the script will normally pause until a line is
complete.
To check if any lines are available in the default character input stream, use
the built-in Function LINES.
See also Data stack
ΓòÉΓòÉΓòÉ 14.9. LINEIN ΓòÉΓòÉΓòÉ
The LINEIN keyword parses a line of text from the default input stream, which
is usually the user entering text at an OS/2 command prompt. (Unlike PULL,
this doesn't first try to remove the next line in the script's data stack.
Otherwise, functionality is the same).
PARSE LINEIN is really just an abbreviation for PARSE VALUE LINEIN() WITH, the
former being a lot easier to notate.
If no line is available, the script will normally pause until a line is
complete. PARSE LINEIN should only be used when direct access to the character
input stream is necessary. Line-by-line input from the user should normally be
done via the PULL (or PARSE PULL) instruction.
To check if any lines are available in the default character input stream, use
the built-in function LINES.
Using PARSE LINEIN by itself (ie, without any token variable names after it)
causes the next line to be removed from standard input, and discarded.
ΓòÉΓòÉΓòÉ 14.10. VERSION ΓòÉΓòÉΓòÉ
The VERSION keyword parses information about the REXX language version\level
and date of the interpreter. This consists of five words (delimited by
blanks): first the string "REXXSAA", then the language level (currently
"4.00"), and finally the release date (currently "13 June 1989").
You should parse this information by words rather than by character position,
as the language version\level could be different lengths for various versions.
ΓòÉΓòÉΓòÉ 15. Terminating a REXX script ΓòÉΓòÉΓòÉ
If you wish to manually stop a running script (ie, tell the interpreter to
abort), press the Control (Ctrl) and Break keys simultaneously. That is, press
and hold down the Ctrl key and then press the Break key once.
A script can stop itself by executing the EXIT Instruction, which consists
simply of the EXIT keyword. An EXIT is implicitly understood to be at the
bottom of a script (ie, when the interpreter gets to the last line in a script,
it automatically ends the script after that last instruction, unless that
instruction causes a jump back to some other instruction in the script such as
END or SIGNAL). So, you don't need the EXIT instruction at the bottom of a
script, although it's OK to put one there.
/* Terminate the script before the last line, if the user answers YES */
SAY "Do you want to terminate before printing the word 'Hello'?"
PULL answer
IF answer == "YES" THEN EXIT
SAY "Hello"
If you wish to return an error number or string to the OS/2 Command Line (or if
the script was launched by another REXX script, return the error to that REXX
script's RC variable), then simply place that error number or string after the
EXIT keyword. Usually, you return a 0 if the script has no errors. (Not
supplying any arg after EXIT tells the interpreter to assume a return of 0).
Here, we return an error number of 30.
EXIT 30
ΓòÉΓòÉΓòÉ 16. Labels (ie, marking a place in your script), and jumping there ΓòÉΓòÉΓòÉ
A label is used to mark a place in your REXX script. A label name can be any
legal variable name. In order to indicate a label, you put a colon :
immediately after the label. Here we have a label named RightHere, marking a
SAY instruction.
RightHere: SAY "This instruction has a label"
The label could optionally be put on a separate line (ie, above the instruction
that it marks). Also, the label may be indented.
The SIGNAL instruction allows you to specify a label to jump to. You specify
the label name after the SIGNAL keyword. The interpreter jumps to the
instruction following that label, and resumes executing instructions at that
place.
/* Terminate the script before the last line, if the user answers YES */
SAY "Do you want to terminate before printing the word 'Hello'?"
PULL answer
IF answer == "YES" THEN SIGNAL Here
SAY "Hello"
Here:
EXIT
If you use SIGNAL within a loop or SELECT group, to jump to a label outside of
that loop or SELECT group, then you effectively jump out of (ie, terminate) the
loop or SELECT group itself.
A common use of SIGNAL is to jump to an error handling routine when something
goes wrong, so that the program can clean up and EXIT.
Note: Make sure that you don't name any variables the same as any labels in
your script. Otherwise, the interpreter will always regard that name as
referring to the label rather than a variable, and perhaps cause an
error condition.
ΓòÉΓòÉΓòÉ 17. Error Trapping (ie, handling errors that the interpreter reports) ΓòÉΓòÉΓòÉ
There are certain error conditions which the interpreter recognizes. The names
of such error conditions are SYNTAX (ie, any error you made in notating your
instructions such as putting the UPPER keyword before PARSE), ERROR (any error
that a Function or subroutine or instruction or external program that you
launch causes as a result of doing its work, such as LINEIN having a problem
reading in the next line), FAILURE (some Functions in DLLs may cause failures
instead of errors), HALT (when the user stops script execution by pressing CTRL
and BREAK) and NOVALUE (when a variable is used without having been assigned a
value). The interpreter does default handling for each of these error
conditions. Sometimes, the default handling is to print a descriptive error
message to the OS/2 command prompt window, and end the script (as with SYNTAX,
ERROR, FAILURE, and HALT). Sometimes, the default handling is to ignore
instances of the error condition (as with NOVALUE).
Besides jumping to a specific label, the SIGNAL instruction also allows you to
replace a default error condition handling with your own REXX instructions.
You can specify a label that the interpreter will automatically jump to if a
certain error condition happens. This is referring to "trapping an error
condition".
Error trapping for one of the above conditions is turned on by putting the
keyword ON after SIGNAL, followed by the condition name. By default, the label
that the interpreter jumps to is the same as the condition name. For example,
assume you put the following line at the top of your script:
SIGNAL ON SYNTAX
Now, whenever the interpreter finds a syntax error in your script, the
interpreter will try to jump to a label named SYNTAX somewhere in your script.
Obviously, you should put that label somewhere in your script, probably before
an EXIT instruction so that the script ends at that point. For example, you
may have these lines at the bottom of your script:
SYNTAX:
SAY "Syntax error"
EXIT
Note that the above routine does what the interpreter's default SYNTAX handling
would do. But, you could do something different, for example, maybe ask the
user if he wants to continue with the script execution, and then jump (ie,
SIGNAL) to some other part of the script.
If you wanted to name your label something other than the same as the condition
name, then you can put the NAME keyword followed by the desired label name.
Here, we jump to the label MyError whenever a syntax error occurs.
SIGNAL ON SYNTAX NAME MyError
You can disable an error trap at any point by putting the keyword OFF after
SIGNAL, followed by the condition name. When the interpreter executes the
following instruction, it no longer jumps to the SYNTAX label when it finds a
syntax error. Instead, it does its default handling for syntax error
conditions.
SIGNAL OFF SYNTAX
You can turn trapping on and off at various places for a given error condition.
Whenever a SIGNAL occurs, a special variable named SIGL is set to the line
number of the instruction which caused the error condition. If the signal was
due to an ERROR trap, then the special variable RC will be set to the error
number or message returned by the Instruction, Function, external program, or
subroutine.
Note: SIGL and RC are variables that are created by the interpreter, which
assigns them values at certain points, as a result of script execution.
You should never name your own variables SIGL or RC, but may reference
these variables. For example, after running an external program, you
can find out if it was successful by checking if RC is 0. An RC value
of 0 usually means "success", whereas any other numeric value is an
error number.
/* This program goes on forever until the user stops it */
SAY " Press Control-C to halt"
SIGNAL ON HALT
DO i=1
SAY i
DO 10000
END
END
EXIT
HALT:
SAY "Died at line" SIGL
You can alternately use the CALL instruction followed by the keyword ON and the
desired condition instead of SIGNAL ON. For example:
CALL ON SYNTAX NAME MyError
The difference is that MyError should be a Subroutine that ends with RETURN.
(Subroutines are discussed later). When the interpreter encounters the RETURN,
it resumes execution after the instruction which caused the error. Using CALL
ON therefore allows you to execute some error handling and then resume the
script from where the error occurred.
ΓòÉΓòÉΓòÉ 18. Writing Functions and Subroutines in REXX ΓòÉΓòÉΓòÉ
See the section Functions for a general discussion of Functions.
You can write your own Functions in REXX, placing them in your script (or
putting them in a separate script that you call from your script). To identify
a Function in your script, you need to put a label at the start of it. The
following script contains an internal Function named square, and calls it with
some data (ie, passes it 1 argument). This Function simply squares that
argument and returns the result as its value.
/* Call the Function square() with an arg, and print the Function's returned
value */
SAY "The results are:" square(3) square(5) square(9)
EXIT
square: /* here's a function to square its argument */
PARSE ARG myarg
RETURN myarg*myarg
The output of this script is: "The results are: 9 25 81".
Remember that a Function is called by typing its name, immediately followed by
a (, then any passed args, and finally a closing ). A Function should also
return a value.
When the interpreter encounters the Function call square(3), it searches the
script for a label called "square". It finds that label on line 5 in the above
example. The interpreter then executes the instructions starting at that line,
until it encounters a RETURN instruction, whereupon it resumes executing the
instruction that originally called the Function, replacing that Function call
with the returned value (ie, square(3) gets replaced by its returned value of
9, and that's what the SAY instruction displays).
Note: The EXIT instruction in the above script causes the interpreter to
finish executing at that line instead of running down into the Function.
It's important to make sure that you don't let the interpreter
accidentally drop down into Functions at the bottom of your script.
It's best to put all your Functions at the bottom of the script, and
place one EXIT above them all. Note that a Function can never
accidentally drop down into another Function below it as long as it ends
with RETURN, since RETURN jumps back to the instruction that originally
called the Function.
While that Function is being executed, the arguments to the Function can be
determined with PARSE ARG in the same way as the arguments to a script. In the
above example, we parse the argument passed to square() into a variable named
myarg. So, for the call square(3), myarg is assigned the value 3.
When the RETURN instruction is reached, the expression specified after RETURN
is evaluated and used as the return value of the Function. The expression can
be a variable's value, mathematical expression, or even the return from another
Function.
You can have more than one RETURN instruction in a Function, each returning a
different value. But, the interpreter always jumps out of a Function
immediately when it encounters a RETURN, and returns to the instruction that
called the Function. Here we have two ways out of the Function answer(), plus
two different possible return values:
answer: /* here's a Function that returns 1 if the user answers "YES", or 0 if
he answers anything else */
SAY "Do you want to learn REXX programming?"
PARSE PULL ans
IF ans == "YES" THEN RETURN 1
RETURN 0
A Function can take multiple arguments, which are each separated with a comma,
as so:
/* Call a Function with 3 arguments */
SAY "The results are:" condition(5,"Yes","No") condition(10,"X","Y")
EXIT
condition: /* If the first argument is less than 10, return the second, else
return the third. */
PARSE ARG c,x,y
IF c<10 THEN RETURN x
ELSE RETURN y
A Subroutine is similar to a Function, except that it need not return a value
(ie, supply a value after the RETURN keyword). It is called with the CALL
instruction. You do not place a ( and ) after the Subroutine name. Any args
passed to the Subroutine are listed after the Subroutine name, and are not
separated by commas (and therefore, the arguments are seen as one long string.
If you want to tokenize it, you'll have to fetch the args into one variable
using PARSE ARG, and then use a PARSE VAR to break up the string into separate
pieces).
/* Call a Subroutine named box to print a string in a box */
CALL box "This is a sentence in a box"
CALL box "Is this a question in a box?"
EXIT
box: /* Print the argument in a box */
PARSE ARG text
SAY "+--------------------------------+"
SAY "|"CENTRE(text,32)"|" /* centre the text in the box */
SAY "+--------------------------------+"
RETURN
If you do choose to return a value from a subroutine (ie, put some expression
after the RETURN keyword), the interpreter stores this value in the special
variable named RESULT. You can't consider a Subroutine itself to directly
return a value like a Function does. For example, consider the following call
to Subroutine box, and assume that box's RETURN instruction returns a value
string of "done":
SAY box "an argument"
This will not print "done". Rather, it will simply print "BOX an argument".
First of all, the interpreter doesn't recognize box as a Subroutine name
because the CALL keyword has been omitted, so box is never even called. The
interpreter thinks that box is a variable name, and since it hasn't been
assigned a value, it defaults to its name uppercased. Even if you put the CALL
keyword before box, that wouldn't result in box being called. The interpreter
would misinterpret CALL as the name of another variable, as the CALL keyword
can't be part of a SAY instruction, and "CALL BOX an argument" would be
printed.
It is possible to call a Function, even a built-in Function, as if it were a
Subroutine (ie, no parentheses around args, and it doesn't directly return a
value). You need to use the CALL instruction to actually call the Function.
The value returned by the Function is placed into the special variable named
RESULT, just like with a Subroutine. (Optionally, you can still use
parentheses and args separated by commas).
/* Print the time, using the CALL instruction */
CALL TIME "CIVIL"
SAY RESULT
ΓòÉΓòÉΓòÉ 18.1. PROCEDURE (ie, different set of variables for a Function versus the rest of the script) ΓòÉΓòÉΓòÉ
If a Function (or Subroutine) does not need to use the variables which the rest
of the script creates, or if the Function uses variables which the rest of the
script does not need to access, then you can start the Function with the
PROCEDURE instruction. This clears all the existing variables away out of
sight, and prepares for a new set of variables. This new set will be destroyed
when the Function returns. The advantage to this is that it ensures that the
Function won't ever alter the value of any variable used by the rest of your
script, even if the Function creates a variable with the same name as one used
elsewhere in your script (ie, the variable in the Function belongs to an
entirely different set of variables than the one with the same name elsewhere).
Likewise, if a Function calls another Function which contains the PROCEDURE
instruction, that second Function won't alter the values of any variables used
by the first Function. So, PROCEDURE is a way to protect variables from being
inadvertently altered by calls to Functions or Subroutines.
The following script calculates the factorial of a number recursively.
PROCEDURE makes recursion possible and easy.
/* Calculate factorial x, that is, 1*2*3* ... *x */
SAY "Enter a number"
PARSE PULL num .
SAY num"!="factorial(num)
EXIT
factorial: /* calculate the factorial of the argument */
PROCEDURE
PARSE ARG num
IF num<3 THEN RETURN num
ELSE RETURN factorial(num-1) * p
First of all, the num variable initially passed to factorial is not the same
num variable used within factorial (ie, in the PARSE ARG instruction) due to
the PROCEDURE instruction. So, that PARSE ARG isn't altering the original num
variable, but rather, creating a new variable named num. Furthermore, that
second num variable is then passed to factorial again. (ie, factorial calls
itself when it goes to return a value). When factorial is called again, it
creates yet another num variable to be used up until that nested call returns.
If the Subroutine or Function needs access to just a few variables used
elsewhere in the script, but wants to protect all other variables, then put the
EXPOSE keyword after PROCEDURE, followed by any variable names that you don't
want hidden away. In this way, your Function can alter the values of
particular variables used elsewhere in the script, while also protecting other
variables. Consider the following:
/* Assign values to variables First and Second */
First = 0
Second = 0
/* Call Function alter */
CALL alter()
SAY "First =" First", Second =" Second
EXIT
alter:
PROCEDURE EXPOSE First /* don't protect the variable First */
First = 1
Second = 1
RETURN 0
The above prints out "First = 1, Second = 0". Note that the alter Function was
able to change the value of First used in the SAY command, but not the value of
Second. That's because the Second variable that alter changes is an entirely
different variable than the Second variable used elsewhere in the script. On
the other hand, the First variable used within alter is the very same one used
elsewhere.
ΓòÉΓòÉΓòÉ 18.2. External REXX Functions (ie, calling Functions within another REXX script) ΓòÉΓòÉΓòÉ
You can write Functions and Subroutines which are not contained in the same
REXX script. In order to do this, write the Function and save it as a separate
file (with a .cmd extension on the filename). This type of Function is called
an "external" Function, as opposed to an "internal" Function which can be found
inside the currently running script.
For example, If you want to call your Function as "foobar()" (or Subroutine as
"CALL foobar"), then you should save it in a file named "foobar.cmd" which can
be found in the current directory or in your path.
The PROCEDURE instruction is automatically executed before running your
external Function, and so it should not be placed at the start of the Function.
It is not possible for an external Function to access any of its calling
script's variables, except by the passing of arguments (which the external
Function accesses via PARSE ARG) or by data items which the calling script
pushes onto the second script's data stack.
For returning from external Functions, as well as the RETURN instruction, there
is EXIT. The EXIT instruction may be used to return any data to the calling
script in the same way as RETURN, but EXIT can be used to return to the calling
script even when it is used inside of some Subroutine within the external
Function itself (ie, EXIT can be used to break out of nested or recursive
calls, and immediately return to the calling script).
ΓòÉΓòÉΓòÉ 19. Data stack (ie, PUSHing, QUEUEing, and PULLing) ΓòÉΓòÉΓòÉ
Each script has a data stack, which is accessed via the PUSH, QUEUE, and PULL
(ie, PARSE PULL) instructions. The PULL instruction can retrieve data (typed
within an OS/2 command prompt window) from the user as we have seen before.
But, if there is some data on the stack then PULL will retrieve that instead.
QUEUE and PUSH can be used to place a data item onto the stack (to be
subsequently retrieved via PULL). PULL actually removes the data item from the
stack, and places it into whatever variable you specify. Here we PUSH or QUEUE
data items, and PULL them.
/* Access the stack */
QUEUE "Hello!"
PARSE PULL answer /* answer contains "Hello!" */
PARSE PULL answer2 /* PARSE PULL gets input from the user now since there's no
data on the stack */
PUSH "67890"
PUSH "12345"
PARSE PULL answer3 /* answer3 contains "12345" */
/* There is one item left on the stack, "67890" */
The difference between PUSH and QUEUE is that when the items are pulled off the
stack, the items which were queued appear in the same order that they were
queued (FIFO, or first in, first out), and the items which were pushed appear
in reverse order (LIFO, or last in, first out). In other words, if you QUEUE
five items, the order that they get pulled out is items 1, 2, 3, 4, 5. If you
instead PUSH those five items, the order that they get pulled out is items 5,
4, 3, 2, 1.
Using PULL (or PARSE PULL) by itself (ie, without any token variable names
after it) causes the next item to be removed from the data stack, and
discarded.
ΓòÉΓòÉΓòÉ 20. Running executables and OS/2 commands ΓòÉΓòÉΓòÉ
A REXX script can run any executable (ie, a program written in another
language), including MS-DOS and Windows programs. For example, a REXX script
can run OS/2's DISKCOPY.COM program, and even receive back an error code
telling whether DISKCOPY performed its operation successfully.
When the interpreter encounters an instruction which doesn't begin with a
recognizable keyword, or isn't a variable assignment, it treats that line as a
string expression which is to be evaluated and passed to the environment. The
default environment for scripts launched from a command prompt or desktop
object is an OS/2 command prompt. So in effect, the interpreter ships off that
line as if the user had typed it at an OS/2 command prompt. Therefore, if the
line is:
del '*.*'
The interpreter doesn't recognize "del" as a keyword (such as CALL, SAY, IF,
etc). Neither does it regard it as a call to a Function because there is no (
after del. It also doesn't regard this as an assignment because there is no =
after del. So, the interpreter decides to pass the line to the OS/2 Command
Prompt. But first, the interpreter evaluates the line. It sees that del is
not in quotes, so it assumes that del is a variable name. It fetches the value
of del, which if del has not been assigned a value defaults to "DEL". Then, it
sees the literal string '*.*'. It removes the outside quotes. (When
evaluating literal strings, the outside quotes are always removed by the
interpreter before it sends the literal string off to some environment. If you
actually wanted a pair of quotes, you'd have to double-quote the string so that
after the interpreter removed the outside pair, one pair would still remain).
So, the actual text that gets passed to the OS/2 command prompt is DEL *.*.
The net result is that the OS/2 command prompt uses the OS/2 del command to
delete all files in the current directory.
In fact, it's best to quote the del too, since you literally want that text
passed to the OS/2 command prompt. You don't want the interpreter to regard
del as a variable name and possibly replace it with some value that isn't what
you want passed to the OS/2 Command Prompt.
'del *.*'
In the above, the interpreter doesn't regard del as a variable when evaluating
the line. del is simply part of the literal string.
Of course, you could use a variable's value to specify the desired program name
to execute. In this case, we want the interpreter to recognize command as a
variable name and replace with its value (ie, 'del').
command = 'del'
command '*.*'
The interpreter suspends your REXX script (on that instruction that launched
the other program) while that program is running. When the OS/2 command prompt
(ie, shell) finishes running the specified executable, it returns (to the
interpreter) whatever value that executable returned. (Most executables return
a 0 if they execute successfully. This is particularly true of the standard
OS/2 commands such as del, copy, etc). The interpreter puts this value in the
special variable RC and resumes executation of your script. So, you can often
check the RC variable to see if the executable did its job successfully.
'del *.*'
IF RC = 0 THEN SAY "It worked"
ELSE SAY "It didn't work"
The OS/2 shell has a START command which causes the launched program to be
started up as a separate process. In this case, control is returned to your
REXX script immediately upon launch of the other program. Your REXX script
doesn't have to wait for that other program to terminate before it can proceed
doing other things. Your script continues executing WHILE the other program is
also executing. In this case, you won't be able to get an error code (returned
in RC) from the other program when it terminates. Here we launch a program
called BLORT.EXE:
'start blort.exe'
ΓòÉΓòÉΓòÉ 21. INTERPRET ΓòÉΓòÉΓòÉ
Suppose you have a variable named inst which contains the string "var=var+1"
(ie, that's its value). You can execute that string as an instruction, by
typing:
INTERPRET inst
Therefore, the variable named var is now incremented.
The INTERPRET instruction may be used to execute a Rexx instruction entered by
the user, or to assign values to variables whose names are not known in
advance.
/* Get the desired name of a variable from the user, and set that variable to
42 */
PARSE PULL var
INTERPRET var "=" 42
ΓòÉΓòÉΓòÉ 22. Tracing (ie, debugging) ΓòÉΓòÉΓòÉ
If a program goes wrong and you need more information in order to work out why,
the interpreter provides you with the ability to trace all or part of your
script to see what is happening.
The most common form of tracing is turned on by using the TRACE instruction
with an argument of R.
TRACE R
This causes each instruction to be listed before it is executed. Also, it
displays the results of each calculation after it has been found.
Another useful trace instruction is:
TRACE ?A
This makes the interpreter list each instruction and stop before it is
executed. You can execute the instruction by pressing return, or you can type
in some instruction to be interpreted, after which the interpreter will pause
again. You can use this to examine the value of the script's variables (by
issuing a SAY instruction with the names of variables whose values you wish to
display) and so forth.
If you want the interpreter to stop only when passing a label, use:
TRACE ?L
This is useful for setting breakpoints in the script, or for making the script
stop at entry to a Function or Subroutine.
ΓòÉΓòÉΓòÉ 23. More helpful error messages ΓòÉΓòÉΓòÉ
The OS/2 Command Prompt can supply helpful descriptive messages about
particular errors you may see the interpreter display. For example, suppose
that the interpreter ends a script (named C:\OS2\MYSCRIPT.CMD) with the
following error message:
6+++ SAY "The sum of the two numbers is" num1 & num2
REX0034: Error 34 running C:\OS2\MYSCRIPT.CMD,line 6:logical value not 0 or 1
The first line of this error message is the actual instruction in your script
that caused the error. It is usually prefaced with the line number.
(Unfortunately, the interpreter doesn't count comments and blank lines, so when
you look for that line number in your script, remember to skip such). The
second line starts with an error number (which I've shown in color). Then, the
name of the script is shown, the line number where the error occurred, and a
description of the error. If this description is not enough, go to a Command
Prompt window, and issue the HELP command followed by that error number.
HELP REX0034
This will display a more descriptive message as so:
REX0034 ***Logical Value not 0 or 1***
Explanation: The expression in an IF, WHEN, DO WHILE, or DO UNTIL phrase must
result in a '0' or a '1', as must any term operated on by a logical operator.