Chapter 14. Input and Output Streams

Table of Contents
The Input and Output Model
Implementation
Operating System Specifics
Examples of Input and Output
Errors during Input and Output
Summary of Rexx I/O Instructions and Methods

Rexx defines Stream class methods to handle input and output and maintains the I/O functions for input and output externals. Using a mixture of Rexx I/O methods and Rexx I/O functions can cause unpredictable results. For example, using the LINEOUT method and the LINEOUT function on the same persistent stream object can cause overlays.

When a Rexx I/O function creates a stream object, the language processor maintains the stream object. When a Rexx I/O method creates a stream object, it is returned to the program to be maintained. Because of this, when Rexx I/O methods and Rexx I/O functions referring to the same stream are in the same program, there are two separate stream objects with different read and write pointers. The program needs to synchronize the read and write pointers of both stream objects, or overlays occur.

To obtain a stream object (for example, MYFIL), you could use:

MyStream = .stream~new("MYFIL")

You can manipulate stream objects with character or line methods:

nextchar = MyStream~charin()
nextline = MyStream~linein()

In addition to stream objects, the language processor defines an external data queue object for interprogram communication. This queue object understands line functions only.

A stream object can have a variety of sources or destinations including files, serial interfaces, displays, or networks. It can be transient or dynamic, for example, data sent or received over a serial interface, or persistent in a static form, for example, a disk file.

Housekeeping for stream objects (opening and closing files, for example) is not explicitly part of the language definition. However, Rexx provides methods, such as CHARIN and LINEIN, that are independent of the operating system and include housekeeping. The COMMAND method provides the stream_command argument for those situations that require more granular access to operating system interfaces.

The Input and Output Model

The model of input and output for Rexx consists of the following logically distinct parts:

The Rexx methods, instructions, and built-in routines manipulate these elements as follows.

Input Streams

Input to Rexx programs is in the form of a serial character stream generated by user interaction or has the characteristics of one generated this way. You can add characters to the end of some stream objects asynchronously; other stream objects might be static or synchronous.

The methods and instructions you can use on input stream objects are:

  • CHARIN method--reads input stream objects as characters.

  • LINEIN method--reads input stream objects as lines.

  • PARSE PULL and PULL instructions--read the default input stream object (.INPUT), if the external data queue is empty. PULL is the same as PARSE UPPER PULL except that uppercase translation takes place for PULL.

  • PARSE LINEIN instruction--reads lines from the default input stream object regardless of the state of the external data queue. Usually, you can use PULL or PARSE PULL to read the default input stream object.

In a persistent stream object, the Rexx language processor maintains a current read position. For a persistent stream:

  • The CHARS method returns the number of characters currently available in an input stream object from the read position through the end of the stream (including any line-end characters).

  • The LINES method determines if any data remains between the current read position and the end of the input stream object.

  • You can move the read position to an arbitrary point in the stream object with:

    • The SEEK or POSITION method of the Stream class

    • The COMMAND method's SEEK or POSITION argument

    • The start argument of the CHARIN method

    • The line argument of the LINEIN method

    When the stream object is opened, this position is the start of the stream.

In a transient stream, no read position is available. For a transient stream:

  • The CHARS and LINES methods attempt to determine if data is present in the input stream object. These methods return the value 1 for a device if data is waiting to be read or a determination cannot be made. Otherwise, these methods return 0.

  • The SEEK and POSITION methods of the Stream class and the COMMAND method's SEEK and POSITION arguments are not applicable to transient streams.

Output Streams

Output stream methods provide for output from a Rexx program. Output stream methods are:

  • SAY instruction--writes to the default output stream object (.OUTPUT).

  • CHAROUT method--writes in character form to either the default or a specified output stream object.

  • LINEOUT method--writes in lines to either the default or a specified output stream object.

LINEOUT and SAY write the new-line character at the end of each line. Depending on the operating system or hardware, other modifications or formatting can be applied; however, the output data remains a single logical line.

The Rexx language processor maintains the current write position in a stream. It is separate from the current read position. Write positioning is usually at the end of the stream (for example, when the stream object is first opened), so that data can be appended to the end of the stream. For persistent stream objects, you can set the write position to the beginning of the stream to overwrite existing data by giving a value of 1 for the CHAROUT start argument or the LINEOUT line argument. You can also use the CHAROUT start argument, the LINEOUT line argument, the SEEK or POSITION method, or the COMMAND method's SEEK or POSITION stream_command to direct sequential output to some arbitrary point in the stream.

