REXX Workshop - Part 2
REXX instructions specify that certain actions are to be taken within the REXX environment. REXX instructions begin with a REXX keyword and can be coded in UPPER, lower, or Mixed Case. A keyword is positional and is coded as the first word of a statement. Found in other positions, a REXX keyword may be treated as a variable or a label.
The following sections introduce the most commonly used REXX instructions. Please refer to other documentation for more details about those instructions which are covered as well as those which are not covered.
Used in this context, flow control refers to the sequence in which program statements are executed. Normally, statements are executed one by one beginning with the first line of a program. The program terminates when the last statement is executed.
The following REXX instructions can be used to alter the sequence in which program statements are executed.
The CALL instruction is used to transfer control to an internal (or external) subroutine. After the subroutine finishes, program execution usually continues with the statement just following the CALL statement.
/* Example of using the CALL Instruction */ . . . Call TOTALUP /* "TOTALUP is a subroutine. */ next instruction . . . Exit TOTALUP: /* Subroutines start with labels. */ . . . Return /* Return control to the main line. */
Subroutines are often located after the program's main line. It is important to code an EXIT instruction before the first subroutine.
REXX statements are executed sequentially until the physical end of the program, at which point program execution stops. If a subroutine is coded following the main line, REXX executes the last statement of the main line and then continues with the first statement of the subroutine unless care is taken to stop the program before this happens. In the following example, there is nothing to prevent the execution of the TOTALUP subroutine after the main line has finished.
/* The main line "falls into" the subroutine.*/ . . . Call TOTALUP . . last statement of main line TOTALUP: /* TOTALUP starts here. */ . . . Return /* End of TOTALUP subroutine. */
To prevent TOTALUP from being executed incorrectly, place an EXIT instruction at the end of the main line, as in the following example.
/* Example of using the EXIT Instruction */ . . . Call TOTALUP next instruction . . . Exit /* Exit terminates the program. */ TOTALUP: /* The subroutine is protected. */ . . . Return
The SIGNAL instruction is often used to trap errors and other conditions which may arise in the course of running a program. But in its most basic format, SIGNAL simply transfers control from one point in a program to another.
/* Example of using the SIGNAL instruction */ . . . Signal SUMS /* Transfer control to SUMS. */ statement not executed . . . SUMS: /* This statement receives control. */ statement executed . . .
Two REXX control structures facilitate decision making logic within an exec. An IF instruction, paired with a THEN instruction and optionally an ELSE instruction, facilitates making a two-way decision. A SELECT instruction, paired with one or more WHEN instructions, facilitates making a many-way decision.
IF ... THEN ... ELSE
To make a binary or two-way decision, use the IF instruction. The format of the IF instruction is as follows:
If expression Then statement1 Else statement2
If the "expression" is true, then "statement1" is executed and "statement2" is bypassed. If the "expression" is false, then "statement1" is bypassed and "statement2" is executed. For example,
/* Example of a simple IF instruction */ x = 2 If x = 1 /* Since x=2 */ Then y = 1 /* statement is bypassed; */ Else y = 2 /* statement is executed. */
Since an ELSE instruction is optional, the following code would achieve the same result.
/* Example of a simple IF without ELSE */ x = 2 y = 2 If x = 1 /* Since x=2 */ Then y = 1 /* statement is bypassed; */
A THEN instruction can be placed on the same line with its corresponding IF instruction.
/* Example of a IF and THEN on same line */ x = 2 y = 2 If x = 1 Then y = 1
IF instructions can also be nested, forming complex decision trees.
/* Example of nested IF instructions */ x = 2 If x = 1 Then y = 1 Else If x = 2 Then y = 2 Else y = 3
Complex decision trees created with nested IF instructions is possible, but difficult to interpret. Instead, try using the SELECT statement.
SELECT ... WHEN ... OTHERWISE ... END
To make a many-way decision, use the SELECT instruction, one or more WHEN instructions, an OTHERWISE instruction, and an END instruction. The format of the SELECT instruction is as follows:
Select; When expression1 Then statement1 When expression2 Then statement2 When expression3 Then statement3 Otherwise statement4 End
- The expression specified on the first WHEN instruction is evaluated. If the result is true, the THEN instruction associated with that WHEN instruction is executed, and all other WHEN instructions are ignored.
- If the expression associated with the first WHEN instruction is false, the corresponding THEN instruction is bypassed and the second WHEN expression is evaluated, and so forth.
- If all cases are false, then any statement associated with the OTHERWISE instruction is executed.
The following exec uses the SELECT instruction to translate a number into a color.
/* Example using the SELECT instruction */ Pull n Select; When n = 1 Then color = 'RED' When n = 2 Then color = 'WHITE' When n = 3 Then color = 'BLUE' Otherwise color = 'UNKNOWN' End Say "The color you specified is" color"." Exit
On occasion, it may be appropriate to do nothing if all cases are false. Since the OTHERWISE instruction is required, use the NOP (no operation) instruction to facilitate taking no action. The preceding example might have been coded as follows:
/* Example using the NOP insruction */ color = 'UNKNOWN' Pull n Select; When n = 1 Then color = 'RED' When n = 2 Then color = 'WHITE' When n = 3 Then color = 'BLUE' Otherwise NOP End Say "The color you specified is" color"." Exit
Exercise: Using Xedit, type in the preceding example. Enhance the code by adding additional WHEN instructions for additional colors. Try the program to assure that the logic is correct.
Loops are fundamental to most programs. REXX offers a powerful and flexible looping construct in the DO instruction. In this section, several variations of the basic DO instruction are given. The ITERATE and LEAVE instructions can be used to alter the loop prior to completion.
DO ... END
A DO instruction is always paired with an END instruction. Statements between the DO and END instruction constitute the "DO group."
Do statement1 statement2 statement3 End
To execute a DO group several times, add a repetitor clause to the DO instruction to indicate how many times the loop should execute. In the simplest case, the repetitor clause is a simple number, variable with a numeric value, or an expression that evaluates to a number.
Do 5 statement1 statement2 statement3 End or Do n - 1 statement1 statement2 statement3 End
The repetitor clause can also include a counter, an initial value, a terminal value, and optionally an increment. For example,
Do i = 1 To 6 /* Loops 6 times */ statement1 statement2 statement3 End or Do i = 9 To 1 By -1 /* Loops 9 times */ statement1 statement2 statement3 End
A DO group can be executed an arbitrary number of times, depending upon some value. Use the WHILE or UNTIL conditional clauses to control the looping in such cases. The WHILE conditional performs a test at the beginning of the loop:
Do While (n < 6) /* Loops if n less than 6 */ statement1 statement2 statement3 End
The loop will continue until n becomes 6 or greater, so provide some means to increment the value of n.
Do While (n < 6) /* Loops if n less than 6 */ statement1 statement2 statement3 n = n + 1 End
Since the UNTIL conditional clause is evaluated at the end of the loop, use the UNTIL clause to guarantee that the loop is executed at least once. For example,
n = 10 Do Until (n > 6) /* Loops until n greater than 6 */ statement1 statement2 statement3 End
Loops can be nested into rather complex forms. Be sure to keep your END statements straight.
Do While (n < 6) x = y + 3 Do i = x To 5 statement statement Do Until (x > 7) statement statement End End statement End
When a counter is used, the counter can be added to the END statement, but this is not generally necessary.
Do i = 1 To 10 statement1 statement2 statement3 End i
Exercise: Create a short program which uses a loop to print out a simple message. Try various forms of looping.
/* Loops */ Say 'Enter a number:' Pull n Do n Say 'Hello number' n End Exit
Use the ITERATE instruction to terminate the current iteration through the DO group, but to continue looping.
Do n = 1 To 10 If n = 5 Then Iterate /* Skips when n = 5 */ Say n End NEXTSTATEMENT:
Use the LEAVE instruction to terminate the current loop altogether.
Do n = 1 To 10 If n = 5 Then Leave /* Ends looping when n = 5 */ Say n End NEXTSTATEMENT:
Procedures are sections of code which are set off by themselves and invoked from the main line. A procedure begins with a valid REXX label, and includes one or more RETURN instructions. For example,
TOTALUP: statement1 statement2 statement3 . . . Return(0)
Procedures can share variables with the main line or can use completely separate copies of variables. Arguments can be passed to procedures when those procedures are invoked; single value "results" can be returned to the main line.
Procedures come in two flavors, subroutines and functions.
Subroutines are invoked by name with the CALL statement:
Call name list-of-arguments for example: Call TOTALUP nmen,nwomen,nchildren
Functions are also invoked by name, but use a different syntax:
x = name(list-of-arguments) for example: x = TOTALUP(nmen,nwomen,nchildren)
Of the two forms, the function procedure is more flexible and should be used.
Use the PROCEDURE instruction to isolate variables in the procedure from the main line and other procedures. If a PROCEDURE instruction is omitted, all variables in the main line are "known" to the procedure, and all variables in the procedure are "known" to the main line.
i = 100 /* variable i in main line */ x = TOTALUP(nmen,nwomen,nchildren) . . . Exit TOTALUP: i = 1 /* changes variable i in main line */ sum = 0 . . . Return(sum)
To isolate variables in a procedure from the main line, use the PROCEDURE instruction.
i = 100 /* variable i in main line */ x = TOTALUP(nmen,nwomen,nchildren) . . . Exit TOTALUP: Procedure i = 1 /* variable i known only to procedure */ sum = 0 . . . Return(sum)
To share some but not all variables between the main line and a procedure, use the EXPOSE condition with a list of shared variables.
i = 100 /* variable i in main line */ x = TOTALUP(nmen,nwomen,nchildren) . . . Exit TOTALUP: Procedure Expose nmen nwomen nchildren i = 1 /* variable i known only to procedure */ sum = 0 . . . Return(sum)
Use the RETURN instruction to return control from a procedure to the main line.
TOTALUP: Procedure Expose nmen nwomen nchildren i = 1 sum = 0 . . . Return(sum)
Supply a result or return value on the RETURN instruction.
TOTALUP: Procedure Expose nmen nwomen nchildren i = 1 sum = 0 . . . Return(sum)
When a procedure is invoked using the CALL instruction, then the returned value is stored in a special variable named RESULT.
i = 100 Call TOTALUP nmen,nwomen,nchildren Say RESULT /* Returned value "sum" is stored in "RESULT" */ . . Exit TOTALUP: Procedure Expose nmen nwomen nchildren i = 1 sum = 0 . . . Return(sum)
When a procedure is invoked as a function, then the function call is replaced by the returned value before the invoking statement is fully evaluated.
i = 100 x = TOTALUP(nmen,nwomen,nchildren) Say x . . Exit TOTALUP: Procedure Expose nmen nwomen nchildren i = 1 sum = 0 . . . Return(sum)
Passing Arguments to a Procedure
Arguments can be passed to a procedure when the procedure is invoked by supplying a list of variable names (or constants).
i = 100 x = TOTALUP(nmen,nwomen,nchildren) Say x . . Exit
Passed arguments can be assigned to variables within a procedure using the ARG instruction.
TOTALUP: Arg men women children i = 1 sum = 0 . . . Return(sum)
Using the Program Stack
The program stack is a buffer or queue which can be used to temporarily store information. Information can be added to the top or bottom of the stack, and can be retrieved back into the REXX exec from the top of the stack. There are several ways to think about the stack, but the following diagram may help.
Push -->. .---> Pull | | -------- | data | -------- | data | -------- | data | -------- | data | -------- | | Queue -->'
The examples below deal with only the simplest case, the one in which a single piece of data is stored into and retrieved from the program stack.
Use the PUSH instruction to place a string onto the stack.
. . . Push 'Save the whales!' . . .
Use the PULL instruction to retrieve a string from the program stack into a variable.
. . . Pull message /* "message" contains the string */ . . .
Note: Used in this way, PULL translates the stacked data to uppercase before returning it to the variable.
The PUSH instruction places strings on the program stack in a last-in-first-out manner, on the "top" of the stack. The following code
. . . Push 'Save the whales!' Push 'Save the day!' . . .
places two string on the stack. A subsequent PULL instruction would retrieve the second string ("Save the day!") because that was the last string placed on the stack, and strings are retrieved from the top of the stack.
To place several strings onto the program stack so that they can be retrieved in order (first-in-first out), use the QUEUE instruction.
. . . Queue 'Save the whales!' Queue 'Save the day!' . . .
A subsequent PULL instruction would retrieve the string "Save the whales!" since that was the first string "queued" up in the stack, and is consequently on the top.
Use the QUEUED( ) built-in function to retrieve the number of strings currently queued in the program stack. Assuming the following code
. . . Queue 'Save the whales!' Queue 'Save the day!' x = Queued() /* How many strings are queued? */ . .
The variable "x" would contain the value "2".
Because REXX is often interpreted, debugging faulty code can be fairly straightforward: insert some debugging statements and rerun the exec. In this section, two suggestions are offered.
The simplest way to debug many execs is to insert a SAY instruction at key points within a segment of code. SAY displays arguments on the terminal and facilitates checking the values of variables as well as the logic flow. For example, SAY instructions inserted strategically in the following code give information about the value of loop counters.
Do While (n < 6) x = y + 3 Do i = x To 5 Say i statement statement Do Until (x > 7) Say 'x='x statement statement End End statement End
Use the TRACE instruction to view the results of an exec, step by step. Tracing may be turned on and off for an entire exec or for segments of code.
The following basic format has proved quite useful.
Trace air Do While (n < 6) x = y + 3 Do i = x To 5 statement statement End statement End Trace off
The CMS Help file on TRACE (HELP REXX TRACE) provides details about a number of options.
There are a few REXX instructions which are hard to classify because of the unique functions they perform. One example, NOP, has been discussed before as a "no operation" place holder, useful as the object of an OTHERWISE instruction.
REXX statements are not fully evaluated until execution time. Even so, there are occasions when a statement is constructed dynamically and must be (essentially) evaluated twice; variables are replaced by their values and then the statement as a whole is interpreted. In the following example, the label "v" is indeterminate until the exec runs. By using the INTERPRET instruction, situations like this are easily handled.
/* Example using INTERPRET */ Pull n If n = 1 Then v = 'DOLPHINS' Else v = 'WHALES' Interpret Signal v DOLPHINS: Say 'Save the dolphins!' Exit WHALES: Say "Save the whales!" Exit
Use the UPPER instruction to translate into uppercase the values of one more more variables.
Upper x y z
REXX includes a large number of built-in functions which accept arguments and return values. The sections below highlight a few of the many built-in REXX functions.
The functions highlighted in this section return commonly useful information.
Use the DATE( ) function to retrieve the current date in a wide range of formats. For example,
d = DATE() /* d receives '26 Oct 1998' */ d = DATE('M') /* d receives 'October' */ d = DATE('S') /* d receives '19981026' */ d = DATE('W') /* d receives 'Monday' */
The DATE( ) function can also convert dates from one format to another.
Use the DATATYPE( ) function to determine what kind of data is represented by a string (numeric, character, binary, whole number, etc.). In its most simplistic format,
a = 'Billy' t = DATATYPE(a) /* t receives 'CHAR' */ b = '365' t = DATATYPE(b) /* t receives 'NUM' */
Use the TIME( ) function to retrieve the current time in one or more formats, or to measure the elapsed time of some process. In its most simplistic format,
t = TIME() /* t receives '17:17:30' */
Use the USERID( ) function to retrieve the name of the current virtual CMS machine name.
u = USERID() /* u receives 'RGE' */
Several REXX built-in functions manipulate numeric data. A few examples follow.
Use the ABS( ) function to return the absolute value of a number.
n = -3 a = ABS(n) /* a receives '3' */
Use the DIGITS( ) function to return the current setting (NUMERIC DIGITS) which governs the precision of calculations.
p = DIGITS() /* p receives '9' (perhaps) */
Use the MAX( ) function to return the largest of a set of numbers.
a = 10 b = 3 c = -20 m = MAX(a,b,c) /* m receives '10' */
Use the MIN( ) function to return the smallest of a set of numbers.
a = 10 b = 3 c = -20 m = MAX(a,b,c) /* m receives '-20' */
Use the SIGN( ) function to return a number representing the sign of a number.
a = 10 s = SIGN(a) /* s receives '1' */ b = 0 s = SIGN(b) /* s receives '0' */ c = -20 s = SIGN(c) /* s receives '-1' */
Use the TRUNC( ) function to return the integer portion of a decimal number and optionally one or more decimal places.
n = 10.923 i = TRUNC(n) /* i receives '10' */ i = TRUNC(n,1) /* i receives '10.9' */
A great many REXX built-in function manipulate string or character data. Below are several examples.
Use the CENTER( ) function to center a string within a field of blanks or other characters.
s = CENTER('REXX',8) /* s receives ' REXX ' */ s = CENTER('REXX',8,'-') /* s receives '--REXX--' */
Use the COPIES( ) function to create a longer string consisting of several copies of a shorter string.
s = COPIES('REXX',2) /* s receives 'REXXREXX' */
Use the INDEX( ) function to return the position of a short string found within a longer string.
needle = 'cbs' haystack = 'abc cbs fox nbc pax tnt upn usa wb' s = INDEX(haystack,needle) /* s receives '5' */
Use the LASTPOS( ) function to return the rightmost (last) position of a short string found within a longer string.
needle = 'cbs' haystack = 'abc cbs fox cbs' s = LASTPOS(haystack,needle) /* s receives '13' */
Use the LEFT( ) function to return the "n" leftmost characters of a string.
longs = 'abc cbs fox cbs' s = LEFT(longs,3) /* s receives 'abc' */ short = 'abc' s = LEFT(short,4) /* s receives 'abc ' */
Use the LENGTH( ) function to return the length of a string.
longs = 'abc cbs fox cbs' s = LENGTH(longs) /* s receives '15' */
Use the POS( ) function to return the location of a short string in a longer string.
needle = 'cbs' haystack = 'abc cbs fox cbs' p = POS(needle,haystack) /* p receives '5' */
Use the REVERSE( ) function to reverse a string, character for character.
s = 'cbs' r = REVERSE(s) /* r receives 'sbc' */
Use the RIGHT( ) function to return the "n" rightmost characters of a string.
longs = 'abc cbs fox cbs' s = RIGHT(longs,3) /* s receives 'cbs' */ short = 'cbs' s = RIGHT(short,4) /* s receives ' cbs' */
Use the SPACE( ) function to manipulate the number of blanks (or other characters) between words in a string.
before = 'a b c' s = SPACE(before,2) /* s receives 'a b c' */ before = 'a b c' s = SPACE(before,2,'*') /* s receives 'a**b**c' */
Use the STRIP( ) function to remove leading and/or trailing blanks from a string.
before = ' Mickey! ' s = STRIP(before) /* s receives 'Mickey!' */
Use the SUBSTR( ) function to select a piece of a longer string.
before = 'abcdefghi' s = SUBSTR(before,1,3) /* s receives 'abc' */ s = SUBSTR(before,4,3) /* s receives 'def' */ s = SUBSTR(before,6) /* s receives 'fghi' */
Use the TRANSLATE( ) function to translate one string to another string according to a template.
before = 'abc' s = TRANSLATE(before) /* s receives 'ABC' */ before = 'abccbafff' s = TRANSLATE('abccbafff','*+','bf') /* s receives 'a*cc*a+++' */
Use the WORD( ) function to return the "nth" word of a longer string.
longs = 'When in the course of human events' w = WORD(longs,6) /* w receives 'human' */
Use the WORDPOS( ) function to return the word position of a word or phrase in a longer string.
longs = 'When in the course of human events' w = WORDPOS('human',longs) /* w receives '6' */
Use the WORDS( ) function to return the number of words in a string.
longs = 'When in the course of human events' w = WORDS(longs) /* w receives '7' */
For more details about these and many other REXX functions, consult the CMS Help files or other online documentation.