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.
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:
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 */
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 */
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
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" */
Numbers contain only numerics (0-9) and special characters (+-.eE).
123
-24.36
85.090
75e-24 /* exponential notation */
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
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'
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.
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'
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?
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,...)
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
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 */
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
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
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
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.
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.
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
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"
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.
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.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*/
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.
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
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
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.
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].
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
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
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.
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.
The REXX instructions QUEUE, PUSH, and PARSE PULL are used to read from and to the stack.
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.
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"
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"
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).
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.
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.
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.
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
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.
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'
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'