7.3. REXX

7.3. REXX

7.3.1. INTRODUCTION

REXX (Restructured Extended Executor Language) is an IBM language developed on all SAA Platforms[1]. REXX is a highly structured programming language, allowing you to use functions, procedures, loops, and a number of built in functions. REXX interfaces with CMS very well, providing CMS file I/O, as well as providing the ability to execute any CMS or CP command, any system program, or any other REXX program, and process the results.

No restrictions are imposed on language format. You can structure your program as you wish, spanning multiple lines, or having the program all on one line (though this is not recommended, and is bad programming style).

There is no limit to the length of the values of variables, as long as they fit into the storage your userid has. Variable names (called symbols) are limited to a length of 250 characters. Compound symbols can be used as arrays.

REXX is an interpreted language. An interpreted language is not compiled. Rather, each instruction is executed step by step. This gives you the flexibility of debugging REXX programs interactively. A REXX compiler is available, but is not currently installed on MAX.

7.3.2. STRUCTURE AND GENERAL SYNTAX

REXX programs can have any filename, but normally have a filetype of either EXEC (Executable Program) or a filetype of XEDIT (XEDIT Editor Macros).

REXX programs must start with a comment (/*.......*/) in the first line of the file. DO NOT FORGET TO DO THIS! You must place a comment in the first line to distinguish a REXX program from a CMS EXEC or CMS EXEC2 program. CMS EXEC and CMS EXEC2 are two other older languages that can have the same filetype as REXX programs, so you need to have some way of differentiating between the different types of programs.

Programs in REXX are composed of several different types of information (called tokens). The classes of tokens are:

7.3.2.1. COMMENTS

A comment is a sequence of characters inside (delimited by) /* and */. You can nest comments by placing one set of comments inside another group of comments. You must remember to end all comments (even ones inside other comments). If you do not complete a comment, then the REXX interpreter will assume that the rest of the file is a comment.

/* this is a comment */

7.3.2.2. LITERAL STRINGS