Note: Once data is in a transient output stream object (for example, a network or serial link), it is no longer accessible to Rexx.

External Data Queue

Rexx provides queuing services entirely separate from interprocess communications queues.

The external data queue is a list of character strings that only line operations can access. It is external to Rexx programs in that other Rexx programs can have access to the queue.

The external data queue forms a Rexx-defined channel of communication between programs. Data in the queue is arbitrary; no characters have any special meaning or effect.

Apart from the explicit Rexx operations described here, no detectable change to the queue occurs while a Rexx program is running, except when control leaves the program and is manipulated by external means (such as when an external command or routine is called).

There are two kinds of queues in Rexx. Both kinds are accessed and processed by name.

Unnamed Queues

One unnamed queue is automatically provided for each Rexx program in operation. Its name is always "QUEUE:", and the language processor creates it when Rexx is called and no queue is currently available. All processes that are children of the process that created the queue can access it as long as the process that created it is still running. However, other processes cannot share the same unnamed queue. The queue is deleted when the process that created it ends.

Named Queues

Your program creates (and deletes) named queues. You can name the queue yourself or leave the naming to the language processor. Your program must know the name of the queue to use a named queue. To obtain the name of the queue, use the RXQUEUE function:

previous_queue=rxqueue("set",newqueuename)

This sets the new queue name and returns the name of the previous queue.

The following Rexx instructions manipulate the queue:

  • PULL or PARSE PULL--reads a string from the head of the queue. If the queue is empty, these instructions take input from .INPUT.

  • PUSH--stacks a line on top of the queue (LIFO).

  • QUEUE--adds a string to the tail of the queue (FIFO).

Rexx functions that manipulate QUEUE: as a device name are:

  • LINEIN("QUEUE:")--reads a string from the head of the queue. If the queue is empty the program waits for an entry to be placed on the queue.

  • LINEOUT("QUEUE:","string")--adds a string to the tail of the queue (FIFO).

  • QUEUED--returns the number of items remaining in the queue.

Here is an example of using a queue:

Figure 14-1. Sample Rexx Procedure Using a Queue

/*                                                                            */
/* push/pull WITHOUT multiprogramming support                                 */
/*                                                                            */
push date() time()                                /* push date and time       */
do 1000                                           /* let's pass some time     */
  nop                                             /* doing nothing            */
end                                               /* end of loop              */
pull a b                                          /* pull them                */
say "Pushed at " a b ", Pulled at " date() time() /* say now and then         */

/*                                                                            */
/*              push/pull WITH multiprogramming support                       */
/*        (no error recovery, or unsupported environment tests)               */
/*                                                                            */
newq = RXQUEUE("Create")                          /* create a unique queue    */
oq = RXQUEUE("Set",newq)                          /* establish new queue      */
push date() time()                                /* push date and time       */
do 1000                                           /* let's spend some time    */
  nop                                             /* doing nothing            */
end                                               /* end of loop              */
pull a b                                          /* get pushed information   */
say "Pushed at " a b ", Pulled at " date() time() /* tell user                */
call RXQUEUE "Delete",newq                   /* destroy unique queue created  */
call RXQUEUE "Set",oq               /* reset to default queue (not required)  */

Special considerations:

  • External programs that must communicate with a Rexx procedure through defined data queues can use the Rexx-provided queue or the queue that QUEUE: references (if the external program runs in a child process), or they can receive the data queue name through some interprocess communication technique, including argument passing, placement on a prearranged logical queue, or the use of usual interprocess communication mechanisms (for example, pipes, shared memory, or IPC queues).

  • Named queues are available across the entire system. Therefore, the names of queues must be unique within the system. If a queue named anyque exists, using the following function:

    newqueue = RXQUEUE("Create", "ANYQUE")

    results in an error.

Multiprogramming Considerations

The top-level Rexx program in a process tree owns an unnamed queue. However, any child process can modify the queue at any time. No specific process or user owns a named queue. The operations that affect the queue are atomic--the subsystem serializes the resource so that no data integrity problems can occur. However, you are responsible for the synchronization of requests so that two processes accessing the same queue get the data in the order it was placed on the queue.

