REXX Workshop - Part 3
Parsing
The REXX PARSE instruction is used to divide strings into pieces and assign those pieces to variables. PARSE is a powerful instruction which can be used in a variety of circumstances. A few of the most common ways of using PARSE are highlighted below. See the CMS Help file for PARSE or other online documentation for a more thorough discussion of this instruction.
Parsing Strings
Strings may be parsed and substrings assigned to variables using the PARSE instruction. In the following example, the value of "line1" is parsed.
line1 = 'The merry wives of Windsor'
Parse Var line1 v1 v2 v3 v4 v5
/* v1 receives "The" */
/* v2 receives "merry" */
/* v3 receives "wives" */
/* v4 receives "of" */
/* v5 receives "Windsor" */
The words in the string "line1" are assigned, one by one, to the variables v1...v5. In this case, there are five words and five variables to receive those words. But observe the following case which uses only four variables:
line1 = 'The merry wives of Windsor'
Parse Var line1 v1 v2 v3 v4
/* v1 receives "The" */
/* v2 receives "merry" */
/* v3 receives "wives" */
/* v4 receives "of Windsor" */
One word is assigned to each of the variables v1, v2, and v3. Then the remainder of the string is assigned to variable v4. To avoid assigning everything that remains to variable v4, use a trailing period ( . ) as in the following example.
line1 = 'The merry wives of Windsor'
Parse Var line1 v1 v2 v3 v4 .
/* v1 receives "The" */
/* v2 receives "merry" */
/* v3 receives "wives" */
/* v4 receives "of" */
/* "Windsor" is discarded */
A period ( . ) can be used as a placeholder, and any word or words that would normally be assigned to a variable in that place are discarded. To parse out only the second and fourth words, for instance, the following statements could be used.
line1 = 'The merry wives of Windsor'
Parse Var line1 . v2 . v4 .
/* "The" is discarded */
/* v2 receives "merry" */
/* "wives" is discarded */
/* v4 receives "of" */
/* "Windsor" is discarded */
In these cases the value of the variable "line1" remains the same. Thus, the string can be parsed again and again in different statements.
But it may be of interest to remove one word from a string. Examine the following code snippet.
line1 = 'The merry wives of Windsor'
Parse Var line1 v1 line1
/* v1 receives "The" */
/* line1 receives "merry wives of Windsor" */
These lines, placed in a loop, would permit each word of the string to be extracted in turn.
It is also possible to parse strings by absolute column number. Observe the following snippet; arguments on the PARSE instruction include absolute column numbers which delimit the substrings assigned to the variables.
line1 = 'abcdefghijklmnopqrstuvwxyz'
Parse Var line1 1 v1 4 . 20 v2 22 . 26 v3
/* v1 receives "abc" */
/* v2 receives "tu" */
/* v3 receives "z"" */
Finally, it is possible to parse strings which contain specific characters. Observe the following example in which specific characters serve to delimit the substrings assigned to the variables.
line1 = '11:25:01'
Parse Var line1 hh ':' mm ':' ss
/* hh receives "11" */
/* mm receives "25" */
/* ss receives "01" */
Please see the online Help and other documentation for a more thorough treatement of the PARSE instruction.
Passing Arguments to Execs
Many execs are designed to accept one or more values when they are run. Values supplied to an exec are contained in a single string, which is passed as an argument to the exec. In the following example, the exec named "TOTAL" receives the argument string "15 22 17".
TOTAL 15 22 17
Code is required within "TOTAL EXEC" to retrieve these three values and to assign them to variables. The PARSE instruction with the "ARG" option can be used.
/* TOTAL EXEC */
Parse Arg nmen nwomen nchildren .
/* nmen receives "15" */
/* nwomen receives "22" */
/* nchildren receives "17" */
The ARG( ) built-in function can be used to determine if an argument has been supplied to an exec. ARG( ) returns "0" if no argument has been supplied.
The number of words supplied to an exec as the argument can be determined by examining the results of "ARG(1)" as in the following example.
/* TOTAL EXEC */
n = Words(Arg(1))
/* n receives the number of words */
/* in the argument string. */
Exercise: Create a REXX exec which receives three numbers as an argument, totals those three numbers, and displays the sum.
Passing Arguments to Procedures
Passing arguments to internal procedures within an exec is similar to passing values to an exec, but there are some differences. In the following example, three values are passed to the TOTALUP function as one argument.
sum = TOTALUP("15 22 17")
Alternatively, three values can be passed as three separate arguments, separated from one another by commas.
sum = TOTALUP(15, 22, 17)
If a single argument is passed to the function, then retrieving the individual values is similar to retrieving those values when passed to an exec.
sum = TOTALUP("15 22 17")
.
.
.
TOTALUP:
Parse Arg nmen nwomen nchildren .
.
.
.
If three separate arguments are passed, they can be retrieved using the ARG( ) built-in function.
sum = TOTALUP(15, 22, 17)
.
.
.
TOTALUP:
nmen = Arg(1)
nwomen = Arg(2)
nchildren = Arg(3)
.
.
.
Stem Variables
REXX supports simple scalar variables which contain a single value.
x = 3
or
s = 'Dumbo'
REXX also supports arrays of numbers or strings. Arrays of values are stored in stem variables. Stem variables take the following form:
name.1
name.2
name.3
.
.
.
name.n
where "name.1" refers to the ".1" element, "name.2" refers to the ".2" element, and so forth.
All the elements of a stem variable can be referred to by the stem name:
name.
While this construction may seem a little odd at first, stem variables offer a great deal of convenience in many circumstances.
Initializing Stem Variables
By default, uninitialized stem variables are given a value equal to their name. Thus in the assignment
v = animal.1 /* v receives 'ANIMAL.1' */
variable "v" receives the string "ANIMAL.1" which is the name of the stem variable set to upper case.
Stem variables can be assigned values like any scalar variable,
animal.1 = 'monkey'
animal.2 = 'turkey'
animal.3 = 'mouse'
and can be manipulated like any scalar variable.
animals = animal.1 animal.2 animal.3
/* animals receives "monkey turkey mouse" */
To initialize all elements of a stem variable to the same value, use the construction
animal. = 'ant' /* all elements set to "ant" */
or
animal. = '' /* all elements set to null */
The Zeroth Element
By convention, the ".0" element contains the number of active elements in the stem variable. For example, if
animal.1 = 'monkey'
animal.2 = 'turkey'
animal.3 = 'mouse'
then animal.0 should hold the value of "3". Please note that the zeroth element is not automatically set. Some commands set the value correctly; at other times, the program must explicitly set the value.
Looping with Stem Variables
Stem variables are most useful when used within loops. Examine the following example:
animal.0 = 3
animal.1 = 'monkey'
animal.2 = 'turkey'
animal.3 = 'mouse'
Do i = 1 To animal.0 /* i takes on the */
Say animal.i /* values 1, 2, 3 */
End
Exercise: Write a simple exec based on the preceding code snippet. Run the exec.
Multi-dimensional Stem Arrays
Stem variables can include two or more dimensions. The following syntax is correct.
pet.1.1 = 'cat'
pet.1.2 = 'tiger'
pet.1.3 = 'lion'
pet.2.1 = 'dog'
pet.2.2 = 'wolf'
pet.2.3 = 'coyote'
Here, the "pet.1.n" elements are all cats and the "pet.2.n" elements are all (sort of) dogs. While perfectly legal, using multi-dimensional stem variables is a bit more difficult and probably should be avoided unless the construct perfectly fits the problem.
Input and Output
CMS EXECIO Command
One of the earlier ways to read and write disk data from a REXX exec was through using the CMS command EXECIO. The following example reads a CMS disk file named "TEST DATA A" into a stem variable within a REXX exec.
'EXECIO * DISKR TEST DATA A (STEM LINES. FINIS'
where
- EXECIO is the name of the CMS command.
- " * " indicates that "all records" are to be included.
- DISKR indicates a "disk read" operation.
- TEST DATA A is the name of the CMS file.
- STEM indicates that records are to be stored in a stem variable.
- LINES. is the name of the stem variable receiving the records.
- FINIS indicates that the file is to be closed after the last record is read.
- Assuming that "TEST DATA A" contains three records, the following exec reads all three records and then displays them on the screen. Please note that EXECIO automatically initializes the zeroth element of the stem variable to be equal to the number of records retrieved from the file.
/* Example of EXECIO Read Operation */ 'EXECIO * DISKR TEST DATA A (STEM LINES. FINIS' Do i = 1 To lines.0 Say lines.i End Exit
Conversely, data from a REXX exec can be written out to a disk file.In the following example, three elements of the stem variable are initialized. Please note that the number of lines ("3") is indicated as the first argument after the EXECIO command. "DISKW" indicates that a "disk write" operation is required. In this example, the file "TEST DATA A" will be created if it does not exist. If the file exists already, the lines will be appended to the existing file.
/* Example of EXECIO Write Operation */ lines.1 = 'Chickens' lines.2 = 'Cows' lines.3 = 'Crows' 'EXECIO 3 DISKW TEST DATA A (STEM LINES. FINIS' Exit
For more complete information about the EXECIO command, please see the online Help fileHELP EXECIO
or other documentation.
I/O With CMS Pipelines
For many applications, the preferred way to do disk I/O is with CMS Pipelines. A pipeline is defined as a set of stages which process records sequentially; records enter the pipeline at the beginning, are handled/transformed by each stage in turn, and may exit the pipeline at the end.Programming with Pipelines is extremely economical because of the amount of work performed by various Pipeline stages. But a thorough discussion of Pipelines is beyond the scope of this effort. A few brief examples should provide sufficient knowledge to use Pipelines to read disk files into a REXX exec and to write information from a REXX exec out to a disk file.
The following pipeline reads a CMS disk file named "TEST DATA A" into a stem variable within a REXX exec.
'PIPE < TEST DATA A | STEM LINES.'
where- PIPE is the name of the CMS command.
- " < " is the pipeline "stage" which reads a CMS file.
- TEST DATA A is the name of the CMS file.
- " | " is the pipeline "stage separator."
- STEM is the pipeline "stage" which stores records in a stem variable.
- LINES. is the name of the stem variable receiving the records.
- Assuming that "TEST DATA A" contains three records, the following exec reads all three records and then displays them on the screen. Please note that the STEM stage automatically initializes the zeroth element of the stem variable to be equal to the number of records retrieved from the pipe.
/* Example of Input with Pipelines */ 'PIPE < TEST DATA A | STEM LINES.' Do i = 1 To lines.0 Say lines.i End Exit
Exercise: Create a file containing several data records, and then create an exec (similar to the one above) which reads those records and displays them on the terminal.Conversely, data from a REXX exec can be written out to a disk file. Two variations are considered.
In the following example, the contents of a stem variable are written out to a disk file. If the disk file does not exist, it is created. If the disk file exists, its contents are replaced. Please note that the zeroth element of the stem variable must be initialized to a positive whole number equal to the number of variables which are to be written to the disk file.
/* Example of Output with Pipelines */ lines.1 = 'Chickens' lines.2 = 'Cows' lines.3 = 'Crows' lines.0 = 3 'PIPE STEM LINES. | > TEST DATA A' Exit
where- " > " is the pipeline "stage" which writes records to the CMS file.
- Exercise: Create the exec above (or a variation on it). Run the exec and then Xedit the data file which is created by the exec.
The values of REXX scalar values can also be written out to a file with Pipelines using the VAR stage. For example,
/* Example of Output with Pipelines */ line1 = 'Chickens' line2 = 'Cows' line3 = 'Crows' 'PIPE VAR LINE1 | > TEST DATA A' /* Creates the file */ 'PIPE VAR LINE2 | >> TEST DATA A' /* Appends to the file */ 'PIPE VAR LINE3 | >> TEST DATA A' /* Appends to the file */ Exit
where- " >> " is the pipeline "stage" which appends records to the CMS file.
- For more complete descriptions of Pipeline stages, please see the online Help files
HELP PIPE MENU
or other Pipeline documentation.
Character I/O
REXX includes several built-in functions which can perform character-oriented I/O against "streams" of data. If the "stream" is identified as a file, then these functions can be used to retrieve characters from that file.In the following example, characters are read one-by-one from a file and displayed on the screen.
/* Example of Reading Characters with CHARIN */ fileid = 'TEST DATA A' /* name of stream */ Do i = 1 By 1 If CHARS(fileid)=0 /* more characters? */ Then Leave Say CHARIN(fileid,,1) /* display one character */ End f = STREAM(fileid,'COMMAND','CLOSE') /* close stream */ Exit
where- CHARS( ) returns 0 if the stream is empty.
- CHARIN( ) returns one or more characters from the stream.
- STREAM( ) issues a "stream command" to close the stream.
- The following example uses the CHAROUT procedure to write strings to a CMS disk file named "TEST DATA A".
/* Example of Writing a Data with CHAROUT */ fileid = 'TEST DATA A' /* name stream */ Call CHAROUT fileid,'One...' /* write */ Call CHAROUT fileid,'Two...' /* write */ Call CHAROUT fileid,'Three...' /* write */ f = STREAM(fileid,'COMMAND','CLOSE') /* closes stream */ Exit
where- CHAROUT writes the string to the output stream/file.
- STREAM( ) issues a "stream command" to close the stream.
- The file now contains a single line equal to
One...Two...Three...
For more complete descriptions of these procedures, please see the online Help filesHELP REXX CHARS HELP REXX CHARIN HELP REXX CHAROUT HELP REXX STREAM
Line I/O
REXX includes several built-in functions which perform line-oriented I/O against "streams" of data. If the "stream" is identified as a file, then these functions can be used to retrieve lines of data from a file.In the following example, lines of data are read from a CMS file named "TEST DATA A" and displayed on the screen. At the end of the loop, the STREAM( ) built-in function is used to close the stream/file so that it can be reused.
/* Example of Reading a File with LINEIN( ) */ fileid = 'TEST DATA A' /* name of "stream" */ nlines = LINES(fileid) /* number lines in stream */ Do i = 1 To nlines Say LINEIN(fileid) /* displays a line */ End f = STREAM(fileid,'COMMAND','CLOSE') /* closes stream */ Exit
where- LINES( ) returns the number of lines in the "stream."
- LINEIN( ) returns the next line in the stream.
- STREAM( ) issues a "stream command" to close the stream.
- The following example uses the LINEOUT procedure to write strings to a CMS disk file named "TEST DATA A".
/* Example of Writing a File with LINEOUT */ fileid = 'TEST DATA A' /* name of "stream" */ Call LINEOUT fileid,'This is line one.' /* write */ Call LINEOUT fileid,'This is line two.' /* write */ Call LINEOUT fileid,'This is line three.' /* write */ f = STREAM(fileid,'COMMAND','CLOSE') /* closes stream */ Exit
where- LINEOUT writes lines of data to the stream/file.
- STREAM( ) issues a "stream command" to close the stream.
- For more complete descriptions of these procedures, please see the online Help files
HELP REXX LINES HELP REXX LINEIN HELP REXX LINEOUT HELP REXX STREAM