A sequence of characters in between a set of double quotes (") or single quotes (') is a literal string. Use two consecutive double quotes ("") to represent a " in a string delimited (surrounded) with double quotes. Use two consecutive single quotes ('') to represent a ' in a string delimited with single quotes. A literal string is considered constant and its contents are not modified. A literal string with no characters (of length 0) is called a null string. The maximum length of a string is 250 characters.

'ILoveMAX'

"Hugga Mugga MAX!!!"

"If it's not in the computer, it doesn't exist."

'A failure won''t appear until a program passes final bug testing' /* You get: won't */

7.3.2.3. HEXADECIMAL STRINGS

Any sequence of numbers (0-9) and hexadecimal letters (A-F, a-f), optionally separated by blanks if you want several numbers. A "X" must follow the string:

"ABCD"X

'1d e6 FF'x

7.3.2.4. VARIABLE NAMES

Variable names can be alphanumeric (a-z, A-Z, 0-9) and can include some special characters (@#$.!?_). The first character must be alphabetic. All lowercase characters are translated to uppercase. The maximum length is 250 characters. You do not need to declare the type of a variable. However, any variable used IS AUTOMATICALLY ASSUMED to be a string until you use the variable as a number:

Hello /* now HELLO = "HELLO" */

hi_there = "XXx" /* now HI_THERE = "XXx" */

counter = 12 /* now COUNTER = 12 */

Arrays are implemented using stem notation:

number.i /* a one dimensional variable */

variable.x.y /* a two dimensional variable */

The power of stems lies in the fact that each stem (.i, .x, or .y in the preceding examples) does not have to be a number. You can also mix strings and numbers with different types of stems. These features lead to a structure-like ability:

i = 1 /* declare i as a number*/

address.i.name = 'Maxwell' /* this is a string */

address.i.phone = 5865550 /* this is a number */

You can declare an entire stem as a certain value if you wish:

address. = "" /* all address. variables are empty strings */

counters. = 12 /* all counters. variables are set to the number 12 */

counters.6 = "Hello" /* except for counters.6 which has been set back to

a string containing "Hello" */

7.3.2.5. NUMBERS

Numbers contain only numerics (0-9) and special characters (+-.eE).

123

-24.36

85.090

75e-24 /* exponential notation */

7.3.2.6. OPERATORS AND PRECEDENCE

The following operators are allowed:

String Concatenation (combine two strings to form one string):

x(space)                                Concatenate with one space in between   
||                                      Concatenate without a space in between  
(abuttal)                               Concatenate without a space in between  

Concatenation without a blank should use the || operator, but it is useful to know that if a string and symbol are abutted (placed together without a space between them), they will be concatenated.

Arithmetic:

+                                       Addition                                
-                                       Subtraction                             
*                                       Multiplication                          
/                                       Division                                
%                                       Divide and return integer portion of    
                                        result                                  
//                                      Divide and return remainder (not        
                                        modulo)                                 
**                                      Raise a number to a whole power         
                                        (Exponentiation)                        
Prefix -                                Negate a value, same as 0-number        
Prefix +                                Same as 0+number                        

Comparison:

==                                      True when terms are strictly equal      
=                                       True when terms are equal               
                                        (numerically or when padded)            
\==, /==, ^==                           True when terms are NOT strictly        
                                        equal (inverse of ==)                   
\=, /=, ^=                              Not equal (inverse of =)                
>                                       Greater than                            
<                                       Less than                               
>>                                      Strictly greater than                   
<<                                      Strictly less than                      
<>                                      Greater than or less than (same as      
                                        not equal)                              
>=                                      Greater than or equal to                
<=                                      Less than or equal to                   

Boolean Logic:

&                                       Logical AND                             
|                                       Inclusive (Logical) OR                  
&&                                      Exclusive OR                            
Prefix ^, Prefix \                      Logical NOT                             

Operator Precedence:

Expression is evaluated from left to right, and expressions in parenthesis are evaluated first. Order of precedence is (highest at the top):

\ ^ - +                                 Prefix operators                        
**                                      Exponentiation                          
* / % //                                Multiply and Divide                     
+ -                                     Add and Subtract                        
x(space) || (abuttal)                   Concatenation with/without blank        
= < > == << >> \= ^= <> \== ^== >=      Comparison Operators                    
>>= <= <<= /= /==                                                               
&                                       And                                     
| &&                                    Or, Exclusive Or                        

7.3.3. INSTRUCTIONS

REXX is similar to the C language in that it has a small number of instructions, but a large number of functions (and users can write their own functions). A description of the most important instructions are described on the following pages. Instructions are not case-sensitive: they can be specified all in uppercase, all in lowercase, or any combination thereof.

Indentation is not necessary for any instruction, but you should practice good programming style at all times and indent where necessary.

All instructions are terminated (ended with) a semicolon (;). If you do not specify a semicolon, one is automatically placed there for you. The only time you MUST insert a semicolon is when you place more than one command on one line. In that case, each command must be separated from the other commands by a semicolon. For example:

SAY "Hi"; x = 12; SAY "x =" x

Do not place more than one command on a line unless you have to. This is bad programming style.

You may break a command over several lines by using a comma (,) at the end of every line, except the last line. Do not break strings over several lines, but you may use the concatenation characters to break up long strings. For example, instead of:

SAY 'Supercalafragalisticexpialadocious'

You can:

SAY 'Supercala'||,

'fragalistic'||,

'expialadocious'

7.3.3.1. SAY

The SAY instruction prints an expression out on the screen.

string = "or within budget."

say "Nothing ever gets built on time" string

produces:

Nothing ever gets built on time or within budget.

7.3.3.2. ADDRESS

The ADDRESS instruction directs commands to a particular environment. It is most often used to execute CMS commands (if the REXX program was run from CMS) or XEDIT commands (if the REXX program was run from XEDIT). You can permanently redirect commands (that is, while this program is running), or only redirect commands for one command.

ADDRESS COMMAND /* Redirects all following commands to CMS */

Any return codes that a command normally returns to CMS can be directed to the REXX program for processing. The following STATE command returns a 0 (zero) if the specified file exists. The return code is automatically stored in the variable "rc".

/* This program deletes TEMP FILE A if it exists */

ADDRESS COMMAND "STATE TEMP FILE A"

if rc = 0 then 'ERASE TEMP FILE A'

7.3.3.3. ARG

The ARG instruction retrieves argument strings provided to the program upon execution. Use ARG in combination with PARSE, otherwise all strings are automatically translated to uppercase.

Given the program TEST EXEC A:

/* A Rexx EXEC! */

ADDRESS COMMAND

parse arg var1 var2 var3

say "arg1 =" var1 "arg2 =" var2 "arg3 =" var3

exit

and running the program in CMS:

TEST Hello There How are you? [RETURN]

You would see:

arg1 = Hello arg2 = There arg3 = How are you?

7.3.3.4. CALL

The CALL instruction is used to invoke other routines.

/* Recursive subroutine example */

/* From IBM CMS REXX Manual, SC24-5239-03, Page 33 */

arg x

call factorial x

say x"! = "result

exit

factorial: procedure

arg n /* Recursive Invocation */

if n = 0 then return 1

call factorial n-1

return result * n

You can optionally return a result upon completion using the syntax:

return = function_name(argument1,argument2,...)

7.3.3.5. DO, WHILE, UNTIL, FOREVER, FOR

The DO instruction allows you to use loops in different forms. The standard DO loop allows you to execute multiple instructions in a group. This is most often used with an if-then-else to execute multiple statements. The general form is:

DO

instruction1

instruction2

...

END

An example would be:

IF x = 12 THEN

DO

SAY "Yup, X does equal twelve. Now changing it to 54."

x = 54

END

Instructions can be repeated for a fixed number of times, modifying a variable in the process with the "to-for" extension. The general form is:

DO name=expression BY expression TO expression FOR expression

instruction1

instruction2

...

END

where the BY, TO, and FOR commands can be intermixed. BY specifies an increment (default increment is 1 if BY is not used), TO specifies an upper limit, and FOR specifies an absolute number of times to repeat a loop.

To increment x from 1 to 20 you could use:

DO x = 1 to 20

SAY x

END

To jump by 2 you could use:

DO x = 1 BY 2 TO 20

SAY x

END

To repeat the loop only 4 times you could use:

DO x = 1 BY 2 TO 20 FOR 4

SAY x

END

A loop can be repeated forever using the FOREVER construct. Use the LEAVE instruction to exit such a loop.

DO FOREVER

x = x + 1

say x

END

The familiar WHILE and UNTIL constructs are also available. WHILE performs the instructions in the loop while the expression is true (evaluated at the top of the loop). UNTIL performs the instructions in the loop until the expression is false (evaluated at the bottom of the loop).

x = 1

DO UNTIL x > 6

x = x + 1

SAY x

END

You can also combine any of the above constructs for the desired effect:

DO x = 1 BY 1 UNTIL x > 6

SAY x

END

7.3.3.6. EXIT

The EXIT instruction is used to leave a program. You can return a value if you wish:

g = 24

exit g * 2

/* This would return 48 to the calling program */

7.3.3.7. IF-THEN-ELSE

The if-then-else expression allows conditional execution of an instruction (or a group of instructions if you use a do-end).

if answer = "YES" & exitflag = 12 then /* & is and */

say "I'm Done!"

else do

exitflag = 12

say "Setting exitflag to 12"

end

7.3.3.8. LEAVE

The LEAVE instruction allows you to exit any loop. It is best used with the do-forever loop. Do not substitute this instruction for loops using good programming practice. Use LEAVE only when another do-end construct is not appropriate. The following program is an example of what NOT to do:

x = 12

DO FOREVER

x = x + 2

if x = 24 then leave

say x

END

Instead, you should use:

do x = 12 by 2 to 24

say x

end

7.3.3.9. PARSE PULL

The PARSE instruction separates data and places them in specified variables. There are several different forms of PARSE, but the PULL instruction will be the only instruction shown (see the ARG[2] instruction to find out how to pull arguments from the command line). The PARSE PULL instruction will input from the current input stack. If the stack is empty, then the instruction will input from the keyboard. See the STACK section for information on how to use stacks. You can optionally use PARSE UPPER PULL to translate the variables to uppercase before separating them into variables.

/* This program is called TEST1 EXEC A */

Say "Please input a word and two numbers"

parse pull string num1 num2

Say "You typed" string "and" num1","num2

exit

TEST1 [RETURN]

Please input a word and two numbers

Hawaii 5 0 [RETURN]

You typed Hawaii and 5,0

/* This program is called TEST2 EXEC A */

Say "Please input a word and two numbers"

parse upper pull string num1 num2

Say "You typed" string "and" num1","num2

exit

TEST2 [RETURN]

Please input a word and two numbers

Hawaii 5 0 [RETURN]

You typed HAWAII and 5,0

You can optionally use the null variable (.) to delete unwanted information. Any data parsed into the null variable is ignored. If you place a null variable at the end of a parse then all extra information is ignored as well.

/* This program is called TEST3 EXEC A */

Say "Tell me something important"

parse upper pull str1 . str2 .

Say "You typed" str1 "and" str2

exit

TEST3 [RETURN]

Tell me something important

New systems generate new problems. [RETURN]

You typed New and generate

You can also parse in between known strings and numbers

/* This program is called TEST4 EXEC A */

Say "Tell me something important"

parse upper pull str1 "generate" str2 .

Say "You typed" str1 "and" str2

exit

TEST4 [RETURN]

Tell me something important

New systems generate new problems. [RETURN]

You typed New systems and new

7.3.3.10. PROCEDURE

The PROCEDURE instruction allows you to declare subroutines or functions. You can return information using RETURN, or you can allow passed variables to be changed using the EXPOSE modifier:

/* This program is called TEST EXEC A */

j = 1; x = 12; string = "Hello"

say "Original variables are: j=" j "x=" x "string=" string"."

call changeit

say "Changed variables are: j=" j "x=" x "string=" string"."

exit

changeit: procedure expose j string

j = 2; x = 6; string = "Goodbye"

return

TEST [RETURN]

Original variables are: j= 1 x= 12 string= Hello.

Changed variables are: j= 2 x= 12 string= Goodbye.

7.3.3.11. PUSH AND QUEUE

The PUSH and QUEUE instructions are used to place information on the stack. PUSH places information on the stack in LIFO (Last In First Out) order, while QUEUE places information on the stack in FIFO (First In First Out) order. See the section on stacks to find out how to use these instructions.

7.3.3.12. SELECT-WHEN-THEN-OTHERWISE-END

The SELECT instruction allows multiple expression evaluation and execution. Each WHEN instruction is evaluated. If the expression is true, then those instructions are executed, and the loop is exited (NO OTHER WHEN EXPRESSIONS ARE EVALUATED). If all WHEN expressions are false, then the instructions in the OTHERWISE construct are executed.

SELECT

WHEN expression THEN instruction

WHEN expression THEN instruction

...

OTHERWISE instruction

END

An example using the DO construct follows:

SELECT

WHEN x = 12 & y = 34 THEN

SAY "X and Y are set wrong"

WHEN x > y THEN DO

SAY "We have an error condition"

x = y - 1

END

OTHERWISE

SAY "Everything's fine"

END

7.3.3.13. SIGNAL ON ERROR

The SIGNAL instruction is used to unconditionally branch to a subroutine if a special occurrence (called a signal) happens. In the case of SIGNAL ON ERROR, REXX will unconditionally branch to a routine called error if a return code (rc) other than zero occurs (see the ADDRESS instruction for a description of how a return code works). You can turn off the signal with SIGNAL OFF ERROR. If the error signal is turned off, then any further nonzero return codes WILL NOT branch to the error subroutine.

Note: If you take a look at the following code, you will notice that SIGNAL ON ERROR is simply a form of GOTO. Gotos are, of course, bad programming style and their use is not recommended. Since you can directly check the return code value (using the rc variable) you should not use SIGNAL ON ERROR, unless necessary. SIGNAL ON ERROR is useful for debugging.

/* Program will signal on error if a file does not exist */

SIGNAL ON ERROR

/* Check to see if the file exists with CMS STATE command */

ADDRESS COMMAND "STATE TEMP FILE A"

SAY "File Exists"

EXIT

ERROR:

SAY "The file does not exist"

7.3.3.14. TRACE

The TRACE instruction allows you to interactively trace your program. You can place the trace instruction anywhere in your program, or alternately have several different types of traces in different portions of the program. There are several different types of tracing, but only a few of the more useful forms are discussed.

trace off /* Turn Tracing off (this is the default) */

trace o /* Turn Tracing off (this is the default) */

trace results /* Show results only (the most useful trace)*/

trace r /* Show results only (the most useful trace)*/

trace all /* Show everything */

trace a /* Show everything */

trace intermediate /* Show intermediate results while processing*/

trace i /* Show intermediate results while processing*/

Insert these commands in your REXX program wherever you require tracing. To start tracing, simply run the program. If you want to stop a program that is being traced, use the CMS command HX (Halt eXecution) to stop the REXX program. You may need to clear a few screens of text before the program finally stops, since Tracing information is buffered until you see it all.

7.3.4. FUNCTIONS

REXX has many built in functions, especially for string handling. Functions may return a value, and you may pass parameters to them (or leave the parameters blank). You may also create your own functions as necessary.

7.3.4.1. SYSTEM BUILT-IN FUNCTIONS

The following list of functions is not complete. Only selected functions (and options to those functions) are listed. Type HELP REXX MENU [RETURN] to see a list of all available functions and their options. Functions are case insensitive, they may be typed in uppercase or lowercase, or any combination thereof.

ABS(number)

Returns the absolute value of a number

abs('24.6') returns 24.6

abs('-8.3') returns 8.3

ADDRESS()

Returns the current operating environment

address() may return 'CMS' if you ran this program from CMS

address() may return 'XEDIT' if you ran this program from XEDIT

BITAND(string1, string2)

Logically ANDs two strings together

bitand('73'x,'27'x) returns '23'x

BITOR(string1,string2)

Logically ORs two strings together

bitor('15'x,'24'x) returns '35'x

BITXOR(string1,string2)

Logically eXclusive ORs two strings together

bitxor('12'x,'22'x) returns '30'x

CENTER(string,length,pad)

Returns a string of specified length, with string centered in it. The default pad character is blank, but a different character can be substituted if desired. If length is smaller than string, then string is truncated on both ends to fit.

center("Max",7) returns " Max "

center("Max",7,"o") returns "ooMaxoo"

center("Death to Apple",6) returns "h to A"

COPIES(string,number)

Returns a string containing the specified number of copies.

copies("Max",4) returns "MaxMaxMaxMax"

C2D(string)

Character to decimal number conversion. This also allows character to EBCDIC code conversion.

c2d('07'x) returns 7

c2d('FF81'x) returns 65409

c2d('b') returns 130 /* EBCDIC Code */

C2X(string)

Character to hexadecimal number conversion. This also allows character to EBCDIC code conversion.

c2x('Max') returns 'D481A7' /* EBCDIC Code */

DATE(option)

Returns the current system date. In the following examples dd = current day, mmm = abbreviation for current month (word), mm = abbreviation for current month (numbers), yyyy = current year, month = current month (for example: "March"), weekday = current day (for example: "Tuesday").

date() returns dd mmm yyyy

date(e) returns dd/mm/yy "European"

date(j) returns yyddd "Julian"

date(m) returns month "Month"

date(o) returns yy/mm/dd "Ordered"

date(s) returns yyyymmdd "Sorted"

date(u) returns mm/dd/yy "USA"

date(w) returns weekday "Weekday"

DELSTR(string,n,length)

Deletes the substring of string that begins at the nth character and is of specified length. If no length is specified, then the rest of the string is deleted.

delstr("All's well that ends",4) returns "All"

delstr("HuggaMugga",3,2) returns "HuaMugga"

D2C(wholenumber)

Decimal to character conversion. The opposite of C2D.

d2c(148) returns "m"

D2X(wholenumber)

Decimal to hexadecimal conversion. The opposite of X2D.

d2x(148) returns 94

FIND(string,phrase)

Returns the word location of phrase in string (words are space delimited). Returns 0 if phrase is not found.

find("Dante's Inferno","Inferno") returns 2

find("Dante's Inferno","Paradiso") returns 0

FORMAT(number,before,after)

Rounds and formats a number. Before specifies the number of blanks to pad before a number up to the decimal place. After specifies the number of zeros to pad after the last digit in the number. If you specify "after" as zero, the number is rounded to the nearest integer.

format("4",5) returns " 4"

format("4.62",5,0) returns " 5"

format("4.62",5,6) returns " 4.620000"

INDEX(haystack,needle,start)

Returns the location of needle in haystack, optionally starting at character position start. Returns a 0 if needle is not found.

index("The Hunting of The Snark","The") returns 1

index("The Hunting of The Snark","The",4) returns 16

index("The Hunting of The Snark","Boojum") returns 0

LEFT(string,length,pad)

Returns a string of specified length, consisting of the input string and padded characters (defaults to spaces). If length is smaller than the length of the input string, then the input string is truncated.

left("Lewis Carroll",20,"x") returns "Lewis Carrollxxxxxxx"

left("Lewis Carroll",5) returns "Lewis"

LENGTH(string)

Returns the number of characters in the specified string.

length("Supercalafragalisticexpialadocious") returns 34

MAX(number,number,...)

Returns the maximum number in a list of numbers

max(1,2,3,4,5) returns 5

max(-1,-2,-3,-4,-5) returns -1

MIN(number,number,...)

Returns the minimum number in a list of numbers

max(1,2,3,4,5) returns 1

max(-1,-2,-3,-4,-5) returns -5

QUEUED()

Returns the number of lines in the queue. See the section on stacks to find out how to use QUEUED()

queued() may return 5

RANDOM(min,max)

Returns a pseudorandom nonnegative whole number. If not specified, min is assumed 0 and max is assumed 999. The range max-min must not exceed 100000.

random() may return 401

random(24) may return 78

random(7,7) will always return 7

random(,20) may return 19

REVERSE(string)

Returns the "mirror image" (swapped) string.

reverse("Rexx") returns "xxeR"

RIGHT(string,length,pad)

Returns a string of specified length, consisting of padded characters (defaults to spaces) and the input string. If length is smaller than the length of the input string, then the input string is rightwise truncated.

right("Lewis Carroll",20,"x") returns "xxxxxxLewis Carroll"

right("Lewis Carroll",5) returns "rroll"

STRIP(string,option,char)

Removes leading and/or trailing characters from string. If the character is not specified it is assumed to be a space. If the option 'L' is specified, then only leading characters are removed. If the option 'T' is specified then only trailing characters. If the option 'B' (Both), or no option, is specified, then both leading and trailing characters are removed.

strip(" Max ") returns "Max"

strip(" Max ","L") returns "Max "

strip(" Max ","T") returns " Max"

strip("xxxMaxxxx","B","x") returns "Ma"

SUBSTR(string,n,length,pad)

Returns the substring of the specified string, starting at n, for the specified length (optional), and optionally padded with pad. If no length is specified, then the length is to the end of the string. If no pad character is specified then the pad character is assumed to be a space.

substr("Boojum",1,3) returns "Boo"

substr("Boojum",4,10,"z") returns "jumzzzzzzz"

TIME(option)

Returns the current system time. In the following examples hh = current hour, mm = current minute, ss = current seconds, and xx = am or pm.

time() returns hh:mm:ss in 24 hour format

time(c) returns hh:mmxx "Civil"

time(h) returns hh since midnight "Hours"

time(m) returns mmmm since midnight "Minutes"

time(s) returns sssss since midnight "Seconds"

TRANSLATE(string)

Returns the string translated to uppercase.

translate("Oohh I'm changing!") returns "OOHH I'M CHANGING!"

USERID()

Returns the current userid.

userid() may return JONES

X2C(hexstring)

Returns the character equivalent of a hexadecimal number. Can be used to return the EBCDIC equivalent of a string of hex numbers.

x2c('D481A7') returns "Max" /* EBCDIC */

X2D(wholenumber)

Hexadecimal to decimal conversion. The opposite of D2X.

x2d('94'x) returns 148

7.3.4.2. USER DEFINED FUNCTIONS

You can create your own functions using the PROCEDURE instruction. The following user defined example calculates the factorial (n!) of a number. If you have a user defined function that is the same as a System Built-In function, then the user defined function is executed.

/* Recursive subroutine example */

/* From IBM CMS REXX Manual, SC24-5239-03, Page 72 */

arg x

say x'! =' factorial(x)

exit

factorial: procedure

arg n

if n = 0 then return 1

return factorial(n-1) * n

7.3.5. CMS AND CP COMMANDS

To execute any CMS or CP command, simply place the command in quotes.

'QUERY NAMES'

Use ADDRESS COMMAND to direct commands directly to CMS (see the ADDRESS instruction). Using ADDRESS processes CMS commands faster.

You can use the return code variable (rc) to check program status when using CMS commands. You can also construct commands "on the fly" using strings. For example, assume we want to check for the existence of a file called TEMP EXEC A. We could use:

/* A REXX Example */

ADDRESS COMMAND "STATE TEMP EXEC A"

if rc <> 0 then

say "File TEMP EXEC A does not exist"

exit

Instead, we could use strings so we can check the existence of any file:

/* Another REXX Example */

say "Please enter a filename filetype and filemode"

parse upper pull filename filetype filemode .

ADDRESS COMMAND "STATE" filename filetype filemode

if rc <> 0 then

say "File" filename filetype filemode "does not exist"

exit

You can process the results from CP (using EXECIO) and some CMS commands through a REXX program by passing the results onto a stack. See the section on Stacks for further information.

7.3.6. FILE INPUT/OUTPUT

Data can be read from and written to any file in CMS using the EXECIO command. Care must be taken, however, since writing to a file will overwrite the contents of that file, unless you specifically add lines to the end of the file. The EXECIO command does much more than just simple file input and output. You can punch files, send messages, and so on. Help on the EXECIO command can be accessed on the system by typing

HELP EXECIO [RETURN].

7.3.6.1. READING FROM A FILE

The format for reading from a file (DISKR, for DISK Read) is:

'EXECIO lines DISKR filename filetype [filemode] [linenum]'

where:

lines Is the number of lines to read from the file. If you want to read all lines, use an asterisk (*).

filename Is the filename of the file in CMS.

filetype Is the filetype of the file in CMS.

filemode Is the filemode of the file in CMS. If you do not specify a filemode then "A" is assumed.

linenum Is the starting line number in the file to read FROM. If you do not specify a line number then:

- the first time the line number starts at 1 (the beginning of the file)

- every other read starts from where you left off from your last read, except when the last read used FINIS. If you use FINIS then the next disk read (without specifying a linenum) will start from the first line of the file (line 1).

All lines read are pushed onto the stack until you pull them off the stack. See the section on stacks for a description of how stacks work in CMS (you don't need to know how they work to use them).

Once the lines are pushed on the stack you can read pull them off the stack using the PARSE PULL command. The QUEUED() function returns the number of lines on the stack.

The following example loads an entire file onto the stack (lines = *, linenum = 1) and prints out the lines.

/* A Rexx Example to show Disk Reads! */

/* Get the input file name */

say "Please enter the filename, filetype, and filemode"

parse upper pull filename filetype filemode .

/* Read the entire file */

'EXECIO * DISKR' filename filetype filemode '1'

'FINIS' filename filetype filemode

DO QUEUED() /* Keep reading until the stack is empty */

parse pull stringline /* Get a line from the stack */

say stringline /* Print the line */

END

EXIT

7.3.6.2. WRITING TO A FILE

The format for writing to a file (DISKW, for DISK Write) is:

'EXECIO lines DISKW filename filetype [filemode] [linenum] (STRING wline'

where:

lines Is the number of lines to write to the file. You can write the same line multiple times with this setting.

filename Is the filename of the file in CMS.

filetype Is the filetype of the file in CMS.

filemode Is the filemode of the file in CMS. If you do not specify a filemode then "A" is assumed.

linenum Is the starting line number in the file to write TO. If you do not specify a line number then each write is appended to the end of the file.

wline Is the string containing the data to be written to the file.

If you write to a line that already exists, then the old line is destroyed and replaced with the new line.

The following example inputs data from the user and stores it in a file.

/* A Rexx Example to show Disk Writes! */

/* Get the input file name */

say "Please enter the filename, filetype, and filemode"

parse upper pull filename filetype filemode .

linenum = 1 /* Set the current line number */

input = "" /* Set startup user input */

DO while input <> "DONE"

say "Please enter line" linenum". Type DONE to stop."

parse pull input

if input <> "DONE" then DO

/* Write to the disk. Note the comma (continuation)

to break the command up over multiple lines */

'EXECIO 1 DISKW' filename filetype filemode linenum,

'(STRING' input

linenum = linenum + 1

END

END

'FINIS' filename filetype filemode

EXIT

7.3.6.3. CLOSING A FILE

Files must be closed after you are finished reading or writing them. Close the files with the FINIS command. The format for closing a file is:

'FINIS filename filetype [filemode]'

where:

filename Is the filename of the file in CMS.

filetype Is the filetype of the file in CMS.

filemode Is the filemode of the file in CMS. If you do not specify a filemode then "A" is assumed.

You can optionally use the FINIS option on the EXECIO DISKW and DISKR, but this is (computer resource) expensive, and is not recommended.

7.3.7. STACKS AND STACK PROCESSING

CMS makes extensive use of stacks for every type of command processing. Since stacks are used universally throughout all CMS applications, you can integrate many pre-existing CMS programs and packages into your programs. Stack processing is one of the ways to read responses from CP and CMS commands (other methods, such as DIAG(08,...), will not be covered). The following sections will discuss stack processing as it pertains to CP and CMS command processing, parameter passing through programs, and executing other CMS programs inside REXX programs.

7.3.7.1. QUEUE, PUSH, AND PARSE PULL

The REXX instructions QUEUE, PUSH, and PARSE PULL are used to read from and to the stack.

7.3.7.1.1. PARSE PULL

The PARSE PULL instruction is used to read information from the stack. If there is no information on the stack, then the program waits for user input and you see a VMREAD in the bottom right hand corner of the screen. The format for PARSE PULL from the stack is exactly the same as for user input. See the preceding information on PARSE PULL in the INSTRUCTIONS section.

7.3.7.1.2. PUSH

The PUSH instruction places information onto the stack on a LIFO (Last In First Out) basis. You can pull the information off the stack using PARSE PULL.

PUSH "The attention span of a computer is only"

PUSH "as long as its electrical cord"

7.3.7.1.3. QUEUE

The QUEUE instruction places information on the stack on a FIFO (First In First Out) basis. You can pull the information off the stack using PARSE PULL. Use QUEUE when interfacing with other CMS programs (since they operate FIFO).

QUEUE "The first myth of management is that it exists"

7.3.7.2. A STACK EXAMPLE

The following program demonstrates how you can place a command on the stack. That command will not be executed until the REXX program has completed. We will use the CP QUERY TIME command for demonstration. The program is:

/* A REXX Example! */

say "Program Starting"

QUEUE "QUERY TIME"

say "Program is Done"

exit

The output from running this program is:

Program Starting

Program is Done

Ready;

TIME IS 10:07:55 CST WEDNESDAY 06/17/92

CONNECT= 00:25:12 VIRTCPU= 000:02.35 TOTCPU = 000:02.92

Note that the time command is not executed until AFTER the program has completed (after the Ready; prompt).

7.3.7.3. CMS COMMANDS

Most CMS commands have an option to place their output on the stack instead of writing to the screen. To place output on the stack, simply place a (STACK option after the CMS command (note that options do not need a closing parenthesis). For example: To query the A disk you would write:

ADDRESS COMMAND "QUERY DISK A (STACK"

The information is placed on the stack on a FIFO (First In First Out) basis, but you can change that by saying (STACK LIFO instead of (STACK.

7.3.7.4. CP COMMANDS

The results from CP commands cannot be placed on the stack using the (STACK option. The (STACK option is only a characteristic of CMS. Instead, you must use the EXECIO CP command. The format is similar to an EXECIO DISKW (disk write):

'EXECIO * CP (STRING wline'

where:

wline Is the string containing the CP command.

For example, to query the system time (the CP QUERY TIME command) you would write:

'EXECIO * CP (STRING QUERY TIME'

The information is placed on the stack on a FIFO (First In First Out) basis, but you can change that with the LIFO (Last In First Out) option:

'EXECIO * CP (LIFO STRING QUERY TIME'

Some CP commands will not produce a response if CP SET IMSG is set off. You may want to set it on to ensure that you will receive information from all CP commands.

7.3.7.5. ACCESSING OTHER CMS PROGRAMS: XEDIT

To show the true power of stacks, let's interface REXX programs with the power of XEDIT. Using stacks, you can execute any XEDIT command line command in your REXX program. (You can also use ADDRESS XEDIT, but we won't cover that here.)

Let's look at how you would edit a file and insert a line if you were typing everything manually:

First, you would want to edit the file with the XEDIT command. If we call the file TEST TEST A you would type:

XEDIT TEST TEST A [RETURN]

You want to insert a line at the top of the file, so you move the cursor to the top of file marker. If you had to type an XEDIT command line command, you would type:

TOP [RETURN]

Now you want to insert a line of text. You move the cursor to the line you want, type "i" to start insert mode, and enter your information:

This is a test [RETURN]

After you are done entering information, you would save the file and exit the editor by typing:

FILE [RETURN]

This entire process can be automated in a REXX program, with slight modifications. To call the editor:

1) Determine all the commands you need to perform. These can be ONLY XEDIT command line commands. Place the commands on the stack in the order that they should be executed. Use the QUEUE instruction since XEDIT processes input on a FIFO basis.

2) Make sure you exit the editor somehow, otherwise you will see the editor screen when you run your REXX program. Use either FFILE or QQUIT to exit the editor Only these XEDIT commands ensure that the editor exits. You may use FILE instead of FFILE if you are editing a file that does not previously exist. Otherwise, use at your own caution!

3) Call the editor with the file you want to change. Use the (NOPROFILE option when editing this way. The NOPROFILE option ensures that PROFILE XEDIT is ignored when you edit files.

A program implementing changes to the preceding file could be written in the following REXX program:

/* A REXX Example */

QUEUE "TOP"

QUEUE "I This is a test"

QUEUE "FILE"

"XEDIT TEST TEST A (NOPROFILE"

EXIT

Using XEDIT allows you to use all XEDIT functions instead of creating them yourself. Some useful XEDIT functions that you may find useful are: selective sorting, global or selective search and replace, and combining or separating files.

7.3.7.6. PROGRAMMING EXAMPLE: A DIRECTORY PROCESSOR

This program example performs a listfile, displays each file, and accumulates a running total of the number of files and the total block size of the files.

/* A REXX Programming Example */

/* This program performs a listfile, processes each file, prints

each file, and accumulates the total block size. The total

block size and total number of files is then printed out at

the end of the program. */

blocksum = 0 /* Initialize the block count */

numfiles = 0 /* Initialize the file count */

/* The following command lists files on the A disk only. The

"ALL" option means show all file information. The "NOHEADER"

option means do not show a title header when you are listing

the files. The "STACK" option means save the contents to the

stack. */

'LISTFILE * * A (ALL NOHEADER STACK'

DO QUEUED() /* Do until the stack is empty */

PARSE UPPER PULL filename filetype filemode . . . fileblock .

blocksum = blocksum + fileblock

numfiles = numfiles + 1

SAY filename filetype filemode "is" fileblock "blocks in size."

END

SAY "Total number of files =" numfiles "files."

SAY "Total file size =" blocksum "blocks."

EXIT

7.3.7.7. CREATING YOUR OWN STACKS

You can create your own stacks (buffers) using the CMS MAKEBUF command and you can delete the buffers with the CMS DROPBUF command. These commands are useful when you wish to read in a certain amount of information, and then abandon the rest (though they have other uses as well). You can create a stack, place information in it, read what you need, and then destroy the rest by dropping the stack.

7.3.7.7.1. MAKEBUF

The CMS MAKEBUF command will create a stack (buffer) within the CMS environment. All subsequent PUSHs, QUEUEs, and PULLs will reference that new stack.

The format is:

'MAKEBUF'

or

ADDRESS COMMAND 'MAKEBUF'

7.3.7.7.2. DROPBUF

The CMS DROPBUF command will erase a stack (buffer) within CMS that has previously been created with MAKEBUF. If you do not specify a stack number then the most recent stack is dropped.. All subsequent PUSHs, QUEUEs, and PULLs will reference the SECOND LAST stack that was created.

The format is:

'DROPBUF'

or

ADDRESS COMMAND 'DROPBUF'