A specific process owns (creates) an unnamed queue. When that process ends, the language processor deletes the queue. Conversely, the named queues created with RxQueue("Create", queuename) exist until you explicitly delete them. The end of a program or procedure that created a named queue does not force the deletion of the private queue. When the process that created a queue ends, any data on the queue remains until the data is read or the queue is deleted. (The function call RxQueue("Delete", queuename) deletes a queue.)

If a data queue is deleted by its creator, a procedure, or a program, the items in the queue are also deleted.

Default Stream Names

A stream name can be a file, a queue, a pipe, or any device that supports character-based input and output. If the stream is a file or device, the name can be any valid file specification.

Windows and *nix define three default streams:

  • stdin (file descriptor 0) - standard input

  • stdout (file descriptor 1) - standard output

  • stderr (file descriptor 2) - standard error (output)

Rexx provides .INPUT and .OUTPUT public objects. They default to the default input and output streams of the operating system. The appropriate default stream object is used when the call to a Rexx I/O function includes no stream name. The following Rexx statements write a line to the default output stream of the operating system:

Lineout(,"Hello World")
.Output~lineout("Hello World")

Rexx reserves the names STDIN, STDOUT, and STDERR to allow Rexx functions to refer to these stream objects. The checks for these names are not case-sensitive; for example, STDIN, stdin, and sTdIn all refer to the standard input stream object. If you need to access a file with one of these names, qualify the name with a directory specification, for example, \stdin.

Rexx also provides access to arbitrary file descriptors that are already open when Rexx is called. The stream name used to access the stream object is HANDLE:x. x is the number of the file descriptor you wish to use. You can use HANDLE:x as any other stream name; it can be the receiver of a Stream class method. If the value of x is not a valid file descriptor, the first I/O operation to that object fails.

Notes:

  1. Once you close a HANDLE:x stream object, you cannot reopen it.

  2. HANDLE:x is reserved. If you wish to access a file or device with this name, include a directory specification before the name. For example, \HANDLE:x accesses the file HANDLE:x in the current directory.

  3. Programs that use the .INPUT and .OUTPUT public objects are independent of the operating environment.

Line versus Character Positioning

Rexx lets you move the read or write position of a persistent stream object to any location within the stream. You can specify this location in terms of characters or lines.

Character positioning is based upon the view of a stream as a simple collection of bytes of data. No special meaning is given to any single character. Character positioning alone can move the stream pointer. For example:

MyStream~charin(10,0)

moves the stream pointer so that the tenth character in MyStream is the next character read. But this does not return any data. If MyStream is opened for reading or writing, any output that was previously written but is still buffered is eliminated. Moving the write position always causes any buffered output to be written.

Line positioning views a stream as a collection of lines of data. There are two ways of positioning by lines. If you open a stream in binary mode and specify a record length of x on the open, a line break occurs every x characters. Line positioning in this case is an extension of character positioning. For example, if you open a stream in binary mode with record length 80, then the following two lines are exactly equivalent.

MyStream~command(position 5 read line)
MyStream~command(position 321 read char)

Remember that streams and other Rexx objects are indexed starting with one rather than zero.

The second way of positioning by lines is for non-binary streams. New-line characters separate lines in non-binary streams. Because the line separator is contained within the stream, ensure accurate line positioning. For example, it is possible to change the line number of the current read position by writing extra new-line characters ahead of the read position or by overwriting existing new-line characters. Thus, line positioning in a non-binary stream object has the following characteristics:

  • To do line positioning, it is necessary to read the stream in circumstances such as switching from character methods to line methods or positioning from the end of the stream.

  • If you rewrite a stream at a point prior to the read position, the line number of the current read position could become inaccurate.

Note that for both character and line positioning, the index starts with one rather than zero. Thus, character position 1 and line position 1 are equivalent, and both point to the top of the persistent stream object. The Rexx I/O processing uses certain optimizations for positioning. These require that no other process is writing to the stream concurrently and no other program uses or manipulates the same low-level drive, directory specification, and file name that the language processor uses to open the file. If you need to work with a stream in these circumstances, use the system I/O functions.